parent
921c641cf6
commit
1e8a4e1554
@ -1,10 +1,52 @@
|
|||||||
package de.mm20.launcher2.badges
|
package de.mm20.launcher2.badges
|
||||||
|
|
||||||
import android.graphics.drawable.Drawable
|
import android.graphics.drawable.Drawable
|
||||||
|
import android.util.Log
|
||||||
|
|
||||||
data class Badge(
|
interface Badge {
|
||||||
var number: Int? = null,
|
val number: Int?
|
||||||
var progress: Float? = null,
|
val progress: Float?
|
||||||
var iconRes: Int? = null,
|
val iconRes: Int?
|
||||||
var icon: Drawable? = null
|
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
|
package de.mm20.launcher2.badges
|
||||||
|
|
||||||
import android.content.Context
|
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.badges.settings.BadgeSettings
|
||||||
import de.mm20.launcher2.preferences.LauncherDataStore
|
|
||||||
import de.mm20.launcher2.search.Searchable
|
import de.mm20.launcher2.search.Searchable
|
||||||
import kotlinx.coroutines.*
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.flow.*
|
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.KoinComponent
|
||||||
import org.koin.core.component.inject
|
|
||||||
|
|
||||||
interface BadgeService {
|
interface BadgeService {
|
||||||
fun getBadge(searchable: Searchable): Flow<Badge?>
|
fun getBadge(searchable: Searchable): Flow<Badge?>
|
||||||
@ -47,44 +62,12 @@ internal class BadgeServiceImpl(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getBadge(searchable: Searchable): Flow<Badge?> = channelFlow {
|
override fun getBadge(searchable: Searchable): Flow<Badge?> {
|
||||||
withContext(Dispatchers.Default) {
|
return badgeProviders.flatMapLatest { providers ->
|
||||||
badgeProviders.collectLatest { providers ->
|
if (providers.isEmpty()) return@flatMapLatest flowOf(null)
|
||||||
if (providers.isEmpty()) {
|
combine(providers.map { it.getBadge(searchable) }) { it.filterNotNull() }
|
||||||
send(null)
|
.map { it.combine() }
|
||||||
return@collectLatest
|
.flowOn(Dispatchers.Default)
|
||||||
}
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -3,6 +3,7 @@ package de.mm20.launcher2.badges.providers
|
|||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.pm.PackageManager
|
import android.content.pm.PackageManager
|
||||||
import de.mm20.launcher2.badges.Badge
|
import de.mm20.launcher2.badges.Badge
|
||||||
|
import de.mm20.launcher2.badges.MutableBadge
|
||||||
import de.mm20.launcher2.graphics.BadgeDrawable
|
import de.mm20.launcher2.graphics.BadgeDrawable
|
||||||
import de.mm20.launcher2.search.AppShortcut
|
import de.mm20.launcher2.search.AppShortcut
|
||||||
import de.mm20.launcher2.search.Searchable
|
import de.mm20.launcher2.search.Searchable
|
||||||
@ -27,7 +28,7 @@ class AppShortcutBadgeProvider(
|
|||||||
} catch (e: PackageManager.NameNotFoundException) {
|
} catch (e: PackageManager.NameNotFoundException) {
|
||||||
return@withContext
|
return@withContext
|
||||||
}
|
}
|
||||||
val badge = Badge(icon = BadgeDrawable(context, icon))
|
val badge = MutableBadge(icon = BadgeDrawable(context, icon))
|
||||||
send(badge)
|
send(badge)
|
||||||
}
|
}
|
||||||
} else if (packageName != null) {
|
} else if (packageName != null) {
|
||||||
@ -39,7 +40,7 @@ class AppShortcutBadgeProvider(
|
|||||||
} catch (e: PackageManager.NameNotFoundException) {
|
} catch (e: PackageManager.NameNotFoundException) {
|
||||||
return@withContext
|
return@withContext
|
||||||
}
|
}
|
||||||
val badge = Badge(icon = BadgeDrawable(context, icon))
|
val badge = MutableBadge(icon = BadgeDrawable(context, icon))
|
||||||
send(badge)
|
send(badge)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@ -1,20 +1,21 @@
|
|||||||
package de.mm20.launcher2.badges.providers
|
package de.mm20.launcher2.badges.providers
|
||||||
|
|
||||||
import de.mm20.launcher2.badges.Badge
|
import de.mm20.launcher2.badges.Badge
|
||||||
|
import de.mm20.launcher2.badges.MutableBadge
|
||||||
import de.mm20.launcher2.search.File
|
import de.mm20.launcher2.search.File
|
||||||
import de.mm20.launcher2.search.Searchable
|
import de.mm20.launcher2.search.Searchable
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.coroutines.flow.flow
|
import kotlinx.coroutines.flow.flow
|
||||||
|
import kotlinx.coroutines.flow.flowOf
|
||||||
|
|
||||||
class CloudBadgeProvider: BadgeProvider {
|
class CloudBadgeProvider: BadgeProvider {
|
||||||
override fun getBadge(searchable: Searchable): Flow<Badge?> = flow {
|
override fun getBadge(searchable: Searchable): Flow<Badge?> {
|
||||||
if (searchable is File) {
|
if (searchable is File) {
|
||||||
val iconResId = searchable.providerIconRes
|
val iconResId = searchable.providerIconRes
|
||||||
if (iconResId != null) {
|
if (iconResId != null) {
|
||||||
emit(Badge(iconRes = iconResId))
|
return flowOf(MutableBadge(iconRes = iconResId))
|
||||||
return@flow
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
emit(null)
|
return flowOf(null)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1,12 +1,13 @@
|
|||||||
package de.mm20.launcher2.badges.providers
|
package de.mm20.launcher2.badges.providers
|
||||||
|
|
||||||
|
import android.util.Log
|
||||||
import de.mm20.launcher2.badges.Badge
|
import de.mm20.launcher2.badges.Badge
|
||||||
|
import de.mm20.launcher2.badges.MutableBadge
|
||||||
import de.mm20.launcher2.notifications.NotificationRepository
|
import de.mm20.launcher2.notifications.NotificationRepository
|
||||||
import de.mm20.launcher2.search.Searchable
|
|
||||||
import de.mm20.launcher2.search.Application
|
import de.mm20.launcher2.search.Application
|
||||||
|
import de.mm20.launcher2.search.Searchable
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.coroutines.flow.channelFlow
|
import kotlinx.coroutines.flow.flowOf
|
||||||
import kotlinx.coroutines.flow.collectLatest
|
|
||||||
import kotlinx.coroutines.flow.map
|
import kotlinx.coroutines.flow.map
|
||||||
import org.koin.core.component.KoinComponent
|
import org.koin.core.component.KoinComponent
|
||||||
import org.koin.core.component.inject
|
import org.koin.core.component.inject
|
||||||
@ -14,35 +15,33 @@ import org.koin.core.component.inject
|
|||||||
class NotificationBadgeProvider : BadgeProvider, KoinComponent {
|
class NotificationBadgeProvider : BadgeProvider, KoinComponent {
|
||||||
private val notificationRepository: NotificationRepository by inject()
|
private val notificationRepository: NotificationRepository by inject()
|
||||||
|
|
||||||
override fun getBadge(searchable: Searchable): Flow<Badge?> = channelFlow {
|
override fun getBadge(searchable: Searchable): Flow<Badge?> {
|
||||||
if (searchable is Application) {
|
if (searchable !is Application) return flowOf(null)
|
||||||
val packageName = searchable.componentName.packageName
|
|
||||||
notificationRepository.notifications.map {
|
val packageName = searchable.componentName.packageName
|
||||||
it.filter { it.packageName == packageName }
|
return notificationRepository.notifications.map {
|
||||||
}.collectLatest {
|
it.filter { it.packageName == packageName && it.canShowBadge }
|
||||||
if (it.isEmpty() || it.none { it.canShowBadge }) {
|
}.map {
|
||||||
send(null)
|
if (it.isEmpty()) {
|
||||||
} else {
|
return@map null
|
||||||
val badge = Badge(
|
} else {
|
||||||
number = it.sumOf {
|
val badge = MutableBadge(
|
||||||
if (it.canShowBadge && !it.isGroupSummary) it.number
|
number = it.sumOf {
|
||||||
else 0
|
if (it.canShowBadge && !it.isGroupSummary) it.number
|
||||||
},
|
else 0
|
||||||
progress = it.mapNotNull {
|
},
|
||||||
val progress = it.progress ?: return@mapNotNull null
|
progress = it.mapNotNull {
|
||||||
val progressMax = it.progressMax ?: return@mapNotNull null
|
val progress = it.progress ?: return@mapNotNull null
|
||||||
return@mapNotNull progress.toFloat() / progressMax.toFloat()
|
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 {
|
return@map badge
|
||||||
it.sumOf { it.toDouble() }.toFloat() / it.size
|
|
||||||
}
|
|
||||||
)
|
|
||||||
send(badge)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
send(null)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -2,6 +2,7 @@ package de.mm20.launcher2.badges.providers
|
|||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import de.mm20.launcher2.badges.Badge
|
import de.mm20.launcher2.badges.Badge
|
||||||
|
import de.mm20.launcher2.badges.MutableBadge
|
||||||
import de.mm20.launcher2.search.SavableSearchable
|
import de.mm20.launcher2.search.SavableSearchable
|
||||||
import de.mm20.launcher2.search.Searchable
|
import de.mm20.launcher2.search.Searchable
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
@ -12,9 +13,10 @@ class PluginBadgeProvider(private val context: Context): BadgeProvider {
|
|||||||
override fun getBadge(searchable: Searchable): Flow<Badge?> {
|
override fun getBadge(searchable: Searchable): Flow<Badge?> {
|
||||||
if (searchable !is SavableSearchable) return flowOf(null)
|
if (searchable !is SavableSearchable) return flowOf(null)
|
||||||
return flow {
|
return flow {
|
||||||
|
emit(null)
|
||||||
val icon = searchable.getProviderIcon(context)
|
val icon = searchable.getProviderIcon(context)
|
||||||
if (icon != null) {
|
if (icon != null) {
|
||||||
emit(Badge(icon = icon))
|
emit(MutableBadge(icon = icon))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,20 +1,22 @@
|
|||||||
package de.mm20.launcher2.badges.providers
|
package de.mm20.launcher2.badges.providers
|
||||||
|
|
||||||
import de.mm20.launcher2.badges.Badge
|
import de.mm20.launcher2.badges.Badge
|
||||||
|
import de.mm20.launcher2.badges.MutableBadge
|
||||||
import de.mm20.launcher2.badges.R
|
import de.mm20.launcher2.badges.R
|
||||||
import de.mm20.launcher2.search.Application
|
import de.mm20.launcher2.search.Application
|
||||||
import de.mm20.launcher2.search.Searchable
|
import de.mm20.launcher2.search.Searchable
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.coroutines.flow.channelFlow
|
import kotlinx.coroutines.flow.channelFlow
|
||||||
|
import kotlinx.coroutines.flow.flowOf
|
||||||
import org.koin.core.component.KoinComponent
|
import org.koin.core.component.KoinComponent
|
||||||
|
|
||||||
class SuspendedAppsBadgeProvider : BadgeProvider, KoinComponent {
|
class SuspendedAppsBadgeProvider : BadgeProvider, KoinComponent {
|
||||||
|
|
||||||
override fun getBadge(searchable: Searchable): Flow<Badge?> = channelFlow {
|
override fun getBadge(searchable: Searchable): Flow<Badge?> {
|
||||||
if (searchable is Application && searchable.isSuspended) {
|
return if (searchable is Application && searchable.isSuspended) {
|
||||||
send(Badge(iconRes = R.drawable.ic_badge_suspended))
|
flowOf(MutableBadge(iconRes = R.drawable.ic_badge_suspended))
|
||||||
} else {
|
} else {
|
||||||
send(null)
|
flowOf(null)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1,6 +1,7 @@
|
|||||||
package de.mm20.launcher2.badges.providers
|
package de.mm20.launcher2.badges.providers
|
||||||
|
|
||||||
import de.mm20.launcher2.badges.Badge
|
import de.mm20.launcher2.badges.Badge
|
||||||
|
import de.mm20.launcher2.badges.MutableBadge
|
||||||
import de.mm20.launcher2.badges.R
|
import de.mm20.launcher2.badges.R
|
||||||
import de.mm20.launcher2.search.AppProfile
|
import de.mm20.launcher2.search.AppProfile
|
||||||
import de.mm20.launcher2.search.AppShortcut
|
import de.mm20.launcher2.search.AppShortcut
|
||||||
@ -13,7 +14,7 @@ class WorkProfileBadgeProvider : BadgeProvider {
|
|||||||
override fun getBadge(searchable: Searchable): Flow<Badge?> = flow {
|
override fun getBadge(searchable: Searchable): Flow<Badge?> = flow {
|
||||||
if (searchable is Application && searchable.profile == AppProfile.Work || searchable is AppShortcut && searchable.profile == AppProfile.Work) {
|
if (searchable is Application && searchable.profile == AppProfile.Work || searchable is AppShortcut && searchable.profile == AppProfile.Work) {
|
||||||
emit(
|
emit(
|
||||||
Badge(
|
MutableBadge(
|
||||||
iconRes = R.drawable.ic_badge_workprofile
|
iconRes = R.drawable.ic_badge_workprofile
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user