• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.isUnspecified
24 import androidx.compose.ui.graphics.luminance
25 
26 /**
27  * Holds parameters and utility methods for setting a custom picker accent color.
28  *
29  * Only valid input color codes with luminance greater than 0.6 will be set on major picker
30  * components.
31  *
32  * This feature can only be used for photo picker opened in [MediaStore#ACTION_PICK_IMAGES] mode.
33  */
34 class AccentColorHelper(val inputColor: Long) {
35 
36     companion object {
37 
38         /**
39          * Creates a [AccentColorHelper] using an Intent. This builder will try to locate an accent
40          * color long by checking the Intent's extras for a [EXTRA_PICK_IMAGES_ACCENT_COLOR] key and
41          * providing that to the AccentColorHelper's constructor.
42          *
43          * @return AccentColorHelper built from the intent's extras
44          */
withIntentnull45         fun withIntent(intent: Intent): AccentColorHelper {
46 
47             val extras: Bundle? = intent.extras
48             if (
49                 extras != null &&
50                     extras.containsKey(MediaStore.EXTRA_PICK_IMAGES_ACCENT_COLOR) &&
51                     intent.action != MediaStore.ACTION_PICK_IMAGES
52             ) {
53                 throw IllegalArgumentException(
54                     "Accent color customisation is not available for ${intent.action}"
55                 )
56             }
57             val inputColor = extras?.getLong(MediaStore.EXTRA_PICK_IMAGES_ACCENT_COLOR, -1) ?: -1
58             return AccentColorHelper(inputColor)
59         }
60     }
61 
62     private val DARK_TEXT_COLOR = "#000000"
63     private val LIGHT_TEXT_COLOR = "#FFFFFF"
64 
65     private val accentColor: Color
66     private val textColorForAccentComponents: Color
67 
68     init {
69 
70         accentColor = checkColorValidityAndGetColor(inputColor)
71 
72         // accentColor being equal to [Color.Unspecified] would mean that the color
73         // passed as an input does not satisfy the validity tests for being an accent
74         // color.
75         if (inputColor > -1 && accentColor.isUnspecified) {
76             throw IllegalArgumentException("Color not valid for accent color")
77         }
78 
79         // Set the Text Color only if the accentColor is not Unspecified
80         textColorForAccentComponents =
81             if (accentColor.isUnspecified) {
82                 Color.Unspecified
83             } else {
84 
85                 Color(
86                     android.graphics.Color.parseColor(
87                         if (isAccentColorBright(accentColor.luminance())) DARK_TEXT_COLOR
88                         else LIGHT_TEXT_COLOR,
89                     ),
90                 )
91             }
92     }
93 
94     /**
95      * Checks input color validity and returns the color without alpha component if valid, or -1.
96      */
checkColorValidityAndGetColornull97     private fun checkColorValidityAndGetColor(color: Long): Color {
98         // Gives us the formatted color string where the mask gives us the color in the RRGGBB
99         // format and the %06X gives zero-padded hex (6 characters long)
100         val hexColor = String.format("#%06X", (0xFFFFFF.toLong() and color))
101         val inputColor = android.graphics.Color.parseColor(hexColor)
102         if (!isColorFeasibleForBothBackgrounds(Color(inputColor).luminance())) {
103             return Color.Unspecified
104         }
105         return Color(inputColor)
106     }
107 
108     /**
109      * Returns true if the input color is within the range of [0.05 to 0.9] so that the color works
110      * both on light and dark background. Range has been set by testing with different colors.
111      */
isColorFeasibleForBothBackgroundsnull112     private fun isColorFeasibleForBothBackgrounds(luminance: Float): Boolean {
113         return luminance >= 0.05 && luminance < 0.9
114     }
115 
116     /** Indicates if the accent color is bright (luminance >= 0.6). */
isAccentColorBrightnull117     private fun isAccentColorBright(accentColorLuminance: Float): Boolean =
118         accentColorLuminance >= 0.6
119 
120     /**
121      * Returns the accent color which has been passed as an input in the picker intent.
122      *
123      * Default value for this is [Color.Unspecified]
124      */
125     fun getAccentColor(): Color {
126         return accentColor
127     }
128 
129     /**
130      * Returns the appropriate text color for components using the accent color as the background
131      * which has been passed as an input in the picker intent.
132      *
133      * Default value for this is [Color.Unspecified]
134      */
getTextColorForAccentComponentsnull135     fun getTextColorForAccentComponents(): Color {
136         return textColorForAccentComponents
137     }
138 
139     /** Indicates that a valid accent color is used for the photopicker theme */
isValidAccentColorSetnull140     fun isValidAccentColorSet(): Boolean {
141         return accentColor != Color.Unspecified
142     }
143 }
144