Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 5 additions & 7 deletions app/build.gradle
Original file line number Diff line number Diff line change
@@ -1,8 +1,4 @@
plugins {
id 'com.android.application'
id 'com.github.triplet.play' version '3.1.0'
}

apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-kapt'
apply plugin: "androidx.navigation.safeargs.kotlin"
Expand All @@ -11,6 +7,7 @@ apply plugin: 'com.google.gms.google-services'
apply plugin: 'dagger.hilt.android.plugin'
apply plugin: 'com.mikepenz.aboutlibraries.plugin'
apply plugin: 'com.google.firebase.firebase-perf'
apply plugin: 'com.github.triplet.play'

android {
compileSdkVersion Config.compile_sdk
Expand Down Expand Up @@ -156,6 +153,9 @@ dependencies {
// Android Material
implementation Libs.material_components

// Color Picker
implementation "com.github.dhaval2404:colorpicker:2.0"

// Dagger
implementation Libs.daggerHiltAndroid
kapt Libs.daggerHiltAndroidCompiler
Expand Down Expand Up @@ -200,14 +200,12 @@ kapt {
correctErrorTypes true
}

import com.github.triplet.gradle.androidpublisher.ResolutionStrategy
play {
def serviceAccountFileName = "google-play-api.json"
if (rootProject.file(serviceAccountFileName).exists()) {
serviceAccountCredentials.set(rootProject.file(serviceAccountFileName))
} else if (System.getenv("ANDROID_PUBLISHER_CREDENTIALS") == null) {
enabled.set(false)
}
resolutionStrategy.set(ResolutionStrategy.IGNORE)
releaseName.set(project.version)
}
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ class DDWidgetProvider : AppWidgetProvider() {
// Setup the widget, and data source / adapter
val widgetView = RemoteViews(context.packageName, R.layout.widget_layout)
val widgetColor = widget.color
val contrastColor = getTextColor(widgetColor)
val contrastColor = widgetColor.textColorForBackground()

// Set background color for widget
widgetView.setInt(R.id.container_actions, "setBackgroundColor", widgetColor)
Expand Down Expand Up @@ -147,8 +147,4 @@ class DDWidgetProvider : AppWidgetProvider() {
return widgetView
}

private fun getTextColor(color: Int): Int = when (color) {
Color.RED -> Color.WHITE
else -> textColorForBackground(color)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ class FilterPreviewBottomSheetDialogFragment : BottomSheetDialogFragment() {
_binding = it
}.root

@Suppress("DEPRECATION")
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
with(binding) {
Expand All @@ -57,17 +58,17 @@ class FilterPreviewBottomSheetDialogFragment : BottomSheetDialogFragment() {
lifecycleScope.launchWhenResumed {
withContext(Dispatchers.IO) {
val filter = devDrawerDatabase.packageFilterDao().findById(navArgs.packageFilterId)
?: throw IllegalArgumentException("Unknown filter")
?: throw IllegalArgumentException("Unknown filter")
val context = requireContext()
val packageManager = context.packageManager
val affectedApps = Firebase.performance.trace("profile_filter_preview") {
packageManager.getInstalledPackages(PackageManager.GET_SIGNATURES)
.asSequence()
.map { it.toPackageHashInfo() }
.filter { filter.matches(it) }
.mapNotNull { it.toAppInfo(context) }
.sortedBy { it.name }
.toList()
.asSequence()
.map { it.toPackageHashInfo() }
.filter { filter.matches(it) }
.mapNotNull { it.toAppInfo(context) }
.sortedBy { it.name }
.toList()
}
logger.warn { "Affected apps: $affectedApps" }
withContext(Dispatchers.Main) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package de.psdev.devdrawer.profiles

import android.database.sqlite.SQLiteConstraintException
import android.os.Bundle
import android.view.*
import androidx.core.view.isVisible
Expand All @@ -8,6 +9,7 @@ import androidx.navigation.fragment.findNavController
import androidx.navigation.fragment.navArgs
import androidx.recyclerview.widget.LinearLayoutManager
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.google.android.material.snackbar.Snackbar
import dagger.hilt.android.AndroidEntryPoint
import de.psdev.devdrawer.BaseFragment
import de.psdev.devdrawer.R
Expand All @@ -18,6 +20,7 @@ import de.psdev.devdrawer.receivers.UpdateReceiver
import de.psdev.devdrawer.utils.awaitSubmit
import de.psdev.devdrawer.utils.consume
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.launch
import mu.KLogging
import reactivecircus.flowbinding.android.view.clicks
import reactivecircus.flowbinding.android.widget.textChanges
Expand All @@ -35,26 +38,26 @@ class WidgetProfileEditFragment : BaseFragment<FragmentWidgetProfileEditBinding>

private val onDeleteClickListener: PackageFilterActionListener = { packageFilter ->
MaterialAlertDialogBuilder(requireContext())
.setTitle("Delete?")
.setNegativeButton("No") { _, _ -> }
.setPositiveButton("Yes") { _, _ ->
lifecycleScope.launchWhenResumed {
devDrawerDatabase.packageFilterDao().deleteById(packageFilter.id)
UpdateReceiver.send(requireContext())
.setTitle("Delete?")
.setNegativeButton(R.string.no) { _, _ -> }
.setPositiveButton(R.string.yes) { _, _ ->
lifecycleScope.launchWhenResumed {
devDrawerDatabase.packageFilterDao().deleteById(packageFilter.id)
UpdateReceiver.send(requireContext())
}
}
}
.show()
.show()
}
private val onPreviewFilterClickListener: PackageFilterActionListener = { packageFilter ->
findNavController().navigate(
WidgetProfileEditFragmentDirections.openFilterPreviewBottomSheetDialogFragment(
packageFilterId = packageFilter.id
)
WidgetProfileEditFragmentDirections.openFilterPreviewBottomSheetDialogFragment(
packageFilterId = packageFilter.id
)
)
}
private val listAdapter: PackageFilterListAdapter = PackageFilterListAdapter(
onDeleteClickListener = onDeleteClickListener,
onPreviewFilterClickListener = onPreviewFilterClickListener
onDeleteClickListener = onDeleteClickListener,
onPreviewFilterClickListener = onPreviewFilterClickListener
)
private var widgetProfile: WidgetProfile? = null

Expand All @@ -66,9 +69,9 @@ class WidgetProfileEditFragment : BaseFragment<FragmentWidgetProfileEditBinding>
}

override fun createViewBinding(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): FragmentWidgetProfileEditBinding = FragmentWidgetProfileEditBinding.inflate(inflater, container, false)

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
Expand All @@ -79,19 +82,19 @@ class WidgetProfileEditFragment : BaseFragment<FragmentWidgetProfileEditBinding>
btnAddFilter.setOnClickListener { _ ->
widgetProfile?.let {
val directions =
WidgetProfileEditFragmentDirections.openAddPackageFilterBottomSheetDialogFragment(
widgetProfileId = it.id
)
WidgetProfileEditFragmentDirections.openAddPackageFilterBottomSheetDialogFragment(
widgetProfileId = it.id
)
findNavController().navigate(directions)
}
}

btnAddSignature.setOnClickListener {
widgetProfile?.let {
val directions =
WidgetProfileEditFragmentDirections.openAppSignatureChooserBottomSheetDialogFragment(
widgetProfileId = it.id
)
WidgetProfileEditFragmentDirections.openAppSignatureChooserBottomSheetDialogFragment(
widgetProfileId = it.id
)
findNavController().navigate(directions)
}
}
Expand Down Expand Up @@ -142,18 +145,22 @@ class WidgetProfileEditFragment : BaseFragment<FragmentWidgetProfileEditBinding>
override fun onOptionsItemSelected(item: MenuItem): Boolean = when (item.itemId) {
R.id.action_delete -> consume {
MaterialAlertDialogBuilder(requireContext())
.setTitle("Delete profile?")
.setNegativeButton("No") { _, _ -> }
.setPositiveButton("Yes") { _, _ ->
widgetProfile?.let { widgetProfile ->
lifecycleScope.launchWhenResumed {
devDrawerDatabase.widgetProfileDao().delete(widgetProfile)
UpdateReceiver.send(requireContext())
findNavController().popBackStack()
.setTitle("Delete profile?")
.setNegativeButton(R.string.no) { _, _ -> }
.setPositiveButton(R.string.yes) { _, _ ->
widgetProfile?.let { widgetProfile ->
lifecycleScope.launch {
try {
devDrawerDatabase.widgetProfileDao().delete(widgetProfile)
UpdateReceiver.send(requireContext())
findNavController().popBackStack()
} catch (e: SQLiteConstraintException) {
Snackbar.make(binding.root, R.string.error_profile_in_use, Snackbar.LENGTH_LONG).show()
}
}
}
}
}
.show()
.show()
}
else -> super.onOptionsItemSelected(item)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ class WidgetProfileListFragment: BaseFragment<FragmentWidgetProfileListBinding>(
try {
devDrawerDatabase.widgetProfileDao().delete(widgetProfile)
} catch (e: SQLiteConstraintException) {
Snackbar.make(binding.root, "Cannot delete profile, still being used by widgets", Snackbar.LENGTH_LONG).show()
Snackbar.make(binding.root, R.string.error_profile_in_use, Snackbar.LENGTH_LONG).show()
}
}
tracker.deselect(selectedProfile)
Expand Down
6 changes: 3 additions & 3 deletions app/src/main/java/de/psdev/devdrawer/utils/Bindings.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ package de.psdev.devdrawer.utils

import android.widget.Button
import android.widget.EditText
import kotlinx.coroutines.channels.SendChannel
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onEach
Expand All @@ -13,5 +13,5 @@ fun MutableStateFlow<String>.receiveTextChangesFrom(editText: EditText) = editTe
.map { it.toString() }
.onEach { value = it }

fun SendChannel<Unit>.receiveClicksFrom(button: Button) = button.clicks()
.onEach { offer(it) }
fun MutableSharedFlow<Unit>.receiveClicksFrom(button: Button) = button.clicks()
.onEach { emit(it) }
59 changes: 44 additions & 15 deletions app/src/main/java/de/psdev/devdrawer/utils/Colors.kt
Original file line number Diff line number Diff line change
Expand Up @@ -7,26 +7,55 @@ import androidx.core.graphics.blue
import androidx.core.graphics.green
import androidx.core.graphics.red

fun Int.rgbToYiq(): Int = ((red * 299) + (green * 587) + (blue * 114)) / 1000

fun getContrastColor(@ColorInt color: Int): Int {
val whiteContrast = ColorUtils.calculateContrast(Color.WHITE, color)
val blackContrast = ColorUtils.calculateContrast(Color.BLACK, color)
return if (whiteContrast > blackContrast) Color.WHITE else Color.BLACK
}

fun textColorForBackground(backgroundColor: Int): Int {
val r = backgroundColor.red * 255
val g = backgroundColor.green * 255
val b = backgroundColor.blue * 255
@ColorInt
fun @receiver:ColorInt Int.textColorForBackground(): Int {
val r = red
val g = green
val b = blue
val yiq = (r * 299 + g * 587 + b * 114) / 1000
return if (yiq >= 128) Color.BLACK else Color.WHITE
return if (yiq >= 160) Color.BLACK else Color.WHITE
}

@ColorInt
fun getDesaturatedColor(@ColorInt color: Int): Int {
fun @receiver:ColorInt Int.getDesaturatedColor(): Int {
val result = FloatArray(size = 3)
Color.colorToHSV(color, result)
Color.colorToHSV(this, result)
result[1] *= 0.6f
return Color.HSVToColor(result)
}

/**
* from https://medium.com/@anthony.st91/sort-things-by-colors-in-android-f34dc2c9f4b7
*/
fun @receiver:ColorInt Int.getHSL(): FloatArray {
val hsl = FloatArray(3)
ColorUtils.colorToHSL(this, hsl)
for (i in hsl.indices) {
hsl[i] = hsl[i] * 100
}
return hsl
}

/**
* from https://medium.com/@anthony.st91/sort-things-by-colors-in-android-f34dc2c9f4b7
*/
fun @receiver:ColorInt IntArray.sortColorList(): List<Int> = sortedWith { o1, o2 ->
val hsl1 = o1.getHSL()
val hsl2 = o2.getHSL()

// Colors have the same Hue
if (hsl1[0] == hsl2[0]) {

// Colors have the same saturation
if (hsl1[1] == hsl2[1]) {
// Compare lightness
(hsl1[2] - hsl2[2]).toInt()

} else {
(hsl1[1] - hsl2[1]).toInt()
}

} else {
(hsl1[0] - hsl2[0]).toInt()
}
}
Loading