Skip to content

Commit dfb4271

Browse files
authored
Merge pull request #16 from PSDev/feature/widget_color
Add ability to edit widget color
2 parents 147cdcc + 80943a4 commit dfb4271

14 files changed

+251
-134
lines changed

app/build.gradle

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,4 @@
1-
plugins {
2-
id 'com.android.application'
3-
id 'com.github.triplet.play' version '3.1.0'
4-
}
5-
1+
apply plugin: 'com.android.application'
62
apply plugin: 'kotlin-android'
73
apply plugin: 'kotlin-kapt'
84
apply plugin: "androidx.navigation.safeargs.kotlin"
@@ -11,6 +7,7 @@ apply plugin: 'com.google.gms.google-services'
117
apply plugin: 'dagger.hilt.android.plugin'
128
apply plugin: 'com.mikepenz.aboutlibraries.plugin'
139
apply plugin: 'com.google.firebase.firebase-perf'
10+
apply plugin: 'com.github.triplet.play'
1411

1512
android {
1613
compileSdkVersion Config.compile_sdk
@@ -156,6 +153,9 @@ dependencies {
156153
// Android Material
157154
implementation Libs.material_components
158155

156+
// Color Picker
157+
implementation "com.github.dhaval2404:colorpicker:2.0"
158+
159159
// Dagger
160160
implementation Libs.daggerHiltAndroid
161161
kapt Libs.daggerHiltAndroidCompiler
@@ -200,14 +200,12 @@ kapt {
200200
correctErrorTypes true
201201
}
202202

203-
import com.github.triplet.gradle.androidpublisher.ResolutionStrategy
204203
play {
205204
def serviceAccountFileName = "google-play-api.json"
206205
if (rootProject.file(serviceAccountFileName).exists()) {
207206
serviceAccountCredentials.set(rootProject.file(serviceAccountFileName))
208207
} else if (System.getenv("ANDROID_PUBLISHER_CREDENTIALS") == null) {
209208
enabled.set(false)
210209
}
211-
resolutionStrategy.set(ResolutionStrategy.IGNORE)
212210
releaseName.set(project.version)
213211
}

app/src/main/java/de/psdev/devdrawer/appwidget/DDWidgetProvider.kt

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,7 @@ class DDWidgetProvider : AppWidgetProvider() {
105105
// Setup the widget, and data source / adapter
106106
val widgetView = RemoteViews(context.packageName, R.layout.widget_layout)
107107
val widgetColor = widget.color
108-
val contrastColor = getTextColor(widgetColor)
108+
val contrastColor = widgetColor.textColorForBackground()
109109

110110
// Set background color for widget
111111
widgetView.setInt(R.id.container_actions, "setBackgroundColor", widgetColor)
@@ -147,8 +147,4 @@ class DDWidgetProvider : AppWidgetProvider() {
147147
return widgetView
148148
}
149149

150-
private fun getTextColor(color: Int): Int = when (color) {
151-
Color.RED -> Color.WHITE
152-
else -> textColorForBackground(color)
153-
}
154150
}

app/src/main/java/de/psdev/devdrawer/profiles/FilterPreviewBottomSheetDialogFragment.kt

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ class FilterPreviewBottomSheetDialogFragment : BottomSheetDialogFragment() {
4949
_binding = it
5050
}.root
5151

52+
@Suppress("DEPRECATION")
5253
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
5354
super.onViewCreated(view, savedInstanceState)
5455
with(binding) {
@@ -57,17 +58,17 @@ class FilterPreviewBottomSheetDialogFragment : BottomSheetDialogFragment() {
5758
lifecycleScope.launchWhenResumed {
5859
withContext(Dispatchers.IO) {
5960
val filter = devDrawerDatabase.packageFilterDao().findById(navArgs.packageFilterId)
60-
?: throw IllegalArgumentException("Unknown filter")
61+
?: throw IllegalArgumentException("Unknown filter")
6162
val context = requireContext()
6263
val packageManager = context.packageManager
6364
val affectedApps = Firebase.performance.trace("profile_filter_preview") {
6465
packageManager.getInstalledPackages(PackageManager.GET_SIGNATURES)
65-
.asSequence()
66-
.map { it.toPackageHashInfo() }
67-
.filter { filter.matches(it) }
68-
.mapNotNull { it.toAppInfo(context) }
69-
.sortedBy { it.name }
70-
.toList()
66+
.asSequence()
67+
.map { it.toPackageHashInfo() }
68+
.filter { filter.matches(it) }
69+
.mapNotNull { it.toAppInfo(context) }
70+
.sortedBy { it.name }
71+
.toList()
7172
}
7273
logger.warn { "Affected apps: $affectedApps" }
7374
withContext(Dispatchers.Main) {

app/src/main/java/de/psdev/devdrawer/profiles/WidgetProfileEditFragment.kt

Lines changed: 39 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package de.psdev.devdrawer.profiles
22

3+
import android.database.sqlite.SQLiteConstraintException
34
import android.os.Bundle
45
import android.view.*
56
import androidx.core.view.isVisible
@@ -8,6 +9,7 @@ import androidx.navigation.fragment.findNavController
89
import androidx.navigation.fragment.navArgs
910
import androidx.recyclerview.widget.LinearLayoutManager
1011
import com.google.android.material.dialog.MaterialAlertDialogBuilder
12+
import com.google.android.material.snackbar.Snackbar
1113
import dagger.hilt.android.AndroidEntryPoint
1214
import de.psdev.devdrawer.BaseFragment
1315
import de.psdev.devdrawer.R
@@ -18,6 +20,7 @@ import de.psdev.devdrawer.receivers.UpdateReceiver
1820
import de.psdev.devdrawer.utils.awaitSubmit
1921
import de.psdev.devdrawer.utils.consume
2022
import kotlinx.coroutines.flow.*
23+
import kotlinx.coroutines.launch
2124
import mu.KLogging
2225
import reactivecircus.flowbinding.android.view.clicks
2326
import reactivecircus.flowbinding.android.widget.textChanges
@@ -35,26 +38,26 @@ class WidgetProfileEditFragment : BaseFragment<FragmentWidgetProfileEditBinding>
3538

3639
private val onDeleteClickListener: PackageFilterActionListener = { packageFilter ->
3740
MaterialAlertDialogBuilder(requireContext())
38-
.setTitle("Delete?")
39-
.setNegativeButton("No") { _, _ -> }
40-
.setPositiveButton("Yes") { _, _ ->
41-
lifecycleScope.launchWhenResumed {
42-
devDrawerDatabase.packageFilterDao().deleteById(packageFilter.id)
43-
UpdateReceiver.send(requireContext())
41+
.setTitle("Delete?")
42+
.setNegativeButton(R.string.no) { _, _ -> }
43+
.setPositiveButton(R.string.yes) { _, _ ->
44+
lifecycleScope.launchWhenResumed {
45+
devDrawerDatabase.packageFilterDao().deleteById(packageFilter.id)
46+
UpdateReceiver.send(requireContext())
47+
}
4448
}
45-
}
46-
.show()
49+
.show()
4750
}
4851
private val onPreviewFilterClickListener: PackageFilterActionListener = { packageFilter ->
4952
findNavController().navigate(
50-
WidgetProfileEditFragmentDirections.openFilterPreviewBottomSheetDialogFragment(
51-
packageFilterId = packageFilter.id
52-
)
53+
WidgetProfileEditFragmentDirections.openFilterPreviewBottomSheetDialogFragment(
54+
packageFilterId = packageFilter.id
55+
)
5356
)
5457
}
5558
private val listAdapter: PackageFilterListAdapter = PackageFilterListAdapter(
56-
onDeleteClickListener = onDeleteClickListener,
57-
onPreviewFilterClickListener = onPreviewFilterClickListener
59+
onDeleteClickListener = onDeleteClickListener,
60+
onPreviewFilterClickListener = onPreviewFilterClickListener
5861
)
5962
private var widgetProfile: WidgetProfile? = null
6063

@@ -66,9 +69,9 @@ class WidgetProfileEditFragment : BaseFragment<FragmentWidgetProfileEditBinding>
6669
}
6770

6871
override fun createViewBinding(
69-
inflater: LayoutInflater,
70-
container: ViewGroup?,
71-
savedInstanceState: Bundle?
72+
inflater: LayoutInflater,
73+
container: ViewGroup?,
74+
savedInstanceState: Bundle?
7275
): FragmentWidgetProfileEditBinding = FragmentWidgetProfileEditBinding.inflate(inflater, container, false)
7376

7477
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
@@ -79,19 +82,19 @@ class WidgetProfileEditFragment : BaseFragment<FragmentWidgetProfileEditBinding>
7982
btnAddFilter.setOnClickListener { _ ->
8083
widgetProfile?.let {
8184
val directions =
82-
WidgetProfileEditFragmentDirections.openAddPackageFilterBottomSheetDialogFragment(
83-
widgetProfileId = it.id
84-
)
85+
WidgetProfileEditFragmentDirections.openAddPackageFilterBottomSheetDialogFragment(
86+
widgetProfileId = it.id
87+
)
8588
findNavController().navigate(directions)
8689
}
8790
}
8891

8992
btnAddSignature.setOnClickListener {
9093
widgetProfile?.let {
9194
val directions =
92-
WidgetProfileEditFragmentDirections.openAppSignatureChooserBottomSheetDialogFragment(
93-
widgetProfileId = it.id
94-
)
95+
WidgetProfileEditFragmentDirections.openAppSignatureChooserBottomSheetDialogFragment(
96+
widgetProfileId = it.id
97+
)
9598
findNavController().navigate(directions)
9699
}
97100
}
@@ -142,18 +145,22 @@ class WidgetProfileEditFragment : BaseFragment<FragmentWidgetProfileEditBinding>
142145
override fun onOptionsItemSelected(item: MenuItem): Boolean = when (item.itemId) {
143146
R.id.action_delete -> consume {
144147
MaterialAlertDialogBuilder(requireContext())
145-
.setTitle("Delete profile?")
146-
.setNegativeButton("No") { _, _ -> }
147-
.setPositiveButton("Yes") { _, _ ->
148-
widgetProfile?.let { widgetProfile ->
149-
lifecycleScope.launchWhenResumed {
150-
devDrawerDatabase.widgetProfileDao().delete(widgetProfile)
151-
UpdateReceiver.send(requireContext())
152-
findNavController().popBackStack()
148+
.setTitle("Delete profile?")
149+
.setNegativeButton(R.string.no) { _, _ -> }
150+
.setPositiveButton(R.string.yes) { _, _ ->
151+
widgetProfile?.let { widgetProfile ->
152+
lifecycleScope.launch {
153+
try {
154+
devDrawerDatabase.widgetProfileDao().delete(widgetProfile)
155+
UpdateReceiver.send(requireContext())
156+
findNavController().popBackStack()
157+
} catch (e: SQLiteConstraintException) {
158+
Snackbar.make(binding.root, R.string.error_profile_in_use, Snackbar.LENGTH_LONG).show()
159+
}
160+
}
153161
}
154162
}
155-
}
156-
.show()
163+
.show()
157164
}
158165
else -> super.onOptionsItemSelected(item)
159166
}

app/src/main/java/de/psdev/devdrawer/profiles/WidgetProfileListFragment.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,7 @@ class WidgetProfileListFragment: BaseFragment<FragmentWidgetProfileListBinding>(
117117
try {
118118
devDrawerDatabase.widgetProfileDao().delete(widgetProfile)
119119
} catch (e: SQLiteConstraintException) {
120-
Snackbar.make(binding.root, "Cannot delete profile, still being used by widgets", Snackbar.LENGTH_LONG).show()
120+
Snackbar.make(binding.root, R.string.error_profile_in_use, Snackbar.LENGTH_LONG).show()
121121
}
122122
}
123123
tracker.deselect(selectedProfile)

app/src/main/java/de/psdev/devdrawer/utils/Bindings.kt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ package de.psdev.devdrawer.utils
22

33
import android.widget.Button
44
import android.widget.EditText
5-
import kotlinx.coroutines.channels.SendChannel
5+
import kotlinx.coroutines.flow.MutableSharedFlow
66
import kotlinx.coroutines.flow.MutableStateFlow
77
import kotlinx.coroutines.flow.map
88
import kotlinx.coroutines.flow.onEach
@@ -13,5 +13,5 @@ fun MutableStateFlow<String>.receiveTextChangesFrom(editText: EditText) = editTe
1313
.map { it.toString() }
1414
.onEach { value = it }
1515

16-
fun SendChannel<Unit>.receiveClicksFrom(button: Button) = button.clicks()
17-
.onEach { offer(it) }
16+
fun MutableSharedFlow<Unit>.receiveClicksFrom(button: Button) = button.clicks()
17+
.onEach { emit(it) }

app/src/main/java/de/psdev/devdrawer/utils/Colors.kt

Lines changed: 44 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -7,26 +7,55 @@ import androidx.core.graphics.blue
77
import androidx.core.graphics.green
88
import androidx.core.graphics.red
99

10-
fun Int.rgbToYiq(): Int = ((red * 299) + (green * 587) + (blue * 114)) / 1000
11-
12-
fun getContrastColor(@ColorInt color: Int): Int {
13-
val whiteContrast = ColorUtils.calculateContrast(Color.WHITE, color)
14-
val blackContrast = ColorUtils.calculateContrast(Color.BLACK, color)
15-
return if (whiteContrast > blackContrast) Color.WHITE else Color.BLACK
16-
}
17-
18-
fun textColorForBackground(backgroundColor: Int): Int {
19-
val r = backgroundColor.red * 255
20-
val g = backgroundColor.green * 255
21-
val b = backgroundColor.blue * 255
10+
@ColorInt
11+
fun @receiver:ColorInt Int.textColorForBackground(): Int {
12+
val r = red
13+
val g = green
14+
val b = blue
2215
val yiq = (r * 299 + g * 587 + b * 114) / 1000
23-
return if (yiq >= 128) Color.BLACK else Color.WHITE
16+
return if (yiq >= 160) Color.BLACK else Color.WHITE
2417
}
2518

2619
@ColorInt
27-
fun getDesaturatedColor(@ColorInt color: Int): Int {
20+
fun @receiver:ColorInt Int.getDesaturatedColor(): Int {
2821
val result = FloatArray(size = 3)
29-
Color.colorToHSV(color, result)
22+
Color.colorToHSV(this, result)
3023
result[1] *= 0.6f
3124
return Color.HSVToColor(result)
25+
}
26+
27+
/**
28+
* from https://medium.com/@anthony.st91/sort-things-by-colors-in-android-f34dc2c9f4b7
29+
*/
30+
fun @receiver:ColorInt Int.getHSL(): FloatArray {
31+
val hsl = FloatArray(3)
32+
ColorUtils.colorToHSL(this, hsl)
33+
for (i in hsl.indices) {
34+
hsl[i] = hsl[i] * 100
35+
}
36+
return hsl
37+
}
38+
39+
/**
40+
* from https://medium.com/@anthony.st91/sort-things-by-colors-in-android-f34dc2c9f4b7
41+
*/
42+
fun @receiver:ColorInt IntArray.sortColorList(): List<Int> = sortedWith { o1, o2 ->
43+
val hsl1 = o1.getHSL()
44+
val hsl2 = o2.getHSL()
45+
46+
// Colors have the same Hue
47+
if (hsl1[0] == hsl2[0]) {
48+
49+
// Colors have the same saturation
50+
if (hsl1[1] == hsl2[1]) {
51+
// Compare lightness
52+
(hsl1[2] - hsl2[2]).toInt()
53+
54+
} else {
55+
(hsl1[1] - hsl2[1]).toInt()
56+
}
57+
58+
} else {
59+
(hsl1[0] - hsl2[0]).toInt()
60+
}
3261
}

0 commit comments

Comments
 (0)