forked from GitHub-Mirror/riotX-android
Merge pull request #313 from vector-im/feature/notif_optim
Improve notification drawer manager: Dagger, throttle, and icon for API 9
This commit is contained in:
commit
7ce476f858
@ -19,7 +19,7 @@ package im.vector.riotx.core.extensions
|
|||||||
import androidx.lifecycle.LifecycleOwner
|
import androidx.lifecycle.LifecycleOwner
|
||||||
import androidx.lifecycle.LiveData
|
import androidx.lifecycle.LiveData
|
||||||
import androidx.lifecycle.Observer
|
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.EventObserver
|
||||||
import im.vector.riotx.core.utils.LiveEvent
|
import im.vector.riotx.core.utils.LiveEvent
|
||||||
|
|
||||||
@ -35,11 +35,11 @@ inline fun <T> LiveData<LiveEvent<T>>.observeEvent(owner: LifecycleOwner, crossi
|
|||||||
this.observe(owner, EventObserver { it.run(observer) })
|
this.observe(owner, EventObserver { it.run(observer) })
|
||||||
}
|
}
|
||||||
|
|
||||||
inline fun <T> LiveData<LiveEvent<T>>.observeEventDebounced(owner: LifecycleOwner, minimumInterval: Long, crossinline observer: (T) -> Unit) {
|
inline fun <T> LiveData<LiveEvent<T>>.observeEventFirstThrottle(owner: LifecycleOwner, minimumInterval: Long, crossinline observer: (T) -> Unit) {
|
||||||
val debouncer = Debouncer(minimumInterval)
|
val firstThrottler = FirstThrottler(minimumInterval)
|
||||||
|
|
||||||
this.observe(owner, EventObserver {
|
this.observe(owner, EventObserver {
|
||||||
if (debouncer.canHandle()) {
|
if (firstThrottler.canHandle()) {
|
||||||
it.run(observer)
|
it.run(observer)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -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
|
private var lastDate = 0L
|
||||||
|
|
||||||
fun canHandle(): Boolean {
|
fun canHandle(): Boolean {
|
@ -33,7 +33,7 @@ import im.vector.riotx.core.di.ScreenComponent
|
|||||||
import im.vector.riotx.core.epoxy.LayoutManagerStateRestorer
|
import im.vector.riotx.core.epoxy.LayoutManagerStateRestorer
|
||||||
import im.vector.riotx.core.error.ErrorFormatter
|
import im.vector.riotx.core.error.ErrorFormatter
|
||||||
import im.vector.riotx.core.extensions.observeEvent
|
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.OnBackPressed
|
||||||
import im.vector.riotx.core.platform.StateView
|
import im.vector.riotx.core.platform.StateView
|
||||||
import im.vector.riotx.core.platform.VectorBaseFragment
|
import im.vector.riotx.core.platform.VectorBaseFragment
|
||||||
@ -81,7 +81,7 @@ class RoomListFragment : VectorBaseFragment(), RoomSummaryController.Listener, O
|
|||||||
setupCreateRoomButton()
|
setupCreateRoomButton()
|
||||||
setupRecyclerView()
|
setupRecyclerView()
|
||||||
roomListViewModel.subscribe { renderState(it) }
|
roomListViewModel.subscribe { renderState(it) }
|
||||||
roomListViewModel.openRoomLiveData.observeEventDebounced(this, 800L) {
|
roomListViewModel.openRoomLiveData.observeEventFirstThrottle(this, 800L) {
|
||||||
navigator.openRoom(requireActivity(), it)
|
navigator.openRoom(requireActivity(), it)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -18,74 +18,39 @@ package im.vector.riotx.features.notifications
|
|||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.graphics.Bitmap
|
import android.graphics.Bitmap
|
||||||
import android.os.Handler
|
|
||||||
import android.os.HandlerThread
|
|
||||||
import androidx.annotation.WorkerThread
|
import androidx.annotation.WorkerThread
|
||||||
import com.bumptech.glide.Glide
|
import com.bumptech.glide.Glide
|
||||||
import com.bumptech.glide.load.DecodeFormat
|
import com.bumptech.glide.load.DecodeFormat
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
|
import javax.inject.Inject
|
||||||
|
import javax.inject.Singleton
|
||||||
|
|
||||||
/**
|
@Singleton
|
||||||
* FIXME It works, but it does not refresh the notification, when it's already displayed
|
class BitmapLoader @Inject constructor(val context: Context) {
|
||||||
*/
|
|
||||||
class BitmapLoader(val context: Context,
|
|
||||||
val listener: BitmapLoaderListener) {
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Avatar Url -> Icon
|
* Avatar Url -> Bitmap
|
||||||
*/
|
*/
|
||||||
private val cache = HashMap<String, Bitmap>()
|
private val cache = HashMap<String, Bitmap?>()
|
||||||
|
|
||||||
// URLs to load
|
|
||||||
private val toLoad = HashSet<String>()
|
|
||||||
|
|
||||||
// Black list of URLs (broken URL, etc.)
|
|
||||||
private val blacklist = HashSet<String>()
|
|
||||||
|
|
||||||
private var uiHandler = Handler()
|
|
||||||
|
|
||||||
private val handlerThread: HandlerThread = HandlerThread("BitmapLoader", Thread.MIN_PRIORITY)
|
|
||||||
private var backgroundHandler: Handler
|
|
||||||
|
|
||||||
init {
|
|
||||||
handlerThread.start()
|
|
||||||
backgroundHandler = Handler(handlerThread.looper)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get icon of a room.
|
* Get icon of a room.
|
||||||
* If already in cache, use it, else load it and call BitmapLoaderListener.onBitmapsLoaded() when ready
|
* If already in cache, use it, else load it and call BitmapLoaderListener.onBitmapsLoaded() when ready
|
||||||
*/
|
*/
|
||||||
|
@WorkerThread
|
||||||
fun getRoomBitmap(path: String?): Bitmap? {
|
fun getRoomBitmap(path: String?): Bitmap? {
|
||||||
if (path == null) {
|
if (path == null) {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
synchronized(cache) {
|
return cache.getOrPut(path) {
|
||||||
if (cache[path] != null) {
|
loadRoomBitmap(path)
|
||||||
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 null
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@WorkerThread
|
@WorkerThread
|
||||||
private fun loadRoomBitmap(path: String) {
|
private fun loadRoomBitmap(path: String): Bitmap? {
|
||||||
val bitmap = path.let {
|
return path.let {
|
||||||
try {
|
try {
|
||||||
Glide.with(context)
|
Glide.with(context)
|
||||||
.asBitmap()
|
.asBitmap()
|
||||||
@ -98,27 +63,5 @@ class BitmapLoader(val context: Context,
|
|||||||
null
|
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()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -17,76 +17,43 @@
|
|||||||
package im.vector.riotx.features.notifications
|
package im.vector.riotx.features.notifications
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.os.Handler
|
import android.os.Build
|
||||||
import android.os.HandlerThread
|
|
||||||
import androidx.annotation.WorkerThread
|
import androidx.annotation.WorkerThread
|
||||||
import androidx.core.graphics.drawable.IconCompat
|
import androidx.core.graphics.drawable.IconCompat
|
||||||
import com.bumptech.glide.Glide
|
import com.bumptech.glide.Glide
|
||||||
import com.bumptech.glide.load.DecodeFormat
|
import com.bumptech.glide.load.DecodeFormat
|
||||||
import com.bumptech.glide.request.RequestOptions
|
import com.bumptech.glide.request.RequestOptions
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
|
import javax.inject.Inject
|
||||||
|
import javax.inject.Singleton
|
||||||
|
|
||||||
/**
|
@Singleton
|
||||||
* FIXME It works, but it does not refresh the notification, when it's already displayed
|
class IconLoader @Inject constructor(val context: Context) {
|
||||||
*/
|
|
||||||
class IconLoader(val context: Context,
|
|
||||||
val listener: IconLoaderListener) {
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Avatar Url -> Icon
|
* Avatar Url -> IconCompat
|
||||||
*/
|
*/
|
||||||
private val cache = HashMap<String, IconCompat>()
|
private val cache = HashMap<String, IconCompat?>()
|
||||||
|
|
||||||
// URLs to load
|
|
||||||
private val toLoad = HashSet<String>()
|
|
||||||
|
|
||||||
// Black list of URLs (broken URL, etc.)
|
|
||||||
private val blacklist = HashSet<String>()
|
|
||||||
|
|
||||||
private var uiHandler = Handler()
|
|
||||||
|
|
||||||
private val handlerThread: HandlerThread = HandlerThread("IconLoader", Thread.MIN_PRIORITY)
|
|
||||||
private var backgroundHandler: Handler
|
|
||||||
|
|
||||||
init {
|
|
||||||
handlerThread.start()
|
|
||||||
backgroundHandler = Handler(handlerThread.looper)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get icon of a user.
|
* Get icon of a user.
|
||||||
* If already in cache, use it, else load it and call IconLoaderListener.onIconsLoaded() when ready
|
* 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? {
|
fun getUserIcon(path: String?): IconCompat? {
|
||||||
if (path == null) {
|
if (path == null || Build.VERSION.SDK_INT < Build.VERSION_CODES.P) {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
synchronized(cache) {
|
return cache.getOrPut(path) {
|
||||||
if (cache[path] != null) {
|
loadUserIcon(path)
|
||||||
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 null
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@WorkerThread
|
@WorkerThread
|
||||||
private fun loadUserIcon(path: String) {
|
private fun loadUserIcon(path: String): IconCompat? {
|
||||||
val iconCompat = path.let {
|
return path.let {
|
||||||
try {
|
try {
|
||||||
Glide.with(context)
|
Glide.with(context)
|
||||||
.asBitmap()
|
.asBitmap()
|
||||||
@ -102,27 +69,5 @@ class IconLoader(val context: Context,
|
|||||||
IconCompat.createWithBitmap(bitmap)
|
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()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -18,6 +18,9 @@ package im.vector.riotx.features.notifications
|
|||||||
import android.app.Notification
|
import android.app.Notification
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.graphics.Bitmap
|
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.NotificationCompat
|
||||||
import androidx.core.app.Person
|
import androidx.core.app.Person
|
||||||
import im.vector.matrix.android.api.session.content.ContentUrlResolver
|
import im.vector.matrix.android.api.session.content.ContentUrlResolver
|
||||||
@ -42,8 +45,18 @@ import javax.inject.Singleton
|
|||||||
@Singleton
|
@Singleton
|
||||||
class NotificationDrawerManager @Inject constructor(private val context: Context,
|
class NotificationDrawerManager @Inject constructor(private val context: Context,
|
||||||
private val activeSessionHolder: ActiveSessionHolder,
|
private val activeSessionHolder: ActiveSessionHolder,
|
||||||
|
private val iconLoader: IconLoader,
|
||||||
|
private val bitmapLoader: BitmapLoader,
|
||||||
private val outdatedDetector: OutdatedEventDetector?) {
|
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
|
//The first time the notification drawer is refreshed, we force re-render of all notifications
|
||||||
private var firstTime = true
|
private var firstTime = true
|
||||||
|
|
||||||
@ -53,22 +66,6 @@ class NotificationDrawerManager @Inject constructor(private val context: Context
|
|||||||
|
|
||||||
private var currentRoomId: String? = null
|
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.
|
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
|
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() {
|
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 session = activeSessionHolder.getActiveSession()
|
||||||
val user = session.getUser(session.sessionParams.credentials.userId)
|
val user = session.getUser(session.sessionParams.credentials.userId)
|
||||||
val myUserDisplayName = user?.displayName ?: ""
|
val myUserDisplayName = user?.displayName ?: ""
|
||||||
|
Loading…
Reference in New Issue
Block a user