Reorganize and group modules

This commit is contained in:
MM20
2022-12-13 17:37:26 +01:00
parent bac24baad2
commit 3f8880a90a
995 changed files with 501 additions and 298 deletions
+1
View File
@@ -0,0 +1 @@
/build
+53
View File
@@ -0,0 +1,53 @@
plugins {
id("com.android.library")
id("kotlin-android")
}
android {
compileSdk = sdk.versions.compileSdk.get().toInt()
defaultConfig {
minSdk = sdk.versions.minSdk.get().toInt()
targetSdk = sdk.versions.targetSdk.get().toInt()
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
consumerProguardFiles("consumer-rules.pro")
}
buildTypes {
release {
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
)
}
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = "1.8"
}
namespace = "de.mm20.launcher2.badges"
}
dependencies {
implementation(libs.bundles.kotlin)
implementation(libs.androidx.core)
implementation(libs.androidx.appcompat)
implementation(libs.bundles.androidx.lifecycle)
implementation(libs.koin.android)
implementation(project(":core:ktx"))
implementation(project(":data:applications"))
implementation(project(":data:appshortcuts"))
implementation(project(":data:notifications"))
implementation(project(":core:preferences"))
implementation(project(":core:base"))
implementation(project(":data:files"))
}
View File
+21
View File
@@ -0,0 +1,21 @@
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.kts.kts.kts.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable
# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile
@@ -0,0 +1,4 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
/
</manifest>
@@ -0,0 +1,10 @@
package de.mm20.launcher2.badges
import android.graphics.drawable.Drawable
data class Badge(
var number: Int? = null,
var progress: Float? = null,
var iconRes: Int? = null,
var icon: Drawable? = null
)
@@ -0,0 +1,86 @@
package de.mm20.launcher2.badges
import android.content.Context
import de.mm20.launcher2.badges.providers.*
import de.mm20.launcher2.preferences.LauncherDataStore
import de.mm20.launcher2.search.Searchable
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.*
import org.koin.core.component.KoinComponent
import org.koin.core.component.inject
interface BadgeRepository {
fun getBadge(searchable: Searchable): Flow<Badge?>
}
internal class BadgeRepositoryImpl(private val context: Context) : BadgeRepository, KoinComponent {
private val dataStore: LauncherDataStore by inject()
private val scope = CoroutineScope(Job() + Dispatchers.Default)
private val badgeProviders = MutableStateFlow<List<BadgeProvider>>(emptyList())
init {
scope.launch {
dataStore.data.map { it.badges }.distinctUntilChanged().collectLatest {
val providers = mutableListOf<BadgeProvider>()
providers += WorkProfileBadgeProvider()
if (it.notifications) {
providers += NotificationBadgeProvider()
}
if (it.cloudFiles) {
providers += CloudBadgeProvider()
}
if (it.shortcuts) {
providers += AppShortcutBadgeProvider(context)
}
if (it.suspendedApps) {
providers += SuspendedAppsBadgeProvider()
}
badgeProviders.value = providers
}
}
}
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)
}
}
}
}
}
@@ -0,0 +1,8 @@
package de.mm20.launcher2.badges
import org.koin.android.ext.koin.androidContext
import org.koin.dsl.module
val badgesModule = module {
single<BadgeRepository> { BadgeRepositoryImpl(androidContext()) }
}
@@ -0,0 +1,57 @@
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.graphics.BadgeDrawable
import de.mm20.launcher2.search.data.LauncherShortcut
import de.mm20.launcher2.search.data.LegacyShortcut
import de.mm20.launcher2.search.Searchable
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(searchable: Searchable): Flow<Badge?> = channelFlow {
if (searchable is LauncherShortcut) {
val componentName = searchable.launcherShortcut.activity
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 if (searchable is LegacyShortcut) {
val packageName = searchable.packageName
if (packageName == null) {
send(null)
return@channelFlow
}
withContext(Dispatchers.IO) {
val icon = try {
context.packageManager.getApplicationIcon(
packageName
)
} catch (e: PackageManager.NameNotFoundException) {
return@withContext
}
val badge = Badge(icon = BadgeDrawable(context, icon))
send(badge)
}
} else {
send(null)
}
}
}
@@ -0,0 +1,14 @@
package de.mm20.launcher2.badges.providers
import de.mm20.launcher2.badges.Badge
import de.mm20.launcher2.search.Searchable
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(searchable: Searchable): Flow<Badge?>
}
@@ -0,0 +1,20 @@
package de.mm20.launcher2.badges.providers
import de.mm20.launcher2.badges.Badge
import de.mm20.launcher2.search.data.File
import de.mm20.launcher2.search.Searchable
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flow
class CloudBadgeProvider: BadgeProvider {
override fun getBadge(searchable: Searchable): Flow<Badge?> = flow {
if (searchable is File) {
val iconResId = searchable.providerIconRes
if (iconResId != null) {
emit(Badge(iconRes = iconResId))
return@flow
}
}
emit(null)
}
}
@@ -0,0 +1,50 @@
package de.mm20.launcher2.badges.providers
import android.app.Notification
import de.mm20.launcher2.badges.Badge
import de.mm20.launcher2.notifications.NotificationRepository
import de.mm20.launcher2.search.Searchable
import de.mm20.launcher2.search.data.LauncherApp
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(searchable: Searchable): Flow<Badge?> = channelFlow {
if (searchable is LauncherApp) {
val packageName = searchable.`package`
notificationRepository.notifications.map {
it.filter { it.packageName == packageName }
}.collectLatest {
if (it.isEmpty()) {
send(null)
} else {
val badge = Badge(
number = it.distinctBy { it.notification.shortcutId }.sumOf {
if(it.notification.shortcutId == null) 0
else it.notification.number
},
progress = it.mapNotNull {
if (!it.notification.extras.containsKey(Notification.EXTRA_PROGRESS)) return@mapNotNull null
val progress = it.notification.extras.getInt(Notification.EXTRA_PROGRESS)
val progressMax = it.notification.extras.getInt(Notification.EXTRA_PROGRESS_MAX).takeIf { it > 0 } ?: return@mapNotNull null
return@mapNotNull progress.toFloat() / progressMax.toFloat()
}
.takeIf { it.isNotEmpty() }
?.let {
it.sumOf { it.toDouble() }.toFloat() / it.size
}
)
send(badge)
}
}
} else {
send(null)
}
}
}
@@ -0,0 +1,35 @@
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 de.mm20.launcher2.search.Searchable
import de.mm20.launcher2.search.data.LauncherApp
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(searchable: Searchable): Flow<Badge?> = channelFlow {
if (searchable is LauncherApp) {
val packageName = searchable.`package`
appRepository.getSuspendedPackages().collectLatest {
if (it.contains(packageName)) {
send(
Badge(
iconRes = R.drawable.ic_badge_suspended
)
)
} else {
send(null)
}
}
} else {
send(null)
}
}
}
@@ -0,0 +1,23 @@
package de.mm20.launcher2.badges.providers
import de.mm20.launcher2.badges.Badge
import de.mm20.launcher2.badges.R
import de.mm20.launcher2.search.data.LauncherApp
import de.mm20.launcher2.search.data.LauncherShortcut
import de.mm20.launcher2.search.Searchable
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flow
class WorkProfileBadgeProvider : BadgeProvider {
override fun getBadge(searchable: Searchable): Flow<Badge?> = flow {
if (searchable is LauncherApp && !searchable.isMainProfile || searchable is LauncherShortcut && !searchable.isMainProfile) {
emit(
Badge(
iconRes = R.drawable.ic_badge_workprofile
)
)
} else {
emit(null)
}
}
}