parent
921c641cf6
commit
1e8a4e1554
@ -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
|
||||
)
|
||||
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<Badge>.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
|
||||
}
|
||||
@ -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<Badge?>
|
||||
@ -47,44 +62,12 @@ internal class BadgeServiceImpl(
|
||||
}
|
||||
}
|
||||
|
||||
override fun getBadge(searchable: Searchable): Flow<Badge?> = 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<Badge?> {
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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<Badge?> = flow {
|
||||
override fun getBadge(searchable: Searchable): Flow<Badge?> {
|
||||
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)
|
||||
}
|
||||
}
|
||||
@ -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<Badge?> = 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<Badge?> {
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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<Badge?> {
|
||||
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))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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<Badge?> = channelFlow {
|
||||
if (searchable is Application && searchable.isSuspended) {
|
||||
send(Badge(iconRes = R.drawable.ic_badge_suspended))
|
||||
override fun getBadge(searchable: Searchable): Flow<Badge?> {
|
||||
return if (searchable is Application && searchable.isSuspended) {
|
||||
flowOf(MutableBadge(iconRes = R.drawable.ic_badge_suspended))
|
||||
} else {
|
||||
send(null)
|
||||
flowOf(null)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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<Badge?> = 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
|
||||
)
|
||||
)
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user