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