Refactor badges and notifications, migrate badge settings

This commit is contained in:
MM20
2022-01-15 14:31:13 +01:00
parent dc64fdebdd
commit 6ca440f553
40 changed files with 937 additions and 303 deletions
+2
View File
@@ -44,6 +44,8 @@ dependencies {
implementation(libs.koin.android)
implementation(project(":ktx"))
implementation(project(":applications"))
implementation(project(":notifications"))
implementation(project(":preferences"))
implementation(project(":base"))
@@ -0,0 +1,88 @@
package de.mm20.launcher2.badges
import android.content.Context
import android.util.Log
import de.mm20.launcher2.badges.providers.*
import de.mm20.launcher2.badges.providers.BadgeProvider
import de.mm20.launcher2.preferences.LauncherDataStore
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.launch
import org.koin.core.component.KoinComponent
import org.koin.core.component.inject
interface BadgeRepository {
fun getBadge(badgeKey: String): Flow<Badge?>
}
internal class BadgeRepositoryImpl(private val context: Context) : BadgeRepository, KoinComponent {
private val dataStore: LauncherDataStore by inject()
private val scope = CoroutineScope(Dispatchers.Main + Job())
private val badgeProviders = MutableStateFlow<List<BadgeProvider>>(emptyList())
init {
scope.launch {
dataStore.data.map { it.badges }.distinctUntilChanged().collectLatest {
val providers = mutableListOf<BadgeProvider>()
if (it.notifications) {
providers += NotificationBadgeProvider()
}
if (it.cloudFiles) {
providers += CloudBadgeProvider()
}
if (it.shortcuts) {
providers += AppShortcutBadgeProvider(context)
}
if (it.suspendedApps) {
providers += SuspendedAppsBadgeProvider()
}
providers += WorkProfileBadgeProvider(context)
badgeProviders.value = providers
}
}
}
override fun getBadge(badgeKey: String): Flow<Badge?> = channelFlow {
badgeProviders.collectLatest { providers ->
if (providers.isEmpty()) {
send(null)
return@collectLatest
}
combine(providers.map { it.getBadge(badgeKey) }) { 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)
}
}
}
}
@@ -5,4 +5,5 @@ import org.koin.dsl.module
val badgesModule = module {
single { BadgeProvider(androidContext()) }
single<BadgeRepository> { BadgeRepositoryImpl(androidContext()) }
}
@@ -0,0 +1,38 @@
package de.mm20.launcher2.badges.providers
import android.content.ComponentName
import android.content.Context
import android.content.pm.PackageManager
import de.mm20.launcher2.badges.Badge
import de.mm20.launcher2.graphics.BadgeDrawable
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.channelFlow
import kotlinx.coroutines.withContext
class AppShortcutBadgeProvider(
private val context: Context
) : BadgeProvider {
override fun getBadge(badgeKey: String): Flow<Badge?> = channelFlow {
if (badgeKey.startsWith("shortcut://")) {
val componentName = ComponentName.unflattenFromString(badgeKey.substring(11))
if (componentName == null) {
send(null)
return@channelFlow
}
withContext(Dispatchers.IO) {
val icon = try {
context.packageManager.getActivityIcon(
componentName
)
} catch (e: PackageManager.NameNotFoundException) {
return@withContext
}
val badge = Badge(icon = BadgeDrawable(context, icon))
send(badge)
}
} else {
send(null)
}
}
}
@@ -0,0 +1,13 @@
package de.mm20.launcher2.badges.providers
import de.mm20.launcher2.badges.Badge
import kotlinx.coroutines.flow.Flow
interface BadgeProvider {
/**
* This must emit a value as soon as possible because the
* BadgeRepository is waiting for values from every provider.
* null must be emitted if no badge should be shown.
*/
fun getBadge(badgeKey: String): Flow<Badge?>
}
@@ -0,0 +1,19 @@
package de.mm20.launcher2.badges.providers
import android.util.Log
import de.mm20.launcher2.badges.Badge
import de.mm20.launcher2.badges.R
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flow
class CloudBadgeProvider: BadgeProvider {
override fun getBadge(badgeKey: String): Flow<Badge?> = flow {
when(badgeKey) {
"gdrive://" -> emit(Badge(iconRes = R.drawable.ic_badge_gdrive))
"onedrive://" -> emit(Badge(iconRes = R.drawable.ic_badge_onedrive))
"owncloud://" -> emit(Badge(iconRes = R.drawable.ic_badge_owncloud))
"nextcloud://" -> emit(Badge(iconRes = R.drawable.ic_badge_nextcloud))
else -> emit(null)
}
}
}
@@ -0,0 +1,34 @@
package de.mm20.launcher2.badges.providers
import de.mm20.launcher2.badges.Badge
import de.mm20.launcher2.notifications.NotificationRepository
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.channelFlow
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.map
import org.koin.core.component.KoinComponent
import org.koin.core.component.inject
class NotificationBadgeProvider : BadgeProvider, KoinComponent {
private val notificationRepository: NotificationRepository by inject()
override fun getBadge(badgeKey: String): Flow<Badge?> = channelFlow {
if (badgeKey.startsWith("app://")) {
val packageName = badgeKey.substring(6)
notificationRepository.notifications.map {
it.filter { it.packageName == packageName }
}.collectLatest {
if (it.isEmpty()) {
send(null)
} else {
val badge = Badge(number = it.sumOf {
it.notification.number
})
send(badge)
}
}
} else {
send(null)
}
}
}
@@ -0,0 +1,33 @@
package de.mm20.launcher2.badges.providers
import de.mm20.launcher2.applications.AppRepository
import de.mm20.launcher2.badges.Badge
import de.mm20.launcher2.badges.R
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.channelFlow
import kotlinx.coroutines.flow.collectLatest
import org.koin.core.component.KoinComponent
import org.koin.core.component.inject
class SuspendedAppsBadgeProvider : BadgeProvider, KoinComponent {
private val appRepository: AppRepository by inject()
override fun getBadge(badgeKey: String): Flow<Badge?> = channelFlow {
if (badgeKey.startsWith("app://")) {
val packageName = badgeKey.substring(6)
appRepository.getSuspendedPackages().collectLatest {
if (it.contains(packageName)) {
send(
Badge(
iconRes = R.drawable.ic_badge_suspended
)
)
} else {
send(null)
}
}
} else {
send(null)
}
}
}
@@ -0,0 +1,28 @@
package de.mm20.launcher2.badges.providers
import android.content.Context
import android.os.Process
import de.mm20.launcher2.badges.Badge
import de.mm20.launcher2.badges.R
import de.mm20.launcher2.ktx.getSerialNumber
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flow
class WorkProfileBadgeProvider(private val context: Context) : BadgeProvider {
override fun getBadge(badgeKey: String): Flow<Badge?> = flow {
if (badgeKey.startsWith("profile://")) {
val serialNumber = badgeKey.substring(10).toLong()
if (serialNumber != Process.myUserHandle().getSerialNumber(context)) {
emit(
Badge(
iconRes = R.drawable.ic_badge_workprofile
)
)
} else {
emit(null)
}
} else {
emit(null)
}
}
}