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.Color;
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.floatingactionbutton.FloatingActionButton;
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 android-commons
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 tabLayout
using {@link MaterialViewThemeUtils#themeTabLayout(TabLayout)}
* and then applies null
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 only for @drawable/selected_check
*/
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 getStateDrawable(@NonNull Drawable drawable, @AttrRes int state, @IdRes int layerId) {
return getStateDrawable(drawable, new int[]{state}, layerId);
}
private Optional 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 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 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)));
}
/**
* @see Upstream Pull Request
*/
@Deprecated(forRemoval = true)
public void themeSecondaryFAB(@NonNull FloatingActionButton fab) {
withScheme(fab.getContext(), scheme -> {
fab.setBackgroundTintList(buildColorStateList(
new Pair<>(android.R.attr.state_enabled, scheme.getSecondaryContainer()),
new Pair<>(-android.R.attr.state_enabled, Color.GRAY)
));
fab.setImageTintList(buildColorStateList(
new Pair<>(android.R.attr.state_enabled, scheme.getOnSecondaryContainer()),
new Pair<>(-android.R.attr.state_enabled, Color.GRAY)
));
return fab;
});
}
}