1 /*
2  * Copyright (C) 2021 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 androidx.appcompat.widget;
18 
19 import static androidx.annotation.RestrictTo.Scope.LIBRARY;
20 
21 import android.content.res.ColorStateList;
22 import android.content.res.Resources;
23 import android.graphics.PorterDuff;
24 import android.graphics.drawable.Drawable;
25 import android.util.AttributeSet;
26 import android.widget.CheckedTextView;
27 
28 import androidx.annotation.RestrictTo;
29 import androidx.appcompat.R;
30 import androidx.appcompat.content.res.AppCompatResources;
31 import androidx.core.graphics.drawable.DrawableCompat;
32 import androidx.core.view.ViewCompat;
33 import androidx.core.widget.CheckedTextViewCompat;
34 
35 import org.jspecify.annotations.NonNull;
36 import org.jspecify.annotations.Nullable;
37 
38 @RestrictTo(LIBRARY)
39 class AppCompatCheckedTextViewHelper {
40     private final @NonNull CheckedTextView mView;
41 
42     private ColorStateList mCheckMarkTintList = null;
43     private PorterDuff.Mode mCheckMarkTintMode = null;
44     private boolean mHasCheckMarkTint = false;
45     private boolean mHasCheckMarkTintMode = false;
46 
47     private boolean mSkipNextApply;
48 
AppCompatCheckedTextViewHelper(@onNull CheckedTextView view)49     AppCompatCheckedTextViewHelper(@NonNull CheckedTextView view) {
50         mView = view;
51     }
52 
loadFromAttributes(@ullable AttributeSet attrs, int defStyleAttr)53     void loadFromAttributes(@Nullable AttributeSet attrs, int defStyleAttr) {
54         TintTypedArray a =
55                 TintTypedArray.obtainStyledAttributes(mView.getContext(), attrs,
56                         R.styleable.CheckedTextView, defStyleAttr, 0);
57         ViewCompat.saveAttributeDataForStyleable(mView, mView.getContext(),
58                 R.styleable.CheckedTextView, attrs, a.getWrappedTypeArray(), defStyleAttr, 0);
59         try {
60             boolean checkMarkDrawableLoaded = false;
61             if (a.hasValue(R.styleable.CheckedTextView_checkMarkCompat)) {
62                 final int resourceId = a.getResourceId(R.styleable.CheckedTextView_checkMarkCompat,
63                         0);
64                 if (resourceId != 0) {
65                     try {
66                         mView.setCheckMarkDrawable(
67                                 AppCompatResources.getDrawable(mView.getContext(), resourceId));
68                         checkMarkDrawableLoaded = true;
69                     } catch (Resources.NotFoundException ignore) {
70                         // Animated checkMarkCompat relies on AAPT2 features. If not found then
71                         // swallow this error and fall back to the regular drawable.
72                     }
73                 }
74             }
75             if (!checkMarkDrawableLoaded && a.hasValue(
76                     R.styleable.CheckedTextView_android_checkMark)) {
77                 final int resourceId = a.getResourceId(
78                         R.styleable.CheckedTextView_android_checkMark, 0);
79                 if (resourceId != 0) {
80                     mView.setCheckMarkDrawable(
81                             AppCompatResources.getDrawable(mView.getContext(), resourceId));
82                 }
83             }
84             if (a.hasValue(R.styleable.CheckedTextView_checkMarkTint)) {
85                 CheckedTextViewCompat.setCheckMarkTintList(mView,
86                         a.getColorStateList(R.styleable.CheckedTextView_checkMarkTint));
87             }
88             if (a.hasValue(R.styleable.CheckedTextView_checkMarkTintMode)) {
89                 CheckedTextViewCompat.setCheckMarkTintMode(mView,
90                         DrawableUtils.parseTintMode(
91                                 a.getInt(R.styleable.CheckedTextView_checkMarkTintMode, -1),
92                                 null));
93             }
94         } finally {
95             a.recycle();
96         }
97     }
98 
setSupportCheckMarkTintList(ColorStateList tint)99     void setSupportCheckMarkTintList(ColorStateList tint) {
100         mCheckMarkTintList = tint;
101         mHasCheckMarkTint = true;
102 
103         applyCheckMarkTint();
104     }
105 
getSupportCheckMarkTintList()106     ColorStateList getSupportCheckMarkTintList() {
107         return mCheckMarkTintList;
108     }
109 
setSupportCheckMarkTintMode(PorterDuff.@ullable Mode tintMode)110     void setSupportCheckMarkTintMode(PorterDuff.@Nullable Mode tintMode) {
111         mCheckMarkTintMode = tintMode;
112         mHasCheckMarkTintMode = true;
113 
114         applyCheckMarkTint();
115     }
116 
getSupportCheckMarkTintMode()117     PorterDuff.Mode getSupportCheckMarkTintMode() {
118         return mCheckMarkTintMode;
119     }
120 
onSetCheckMarkDrawable()121     void onSetCheckMarkDrawable() {
122         if (mSkipNextApply) {
123             mSkipNextApply = false;
124             return;
125         }
126 
127         mSkipNextApply = true;
128         applyCheckMarkTint();
129     }
130 
applyCheckMarkTint()131     void applyCheckMarkTint() {
132         Drawable checkMarkDrawable = CheckedTextViewCompat.getCheckMarkDrawable(mView);
133 
134         if (checkMarkDrawable != null && (mHasCheckMarkTint || mHasCheckMarkTintMode)) {
135             checkMarkDrawable = DrawableCompat.wrap(checkMarkDrawable);
136             checkMarkDrawable = checkMarkDrawable.mutate();
137             if (mHasCheckMarkTint) {
138                 DrawableCompat.setTintList(checkMarkDrawable, mCheckMarkTintList);
139             }
140             if (mHasCheckMarkTintMode) {
141                 DrawableCompat.setTintMode(checkMarkDrawable, mCheckMarkTintMode);
142             }
143             // The drawable (or one of its children) may not have been
144             // stateful before applying the tint, so let's try again.
145             if (checkMarkDrawable.isStateful()) {
146                 checkMarkDrawable.setState(mView.getDrawableState());
147             }
148             mView.setCheckMarkDrawable(checkMarkDrawable);
149         }
150     }
151 }
152