Welcome to mirror list, hosted at ThFree Co, Russian Federation.

DeckViewThemeUtils.java « theme « ui « deck « nextcloud « niedermann « it « java « main « src « app - github.com/stefan-niedermann/nextcloud-deck.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
blob: 3b0b523ea857d1f7361775500ec6da410ad97a25 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
package it.niedermann.nextcloud.deck.ui.theme;

import static com.nextcloud.android.common.ui.util.ColorStateListUtilsKt.buildColorStateList;
import static com.nextcloud.android.common.ui.util.PlatformThemeUtil.isDarkMode;

import android.content.Context;
import android.content.res.ColorStateList;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.LayerDrawable;
import android.graphics.drawable.StateListDrawable;
import android.os.Build;
import android.widget.ImageView;
import android.widget.TextView;

import androidx.annotation.AttrRes;
import androidx.annotation.ColorInt;
import androidx.annotation.ColorRes;
import androidx.annotation.DrawableRes;
import androidx.annotation.IdRes;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.content.ContextCompat;
import androidx.core.content.res.ResourcesCompat;
import androidx.core.graphics.drawable.DrawableCompat;
import androidx.core.widget.TextViewCompat;

import com.google.android.material.search.SearchBar;
import com.google.android.material.search.SearchView;
import com.google.android.material.tabs.TabLayout;
import com.nextcloud.android.common.ui.theme.MaterialSchemes;
import com.nextcloud.android.common.ui.theme.ViewThemeUtilsBase;
import com.nextcloud.android.common.ui.theme.utils.AndroidViewThemeUtils;
import com.nextcloud.android.common.ui.theme.utils.ColorRole;
import com.nextcloud.android.common.ui.theme.utils.MaterialViewThemeUtils;

import java.lang.reflect.InvocationTargetException;
import java.util.Optional;

import it.niedermann.nextcloud.deck.R;
import it.niedermann.nextcloud.deck.ui.view.EmptyContentView;
import kotlin.Pair;

/**
 * UI Elements which are not yet supported by the <a href="https://github.com/nextcloud/android-common"><code>android-commons</code></a> library.
 * Ideally there should at least be one Pull Request for Upstream for each method here.
 */
public class DeckViewThemeUtils extends ViewThemeUtilsBase {

    private final AndroidViewThemeUtils platform;
    private final MaterialViewThemeUtils material;

    public DeckViewThemeUtils(
            @NonNull MaterialSchemes schemes,
            @NonNull MaterialViewThemeUtils material,
            @NonNull AndroidViewThemeUtils platform
    ) {
        super(schemes);
        this.material = material;
        this.platform = platform;
    }

    /**
     * Themes the <code>tabLayout</code> using {@link MaterialViewThemeUtils#themeTabLayout(TabLayout)}
     * and then applies <code>null</code> as {@link TabLayout#setBackground(Drawable)}.
     */
    public void themeTabLayoutOnTransparent(@NonNull TabLayout tabLayout) {
        this.material.themeTabLayout(tabLayout);
        tabLayout.setBackground(null);
    }

    public void themeSearchBar(@NonNull SearchBar searchBar) {
        withScheme(searchBar.getContext(), scheme -> {
            final var colorStateList = ColorStateList.valueOf(
                    isDarkMode(searchBar.getContext())
                            ? scheme.getSurface()
                            : scheme.getSurfaceVariant());

            searchBar.setBackgroundTintList(colorStateList);

            final var menu = searchBar.getMenu();
            for (int i = 0; i < menu.size(); i++) {
                if (menu.getItem(i).getItemId() != R.id.avatar) {
                    platform.colorToolbarMenuIcon(searchBar.getContext(), menu.getItem(i));
                }
            }

            return searchBar;
        });
    }

    public void themeEmptyContentView(@NonNull EmptyContentView emptyContentView) {
        withScheme(emptyContentView.getContext(), scheme -> {
            platform.colorImageView(emptyContentView.getImage(), ColorRole.SURFACE_VARIANT);
            platform.colorTextView(emptyContentView.getTitle(), ColorRole.ON_BACKGROUND);
            platform.colorTextView(emptyContentView.getDescription(), ColorRole.ON_BACKGROUND);
            return emptyContentView;
        });
    }

    public void themeSearchView(@NonNull SearchView searchView) {
        withScheme(searchView.getContext(), scheme -> {
            searchView.setBackgroundTintList(ColorStateList.valueOf(scheme.getSurface()));
            return searchView;
        });
    }

    public void colorTextViewCompoundDrawables(@NonNull TextView textView) {
        withScheme(textView.getContext(), scheme -> {
            TextViewCompat.setCompoundDrawableTintList(textView, ColorStateList.valueOf(scheme.getOnSurfaceVariant()));
            return textView;
        });
    }

    public Drawable themeNavigationViewIcon(@NonNull Context context, @DrawableRes int icon) {
        return withScheme(context, scheme -> {
            final var colorStateList = buildColorStateList(
                    new Pair<>(android.R.attr.state_checked, scheme.getOnSecondaryContainer()),
                    new Pair<>(-android.R.attr.state_checked, scheme.getOnSurfaceVariant())
            );

            final var drawable = ContextCompat.getDrawable(context, icon);
            assert drawable != null;
            final var wrapped = DrawableCompat.wrap(drawable).mutate();
            DrawableCompat.setTintList(wrapped, colorStateList);
            wrapped.invalidateSelf();

            return wrapped;
        });
    }

    /**
     * There is currently no way to retrieve the actual color used for generating the current scheme.
     * Therefore we let pass it as argument.
     */
    @Nullable
    public Drawable getColoredBoardDrawable(@NonNull Context context, @ColorInt int boardColor) {
        final var drawable = ResourcesCompat.getDrawable(context.getResources(), R.drawable.circle_36dp, null);
        return drawable == null ? null : platform.colorDrawable(drawable, boardColor);
    }

    /**
     * Use <strong>only</strong> for <code>@drawable/selected_check</code>
     */
    public void themeSelectedCheck(@NonNull Context context, @NonNull Drawable selectedCheck) {
        getStateDrawable(selectedCheck, android.R.attr.state_selected, R.id.foreground)
                .ifPresent(drawable -> platform.tintDrawable(context, drawable, ColorRole.ON_PRIMARY));
        getStateDrawable(selectedCheck, android.R.attr.state_selected, R.id.background)
                .ifPresent(drawable -> platform.tintDrawable(context, drawable, ColorRole.PRIMARY));
    }

    private Optional<Drawable> getStateDrawable(@NonNull Drawable drawable, @AttrRes int state, @IdRes int layerId) {
        return getStateDrawable(drawable, new int[]{state}, layerId);
    }

    private Optional<Drawable> getStateDrawable(@NonNull Drawable drawable, @AttrRes int[] states, @IdRes int layerId) {
        try {
            final var stateListDrawable = ((StateListDrawable) drawable);
            return findStateDrawableIndex(stateListDrawable, states)
                    .flatMap(stateIndex -> getStateDrawable(stateListDrawable, stateIndex))
                    .map(layerDrawable -> layerDrawable.findDrawableByLayerId(layerId));
        } catch (Exception e) {
            return Optional.empty();
        }
    }

    private Optional<Integer> findStateDrawableIndex(@NonNull StateListDrawable drawable, int[] states) {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
            return Optional.of(drawable.findStateDrawableIndex(states));
        } else {
            try {
                // getStateDrawableIndex has been renamed and made public since API 29 / Android 10 / Android Q

                //noinspection JavaReflectionMemberAccess
                final var getStateDrawableIndex = StateListDrawable.class.getMethod("getStateDrawableIndex", int[].class);
                //noinspection PrimitiveArrayArgumentToVarargsMethod
                return Optional.ofNullable((Integer) getStateDrawableIndex.invoke(drawable, states));
            } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
                return Optional.empty();
            }
        }
    }

    private Optional<LayerDrawable> getStateDrawable(@NonNull StateListDrawable drawable, int index) {
        Drawable result;

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
            result = drawable.getStateDrawable(index);
        } else {
            try {
                result = (Drawable) StateListDrawable.class.getMethod("getStateDrawable", int.class).invoke(drawable, index);
            } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
                return Optional.empty();
            }
        }

        if (result instanceof LayerDrawable) {
            return Optional.of((LayerDrawable) result);
        }

        return Optional.empty();
    }

    @Deprecated(forRemoval = true)
    public static Drawable getTintedImageView(@NonNull Context context, @DrawableRes int imageId, @ColorInt int color) {
        final var drawable = ContextCompat.getDrawable(context, imageId);
        assert drawable != null;
        final var wrapped = DrawableCompat.wrap(drawable).mutate();
        DrawableCompat.setTint(wrapped, color);
        return drawable;
    }

    @Deprecated(forRemoval = true)
    public static void setImageColor(@NonNull Context context, @NonNull ImageView imageView, @ColorRes int colorRes) {
        imageView.setImageTintList(ColorStateList.valueOf(ContextCompat.getColor(context, colorRes)));
    }
}