1 /*
2  * Copyright 2024 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.photopicker.core.theme
18 
19 import android.content.Intent
20 import android.os.Bundle
21 import android.provider.MediaStore
22 import androidx.compose.ui.graphics.Color
23 import androidx.compose.ui.graphics.luminance
24 
25 /**
26  * Holds parameters and utility methods for setting a custom picker accent color.
27  *
28  * Only valid input color codes with luminance greater than 0.6 will be set on major picker
29  * components.
30  *
31  * This feature can only be used for photo picker opened in [MediaStore#ACTION_PICK_IMAGES] mode.
32  */
33 class AccentColorHelper(
34     intent: Intent?
35 ) {
36     private val DARK_TEXT_COLOR = "#000000"
37     private val LIGHT_TEXT_COLOR = "#FFFFFF"
38 
39     private var accentColor = Color.Unspecified
40 
41     private var textColorForAccentComponents = Color.Unspecified
42 
43     init {
<lambda>null44         intent?.let {
45             val extras: Bundle? = intent.extras
46             val inputColor = extras?.getLong(
47                 MediaStore.EXTRA_PICK_IMAGES_ACCENT_COLOR,
48                 -1
49             ) ?: -1
50 
51             if (inputColor > -1) { // if the accent color is present in extras.
52                 if (intent.action != MediaStore.ACTION_PICK_IMAGES) {
53                     throw IllegalArgumentException(
54                         "Accent color customisation is not " + "available for " + intent.action +
55                                 " action.",
56                     )
57                 }
58                 accentColor = checkColorValidityAndGetColor(inputColor)
59 
60                 // pickerAccentColor being equal to Color.Unspecified would mean that the color
61                 // passed as an input does not satisfy the validity tests for being an accent
62                 // color.
63                 if (accentColor != Color.Unspecified) {
64                     textColorForAccentComponents = Color(
65                         android.graphics.Color.parseColor(
66                             if (isAccentColorBright(accentColor.luminance()))
67                                 DARK_TEXT_COLOR
68                             else
69                                 LIGHT_TEXT_COLOR,
70                         ),
71                     )
72                 } else {
73                     throw IllegalArgumentException("Color not valid for accent color")
74                 }
75             }
76         }
77     }
78 
79 
80     /**
81      * Checks input color validity and returns the color without alpha component if valid, or -1.
82      */
checkColorValidityAndGetColornull83     private fun checkColorValidityAndGetColor(color: Long): Color {
84         // Gives us the formatted color string where the mask gives us the color in the RRGGBB
85         // format and the %06X gives zero-padded hex (6 characters long)
86         val hexColor = String.format("#%06X", (0xFFFFFF.toLong() and color))
87         val inputColor = android.graphics.Color.parseColor(hexColor)
88         if (!isColorFeasibleForBothBackgrounds(Color(inputColor).luminance())) {
89             return Color.Unspecified
90         }
91         return Color(inputColor)
92     }
93 
94 
95     /**
96      * Returns true if the input color is within the range of [0.05 to 0.9] so that the color works
97      * both on light and dark background. Range has been set by testing with different colors.
98      */
isColorFeasibleForBothBackgroundsnull99     private fun isColorFeasibleForBothBackgrounds(luminance: Float): Boolean {
100         return luminance >= 0.05 && luminance < 0.9
101     }
102 
103     /**
104      * Indicates if the accent color is bright (luminance >= 0.6).
105      */
isAccentColorBrightnull106     private fun isAccentColorBright(accentColorLuminance: Float): Boolean =
107         accentColorLuminance >= 0.6
108 
109     /**
110      * Returns the accent color which has been passed as an input in the picker intent.
111      *
112      * Default value for this is [Color.Unspecified]
113      */
114     fun getAccentColor(): Color {
115         return accentColor
116     }
117 
118     /**
119      * Returns the appropriate text color for components using the accent color as the background
120      * which has been passed as an input in the picker intent.
121      *
122      * Default value for this is [Color.Unspecified]
123      */
getTextColorForAccentComponentsnull124     fun getTextColorForAccentComponents(): Color {
125         return textColorForAccentComponents
126     }
127 }
128