diff --git a/services/badges/src/main/java/de/mm20/launcher2/badges/Badge.kt b/services/badges/src/main/java/de/mm20/launcher2/badges/Badge.kt index c2cfb6db..a91d0f40 100644 --- a/services/badges/src/main/java/de/mm20/launcher2/badges/Badge.kt +++ b/services/badges/src/main/java/de/mm20/launcher2/badges/Badge.kt @@ -1,10 +1,52 @@ package de.mm20.launcher2.badges import android.graphics.drawable.Drawable +import android.util.Log -data class Badge( - var number: Int? = null, - var progress: Float? = null, - var iconRes: Int? = null, - var icon: Drawable? = null -) \ No newline at end of file +interface Badge { + val number: Int? + val progress: Float? + val iconRes: Int? + val icon: Drawable? +} + +fun Badge( + number: Int? = null, + progress: Float? = null, + iconRes: Int? = null, + icon: Drawable? = null +): Badge = MutableBadge(number, progress, iconRes, icon) + +internal data class MutableBadge( + override var number: Int? = null, + override var progress: Float? = null, + override var iconRes: Int? = null, + override var icon: Drawable? = null +): Badge + +fun Collection.combine(): Badge? { + if (isEmpty()) return null + val badge = MutableBadge() + var progresses = 0 + forEach { + if (it.icon != null && badge.icon == null) badge.icon = it.icon + if (it.iconRes != null && badge.iconRes == null) badge.iconRes = it.iconRes + it.number?.let { a -> + badge.number?.let { b -> badge.number = a + b } ?: run { + badge.number = a + } + } + it.progress?.let { a -> + badge.progress?.let { b -> + badge.progress = a + b + } ?: run { + badge.progress = a + } + progresses++ + } + } + if (progresses > 0) { + badge.progress?.let { badge.progress = it / progresses } + } + return badge +} \ No newline at end of file diff --git a/services/badges/src/main/java/de/mm20/launcher2/badges/BadgeService.kt b/services/badges/src/main/java/de/mm20/launcher2/badges/BadgeService.kt index 7d1fe414..f31ee849 100644 --- a/services/badges/src/main/java/de/mm20/launcher2/badges/BadgeService.kt +++ b/services/badges/src/main/java/de/mm20/launcher2/badges/BadgeService.kt @@ -1,14 +1,29 @@ package de.mm20.launcher2.badges import android.content.Context -import de.mm20.launcher2.badges.providers.* +import de.mm20.launcher2.badges.providers.AppShortcutBadgeProvider +import de.mm20.launcher2.badges.providers.BadgeProvider +import de.mm20.launcher2.badges.providers.CloudBadgeProvider +import de.mm20.launcher2.badges.providers.NotificationBadgeProvider +import de.mm20.launcher2.badges.providers.PluginBadgeProvider +import de.mm20.launcher2.badges.providers.SuspendedAppsBadgeProvider +import de.mm20.launcher2.badges.providers.WorkProfileBadgeProvider import de.mm20.launcher2.badges.settings.BadgeSettings -import de.mm20.launcher2.preferences.LauncherDataStore import de.mm20.launcher2.search.Searchable -import kotlinx.coroutines.* -import kotlinx.coroutines.flow.* +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.Job +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.collectLatest +import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.flatMapLatest +import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.flow.flowOn +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.launch import org.koin.core.component.KoinComponent -import org.koin.core.component.inject interface BadgeService { fun getBadge(searchable: Searchable): Flow @@ -47,44 +62,12 @@ internal class BadgeServiceImpl( } } - override fun getBadge(searchable: Searchable): Flow = channelFlow { - withContext(Dispatchers.Default) { - badgeProviders.collectLatest { providers -> - if (providers.isEmpty()) { - send(null) - return@collectLatest - } - combine(providers.map { it.getBadge(searchable) }) { badges -> - if (badges.all { it == null }) { - return@combine null - } - val badge = Badge() - var progresses = 0 - badges.filterNotNull().forEach { - if (it.icon != null && badge.icon == null) badge.icon = it.icon - if (it.iconRes != null && badge.iconRes == null) badge.iconRes = it.iconRes - it.number?.let { a -> - badge.number?.let { b -> badge.number = a + b } ?: run { - badge.number = a - } - } - it.progress?.let { a -> - badge.progress?.let { b -> - badge.progress = a + b - } ?: run { - badge.progress = a - } - progresses++ - } - } - if (progresses > 0) { - badge.progress?.let { badge.progress = it / progresses } - } - return@combine badge - }.collectLatest { - send(it) - } - } + override fun getBadge(searchable: Searchable): Flow { + return badgeProviders.flatMapLatest { providers -> + if (providers.isEmpty()) return@flatMapLatest flowOf(null) + combine(providers.map { it.getBadge(searchable) }) { it.filterNotNull() } + .map { it.combine() } + .flowOn(Dispatchers.Default) } } diff --git a/services/badges/src/main/java/de/mm20/launcher2/badges/providers/AppShortcutBadgeProvider.kt b/services/badges/src/main/java/de/mm20/launcher2/badges/providers/AppShortcutBadgeProvider.kt index 127b5afd..b28c0bb3 100644 --- a/services/badges/src/main/java/de/mm20/launcher2/badges/providers/AppShortcutBadgeProvider.kt +++ b/services/badges/src/main/java/de/mm20/launcher2/badges/providers/AppShortcutBadgeProvider.kt @@ -3,6 +3,7 @@ package de.mm20.launcher2.badges.providers import android.content.Context import android.content.pm.PackageManager import de.mm20.launcher2.badges.Badge +import de.mm20.launcher2.badges.MutableBadge import de.mm20.launcher2.graphics.BadgeDrawable import de.mm20.launcher2.search.AppShortcut import de.mm20.launcher2.search.Searchable @@ -27,7 +28,7 @@ class AppShortcutBadgeProvider( } catch (e: PackageManager.NameNotFoundException) { return@withContext } - val badge = Badge(icon = BadgeDrawable(context, icon)) + val badge = MutableBadge(icon = BadgeDrawable(context, icon)) send(badge) } } else if (packageName != null) { @@ -39,7 +40,7 @@ class AppShortcutBadgeProvider( } catch (e: PackageManager.NameNotFoundException) { return@withContext } - val badge = Badge(icon = BadgeDrawable(context, icon)) + val badge = MutableBadge(icon = BadgeDrawable(context, icon)) send(badge) } } else { diff --git a/services/badges/src/main/java/de/mm20/launcher2/badges/providers/CloudBadgeProvider.kt b/services/badges/src/main/java/de/mm20/launcher2/badges/providers/CloudBadgeProvider.kt index cafffe20..934aff41 100644 --- a/services/badges/src/main/java/de/mm20/launcher2/badges/providers/CloudBadgeProvider.kt +++ b/services/badges/src/main/java/de/mm20/launcher2/badges/providers/CloudBadgeProvider.kt @@ -1,20 +1,21 @@ package de.mm20.launcher2.badges.providers import de.mm20.launcher2.badges.Badge +import de.mm20.launcher2.badges.MutableBadge import de.mm20.launcher2.search.File import de.mm20.launcher2.search.Searchable import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.flow +import kotlinx.coroutines.flow.flowOf class CloudBadgeProvider: BadgeProvider { - override fun getBadge(searchable: Searchable): Flow = flow { + override fun getBadge(searchable: Searchable): Flow { if (searchable is File) { val iconResId = searchable.providerIconRes if (iconResId != null) { - emit(Badge(iconRes = iconResId)) - return@flow + return flowOf(MutableBadge(iconRes = iconResId)) } } - emit(null) + return flowOf(null) } } \ No newline at end of file diff --git a/services/badges/src/main/java/de/mm20/launcher2/badges/providers/NotificationBadgeProvider.kt b/services/badges/src/main/java/de/mm20/launcher2/badges/providers/NotificationBadgeProvider.kt index 9bf7240b..44ebd880 100644 --- a/services/badges/src/main/java/de/mm20/launcher2/badges/providers/NotificationBadgeProvider.kt +++ b/services/badges/src/main/java/de/mm20/launcher2/badges/providers/NotificationBadgeProvider.kt @@ -1,12 +1,13 @@ package de.mm20.launcher2.badges.providers +import android.util.Log import de.mm20.launcher2.badges.Badge +import de.mm20.launcher2.badges.MutableBadge import de.mm20.launcher2.notifications.NotificationRepository -import de.mm20.launcher2.search.Searchable import de.mm20.launcher2.search.Application +import de.mm20.launcher2.search.Searchable import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.channelFlow -import kotlinx.coroutines.flow.collectLatest +import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.map import org.koin.core.component.KoinComponent import org.koin.core.component.inject @@ -14,35 +15,33 @@ import org.koin.core.component.inject class NotificationBadgeProvider : BadgeProvider, KoinComponent { private val notificationRepository: NotificationRepository by inject() - override fun getBadge(searchable: Searchable): Flow = channelFlow { - if (searchable is Application) { - val packageName = searchable.componentName.packageName - notificationRepository.notifications.map { - it.filter { it.packageName == packageName } - }.collectLatest { - if (it.isEmpty() || it.none { it.canShowBadge }) { - send(null) - } else { - val badge = Badge( - number = it.sumOf { - if (it.canShowBadge && !it.isGroupSummary) it.number - else 0 - }, - progress = it.mapNotNull { - val progress = it.progress ?: return@mapNotNull null - val progressMax = it.progressMax ?: return@mapNotNull null - return@mapNotNull progress.toFloat() / progressMax.toFloat() + override fun getBadge(searchable: Searchable): Flow { + if (searchable !is Application) return flowOf(null) + + val packageName = searchable.componentName.packageName + return notificationRepository.notifications.map { + it.filter { it.packageName == packageName && it.canShowBadge } + }.map { + if (it.isEmpty()) { + return@map null + } else { + val badge = MutableBadge( + number = it.sumOf { + if (it.canShowBadge && !it.isGroupSummary) it.number + else 0 + }, + progress = it.mapNotNull { + val progress = it.progress ?: return@mapNotNull null + val progressMax = it.progressMax ?: return@mapNotNull null + return@mapNotNull progress.toFloat() / progressMax.toFloat() + } + .takeIf { it.isNotEmpty() } + ?.let { + it.sumOf { it.toDouble() }.toFloat() / it.size } - .takeIf { it.isNotEmpty() } - ?.let { - it.sumOf { it.toDouble() }.toFloat() / it.size - } - ) - send(badge) - } + ) + return@map badge } - } else { - send(null) } } } \ No newline at end of file diff --git a/services/badges/src/main/java/de/mm20/launcher2/badges/providers/PluginBadgeProvider.kt b/services/badges/src/main/java/de/mm20/launcher2/badges/providers/PluginBadgeProvider.kt index 53962e76..0745e4b0 100644 --- a/services/badges/src/main/java/de/mm20/launcher2/badges/providers/PluginBadgeProvider.kt +++ b/services/badges/src/main/java/de/mm20/launcher2/badges/providers/PluginBadgeProvider.kt @@ -2,6 +2,7 @@ package de.mm20.launcher2.badges.providers import android.content.Context import de.mm20.launcher2.badges.Badge +import de.mm20.launcher2.badges.MutableBadge import de.mm20.launcher2.search.SavableSearchable import de.mm20.launcher2.search.Searchable import kotlinx.coroutines.flow.Flow @@ -12,9 +13,10 @@ class PluginBadgeProvider(private val context: Context): BadgeProvider { override fun getBadge(searchable: Searchable): Flow { if (searchable !is SavableSearchable) return flowOf(null) return flow { + emit(null) val icon = searchable.getProviderIcon(context) if (icon != null) { - emit(Badge(icon = icon)) + emit(MutableBadge(icon = icon)) } } } diff --git a/services/badges/src/main/java/de/mm20/launcher2/badges/providers/SuspendedAppsBadgeProvider.kt b/services/badges/src/main/java/de/mm20/launcher2/badges/providers/SuspendedAppsBadgeProvider.kt index a2c9710f..6dc8495d 100644 --- a/services/badges/src/main/java/de/mm20/launcher2/badges/providers/SuspendedAppsBadgeProvider.kt +++ b/services/badges/src/main/java/de/mm20/launcher2/badges/providers/SuspendedAppsBadgeProvider.kt @@ -1,20 +1,22 @@ package de.mm20.launcher2.badges.providers import de.mm20.launcher2.badges.Badge +import de.mm20.launcher2.badges.MutableBadge import de.mm20.launcher2.badges.R import de.mm20.launcher2.search.Application import de.mm20.launcher2.search.Searchable import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.channelFlow +import kotlinx.coroutines.flow.flowOf import org.koin.core.component.KoinComponent class SuspendedAppsBadgeProvider : BadgeProvider, KoinComponent { - override fun getBadge(searchable: Searchable): Flow = channelFlow { - if (searchable is Application && searchable.isSuspended) { - send(Badge(iconRes = R.drawable.ic_badge_suspended)) + override fun getBadge(searchable: Searchable): Flow { + return if (searchable is Application && searchable.isSuspended) { + flowOf(MutableBadge(iconRes = R.drawable.ic_badge_suspended)) } else { - send(null) + flowOf(null) } } } \ No newline at end of file diff --git a/services/badges/src/main/java/de/mm20/launcher2/badges/providers/WorkProfileBadgeProvider.kt b/services/badges/src/main/java/de/mm20/launcher2/badges/providers/WorkProfileBadgeProvider.kt index 99ed08ba..df8e1c96 100644 --- a/services/badges/src/main/java/de/mm20/launcher2/badges/providers/WorkProfileBadgeProvider.kt +++ b/services/badges/src/main/java/de/mm20/launcher2/badges/providers/WorkProfileBadgeProvider.kt @@ -1,6 +1,7 @@ package de.mm20.launcher2.badges.providers import de.mm20.launcher2.badges.Badge +import de.mm20.launcher2.badges.MutableBadge import de.mm20.launcher2.badges.R import de.mm20.launcher2.search.AppProfile import de.mm20.launcher2.search.AppShortcut @@ -13,7 +14,7 @@ class WorkProfileBadgeProvider : BadgeProvider { override fun getBadge(searchable: Searchable): Flow = flow { if (searchable is Application && searchable.profile == AppProfile.Work || searchable is AppShortcut && searchable.profile == AppProfile.Work) { emit( - Badge( + MutableBadge( iconRes = R.drawable.ic_badge_workprofile ) )