added resources hooks, palettes manager and new color pickers
This commit is contained in:
Binary file not shown.
@@ -53,6 +53,7 @@ import java.util.Map;
|
||||
|
||||
import ru.vtosters.lite.ui.components.DockBarEditorManager;
|
||||
import ru.vtosters.lite.ui.items.DockBarTab;
|
||||
import ru.vtosters.lite.utils.ThemesUtils;
|
||||
|
||||
public class DockBarInjector {
|
||||
private static final DockBarEditorManager sManager = DockBarEditorManager.getInstance();
|
||||
@@ -86,14 +87,11 @@ public class DockBarInjector {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
for (DockBarTab tab : sManager.getSelectedTabs()) {
|
||||
MenuItem add = menu.add(0, tab.id, 0, tab.titleID);
|
||||
Drawable drawable = getResources().getDrawable(tab.iconID);
|
||||
for (DockBarTab tab : sManager.getSelectedTabs())
|
||||
menu.add(0, tab.id, 0, tab.titleID)
|
||||
.setIcon(tab.iconID)
|
||||
.setCheckable(true);
|
||||
|
||||
add.setIcon(new RecoloredDrawable(drawable, getCSTDock()));
|
||||
add.setTitle(getGlobalContext().getString(tab.titleID));
|
||||
add.setCheckable(true);
|
||||
}
|
||||
if (getBoolValue("dockbar_tab_titles", true)) {
|
||||
var menuView = (BottomNavigationMenuView) navigationView.getChildAt(0);
|
||||
for (int i = 0; i < menuView.getChildCount(); i++) {
|
||||
|
||||
@@ -17,6 +17,9 @@ import static ru.vtosters.lite.utils.Preferences.getBoolValue;
|
||||
import static ru.vtosters.lite.utils.Preferences.hasVerification;
|
||||
import static ru.vtosters.lite.utils.Preferences.podcastcatalog;
|
||||
import static ru.vtosters.lite.utils.Preferences.postsrecomm;
|
||||
import static ru.vtosters.lite.utils.ThemesUtils.accentColors;
|
||||
import static ru.vtosters.lite.utils.ThemesUtils.getAccentColor;
|
||||
import static ru.vtosters.lite.utils.ThemesUtils.hex2;
|
||||
import static ru.vtosters.lite.utils.VTVerifications.isDeveloper;
|
||||
import static ru.vtosters.lite.utils.VTVerifications.isPrometheus;
|
||||
import static ru.vtosters.lite.utils.VTVerifications.isVerified;
|
||||
@@ -33,6 +36,7 @@ import org.json.JSONObject;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.text.ParseException;
|
||||
import java.util.Locale;
|
||||
import java.util.Objects;
|
||||
import java.util.Random;
|
||||
|
||||
@@ -63,6 +67,17 @@ public class JsonInjectors {
|
||||
newItem.put(vktesters(id));
|
||||
}
|
||||
|
||||
// get text color from all items
|
||||
for (int i = 0; i < newItem.length(); i++) {
|
||||
var item = newItem.getJSONObject(i);
|
||||
if (item.has("text_color")) {
|
||||
var textColor = item.getString("text_color");
|
||||
if (accentColors.contains(textColor.toLowerCase())) {
|
||||
item.put("text_color", hex2(getAccentColor()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
orig.put("buttons", newItem);
|
||||
|
||||
return orig;
|
||||
|
||||
@@ -11,13 +11,11 @@ import android.os.Build;
|
||||
import android.preference.PreferenceManager;
|
||||
|
||||
import ru.vtosters.lite.downloaders.notifications.NotificationChannels;
|
||||
import ru.vtosters.lite.themes.ThemesCore;
|
||||
import ru.vtosters.lite.ui.dialogs.DisableBattery;
|
||||
import ru.vtosters.lite.ui.dialogs.InstallGMS;
|
||||
import ru.vtosters.lite.ui.dialogs.OTADialog;
|
||||
import ru.vtosters.lite.ui.dialogs.ServerDialog;
|
||||
import ru.vtosters.lite.ui.dialogs.Start;
|
||||
import ru.vtosters.lite.utils.ThemesUtils;
|
||||
|
||||
public class MainActivityInjector {
|
||||
public static void inject(Activity activity) {
|
||||
@@ -36,7 +34,6 @@ public class MainActivityInjector {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
NotificationChannels.createChannels();
|
||||
}
|
||||
ThemesCore.setThemedColors(ThemesUtils.getAccentColor());
|
||||
// VKIDProtection.alert(activity);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
package ru.vtosters.lite.res;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.graphics.Color;
|
||||
import android.util.SparseArray;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ImageView;
|
||||
|
||||
import com.vk.core.ui.themes.VKThemeHelper;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import ru.vtosters.lite.res.models.ViewModel;
|
||||
import ru.vtosters.lite.res.modifiers.FloatingActionButtonRecolor;
|
||||
import ru.vtosters.lite.res.modifiers.IModifier;
|
||||
import ru.vtosters.lite.res.modifiers.ImageViewRecolor;
|
||||
import ru.vtosters.lite.res.modifiers.ProgressBarRecolor;
|
||||
import ru.vtosters.lite.res.modifiers.SwitchCompatRecolor;
|
||||
import ru.vtosters.lite.res.modifiers.TabLayoutRecolor;
|
||||
import ru.vtosters.lite.res.modifiers.TextViewRecolor;
|
||||
import ru.vtosters.lite.res.modifiers.TintTextViewRecolor;
|
||||
import ru.vtosters.lite.res.modifiers.ToolbarRecolor;
|
||||
import ru.vtosters.lite.res.modifiers.ViewRecolor;
|
||||
|
||||
public class Recolor {
|
||||
|
||||
private static final List<IModifier> recolors = new ArrayList<>();
|
||||
|
||||
static {
|
||||
recolors.add(new FloatingActionButtonRecolor());
|
||||
recolors.add(new ImageViewRecolor());
|
||||
recolors.add(new ProgressBarRecolor());
|
||||
recolors.add( new SwitchCompatRecolor());
|
||||
recolors.add(new TabLayoutRecolor());
|
||||
recolors.add(new TextViewRecolor());
|
||||
recolors.add(new TintTextViewRecolor());
|
||||
recolors.add(new ToolbarRecolor());
|
||||
recolors.add(new ViewRecolor());
|
||||
}
|
||||
|
||||
public static void recolorViewOrViewGroup(View target, SparseArray<ViewModel> satts) {
|
||||
if (target instanceof ViewGroup) {
|
||||
var viewGroup = (ViewGroup) target;
|
||||
for (int i = 0; i < viewGroup.getChildCount(); i++)
|
||||
recolorViewOrViewGroup(viewGroup.getChildAt(i), satts);
|
||||
}
|
||||
var viewModel = satts.get(target.getId(), null);
|
||||
recolorView(target, viewModel);
|
||||
}
|
||||
|
||||
private static void recolorView(View target, ViewModel viewModel) {
|
||||
if (viewModel != null)
|
||||
for (var recolor : recolors) {
|
||||
if (!recolor.isModified(target)) continue;
|
||||
for (var attr : viewModel.attrs) {
|
||||
//if (!VTLColors.hasColor(attr.second)) continue;
|
||||
recolor.modify(target, attr.first, VKThemeHelper.d(attr.second));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static void recolorImageView(ImageView target) {
|
||||
|
||||
}
|
||||
|
||||
public static void recolorRipple(View target) {
|
||||
|
||||
}
|
||||
|
||||
public static void recolorActivity(Activity activity) {
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,128 @@
|
||||
package ru.vtosters.lite.res;
|
||||
|
||||
import static ru.vtosters.lite.utils.AndroidUtils.getResources;
|
||||
import static ru.vtosters.lite.utils.Preferences.dockbar_accent;
|
||||
import static ru.vtosters.lite.utils.ThemesUtils.getColorFromAttr;
|
||||
import static ru.vtosters.lite.utils.ThemesUtils.isCustomThemeApplied;
|
||||
import static ru.vtosters.lite.utils.ThemesUtils.isDarkTheme;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import com.vk.core.ui.themes.VKThemeHelper;
|
||||
import com.vk.core.util.ContextExtKt;
|
||||
import com.vtosters.lite.R;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import ru.vtosters.lite.res.colors.AccentColors;
|
||||
import ru.vtosters.lite.res.colors.BackgroundColors;
|
||||
import ru.vtosters.lite.res.colors.BarColors;
|
||||
import ru.vtosters.lite.res.colors.ConventionColors;
|
||||
import ru.vtosters.lite.res.colors.IColor;
|
||||
import ru.vtosters.lite.res.colors.ModalConversationColors;
|
||||
|
||||
public class VTLColors {
|
||||
|
||||
public static final List<IColor> colors = Arrays.asList(
|
||||
new AccentColors(), new BackgroundColors(),
|
||||
new BarColors(), new ConventionColors(),
|
||||
new ModalConversationColors()
|
||||
);
|
||||
|
||||
public static int getAccentColor() {
|
||||
return isCustomThemeApplied()
|
||||
? getColor(R.attr.accent, R.attr.accentColor, R.attr.colorAccent, R.attr.vk_accent)
|
||||
: getColorFromAttr(R.attr.accent);
|
||||
}
|
||||
|
||||
public static int getTextPrimaryColor() {
|
||||
return isCustomThemeApplied()
|
||||
? getColor(R.attr.text_primary)
|
||||
: getColorFromAttr(R.attr.text_primary);
|
||||
}
|
||||
|
||||
public static int getTextSecondaryColor() {
|
||||
return isCustomThemeApplied()
|
||||
? getColor(R.attr.text_secondary)
|
||||
: getColorFromAttr(R.attr.text_secondary);
|
||||
}
|
||||
|
||||
public static int getTabbarBackground() {
|
||||
return isCustomThemeApplied()
|
||||
? getColor(R.attr.tabbar_background)
|
||||
: getColorFromAttr(R.attr.tabbar_background);
|
||||
}
|
||||
|
||||
public static int getTabbarActiveColor() {
|
||||
if (isCustomThemeApplied())
|
||||
return getColor(R.attr.tabbar_active_icon);
|
||||
return dockbar_accent()
|
||||
? getAccentColor()
|
||||
: getResources().getColor(isDarkTheme() ? R.color.white : R.color.gray_700);
|
||||
}
|
||||
|
||||
public static int getTabbarInactiveColor() {
|
||||
if (isCustomThemeApplied())
|
||||
return getColor(R.attr.tabbar_inactive_icon);
|
||||
return getResources().getColor(isDarkTheme() ? R.color.gray_500 : R.color.vk_steel_gray_300);
|
||||
}
|
||||
|
||||
public static int getBackgroundContent() {
|
||||
return isCustomThemeApplied()
|
||||
? getColor(R.attr.background_content)
|
||||
: getColorFromAttr(R.attr.background_content);
|
||||
}
|
||||
|
||||
public static int getBackgroundPage() {
|
||||
return isCustomThemeApplied()
|
||||
? getColor(R.attr.background_page)
|
||||
: getColorFromAttr(R.attr.background_page);
|
||||
}
|
||||
|
||||
public static int getHeaderBackground() {
|
||||
return isCustomThemeApplied()
|
||||
? getColor(R.attr.header_background)
|
||||
: getColorFromAttr(R.attr.header_background);
|
||||
}
|
||||
|
||||
public static int getHeaderText() {
|
||||
return isCustomThemeApplied()
|
||||
? getColor(R.attr.header_text)
|
||||
: getColorFromAttr(R.attr.header_text);
|
||||
}
|
||||
|
||||
//region Hooks
|
||||
|
||||
public static boolean hasColor(int attr) {
|
||||
for (var color : colors) {
|
||||
if (color.has(attr))
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public static int getColor(int... attrs) {
|
||||
for (var color : colors) {
|
||||
for (var attr : attrs) {
|
||||
if (color.has(attr))
|
||||
return color.get(attr);
|
||||
}
|
||||
}
|
||||
return getColorFromAttr(attrs[0]);
|
||||
}
|
||||
|
||||
public static int getColor(int attr) {
|
||||
// for (var color : colors) {
|
||||
// if (color.has(attr))
|
||||
// return color.get(attr);
|
||||
// }
|
||||
// return getColorFromAttr(attr);
|
||||
return VKThemeHelper.d(attr);
|
||||
}
|
||||
|
||||
public static int getColor(Context context, int colorId) {
|
||||
return ContextExtKt.h(context, colorId);
|
||||
}
|
||||
//endregion
|
||||
}
|
||||
@@ -0,0 +1,128 @@
|
||||
package ru.vtosters.lite.res;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.ColorStateList;
|
||||
import android.content.res.Resources;
|
||||
import android.content.res.TypedArray;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.os.Build;
|
||||
import android.util.AttributeSet;
|
||||
import android.util.SparseIntArray;
|
||||
import android.util.TypedValue;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.vtosters.lite.R;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.HashMap;
|
||||
|
||||
import ru.vtosters.lite.utils.ReflectionUtils;
|
||||
import ru.vtosters.lite.utils.ThemesUtils;
|
||||
|
||||
public class VTLResources {
|
||||
private static final int STYLE_NUM_ENTRIES = Build.VERSION.SDK_INT >= 29 ? 7 : 6;
|
||||
public static final SparseIntArray forcedAttrs = new SparseIntArray();
|
||||
public static final SparseIntArray otherAttrs = new SparseIntArray();
|
||||
|
||||
private static Field dataField;
|
||||
|
||||
static {
|
||||
try {
|
||||
dataField = ReflectionUtils.getDeclaredField(TypedArray.class, "mData");
|
||||
} catch (NoSuchFieldException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
otherAttrs.put(R.attr.vklib_alv_optionIconTint, R.attr.im_dropdown_icon_color);
|
||||
otherAttrs.put(R.attr.vkim_colorPrimary, R.attr.accent);
|
||||
otherAttrs.put(R.attr.statusBarColorBack, R.attr.statusBarColorBack);
|
||||
otherAttrs.put(R.attr.statusBarColorFront, R.attr.statusBarColorFront);
|
||||
otherAttrs.put(android.R.attr.backgroundTint, R.attr.accent);
|
||||
otherAttrs.put(android.R.attr.tint, R.attr.accent);
|
||||
otherAttrs.put(android.R.attr.color, R.attr.accent);
|
||||
|
||||
forcedAttrs.put(-0x8e5515, R.attr.accent);
|
||||
forcedAttrs.put(-0xe6e6e6, R.attr.background_content);
|
||||
}
|
||||
|
||||
private static void prepareTypedArray(TypedArray target, int[] attrs) {
|
||||
// if (attrs == null) return;
|
||||
// var data = getData(target);
|
||||
// for (int i = 0; i < attrs.length; i++) {
|
||||
// var attr = attrs[i];
|
||||
// var replacement = otherAttrs.get(attr, -1);
|
||||
// if (replacement == -1) continue;
|
||||
// var index = STYLE_NUM_ENTRIES * i;
|
||||
// var type = data[index];
|
||||
// if (type == TypedValue.TYPE_ATTRIBUTE)
|
||||
// replacement = data[index + 0x1];
|
||||
// else if (type == TypedValue.TYPE_INT_COLOR_ARGB8 || type == TypedValue.TYPE_INT_COLOR_RGB8)
|
||||
// replacement = forcedAttrs.get(data[index + 0x1], 0);
|
||||
// else
|
||||
// continue;
|
||||
// try {
|
||||
// data[index] = TypedValue.TYPE_INT_COLOR_RGB8; // type
|
||||
// data[index + 0x1] = ThemesUtils.getColorFromAttr(attr); // data
|
||||
// data[index + 0x2] = 0x0; // asset cookie
|
||||
// data[index + 0x3] = 0x0; // resource id
|
||||
//
|
||||
// } catch (Throwable ignored) {
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
||||
private static int[] getData(TypedArray target) {
|
||||
try {
|
||||
return (int[]) dataField.get(target);
|
||||
} catch (IllegalAccessException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return new int[0];
|
||||
}
|
||||
|
||||
|
||||
//region Hooks
|
||||
|
||||
public static TypedArray obtainStyledAttributes(Context context, int[] attrs) {
|
||||
var target = context.obtainStyledAttributes(attrs);
|
||||
prepareTypedArray(target, attrs);
|
||||
return target;
|
||||
}
|
||||
|
||||
public static TypedArray obtainStyledAttributes(Context context, int resId, int[] attrs) {
|
||||
var target = context.obtainStyledAttributes(resId, attrs);
|
||||
prepareTypedArray(target, attrs);
|
||||
return target;
|
||||
}
|
||||
|
||||
public static TypedArray obtainStyledAttributes(Context context, AttributeSet set, int[] attrs) {
|
||||
var target = context.obtainStyledAttributes(set, attrs);
|
||||
prepareTypedArray(target, attrs);
|
||||
return target;
|
||||
}
|
||||
|
||||
public static TypedArray obtainStyledAttributes(Context context, AttributeSet set, int[] attrs, int defStyleAttr, int defStyleRes) {
|
||||
var target = context.obtainStyledAttributes(set, attrs, defStyleAttr, defStyleRes);
|
||||
prepareTypedArray(target, attrs);
|
||||
return target;
|
||||
}
|
||||
|
||||
public static int getColor(Resources res, int colorId) {
|
||||
return res.getColor(colorId);
|
||||
}
|
||||
|
||||
public static int getColor(Resources res, int colorId, Resources.Theme theme) {
|
||||
return res.getColor(colorId, theme);
|
||||
}
|
||||
|
||||
public static ColorStateList getColorStateList(Resources res, int colorId) {
|
||||
return res.getColorStateList(colorId);
|
||||
}
|
||||
|
||||
public static ColorStateList getColorStateList(Resources res, int colorId, Resources.Theme theme) {
|
||||
return res.getColorStateList(colorId, theme);
|
||||
}
|
||||
//endregion
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
package ru.vtosters.lite.res.colors;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import com.vtosters.lite.R;
|
||||
|
||||
public class AccentColors implements IColor {
|
||||
|
||||
public static final List<Integer> attrs = Arrays.asList(
|
||||
R.attr.accent,
|
||||
R.attr.colorAccent,
|
||||
R.attr.counter_primary_background,
|
||||
R.attr.header_tab_active_indicator,
|
||||
R.attr.header_tint,
|
||||
R.attr.im_attach_tint,
|
||||
R.attr.im_dropdown_icon_color,
|
||||
R.attr.im_text_name,
|
||||
R.attr.link_alternate,
|
||||
R.attr.text_link,
|
||||
R.attr.vkim_colorPrimary
|
||||
);
|
||||
|
||||
@Override
|
||||
public boolean has(int attr) {
|
||||
return attrs.contains(attr);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int get(int attr) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
package ru.vtosters.lite.res.colors;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import com.vtosters.lite.R;
|
||||
|
||||
public class BackgroundColors implements IColor {
|
||||
|
||||
public static final List<Integer> attrs = Arrays.asList(
|
||||
R.attr.background_content,
|
||||
R.attr.background_page,
|
||||
R.attr.background_light,
|
||||
R.attr.background_suggestions,
|
||||
R.attr.boxBackgroundColor,
|
||||
R.attr.colorPrimaryDark,
|
||||
R.attr.content_tint_background,
|
||||
R.attr.control_background,
|
||||
R.attr.im_history_banner_bg_color,
|
||||
R.attr.im_history_banner_bg_color,
|
||||
R.attr.im_toolbar_voice_msg_background,
|
||||
R.attr.input_background,
|
||||
R.attr.posting_input_background,
|
||||
R.attr.vk_landing_background,
|
||||
R.attr.input_border,
|
||||
R.attr.vk_background_content,
|
||||
R.attr.landing_background
|
||||
);
|
||||
|
||||
@Override
|
||||
public boolean has(int attr) {
|
||||
return attrs.contains(attr);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int get(int attr) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package ru.vtosters.lite.res.colors;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import com.vtosters.lite.R;
|
||||
|
||||
public class BarColors implements IColor {
|
||||
|
||||
public static final List<Integer> attrs = Arrays.asList(
|
||||
R.attr.header_alternate_background,
|
||||
R.attr.header_background,
|
||||
R.attr.search_bar_background,
|
||||
R.attr.statusBarColorBack,
|
||||
R.attr.statusBarColorFront,
|
||||
R.attr.statusbar_alternate_legacy_background,
|
||||
R.attr.tabbar_background,
|
||||
R.attr.tabbar_tablet_background,
|
||||
R.attr.toolbar_attach_background_from,
|
||||
R.attr.toolbar_attach_background_to
|
||||
);
|
||||
|
||||
@Override
|
||||
public boolean has(int attr) {
|
||||
return attrs.contains(attr);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int get(int attr) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package ru.vtosters.lite.res.colors;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import com.vtosters.lite.R;
|
||||
|
||||
public class ConventionColors implements IColor {
|
||||
|
||||
public static final List<Integer> attrs = Arrays.asList(
|
||||
R.attr.im_bg_chat,
|
||||
R.attr.im_bubble_incoming,
|
||||
R.attr.im_bubble_outgoing,
|
||||
R.attr.im_bubble_wallpaper_incoming,
|
||||
R.attr.im_bubble_wallpaper_outgoing
|
||||
);
|
||||
|
||||
@Override
|
||||
public boolean has(int attr) {
|
||||
return attrs.contains(attr);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int get(int attr) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package ru.vtosters.lite.res.colors;
|
||||
|
||||
public interface IColor {
|
||||
|
||||
boolean has(int attr);
|
||||
|
||||
// need call has function before calling
|
||||
int get(int attr);
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package ru.vtosters.lite.res.colors;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import com.vtosters.lite.R;
|
||||
|
||||
public class ModalConversationColors implements IColor {
|
||||
|
||||
public static final List<Integer> attrs = Arrays.asList(
|
||||
R.attr.modal_card_background,
|
||||
R.attr.header_background
|
||||
);
|
||||
|
||||
@Override
|
||||
public boolean has(int attr) {
|
||||
return attrs.contains(attr);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int get(int attr) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
package ru.vtosters.lite.res.managers;
|
||||
|
||||
import android.os.Environment;
|
||||
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import ru.vtosters.lite.res.models.PaletteModel;
|
||||
import ru.vtosters.lite.utils.AndroidUtils;
|
||||
import ru.vtosters.lite.utils.IOUtils;
|
||||
|
||||
public class PalettesManager {
|
||||
|
||||
private File palettesDir;
|
||||
|
||||
private static final List<String> defaultPalettes = Arrays.asList(
|
||||
"material.json",
|
||||
"html_w3c.json"
|
||||
);
|
||||
|
||||
private static PalettesManager instance;
|
||||
|
||||
private List<PaletteModel> palettes;
|
||||
|
||||
public static PalettesManager getInstance() {
|
||||
if (instance == null)
|
||||
instance = new PalettesManager();
|
||||
return instance;
|
||||
}
|
||||
|
||||
public PalettesManager() {
|
||||
init();
|
||||
}
|
||||
|
||||
private void init() {
|
||||
palettes = new ArrayList<>();
|
||||
palettesDir = new File(Environment.getExternalStorageDirectory(), "VTL/themes/palettes");
|
||||
if (!palettesDir.exists())
|
||||
palettesDir.mkdirs();
|
||||
for (String palette : defaultPalettes) {
|
||||
var file = new File(palettesDir, palette);
|
||||
if (!file.exists())
|
||||
copyPaletteFromAssets(palette, file);
|
||||
}
|
||||
loadPalettes();
|
||||
}
|
||||
|
||||
private void copyPaletteFromAssets(String paletteAsset, File out) {
|
||||
try {
|
||||
var assetsManager = AndroidUtils.getGlobalContext().getAssets();
|
||||
var is = assetsManager.open("vtl_themes/palettes/" + paletteAsset);
|
||||
IOUtils.copyFile(is, out);
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
private void loadPalettes() {
|
||||
var arr = palettesDir.listFiles((dir, name) -> name.endsWith(".json"));
|
||||
if (arr == null || arr.length == 0)
|
||||
throw new RuntimeException("Can't load palettes");
|
||||
for (var file : arr) {
|
||||
try {
|
||||
var json = new JSONObject(IOUtils.readAllLines(file));
|
||||
palettes.add(PaletteModel.fromJson(json));
|
||||
} catch (IOException | JSONException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void reload() {
|
||||
init();
|
||||
}
|
||||
|
||||
public PaletteModel getPalette(String id) {
|
||||
for (PaletteModel palette : palettes) {
|
||||
if (id.equals(palette.id))
|
||||
return palette;
|
||||
}
|
||||
throw new NullPointerException("Can't find palette with id " + id);
|
||||
}
|
||||
|
||||
public PaletteModel getPalette(int i) {
|
||||
return palettes.get(i);
|
||||
}
|
||||
|
||||
public int getPalettesCount() {
|
||||
return palettes.size();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,303 @@
|
||||
package ru.vtosters.lite.res.managers;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.ContentResolver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.os.Environment;
|
||||
import android.os.StrictMode;
|
||||
import android.util.Log;
|
||||
|
||||
import com.vtosters.lite.auth.VKAccountManager;
|
||||
|
||||
import org.json.JSONException;
|
||||
|
||||
import java.io.DataInputStream;
|
||||
import java.io.DataOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.zip.GZIPInputStream;
|
||||
import java.util.zip.GZIPOutputStream;
|
||||
|
||||
import ru.vtosters.lite.ui.dialogs.ThemesEditorBottomSheetDialog;
|
||||
import ru.vtosters.lite.res.models.ColorModel;
|
||||
import ru.vtosters.lite.res.models.ThemeModel;
|
||||
import ru.vtosters.lite.utils.AndroidUtils;
|
||||
import ru.vtosters.lite.utils.IOUtils;
|
||||
import ru.vtosters.lite.utils.ThemesUtils;
|
||||
|
||||
public class ThemesManager {
|
||||
|
||||
public static final List<ColorModel> DEFAULT_COLORS = Arrays.asList(
|
||||
ColorModel.valuesOf("windowBackground", "ff000000"),
|
||||
ColorModel.valuesOf("accent", "ff8eff71"),
|
||||
ColorModel.valuesOf("background_content", "ff000000"),
|
||||
ColorModel.valuesOf("background_keyboard", "ff000000"),
|
||||
ColorModel.valuesOf("background_light", "ff000000"),
|
||||
ColorModel.valuesOf("background_page", "ff000000"),
|
||||
ColorModel.valuesOf("background_suggestions", "ff000000"),
|
||||
ColorModel.valuesOf("boxBackgroundColor", "ff000000"),
|
||||
ColorModel.valuesOf("colorAccent", "ff8eff71"),
|
||||
ColorModel.valuesOf("colorPrimaryDark", "ff000000"),
|
||||
ColorModel.valuesOf("content_tint_background", "ff000000"),
|
||||
ColorModel.valuesOf("control_background", "ff000000"),
|
||||
ColorModel.valuesOf("counter_primary_background", "ff8eff71"),
|
||||
ColorModel.valuesOf("header_alternate_background", "ff000000"),
|
||||
ColorModel.valuesOf("header_background", "ff000000"),
|
||||
ColorModel.valuesOf("header_tab_active_indicator", "ff8eff71"),
|
||||
ColorModel.valuesOf("header_tint", "ff8eff71"),
|
||||
ColorModel.valuesOf("im_attach_tint", "ff8eff71"),
|
||||
ColorModel.valuesOf("im_bg_chat", "ff000000"),
|
||||
ColorModel.valuesOf("im_bubble_incoming", "ff000000"),
|
||||
ColorModel.valuesOf("im_bubble_outgoing", "ff121212"),
|
||||
ColorModel.valuesOf("im_bubble_wallpaper_incoming", "ff000000"),
|
||||
ColorModel.valuesOf("im_bubble_wallpaper_outgoing", "ff121212"),
|
||||
ColorModel.valuesOf("im_dropdown_icon_color", "ff8eff71"),
|
||||
ColorModel.valuesOf("im_history_banner_bg_color", "ff000000"),
|
||||
ColorModel.valuesOf("im_text_name", "ff8eff71"),
|
||||
ColorModel.valuesOf("im_toolbar_voice_msg_background", "ff000000"),
|
||||
ColorModel.valuesOf("input_background", "ff000000"),
|
||||
ColorModel.valuesOf("input_border", "ff000000"),
|
||||
ColorModel.valuesOf("landing_background", "ff000000"),
|
||||
ColorModel.valuesOf("link_alternate", "ff8eff71"),
|
||||
ColorModel.valuesOf("loader_background", "ff000000"),
|
||||
ColorModel.valuesOf("modal_card_background", "ff000000"),
|
||||
ColorModel.valuesOf("overlay_status_background", "ff000000"),
|
||||
ColorModel.valuesOf("overlay_status_foreground", "ff000000"),
|
||||
ColorModel.valuesOf("posting_input_background", "ff000000"),
|
||||
ColorModel.valuesOf("search_bar_background", "ff000000"),
|
||||
ColorModel.valuesOf("statusBarColorBack", "ff000000"),
|
||||
ColorModel.valuesOf("statusBarColorFront", "ff000000"),
|
||||
ColorModel.valuesOf("statusbar_alternate_legacy_background", "ff000000"),
|
||||
ColorModel.valuesOf("tabbar_background", "ff000000"),
|
||||
ColorModel.valuesOf("tabbar_tablet_background", "ff000000"),
|
||||
ColorModel.valuesOf("text_link", "ff8eff71"),
|
||||
ColorModel.valuesOf("toolbar_attach_background_from", "ff000000"),
|
||||
ColorModel.valuesOf("toolbar_attach_background_to", "ff000000"),
|
||||
ColorModel.valuesOf("vk_background_content", "ff000000"),
|
||||
ColorModel.valuesOf("vk_header_background", "ff000000"),
|
||||
ColorModel.valuesOf("vk_landing_background", "ff000000"),
|
||||
ColorModel.valuesOf("vkim_bgFillDrawable", "ff000000"),
|
||||
ColorModel.valuesOf("vkim_colorPrimary", "ff8eff71")
|
||||
);
|
||||
|
||||
private static File themesDir;
|
||||
private static ThemesManager instance;
|
||||
private static ThemeModel currentTheme = null;
|
||||
private List<ThemeModel> themes = new ArrayList<>();
|
||||
|
||||
public static ThemesManager getInstance() {
|
||||
if (instance == null)
|
||||
instance = new ThemesManager();
|
||||
return instance;
|
||||
}
|
||||
|
||||
public ThemesManager() {
|
||||
init();
|
||||
}
|
||||
|
||||
private void init() {
|
||||
themes = new ArrayList<>();
|
||||
currentTheme = new ThemeModel().setName("Тема по умолчанию")
|
||||
.setAuthor("Разработчики ВК")
|
||||
.setDarkMode(ThemesUtils.isDarkTheme())
|
||||
.setId("default");
|
||||
themes.add(currentTheme);
|
||||
themesDir = new File(Environment.getExternalStorageDirectory(), "VTL/themes");
|
||||
if (!themesDir.exists()) {
|
||||
themesDir.mkdirs();
|
||||
return;
|
||||
}
|
||||
// Support themes from Sova V RE (.svt)
|
||||
var files = themesDir.listFiles((dir, name) -> name.endsWith(".vtlt") || name.endsWith(".svt") || name.endsWith(".json"));
|
||||
if (files == null || files.length == 0)
|
||||
return;
|
||||
var currentThemeId = AndroidUtils.getPreferences().getString("vtl_theme_id", "default");
|
||||
for (File file : files) {
|
||||
try {
|
||||
ThemeModel theme;
|
||||
var abs = file.getAbsolutePath();
|
||||
if (abs.endsWith(".svt") || abs.endsWith(".json"))
|
||||
theme = svt2vtlt(file);
|
||||
else
|
||||
theme = read(file);
|
||||
if (theme != null) {
|
||||
Log.d("ThemesManager", theme.toString());
|
||||
if (currentThemeId.equals(theme.id))
|
||||
currentTheme = theme;
|
||||
themes.add(theme);
|
||||
}
|
||||
} catch (IOException | JSONException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
if ("default".equals(currentTheme.id))
|
||||
setTheme(currentTheme);
|
||||
}
|
||||
|
||||
private ThemeModel svt2vtlt(File file) {
|
||||
try {
|
||||
var theme = read(file);
|
||||
save(theme);
|
||||
file.delete();
|
||||
return theme;
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public void reload() {
|
||||
init();
|
||||
}
|
||||
|
||||
public File getThemeFile(ThemeModel theme) throws IOException {
|
||||
if (!themesDir.exists()) {
|
||||
themesDir.mkdirs();
|
||||
throw new FileNotFoundException("theme file not found");
|
||||
}
|
||||
|
||||
var themeFile = new File(themesDir, theme.id + ".svt");
|
||||
if (!themeFile.exists()) {
|
||||
themeFile = new File(themesDir, theme.id + ".vtlt");
|
||||
if (!themeFile.exists())
|
||||
themeFile.createNewFile();
|
||||
}
|
||||
|
||||
return themeFile;
|
||||
}
|
||||
|
||||
public void setTheme(ThemeModel theme) {
|
||||
AndroidUtils.getPreferences()
|
||||
.edit()
|
||||
.putString("vtl_theme_id", theme.id)
|
||||
.commit();
|
||||
}
|
||||
|
||||
public ThemeModel getTheme(int i) {
|
||||
return themes.get(i);
|
||||
}
|
||||
|
||||
public ThemeModel getCurrentTheme() {
|
||||
return currentTheme;
|
||||
}
|
||||
|
||||
public int getThemesCount() {
|
||||
return themes.size();
|
||||
}
|
||||
|
||||
public boolean isCurrentTheme(ThemeModel theme) {
|
||||
return currentTheme.id.equals(theme.id);
|
||||
}
|
||||
|
||||
public boolean hasTheme(ThemeModel theme) {
|
||||
return hasTheme(theme.id);
|
||||
}
|
||||
|
||||
public boolean hasTheme(String id) {
|
||||
for (ThemeModel theme : themes) {
|
||||
if (id.equals(theme.id))
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/*
|
||||
* Theme file storing method from Sova V RE is storing json clearly or compressed with gzip.
|
||||
* If first byte byte is 0x1 or 0x2 json has been compressed with gzip,
|
||||
* but if first byte is 0x2 next int is length of json string. */
|
||||
|
||||
private ThemeModel read(InputStream fis) throws IOException, JSONException {
|
||||
InputStream is = fis;
|
||||
var first = is.read();
|
||||
if (first > 0x0 && first < 0x3) {
|
||||
var gzis = new GZIPInputStream(is);
|
||||
var dis = new DataInputStream(gzis);
|
||||
if (first == 0x2)
|
||||
dis.readInt();
|
||||
is = dis;
|
||||
}
|
||||
return ThemeModel.fromJson(IOUtils.readAllLines(is));
|
||||
}
|
||||
|
||||
private ThemeModel read(File file) throws IOException, JSONException {
|
||||
return read(new FileInputStream(file));
|
||||
}
|
||||
|
||||
public void save(ThemeModel theme) throws IOException, JSONException {
|
||||
var buffer = theme.toJson().toString().getBytes(StandardCharsets.UTF_8);
|
||||
var fos = new FileOutputStream(getThemeFile(theme));
|
||||
// writing with data length
|
||||
fos.write(0x2);
|
||||
var gzos = new GZIPOutputStream(fos);
|
||||
var dos = new DataOutputStream(gzos);
|
||||
dos.writeInt(buffer.length);
|
||||
dos.write(buffer);
|
||||
dos.close();
|
||||
}
|
||||
|
||||
public void delete(ThemeModel theme) {
|
||||
try {
|
||||
getThemeFile(theme).delete();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
themes.remove(theme);
|
||||
if (isCurrentTheme(theme)) {
|
||||
setTheme(null);
|
||||
}
|
||||
}
|
||||
|
||||
public void create(Activity activity, String name) throws JSONException, IOException {
|
||||
var id = name.toLowerCase().replace(" ", "-");
|
||||
if (hasTheme(id)) {
|
||||
var i = 1;
|
||||
while (hasTheme(id + "-" + i)) {
|
||||
i++;
|
||||
}
|
||||
id += "-" + i;
|
||||
}
|
||||
var userProfile = VKAccountManager.d().e1();
|
||||
ThemeModel theme = new ThemeModel()
|
||||
.setName(name)
|
||||
.setAuthor(userProfile.c + " " + userProfile.e)
|
||||
.setDarkMode(true)
|
||||
.setColors(DEFAULT_COLORS)
|
||||
.setId(id);
|
||||
themes.add(theme);
|
||||
ThemesEditorBottomSheetDialog.create(activity, theme, false);
|
||||
save(theme);
|
||||
}
|
||||
|
||||
public void copy(ContentResolver contentResolver, Uri uri) throws IOException, JSONException {
|
||||
var theme = read(contentResolver.openInputStream(uri));
|
||||
if (isCurrentTheme(theme)) {
|
||||
int i = 1;
|
||||
while (hasTheme(theme.id + "-" + i)) {
|
||||
i++;
|
||||
}
|
||||
theme.id += "-" + i;
|
||||
}
|
||||
themes.add(theme);
|
||||
save(theme);
|
||||
}
|
||||
|
||||
public void share(Context context, ThemeModel theme) throws Exception {
|
||||
var vmPolicy = StrictMode.getVmPolicy();
|
||||
StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder().build());
|
||||
var intent = new Intent(Intent.ACTION_SEND)
|
||||
.setType("file/*")
|
||||
.putExtra(Intent.EXTRA_STREAM, Uri.fromFile(getThemeFile(theme)));
|
||||
context.startActivity(Intent.createChooser(intent, ""));
|
||||
StrictMode.setVmPolicy(vmPolicy);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
package ru.vtosters.lite.res.models;
|
||||
|
||||
import android.graphics.Color;
|
||||
|
||||
public class ColorModel {
|
||||
|
||||
public String name;
|
||||
public int value;
|
||||
|
||||
public ColorModel setName(String name) {
|
||||
this.name = name;
|
||||
return this;
|
||||
}
|
||||
|
||||
public ColorModel setValue(int value) {
|
||||
this.value = value;
|
||||
return this;
|
||||
}
|
||||
|
||||
public ColorModel setValue(String value) {
|
||||
this.value = Color.parseColor(value.startsWith("#") ? value : "#" + value);
|
||||
return this;
|
||||
}
|
||||
|
||||
public static ColorModel valuesOf(String name, String color) {
|
||||
return new ColorModel()
|
||||
.setName(name)
|
||||
.setValue(color);
|
||||
}
|
||||
|
||||
public static ColorModel valuesOf(String name, int color) {
|
||||
return new ColorModel()
|
||||
.setName(name)
|
||||
.setValue(color);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
package ru.vtosters.lite.res.models;
|
||||
|
||||
import android.graphics.Color;
|
||||
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class PaletteModel {
|
||||
public String id;
|
||||
public String name;
|
||||
public String author;
|
||||
public List<ColorModel> colors = new ArrayList<>();
|
||||
|
||||
public PaletteModel() {
|
||||
|
||||
}
|
||||
|
||||
public PaletteModel(String id, String name, String author, List<ColorModel> colors) {
|
||||
this.id = id;
|
||||
this.name = name;
|
||||
this.author = author;
|
||||
this.colors = colors;
|
||||
}
|
||||
|
||||
public PaletteModel setId(String id) {
|
||||
this.id = id;
|
||||
return this;
|
||||
}
|
||||
|
||||
public PaletteModel setName(String name) {
|
||||
this.name = name;
|
||||
return this;
|
||||
}
|
||||
|
||||
public PaletteModel setAuthor(String author) {
|
||||
this.author = author;
|
||||
return this;
|
||||
}
|
||||
|
||||
public PaletteModel setColors(List<ColorModel> colors) {
|
||||
this.colors = colors;
|
||||
return this;
|
||||
}
|
||||
|
||||
public PaletteModel setColor(String name, String color) {
|
||||
this.colors.add(ColorModel.valuesOf(name, Color.parseColor("#" + color)));
|
||||
return this;
|
||||
}
|
||||
|
||||
public PaletteModel setColor(String name, int color) {
|
||||
this.colors.add(ColorModel.valuesOf(name, color));
|
||||
return this;
|
||||
}
|
||||
|
||||
public static PaletteModel valuesOf(String id, String name, String author, List<ColorModel> colors) {
|
||||
return new PaletteModel()
|
||||
.setId(id)
|
||||
.setName(name)
|
||||
.setAuthor(author)
|
||||
.setColors(colors);
|
||||
}
|
||||
|
||||
public static PaletteModel fromJson(JSONObject json) throws JSONException {
|
||||
var palette = new PaletteModel()
|
||||
.setId(json.getString("palette_id"))
|
||||
.setName(json.getString("name"))
|
||||
.setAuthor(json.getString("author"));
|
||||
var paletteNode = json.getJSONArray("palette");
|
||||
for (int i = 0; i < paletteNode.length(); i++) {
|
||||
var colorNode = paletteNode.getJSONObject(i);
|
||||
var name = colorNode.getString("name");
|
||||
if (colorNode.has("colors")) {
|
||||
var colorsNode = colorNode.getJSONArray("colors");
|
||||
for (int j = 0; j < colorsNode.length(); j++) {
|
||||
var subColorNode = colorsNode.getJSONObject(j);
|
||||
var index = subColorNode.getString("index");
|
||||
palette.setColor(name + " " + index, subColorNode.getString("color"));
|
||||
}
|
||||
} else {
|
||||
palette.setColor(name, colorNode.getString("color"));
|
||||
}
|
||||
}
|
||||
return palette;
|
||||
}
|
||||
|
||||
public boolean hasColor(String name) {
|
||||
for (ColorModel item : colors)
|
||||
if (name.equals(item.name))
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
public int getColor(String name) {
|
||||
for (ColorModel item : colors)
|
||||
if (name.equals(item.name))
|
||||
return item.value;
|
||||
throw new NullPointerException("Color with name " + name + " not found");
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Palette{" +
|
||||
"id='" + id + '\'' +
|
||||
", name='" + name + '\'' +
|
||||
", author='" + author + '\'' +
|
||||
", colors=" + colors +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,190 @@
|
||||
package ru.vtosters.lite.res.models;
|
||||
|
||||
import android.graphics.Color;
|
||||
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import ru.vtosters.lite.res.managers.PalettesManager;
|
||||
|
||||
public class ThemeModel {
|
||||
|
||||
public String author;
|
||||
public String name;
|
||||
public boolean isDarkMode;
|
||||
public String id;
|
||||
public List<String> paletteIds = new ArrayList<>();
|
||||
public List<ColorModel> colors = new ArrayList<>();
|
||||
|
||||
public ThemeModel() {
|
||||
}
|
||||
|
||||
public ThemeModel(String author, String name, boolean darkMode, String id, List<String> palettes, List<ColorModel> colors) {
|
||||
this.author = author;
|
||||
this.name = name;
|
||||
this.isDarkMode = darkMode;
|
||||
this.id = id;
|
||||
if (palettes != null && palettes.size() > 0)
|
||||
this.paletteIds = palettes;
|
||||
this.colors = colors;
|
||||
}
|
||||
|
||||
public ThemeModel setAuthor(String author) {
|
||||
this.author = author;
|
||||
return this;
|
||||
}
|
||||
|
||||
public ThemeModel setName(String name) {
|
||||
this.name = name;
|
||||
return this;
|
||||
}
|
||||
|
||||
public ThemeModel setDarkMode(boolean b) {
|
||||
this.isDarkMode = b;
|
||||
return this;
|
||||
}
|
||||
|
||||
public ThemeModel setId(String id) {
|
||||
this.id = id;
|
||||
return this;
|
||||
}
|
||||
|
||||
public ThemeModel setPaletteIds(List<String> paletteIds) {
|
||||
this.paletteIds = paletteIds;
|
||||
return this;
|
||||
}
|
||||
|
||||
public ThemeModel setPalette(String palette) {
|
||||
this.paletteIds.add(palette);
|
||||
return this;
|
||||
}
|
||||
|
||||
public ThemeModel setColors(List<ColorModel> colors) {
|
||||
this.colors = colors;
|
||||
return this;
|
||||
}
|
||||
|
||||
public ThemeModel setColor(String colorName, int color) {
|
||||
this.colors.add(ColorModel.valuesOf(colorName, color));
|
||||
return this;
|
||||
}
|
||||
|
||||
public ThemeModel setColor(String colorName, String color) {
|
||||
this.colors.add(ColorModel.valuesOf(colorName, Color.parseColor(color.startsWith("#") ? color : "#" + color)));
|
||||
return this;
|
||||
}
|
||||
|
||||
public boolean hasColor(String colorName) {
|
||||
for (var item : colors)
|
||||
if (colorName.equals(item.name))
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
public int getColor(String name) {
|
||||
for (var item : colors)
|
||||
if (name.equals(item.name))
|
||||
return item.value;
|
||||
throw new IllegalArgumentException("Color with name " + name + " not found");
|
||||
}
|
||||
|
||||
public boolean hasPalettes() {
|
||||
return paletteIds.size() > 0;
|
||||
}
|
||||
|
||||
public PaletteModel getUsedPalette(String id) {
|
||||
if (paletteIds.contains(id))
|
||||
return PalettesManager.getInstance().getPalette(id);
|
||||
throw new IllegalArgumentException("Palette with id " + id + " not found");
|
||||
}
|
||||
|
||||
public static ThemeModel fromTheme(ThemeModel theme) {
|
||||
return valuesOf(
|
||||
theme.author,
|
||||
theme.name,
|
||||
theme.isDarkMode,
|
||||
theme.id,
|
||||
theme.paletteIds,
|
||||
theme.colors
|
||||
);
|
||||
}
|
||||
|
||||
public static ThemeModel valuesOf(String author, String name, boolean darkMode, String id, List<String> palettes, List<ColorModel> colors) {
|
||||
var theme = new ThemeModel()
|
||||
.setAuthor(author)
|
||||
.setName(name)
|
||||
.setDarkMode(darkMode)
|
||||
.setId(id)
|
||||
.setColors(colors);
|
||||
return palettes != null
|
||||
? theme.setPaletteIds(palettes)
|
||||
: theme;
|
||||
}
|
||||
|
||||
public static ThemeModel fromJson(String json) throws JSONException {
|
||||
return fromJson(new JSONObject(json));
|
||||
}
|
||||
|
||||
public static ThemeModel fromJson(JSONObject json) {
|
||||
List<String> palettes = null;
|
||||
if (json.has("palettes")) {
|
||||
palettes = new ArrayList<>();
|
||||
var arr = json.optString("palettes")
|
||||
.split("\\||,|&|;");
|
||||
Collections.addAll(palettes, arr);
|
||||
}
|
||||
List<ColorModel> colors = new ArrayList<>();
|
||||
var colorsJson = json.optJSONObject("colors");
|
||||
var names = colorsJson.names();
|
||||
for (int i = 0; i < colorsJson.length(); i++) {
|
||||
var name = names.optString(i);
|
||||
var value = colorsJson.optString(name);
|
||||
int color;
|
||||
if (value.contains(".")) {
|
||||
var paletteId = value.substring(0, value.indexOf("."));
|
||||
var palette = PalettesManager.getInstance().getPalette(paletteId);
|
||||
var paletteColor = value.substring(value.indexOf(".") + 1);
|
||||
color = palette.getColor(paletteColor);
|
||||
} else {
|
||||
color = Color.parseColor(value.startsWith("#") ? value : "#" + value);
|
||||
}
|
||||
colors.add(ColorModel.valuesOf(name, color));
|
||||
}
|
||||
return valuesOf(
|
||||
json.optString("author"),
|
||||
json.optString("name"),
|
||||
json.optBoolean("dark"),
|
||||
json.optString("id"),
|
||||
palettes,
|
||||
colors
|
||||
);
|
||||
}
|
||||
|
||||
public JSONObject toJson() throws JSONException {
|
||||
var colorsJson = new JSONObject();
|
||||
for (var item : colors)
|
||||
colorsJson.put(item.name, Integer.toHexString(item.value).toUpperCase());
|
||||
return new JSONObject()
|
||||
.put("author", this.author)
|
||||
.put("name", this.name)
|
||||
.put("dark", this.isDarkMode)
|
||||
.put("id", this.id)
|
||||
.put("colors", colorsJson);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Theme{" +
|
||||
"author='" + author + '\'' +
|
||||
", name='" + name + '\'' +
|
||||
", isDarkMode=" + isDarkMode +
|
||||
", id='" + id + '\'' +
|
||||
", paletteIds=" + paletteIds +
|
||||
", colors=" + colors +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package ru.vtosters.lite.res.models;
|
||||
|
||||
import android.util.Pair;
|
||||
|
||||
import androidx.annotation.IdRes;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class ViewModel {
|
||||
|
||||
@IdRes public int id;
|
||||
public List<Pair<Integer, Integer>> attrs = new ArrayList<>();
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package ru.vtosters.lite.res.modifiers;
|
||||
|
||||
import android.content.res.ColorStateList;
|
||||
import android.view.View;
|
||||
|
||||
import com.google.android.material.floatingactionbutton.FloatingActionButton;
|
||||
import com.vtosters.lite.R;
|
||||
|
||||
public class FloatingActionButtonRecolor implements IModifier<View> {
|
||||
|
||||
public static final int tint = R.attr.tint;
|
||||
public static final int androidTint = android.R.attr.tint;
|
||||
|
||||
@Override
|
||||
public boolean isModified(View target) {
|
||||
return target instanceof FloatingActionButton;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void modify(View target, int attr, int color) {
|
||||
var fab = (FloatingActionButton) target;
|
||||
switch (attr) {
|
||||
case tint:
|
||||
case androidTint:
|
||||
fab.setImageTintList(ColorStateList.valueOf(color));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
package ru.vtosters.lite.res.modifiers;
|
||||
|
||||
public interface IModifier<T> {
|
||||
|
||||
boolean isModified(T target);
|
||||
|
||||
void modify(T target, int attr, int color);
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package ru.vtosters.lite.res.modifiers;
|
||||
|
||||
import android.content.res.ColorStateList;
|
||||
import android.view.View;
|
||||
import android.widget.ImageView;
|
||||
|
||||
public class ImageViewRecolor implements IModifier<View> {
|
||||
|
||||
public static final int tint = com.vtosters.lite.R.attr.tint;
|
||||
public static final int androidTint = android.R.attr.tint;
|
||||
|
||||
@Override
|
||||
public boolean isModified(View target) {
|
||||
return target instanceof ImageView;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void modify(View target, int attr, int color) {
|
||||
var iv = (ImageView) target;
|
||||
switch (attr) {
|
||||
case tint:
|
||||
case androidTint:
|
||||
iv.setImageTintList(ColorStateList.valueOf(color));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package ru.vtosters.lite.res.modifiers;
|
||||
|
||||
import android.content.res.ColorStateList;
|
||||
import android.view.View;
|
||||
import android.widget.ProgressBar;
|
||||
|
||||
public class ProgressBarRecolor implements IModifier<View> {
|
||||
|
||||
public static final int progressBackgroundTint = android.R.attr.progressBackgroundTint;
|
||||
public static final int progressTint = android.R.attr.progressTint;
|
||||
public static final int indeterminateTint = android.R.attr.indeterminateTint;
|
||||
|
||||
@Override
|
||||
public boolean isModified(View target) {
|
||||
return target instanceof ProgressBar;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void modify(View target, int attr, int color) {
|
||||
var pb = (ProgressBar) target;
|
||||
switch (attr) {
|
||||
case progressBackgroundTint:
|
||||
pb.setProgressBackgroundTintList(ColorStateList.valueOf(color));
|
||||
break;
|
||||
case progressTint:
|
||||
pb.setProgressTintList(ColorStateList.valueOf(color));
|
||||
break;
|
||||
case indeterminateTint:
|
||||
pb.setIndeterminateTintList(ColorStateList.valueOf(color));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package ru.vtosters.lite.res.modifiers;
|
||||
|
||||
import android.content.res.ColorStateList;
|
||||
import android.view.View;
|
||||
|
||||
import androidx.appcompat.widget.SwitchCompat;
|
||||
import com.vtosters.lite.R;
|
||||
|
||||
public class SwitchCompatRecolor implements IModifier<View> {
|
||||
|
||||
public static final int thumbTint = R.attr.thumbTint;
|
||||
public static final int trackTint = R.attr.trackTint;
|
||||
|
||||
@Override
|
||||
public boolean isModified(View target) {
|
||||
return target instanceof SwitchCompat;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void modify(View target, int attr, int color) {
|
||||
var sc = (SwitchCompat) target;
|
||||
switch (attr) {
|
||||
case thumbTint:
|
||||
sc.setThumbTintList(ColorStateList.valueOf(color));
|
||||
break;
|
||||
case trackTint:
|
||||
sc.setTrackTintList(ColorStateList.valueOf(color));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package ru.vtosters.lite.res.modifiers;
|
||||
|
||||
import android.view.View;
|
||||
|
||||
import com.google.android.material.tabs.TabLayout;
|
||||
import com.vtosters.lite.R;
|
||||
|
||||
public class TabLayoutRecolor implements IModifier<View> {
|
||||
|
||||
public static final int tabIndicatorColor = R.attr.tabIndicatorColor;
|
||||
|
||||
@Override
|
||||
public boolean isModified(View target) {
|
||||
return target instanceof TabLayout;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void modify(View target, int attr, int color) {
|
||||
var tl = (TabLayout) target;
|
||||
switch (attr) {
|
||||
case tabIndicatorColor:
|
||||
tl.setSelectedTabIndicatorColor(color);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
package ru.vtosters.lite.res.modifiers;
|
||||
|
||||
import android.graphics.Color;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.view.View;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.core.widget.TextViewCompat;
|
||||
|
||||
import com.vk.core.drawable.RecoloredDrawable;
|
||||
|
||||
public class TextViewRecolor implements IModifier<View> {
|
||||
|
||||
public static final int textColor = android.R.attr.textColor;
|
||||
public static final int textColorLink = android.R.attr.textColorLink;
|
||||
public static final int textColorHint = android.R.attr.textColorHint;
|
||||
|
||||
@Override
|
||||
public boolean isModified(View target) {
|
||||
return target instanceof TextView;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void modify(View target, int attr, int color) {
|
||||
var tv = (TextView) target;
|
||||
switch (attr) {
|
||||
case textColor:
|
||||
tv.setTextColor(color);
|
||||
break;
|
||||
case textColorLink:
|
||||
tv.setLinkTextColor(color);
|
||||
break;
|
||||
case textColorHint:
|
||||
tv.setHintTextColor(color);
|
||||
break;
|
||||
}
|
||||
var drawables = tv.getCompoundDrawablesRelative();
|
||||
|
||||
for (int i = 0; i < drawables.length; i++) {
|
||||
var drawable = drawables[i];
|
||||
if (drawable != null)
|
||||
drawables[i] = new RecoloredDrawable(drawable, Color.BLACK);
|
||||
}
|
||||
tv.invalidate();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package ru.vtosters.lite.res.modifiers;
|
||||
|
||||
import android.view.View;
|
||||
|
||||
import com.vk.core.view.TintTextView;
|
||||
import com.vtosters.lite.R;
|
||||
|
||||
public class TintTextViewRecolor implements IModifier<View> {
|
||||
|
||||
public static final int drawableTint = R.attr.drawableTint;
|
||||
|
||||
@Override
|
||||
public boolean isModified(View target) {
|
||||
return target instanceof TintTextView;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void modify(View target, int attr, int color) {
|
||||
var ttv = (TintTextView) target;
|
||||
switch (attr) {
|
||||
case drawableTint:
|
||||
ttv.setDrawableTint(color);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
package ru.vtosters.lite.res.modifiers;
|
||||
|
||||
import android.view.View;
|
||||
|
||||
import androidx.appcompat.widget.Toolbar;
|
||||
import com.vtosters.lite.R;
|
||||
|
||||
public class ToolbarRecolor implements IModifier<View> {
|
||||
|
||||
public static final int subtitleTextColor = R.attr.subtitleTextColor;
|
||||
public static final int titleTextColor = R.attr.titleTextColor;
|
||||
|
||||
@Override
|
||||
public boolean isModified(View target) {
|
||||
return target instanceof Toolbar;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void modify(View target, int attr, int color) {
|
||||
var t = (Toolbar) target;
|
||||
switch (attr) {
|
||||
case subtitleTextColor:
|
||||
t.setSubtitleTextColor(color);
|
||||
break;
|
||||
case titleTextColor:
|
||||
t.setTitleTextColor(color);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package ru.vtosters.lite.res.modifiers;
|
||||
|
||||
import android.content.res.ColorStateList;
|
||||
import android.view.View;
|
||||
|
||||
public class ViewRecolor implements IModifier<View> {
|
||||
|
||||
public static final int backgroundTint = android.R.attr.backgroundTint;
|
||||
public static final int background = android.R.attr.background;
|
||||
|
||||
@Override
|
||||
public boolean isModified(View target) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void modify(View target, int attr, int color) {
|
||||
switch (attr) {
|
||||
case backgroundTint:
|
||||
target.setBackgroundTintList(ColorStateList.valueOf(color));
|
||||
break;
|
||||
case background:
|
||||
target.setBackgroundColor(color);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,135 @@
|
||||
package ru.vtosters.lite.res.proxy;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.Resources;
|
||||
import android.content.res.XmlResourceParser;
|
||||
import android.util.AttributeSet;
|
||||
import android.util.Pair;
|
||||
import android.util.SparseArray;
|
||||
import android.util.Xml;
|
||||
import android.view.InflateException;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import org.xmlpull.v1.XmlPullParser;
|
||||
import org.xmlpull.v1.XmlPullParserException;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import ru.vtosters.lite.res.Recolor;
|
||||
import ru.vtosters.lite.res.models.ViewModel;
|
||||
import ru.vtosters.lite.utils.AndroidUtils;
|
||||
|
||||
public class ProxyLayoutInflater extends LayoutInflater {
|
||||
|
||||
private static final List<Integer> parsedLayouts = new ArrayList<>();
|
||||
private static final SparseArray<SparseArray<ViewModel>> layouts = new SparseArray<>();
|
||||
|
||||
public ProxyLayoutInflater(LayoutInflater layoutInflater, Context context) {
|
||||
super(layoutInflater, context);
|
||||
}
|
||||
|
||||
@Override
|
||||
public LayoutInflater cloneInContext(Context newContext) {
|
||||
return new ProxyLayoutInflater(this, newContext);
|
||||
}
|
||||
|
||||
@Override
|
||||
public View inflate(int resource, @Nullable ViewGroup root, boolean attachToRoot) {
|
||||
//parseLayout(resource);
|
||||
var target = super.inflate(resource, root, attachToRoot);
|
||||
//Recolor.recolorViewOrViewGroup(target, layouts.get(resource));
|
||||
return target;
|
||||
}
|
||||
|
||||
public View createView(AttributeSet attrs, String name, String prefix)
|
||||
throws ClassNotFoundException {
|
||||
var target = super.createView(name, prefix, attrs);
|
||||
return target;
|
||||
}
|
||||
|
||||
public static void parseLayout(int layoutId) {
|
||||
if (parsedLayouts.contains(layoutId)) return;
|
||||
var sattrs = new SparseArray<ViewModel>();
|
||||
var parser = AndroidUtils.getResources().getLayout(layoutId);
|
||||
try {
|
||||
checkParser(parser);
|
||||
while (parser.next() != XmlPullParser.END_DOCUMENT) {
|
||||
var name = parser.getName();
|
||||
var attrs = Xml.asAttributeSet(parser);
|
||||
if ("include".equals(name) || "ViewStub".equals(name))
|
||||
parseNodeWithLayoutAttr(attrs);
|
||||
else
|
||||
parseOtherNode(attrs, sattrs);
|
||||
}
|
||||
layouts.put(layoutId, sattrs);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
parsedLayouts.add(layoutId);
|
||||
}
|
||||
|
||||
// parse nodes with names <include> and <ViewStub>, which contain attribute "layout"
|
||||
private static void parseNodeWithLayoutAttr(AttributeSet attrs) {
|
||||
for (int i = 0; i < attrs.getAttributeCount(); i++) {
|
||||
if ("layout".equals(attrs.getAttributeName(i))) {
|
||||
var avalue = attrs.getAttributeResourceValue(i, 0);
|
||||
if (avalue == 0) continue;
|
||||
parseLayout(avalue);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void parseOtherNode(AttributeSet attrs, SparseArray<ViewModel> sattrs) {
|
||||
int id = -1;
|
||||
for (int i = 0; i < attrs.getAttributeCount(); i++)
|
||||
if ("id".equals(attrs.getAttributeName(i))) {
|
||||
var avalue = attrs.getAttributeValue(i);
|
||||
if (avalue != null)
|
||||
id = Integer.parseInt(avalue.substring(1));
|
||||
break;
|
||||
}
|
||||
if (id == -1) return;
|
||||
var viewModel = new ViewModel();
|
||||
viewModel.id = id;
|
||||
for (int i = 0; i < attrs.getAttributeCount(); i++) {
|
||||
var aname = attrs.getAttributeNameResource(i);
|
||||
var avalue = attrs.getAttributeValue(i);
|
||||
if (!avalue.startsWith("?")) continue;
|
||||
int val;
|
||||
if (avalue.matches("\\?\\d+")) {
|
||||
val = Integer.parseInt(avalue.substring(1)); // strip "?"
|
||||
} else {
|
||||
var androidAttr = avalue.contains("android:");
|
||||
try {
|
||||
val = AndroidUtils.getResources().getIdentifier(
|
||||
avalue.substring(androidAttr ? 9 : 1), // strip "@android:" or "@"
|
||||
"attr",
|
||||
androidAttr ? "android" : AndroidUtils.getPackageName());
|
||||
} catch (Resources.NotFoundException ignored) {
|
||||
val = 0;
|
||||
}
|
||||
}
|
||||
viewModel.attrs.add(new Pair<>(aname, val));
|
||||
}
|
||||
sattrs.put(id, viewModel);
|
||||
}
|
||||
|
||||
private static void checkParser(XmlResourceParser parser)
|
||||
throws XmlPullParserException, IOException {
|
||||
int type;
|
||||
while ((type = parser.next()) != XmlPullParser.START_TAG
|
||||
&& type != XmlPullParser.END_DOCUMENT) {
|
||||
// Empty
|
||||
}
|
||||
if (type != XmlPullParser.START_TAG)
|
||||
throw new InflateException(parser.getPositionDescription()
|
||||
+ ": No start tag found!");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
package ru.vtosters.lite.res.proxy;
|
||||
|
||||
import android.content.res.ColorStateList;
|
||||
import android.content.res.Resources;
|
||||
import android.graphics.drawable.Drawable;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.HashMap;
|
||||
|
||||
import ru.vtosters.lite.utils.ReflectionUtils;
|
||||
import ru.vtosters.lite.utils.ThemesUtils;
|
||||
|
||||
public class ProxyResources extends Resources {
|
||||
private static final HashMap<Class<?>, Field> resourceFieldsMap = new HashMap<>();
|
||||
|
||||
private Resources mOriginalResource;
|
||||
|
||||
public ProxyResources(Resources original) {
|
||||
super(original.getAssets(), original.getDisplayMetrics(), original.getConfiguration());
|
||||
mOriginalResource = original;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getColor(int id, @Nullable Resources.Theme theme) throws Resources.NotFoundException {
|
||||
return mOriginalResource.getColor(id, theme);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public ColorStateList getColorStateList(int id, @Nullable Resources.Theme theme) throws Resources.NotFoundException {
|
||||
return mOriginalResource.getColorStateList(id, theme);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public Drawable getDrawableForDensity(int id, int density, @Nullable Resources.Theme theme) {
|
||||
return mOriginalResource.getDrawableForDensity(id, density, theme);
|
||||
}
|
||||
|
||||
public static void addProxyResources(Object target) {
|
||||
// if (!ThemesUtils.isCustomThemeApplied()) return;
|
||||
// try {
|
||||
// Class<?> clz = target.getClass();
|
||||
// Field field = null;
|
||||
// if (!resourceFieldsMap.containsKey(clz)) {
|
||||
// loop:
|
||||
// while (clz != null) {
|
||||
// for (var f : clz.getDeclaredFields()) {
|
||||
// if (f.getType() == Resources.class) {
|
||||
// field = f;
|
||||
// break loop;
|
||||
// }
|
||||
// }
|
||||
// for (var f : clz.getFields()) {
|
||||
// if (f.getType() == Resources.class) {
|
||||
// field = f;
|
||||
// break loop;
|
||||
// }
|
||||
// }
|
||||
// clz = clz.getSuperclass();
|
||||
// }
|
||||
// resourceFieldsMap.put(target.getClass(), field);
|
||||
// } else {
|
||||
// field = resourceFieldsMap.get(clz);
|
||||
// }
|
||||
// if (field == null) return;
|
||||
// field.setAccessible(true);
|
||||
// ReflectionUtils.unfinalField(field);
|
||||
// var original = (Resources) field.get(target);
|
||||
// if (original != null && !(original instanceof ProxyResources))
|
||||
// field.set(target, new ProxyResources(original));
|
||||
// } catch (Throwable e) {
|
||||
// e.printStackTrace();
|
||||
// }
|
||||
}
|
||||
}
|
||||
@@ -1,147 +0,0 @@
|
||||
package ru.vtosters.lite.themes;
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.Log;
|
||||
import android.util.SparseBooleanArray;
|
||||
import android.util.SparseIntArray;
|
||||
|
||||
import androidx.annotation.IdRes;
|
||||
|
||||
import com.vk.core.ui.themes.VKTheme;
|
||||
|
||||
import java.util.HashMap;
|
||||
|
||||
import ru.vtosters.lite.utils.ThemesUtils;
|
||||
|
||||
|
||||
public class ThemesCore {
|
||||
private static final String TAG = "ThemesCore";
|
||||
|
||||
public static SparseIntArray themedColors = new SparseIntArray();
|
||||
public static SparseBooleanArray ACCENT_THEME_ONLY_LIGHT = new SparseBooleanArray();
|
||||
public static SparseBooleanArray ACCENT_THEME_ONLY_NOMILK_LIGHT = new SparseBooleanArray();
|
||||
public static SparseBooleanArray ACCENT_THEME_ONLY_NOMILK = new SparseBooleanArray();
|
||||
public static SparseBooleanArray ACCENT_THEME_ONLY_MILK_LIGHT = new SparseBooleanArray();
|
||||
public static SparseBooleanArray ACCENT_THEME_ONLY_MILK_DARK = new SparseBooleanArray();
|
||||
|
||||
private static boolean cachedAccents = false;
|
||||
|
||||
private static int wallDarkColor;
|
||||
private static int wallLightColor;
|
||||
|
||||
public static void setExceptions() {
|
||||
ACCENT_THEME_ONLY_LIGHT.clear();
|
||||
ACCENT_THEME_ONLY_NOMILK_LIGHT.clear();
|
||||
ACCENT_THEME_ONLY_NOMILK.clear();
|
||||
ACCENT_THEME_ONLY_MILK_LIGHT.clear();
|
||||
ACCENT_THEME_ONLY_MILK_DARK.clear();
|
||||
|
||||
ACCENT_THEME_ONLY_LIGHT.put(com.vtosters.lite.R.attr.button_primary_background, false);
|
||||
ACCENT_THEME_ONLY_LIGHT.put(com.vtosters.lite.R.attr.button_secondary_foreground, false);
|
||||
ACCENT_THEME_ONLY_LIGHT.put(com.vtosters.lite.R.attr.button_tertiary_foreground, false);
|
||||
ACCENT_THEME_ONLY_LIGHT.put(com.vtosters.lite.R.attr.button_muted_foreground, false);
|
||||
ACCENT_THEME_ONLY_LIGHT.put(com.vtosters.lite.R.attr.button_outline_border, false);
|
||||
ACCENT_THEME_ONLY_LIGHT.put(com.vtosters.lite.R.attr.text_name, false);
|
||||
ACCENT_THEME_ONLY_LIGHT.put(com.vtosters.lite.R.attr.newsfeed_post_title_color, false);
|
||||
ACCENT_THEME_ONLY_LIGHT.put(com.vtosters.lite.R.attr.attach_picker_tab_active_background, false);
|
||||
ACCENT_THEME_ONLY_LIGHT.put(com.vtosters.lite.R.attr.attach_picker_tab_active_text, false);
|
||||
ACCENT_THEME_ONLY_LIGHT.put(com.vtosters.lite.R.attr.newsfeed_action_color, false);
|
||||
ACCENT_THEME_ONLY_LIGHT.put(com.vtosters.lite.R.attr.counter_primary_background, false);
|
||||
ACCENT_THEME_ONLY_LIGHT.put(com.vtosters.lite.R.attr.action_sheet_action_foreground, false);
|
||||
|
||||
ACCENT_THEME_ONLY_NOMILK_LIGHT.put(com.vtosters.lite.R.attr.header_background, false);
|
||||
|
||||
ACCENT_THEME_ONLY_MILK_LIGHT.put(com.vtosters.lite.R.attr.header_tint, false);
|
||||
ACCENT_THEME_ONLY_MILK_LIGHT.put(com.vtosters.lite.R.attr.header_tint_alternate, false);
|
||||
ACCENT_THEME_ONLY_MILK_LIGHT.put(com.vtosters.lite.R.attr.toolbarIconsColor, false);
|
||||
ACCENT_THEME_ONLY_MILK_LIGHT.put(com.vtosters.lite.R.attr.header_tab_active_indicator, false);
|
||||
ACCENT_THEME_ONLY_MILK_LIGHT.put(com.vtosters.lite.R.attr.im_dropdown_arrow_tint, false);
|
||||
|
||||
ACCENT_THEME_ONLY_NOMILK.put(com.vtosters.lite.R.attr.im_dropdown_icon_color, false);
|
||||
}
|
||||
|
||||
public static void setThemedColors(int accentColor) {
|
||||
cachedAccents = true;
|
||||
themedColors.clear();
|
||||
|
||||
themedColors.put(com.vtosters.lite.R.attr.text_link, accentColor);
|
||||
themedColors.put(com.vtosters.lite.R.attr.accent, accentColor);
|
||||
themedColors.put(com.vtosters.lite.R.attr.newsfeed_dropdown_color, accentColor);
|
||||
|
||||
themedColors.put(com.vtosters.lite.R.attr.vk_accent, accentColor);
|
||||
themedColors.put(com.vtosters.lite.R.attr.accentColor, accentColor);
|
||||
themedColors.put(com.vtosters.lite.R.attr.text_name, accentColor);
|
||||
themedColors.put(com.vtosters.lite.R.attr.tabbar_active_icon, accentColor);
|
||||
//themedColors.put(com.vtosters.lite.R.attr.header_background, accentColor);
|
||||
themedColors.put(com.vtosters.lite.R.attr.header_tint, accentColor);
|
||||
themedColors.put(com.vtosters.lite.R.attr.header_tint_alternate, accentColor);
|
||||
themedColors.put(com.vtosters.lite.R.attr.activity_indicator_tint, accentColor);
|
||||
themedColors.put(com.vtosters.lite.R.attr.toolbarIconsColor, accentColor);
|
||||
|
||||
themedColors.put(com.vtosters.lite.R.attr.button_primary_background, accentColor);
|
||||
themedColors.put(com.vtosters.lite.R.attr.button_secondary_foreground, accentColor);
|
||||
themedColors.put(com.vtosters.lite.R.attr.button_tertiary_foreground, accentColor);
|
||||
themedColors.put(com.vtosters.lite.R.attr.button_outline_border, accentColor);
|
||||
themedColors.put(com.vtosters.lite.R.attr.button_muted_foreground, accentColor);
|
||||
themedColors.put(com.vtosters.lite.R.attr.header_tab_active_indicator, accentColor);
|
||||
|
||||
themedColors.put(com.vtosters.lite.R.attr.im_dropdown_icon_color, accentColor);
|
||||
themedColors.put(com.vtosters.lite.R.attr.im_dropdown_arrow_tint, accentColor);
|
||||
|
||||
themedColors.put(com.vtosters.lite.R.attr.im_attach_tint, accentColor);
|
||||
themedColors.put(com.vtosters.lite.R.attr.im_forward_line_tint, accentColor);
|
||||
themedColors.put(com.vtosters.lite.R.attr.link_alternate, accentColor);
|
||||
themedColors.put(com.vtosters.lite.R.attr.newsfeed_post_title_color, accentColor);
|
||||
|
||||
themedColors.put(com.vtosters.lite.R.attr.attach_picker_tab_active_background, accentColor);
|
||||
themedColors.put(com.vtosters.lite.R.attr.attach_picker_tab_active_text, accentColor);
|
||||
themedColors.put(com.vtosters.lite.R.attr.newsfeed_action_color, accentColor);
|
||||
themedColors.put(com.vtosters.lite.R.attr.counter_primary_background, accentColor);
|
||||
|
||||
themedColors.put(com.vtosters.lite.R.attr.action_sheet_action_foreground, accentColor);
|
||||
|
||||
// messages
|
||||
wallDarkColor = ThemesUtils.darken(accentColor, 0.25f);
|
||||
wallLightColor = ThemesUtils.lighten(accentColor, 0.25f);
|
||||
|
||||
//themedColors.put(com.vtosters.lite.R.attr.im_ic_send_msg, accentColor);
|
||||
themedColors.put(com.vtosters.lite.R.attr.im_bubble_wallpaper_outgoing, accentColor);
|
||||
themedColors.put(com.vtosters.lite.R.attr.im_bubble_outgoing, accentColor);
|
||||
themedColors.put(com.vtosters.lite.R.attr.im_bubble_outgoing_highlighted, accentColor);
|
||||
}
|
||||
|
||||
public static int getThemedAttr(Context context, @IdRes int attr) {
|
||||
if (attr == com.vtosters.lite.R.attr.im_bubble_wallpaper_outgoing || attr == com.vtosters.lite.R.attr.im_bubble_outgoing) {
|
||||
return (!ThemesUtils.isDarkTheme()) ? wallLightColor : wallDarkColor;
|
||||
}
|
||||
|
||||
if (attr == com.vtosters.lite.R.attr.im_bubble_outgoing_highlighted) {
|
||||
return (!ThemesUtils.isDarkTheme()) ? ThemesUtils.lighten(wallLightColor, 0.2f) : ThemesUtils.darken(wallDarkColor, 0.2f);
|
||||
}
|
||||
|
||||
return themedColors.get(attr);
|
||||
}
|
||||
|
||||
public static boolean hasThemedAttr(Context context, int attrID) {
|
||||
try {
|
||||
Log.d(TAG, "Requesting color by attr " + context.getResources().getResourceName(attrID));
|
||||
} catch (Exception e) {
|
||||
}
|
||||
|
||||
if (isCachedAccents()) {
|
||||
if (ThemesUtils.getCurrentTheme() == VKTheme.VKAPP_LIGHT && ThemesUtils.getCurrentTheme() == VKTheme.VKAPP_MILK_LIGHT) {
|
||||
return (themedColors.get(attrID) != 0
|
||||
&& ACCENT_THEME_ONLY_MILK_DARK.get(attrID, true)
|
||||
&& ACCENT_THEME_ONLY_NOMILK_LIGHT.get(attrID, true));
|
||||
} else {
|
||||
return (themedColors.get(attrID) != 0
|
||||
&& ACCENT_THEME_ONLY_MILK_LIGHT.get(attrID, true)
|
||||
&& ACCENT_THEME_ONLY_NOMILK_LIGHT.get(attrID, true));
|
||||
}
|
||||
} else return themedColors.get(attrID) != 0;
|
||||
}
|
||||
|
||||
public static boolean isCachedAccents() {
|
||||
return cachedAccents;
|
||||
}
|
||||
}
|
||||
@@ -1,45 +0,0 @@
|
||||
package ru.vtosters.lite.themes;
|
||||
|
||||
import android.graphics.drawable.BitmapDrawable;
|
||||
import android.graphics.drawable.ColorDrawable;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.graphics.drawable.DrawableContainer;
|
||||
import android.graphics.drawable.InsetDrawable;
|
||||
import android.graphics.drawable.LayerDrawable;
|
||||
import android.graphics.drawable.StateListDrawable;
|
||||
import android.view.View;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.vtosters.lite.R;
|
||||
|
||||
import ru.vtosters.lite.utils.ThemesUtils;
|
||||
|
||||
public class ThemesHacks {
|
||||
// Фиксит селектор (все/свои/архив) в профиле
|
||||
public static void fixProfileSelector(View view) {
|
||||
if (!ThemesUtils.isMilkshake()) return;
|
||||
var id = view.getId();
|
||||
|
||||
if (id == R.id.profile_wall_owner_posts || id == R.id.profile_wall_all_posts || id == R.id.profile_wall_archived_posts) {
|
||||
StateListDrawable states = (StateListDrawable) ((LayerDrawable) view.getBackground()).getDrawable(0);
|
||||
LayerDrawable selected = (LayerDrawable) ((DrawableContainer.DrawableContainerState) states.getConstantState()).getChildren()[0];
|
||||
((ColorDrawable) selected.getDrawable(0)).setColor(ThemesUtils.getAccentColor());
|
||||
}
|
||||
}
|
||||
|
||||
public static void fixThemedFeed(TextView textView, int i) {
|
||||
if (i != R.layout.themed_feed_tab_2) {
|
||||
return;
|
||||
}
|
||||
textView.setTextColor(ThemesUtils.themeCSL(textView.getTextColors()));
|
||||
((InsetDrawable) ((DrawableContainer.DrawableContainerState) ((StateListDrawable) textView.getBackground()).getConstantState()).getChildren()[0]).getDrawable().setTint(ThemesUtils.lighten(ThemesUtils.getAccentColor(), 0.15f));
|
||||
}
|
||||
|
||||
public static void fixDropdown(Drawable drawable) {
|
||||
if (!ThemesUtils.isDarkTheme() && ThemesUtils.isMilkshake() && ThemesCore.isCachedAccents()) {
|
||||
StateListDrawable stateListDrawable = (StateListDrawable) drawable;
|
||||
((BitmapDrawable) ((DrawableContainer.DrawableContainerState) stateListDrawable.getConstantState()).getChildren()[0]).setTint(ThemesUtils.getAccentColor());
|
||||
((BitmapDrawable) ((DrawableContainer.DrawableContainerState) stateListDrawable.getConstantState()).getChildren()[1]).setTint(ThemesUtils.halfAlpha(ThemesUtils.getAccentColor()));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,96 +0,0 @@
|
||||
package ru.vtosters.lite.themes;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.ColorStateList;
|
||||
import android.content.res.Resources;
|
||||
import android.content.res.TypedArray;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.util.AttributeSet;
|
||||
import android.util.Log;
|
||||
import android.util.TypedValue;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import ru.vtosters.lite.utils.ThemesUtils;
|
||||
|
||||
public class VTLResources extends Resources {
|
||||
private static final String TAG = "VTLResources";
|
||||
private final Context context;
|
||||
private static Field typedArrayField;
|
||||
|
||||
private static final List<Integer> attributesToTheme = new ArrayList<>();
|
||||
|
||||
static {
|
||||
try {
|
||||
typedArrayField = TypedArray.class.getDeclaredField("mData");
|
||||
typedArrayField.setAccessible(true);
|
||||
} catch (NoSuchFieldException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
attributesToTheme.add(16843041);
|
||||
attributesToTheme.add(android.R.attr.backgroundTint);
|
||||
attributesToTheme.add(16843173);
|
||||
}
|
||||
|
||||
public VTLResources(Context context, Resources parent) {
|
||||
super(parent.getAssets(), parent.getDisplayMetrics(), parent.getConfiguration());
|
||||
this.context = context;
|
||||
Log.d(TAG, "VTLResources: init");
|
||||
}
|
||||
|
||||
@Override
|
||||
public TypedArray obtainAttributes(AttributeSet set, int[] attrs) {
|
||||
var typedArray = super.obtainAttributes(set, attrs);
|
||||
var data = getArrayData(typedArray);
|
||||
for (int i = 0; i < attrs.length; i++) {
|
||||
try {
|
||||
int attrID = attrs[i];
|
||||
try {
|
||||
} catch (Exception ignored) {}
|
||||
if (attributesToTheme.contains(attrID)) {
|
||||
int type = data[i*6];
|
||||
int cnt = data[(i*6)+1];
|
||||
|
||||
if (type == TypedValue.TYPE_ATTRIBUTE && isAttrThemeable(cnt)) {
|
||||
data[i*6] = TypedValue.TYPE_INT_COLOR_RGB8;
|
||||
data[(i*6)+1] = ThemesUtils.getAccentColor();
|
||||
data[(i*6)+2] = 0; // clear reference content
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "TAVzlom failed! (obtainAttributes)");
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
return typedArray;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Drawable getDrawable(int id, @Nullable Theme theme) throws NotFoundException {
|
||||
var drawable = super.getDrawable(id, theme);
|
||||
if (id == com.vtosters.lite.R.drawable.newsfeed_tab_dropdown_16) {
|
||||
ThemesHacks.fixDropdown(drawable);
|
||||
}
|
||||
return drawable;
|
||||
}
|
||||
|
||||
private static boolean isAttrThemeable(int attrID) {
|
||||
return !ThemesUtils.isDarkTheme() && attrID == com.vtosters.lite.R.attr.button_primary_background;
|
||||
}
|
||||
|
||||
private static int[] getArrayData(TypedArray arr) {
|
||||
try {
|
||||
return (int[]) typedArrayField.get(arr);
|
||||
} catch (IllegalAccessException e) {
|
||||
Log.e(TAG, "getArrayData: ", e);
|
||||
e.printStackTrace();
|
||||
return new int[0];
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,36 +0,0 @@
|
||||
package ru.vtosters.lite.themes;
|
||||
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
import ru.vtosters.lite.themes.hooks.*;
|
||||
|
||||
public class ViewInjector {
|
||||
public static ArrayList<BaseHook> hooks = new ArrayList<>();
|
||||
|
||||
static {
|
||||
hooks.add(new ButtonHook());
|
||||
hooks.add(new ImageViewHook());
|
||||
hooks.add(new TabLayoutHook());
|
||||
hooks.add(new TextViewHook());
|
||||
hooks.add(new ToolbarHook());
|
||||
hooks.add(new VKImageViewHook());
|
||||
hooks.add(new WaveformViewHook());
|
||||
}
|
||||
|
||||
public static View inject(View view, int i, boolean z) {
|
||||
for (BaseHook hook : hooks) {
|
||||
hook.inject(view, i, z);
|
||||
}
|
||||
if (view instanceof ViewGroup) {
|
||||
var viewGroup = (ViewGroup) view;
|
||||
for (int i2 = 0; i2 < viewGroup.getChildCount(); i2++) {
|
||||
inject(viewGroup.getChildAt(i2), i, false);
|
||||
}
|
||||
}
|
||||
|
||||
return view;
|
||||
}
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
package ru.vtosters.lite.themes.hooks;
|
||||
|
||||
import android.view.View;
|
||||
|
||||
public interface BaseHook {
|
||||
void inject(View view, int i, boolean z);
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
package ru.vtosters.lite.themes.hooks;
|
||||
|
||||
import android.content.res.ColorStateList;
|
||||
import android.view.View;
|
||||
import android.widget.Button;
|
||||
|
||||
import ru.vtosters.lite.utils.ThemesUtils;
|
||||
|
||||
public class ButtonHook implements BaseHook {
|
||||
@Override
|
||||
public void inject(View view, int i, boolean z) {
|
||||
if (view instanceof Button) {
|
||||
var button = (Button) view;
|
||||
if (button.getBackgroundTintList() != null && ThemesUtils.isAccentedColor(button.getBackgroundTintList())) {
|
||||
button.setBackgroundTintList(ColorStateList.valueOf(ThemesUtils.getAccentColor()));
|
||||
}
|
||||
if (TextViewHook.isPositiveButton(button) || TextViewHook.isVkUiButton(view)) {
|
||||
button.setBackgroundTintList(ColorStateList.valueOf(ThemesUtils.getAccentColor()));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,31 +0,0 @@
|
||||
package ru.vtosters.lite.themes.hooks;
|
||||
|
||||
import android.graphics.PorterDuff;
|
||||
import android.view.View;
|
||||
import android.widget.ImageView;
|
||||
|
||||
import androidx.appcompat.widget.AppCompatImageView;
|
||||
import androidx.core.widget.ImageViewCompat;
|
||||
|
||||
import ru.vtosters.lite.utils.ThemesUtils;
|
||||
|
||||
public class ImageViewHook implements BaseHook {
|
||||
@Override
|
||||
public void inject(View view, int i, boolean z) {
|
||||
if (view instanceof AppCompatImageView) {
|
||||
var imageView = (AppCompatImageView) view;
|
||||
if (imageView.getImageTintList() != null) {
|
||||
imageView.setImageTintList(ThemesUtils.themeCSL(imageView.getImageTintList()));
|
||||
imageView.setColorFilter(ThemesUtils.getAccentColor(), PorterDuff.Mode.SRC_IN);
|
||||
}
|
||||
}
|
||||
|
||||
if (view instanceof ImageView) {
|
||||
var imageView = (ImageView) view;
|
||||
if (imageView.getImageTintList() != null) {
|
||||
imageView.setImageTintList(ThemesUtils.themeCSL(imageView.getImageTintList()));
|
||||
imageView.setColorFilter(ThemesUtils.getAccentColor(), PorterDuff.Mode.SRC_IN);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
package ru.vtosters.lite.themes.hooks;
|
||||
|
||||
import android.content.res.ColorStateList;
|
||||
import android.view.View;
|
||||
|
||||
import com.vk.core.view.VKTabLayout;
|
||||
|
||||
import ru.vtosters.lite.utils.ThemesUtils;
|
||||
|
||||
|
||||
public class TabLayoutHook implements BaseHook {
|
||||
@Override
|
||||
public void inject(View view, int i, boolean z) {
|
||||
if (view instanceof VKTabLayout) {
|
||||
var tabLayout = (VKTabLayout) view;
|
||||
tabLayout.setSelectedTabIndicatorColor(ThemesUtils.getAccentColor());
|
||||
tabLayout.setTabRippleColor(ColorStateList.valueOf(ThemesUtils.getAccentColor()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,84 +0,0 @@
|
||||
package ru.vtosters.lite.themes.hooks;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.res.ColorStateList;
|
||||
import android.view.View;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.appcompat.widget.AppCompatCheckedTextView;
|
||||
|
||||
import com.vk.core.view.TintTextView;
|
||||
import com.vk.core.view.links.LinkedTextView;
|
||||
import com.vtosters.lite.R;
|
||||
|
||||
import ru.vtosters.lite.themes.ThemesHacks;
|
||||
import ru.vtosters.lite.themes.utils.VkUiThemer;
|
||||
import ru.vtosters.lite.utils.ThemesUtils;
|
||||
|
||||
public class TextViewHook implements BaseHook {
|
||||
|
||||
private static final String TAG = "TextViewHook";
|
||||
|
||||
@SuppressLint("RestrictedApi")
|
||||
@Override
|
||||
public void inject(View view, int i, boolean z) {
|
||||
if (view instanceof TextView) {
|
||||
var textView = (TextView) view;
|
||||
|
||||
if (ThemesUtils.isAccentedColor(textView.getCurrentTextColor())) {
|
||||
textView.setTextColor(ThemesUtils.getAccentColor());
|
||||
}
|
||||
|
||||
if (textView.getLinkTextColors() != null && ThemesUtils.isAccentedColor(textView.getLinkTextColors())) {
|
||||
textView.setLinkTextColor(ThemesUtils.getAccentColor());
|
||||
}
|
||||
|
||||
if (isPositiveButton(textView) || isVkUiButton(textView)) {
|
||||
view.setBackgroundTintList(ColorStateList.valueOf(ThemesUtils.getAccentColor()));
|
||||
}
|
||||
|
||||
if (view instanceof TintTextView) {
|
||||
if (eligibleForStartTinting((TintTextView) view)) {
|
||||
((TintTextView) view).setDrawableStartTint(ThemesUtils.getAccentColor());
|
||||
}
|
||||
if (eligibleForTinting(view.getId())) {
|
||||
((TintTextView) view).setDrawableTint(ThemesUtils.getAccentColor());
|
||||
}
|
||||
}
|
||||
|
||||
if (view instanceof LinkedTextView) {
|
||||
((LinkedTextView) view).setLinkTextColor(ThemesUtils.getAccentColor());
|
||||
}
|
||||
|
||||
if (view instanceof AppCompatCheckedTextView) {
|
||||
var check = (AppCompatCheckedTextView) view;
|
||||
if (ThemesUtils.isAccentedColor(check.getCheckMarkTintList())) {
|
||||
check.setCheckMarkTintList(ColorStateList.valueOf(ThemesUtils.getAccentColor()));
|
||||
}
|
||||
}
|
||||
|
||||
VkUiThemer.autoThemeVkuiButtons(textView);
|
||||
ThemesHacks.fixProfileSelector(textView);
|
||||
ThemesHacks.fixThemedFeed(textView, i);
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean eligibleForStartTinting(TintTextView view) {
|
||||
return ThemesUtils.isAccentedColor(view.getTextColors()) || view.getTextColors().getDefaultColor() == ThemesUtils.getAccentColor();
|
||||
}
|
||||
|
||||
public static boolean eligibleForTinting(int id) {
|
||||
return !ThemesUtils.isDarkTheme() && (id == com.vtosters.lite.R.id.attach_title);
|
||||
}
|
||||
|
||||
public static boolean isPositiveButton(View view) {
|
||||
return !ThemesUtils.isDarkTheme() && (view.getId() == R.id.positive || view.getId() == R.id.positive_button);
|
||||
}
|
||||
|
||||
public static boolean isVkUiButton(View view) {
|
||||
return !ThemesUtils.isDarkTheme() && (
|
||||
view.getId() == R.id.empty_button
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
package ru.vtosters.lite.themes.hooks;
|
||||
|
||||
import android.view.View;
|
||||
import android.widget.Toolbar;
|
||||
|
||||
public class ToolbarHook implements BaseHook {
|
||||
@Override
|
||||
public void inject(View view, int i, boolean z) {
|
||||
if (view instanceof Toolbar) {
|
||||
// wtf?
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,33 +0,0 @@
|
||||
package ru.vtosters.lite.themes.hooks;
|
||||
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.PorterDuff;
|
||||
import android.graphics.PorterDuffColorFilter;
|
||||
import android.graphics.drawable.BitmapDrawable;
|
||||
import android.util.Log;
|
||||
import android.view.View;
|
||||
|
||||
import com.vk.imageloader.view.VKImageView;
|
||||
|
||||
import ru.vtosters.lite.utils.ThemesUtils;
|
||||
|
||||
public class VKImageViewHook implements BaseHook {
|
||||
private static Paint paint = new Paint();
|
||||
static {
|
||||
paint.setColorFilter(new PorterDuffColorFilter(ThemesUtils.getAccentColor(), PorterDuff.Mode.SRC_IN));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void inject(View view, int i, boolean z) {
|
||||
if (view instanceof VKImageView) {
|
||||
var imageView = (VKImageView) view;
|
||||
if (imageView.getImageTintList() != null) {
|
||||
imageView.setImageTintList(ThemesUtils.themeCSL(imageView.getImageTintList()));
|
||||
imageView.setColorFilter(ThemesUtils.getAccentColor(), PorterDuff.Mode.SRC_IN);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,16 +0,0 @@
|
||||
package ru.vtosters.lite.themes.hooks;
|
||||
|
||||
import android.view.View;
|
||||
|
||||
import com.vtosters.lite.ui.widget.WaveformView;
|
||||
|
||||
public class WaveformViewHook implements BaseHook {
|
||||
@Override
|
||||
public void inject(View view, int i, boolean z) {
|
||||
if (view instanceof WaveformView) {
|
||||
var waveformView = (WaveformView) view;
|
||||
//waveformView.set(ThemesUtils.getAccentColor());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,90 +0,0 @@
|
||||
package ru.vtosters.lite.themes.utils;
|
||||
|
||||
import android.content.res.ColorStateList;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.graphics.drawable.DrawableContainer;
|
||||
import android.graphics.drawable.GradientDrawable;
|
||||
import android.graphics.drawable.RippleDrawable;
|
||||
import android.graphics.drawable.StateListDrawable;
|
||||
import android.os.Build;
|
||||
import android.util.Log;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.vk.core.ui.themes.VKTheme;
|
||||
|
||||
import ru.vtosters.lite.utils.ThemesUtils;
|
||||
|
||||
public class VkUiThemer {
|
||||
public static void autoThemeVkuiButtons(TextView view) {
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) return;
|
||||
if (ThemesUtils.isDarkTheme())
|
||||
return; // no need to theme b/w buttons, well it should skip, but I do it for extra performance
|
||||
|
||||
try {
|
||||
if (!(view.getBackground() instanceof StateListDrawable)) return;
|
||||
|
||||
StateListDrawable states = ((StateListDrawable) view.getBackground());
|
||||
if (states.getConstantState() == null) return;
|
||||
DrawableContainer.DrawableContainerState dStates = (DrawableContainer.DrawableContainerState) states.getConstantState();
|
||||
|
||||
if (dStates.getChildCount() == 2) {
|
||||
Drawable target = dStates.getChildren()[0];
|
||||
if (!(target instanceof RippleDrawable)) return;
|
||||
|
||||
RippleDrawable ripple = (RippleDrawable) target;
|
||||
for (int i = 0; i < ripple.getNumberOfLayers(); i++) {
|
||||
if (ripple.getId(i) == android.R.id.mask) continue;
|
||||
|
||||
Drawable currentTarget = ripple.getDrawable(i);
|
||||
|
||||
if (!(currentTarget instanceof GradientDrawable)) continue;
|
||||
|
||||
//if (TSColorRefs.TS_DEBUG_MODE) Log.d("TSCore", "[tsh] finally, obamium = ["+currentTarget.toString()+"]");
|
||||
|
||||
GradientDrawable spd = (GradientDrawable) currentTarget;
|
||||
if (spd.getShape() != GradientDrawable.RECTANGLE) continue;
|
||||
|
||||
Log.d("TSCore", "[tsh] got a color!");
|
||||
|
||||
ColorStateList csl = spd.getColor();
|
||||
if (csl != null) {
|
||||
Log.d("TSCore", "[tsh] csl2 <def: " + csl.getDefaultColor() + " = " + ThemesUtils.hex(csl.getDefaultColor()) + "> | " + csl);
|
||||
}
|
||||
|
||||
if (ThemesUtils.isAccentedColor(spd.getColor())) {
|
||||
Log.d("TSCore", "[tsh] theming CSL");
|
||||
spd.setTint(ThemesUtils.getAccentColor());
|
||||
}
|
||||
}
|
||||
|
||||
Drawable draw2 = dStates.getChildren()[0];
|
||||
if (!(draw2 instanceof GradientDrawable)) return;
|
||||
GradientDrawable spd = (GradientDrawable) draw2;
|
||||
if (spd.getShape() != GradientDrawable.RECTANGLE) return;
|
||||
|
||||
Log.d("TSCore", "[tsh:disabled] got a color!");
|
||||
|
||||
ColorStateList csl = spd.getColor();
|
||||
if (csl != null) {
|
||||
Log.d("TSCore", "[tsh:disabled] csl2 <def: " + csl.getDefaultColor() + " = " + ThemesUtils.hex(csl.getDefaultColor()) + "> | " + csl);
|
||||
}
|
||||
|
||||
if (ThemesUtils.isAccentedColor(spd.getColor())) {
|
||||
Log.d("TSCore", "[tsh:disabled] theming CSL");
|
||||
spd.setTint(ThemesUtils.getAccentColor());
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
// This must go wrong, so we ignore any exceptions that may occur
|
||||
e.printStackTrace(); // REMOVE THIS!
|
||||
}
|
||||
}
|
||||
|
||||
public static void fixDropdown(Drawable drawable) {
|
||||
if (ThemesUtils.getCurrentTheme() == VKTheme.VKAPP_MILK_LIGHT) {
|
||||
var stateListDrawable = (StateListDrawable) drawable;
|
||||
((DrawableContainer.DrawableContainerState) stateListDrawable.getConstantState()).getChildren()[0].setTint(ThemesUtils.getAccentColor());
|
||||
((DrawableContainer.DrawableContainerState) stateListDrawable.getConstantState()).getChildren()[1].setTint(ThemesUtils.halfAlpha(ThemesUtils.getAccentColor()));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package ru.vtosters.lite.ui.activities;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.view.MenuInflater;
|
||||
|
||||
import com.vk.core.ui.themes.ThemableActivity;
|
||||
|
||||
import ru.vtosters.lite.utils.LifecycleUtils;
|
||||
|
||||
public class VTLThemableActivity extends ThemableActivity {
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle bundle) {
|
||||
super.onCreate(bundle);
|
||||
LifecycleUtils.addActivity(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDestroy() {
|
||||
super.onDestroy();
|
||||
LifecycleUtils.removeActivity(this);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
package ru.vtosters.lite.ui.adapters;
|
||||
|
||||
import android.graphics.Color;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.FrameLayout;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import com.vk.core.drawable.BorderDrawable;
|
||||
import com.vk.core.util.Screen;
|
||||
import com.vk.stories.view.StoryCircleImageView;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import ru.vtosters.lite.res.models.ColorModel;
|
||||
import ru.vtosters.lite.utils.AndroidUtils;
|
||||
import ru.vtosters.lite.utils.ThemesUtils;
|
||||
|
||||
public class ColorPickerAdapter extends RecyclerView.Adapter<ColorPickerAdapter.ColorPickerViewHolder> {
|
||||
|
||||
private List<ColorModel> mColors;
|
||||
private OnColorSelectListener mSelectListener;
|
||||
|
||||
public ColorPickerAdapter(List<ColorModel> colors, OnColorSelectListener selectListener) {
|
||||
this.mColors = colors;
|
||||
this.mSelectListener = selectListener;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public ColorPickerViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
|
||||
var container = LayoutInflater.from(parent.getContext())
|
||||
.inflate(AndroidUtils.getIdentifier("color_picker_item", "layout"), parent, false);
|
||||
return new ColorPickerViewHolder(container);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(@NonNull ColorPickerViewHolder holder, int pos) {
|
||||
holder.bind(mColors.get(pos));
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemCount() {
|
||||
return mColors.size();
|
||||
}
|
||||
|
||||
public class ColorPickerViewHolder extends RecyclerView.ViewHolder {
|
||||
private View mContainer;
|
||||
private FrameLayout mColorPreviewBorder;
|
||||
private StoryCircleImageView mColorPreview;
|
||||
private TextView mColorName;
|
||||
private TextView mColorValue;
|
||||
|
||||
public ColorPickerViewHolder(@NonNull View view) {
|
||||
super(view);
|
||||
|
||||
mContainer = view;
|
||||
mColorPreviewBorder = view.findViewById(AndroidUtils.getIdentifier("color_preview_border", "id"));
|
||||
mColorPreview = view.findViewById(AndroidUtils.getIdentifier("color_preview", "id"));
|
||||
mColorName = view.findViewById(AndroidUtils.getIdentifier("color_name", "id"));
|
||||
mColorValue = view.findViewById(AndroidUtils.getIdentifier("color_value", "id"));
|
||||
}
|
||||
|
||||
public void bind(ColorModel color) {
|
||||
mContainer.setOnClickListener(v -> mSelectListener.onColorSelected(ColorPickerAdapter.this, color));
|
||||
mColorPreviewBorder.setBackground(new BorderDrawable(ThemesUtils.isDarkTheme() ? Color.WHITE : Color.BLACK, Screen.c(24.0f), Screen.c(1.0f)));
|
||||
mColorPreview.setPlaceholderColor(color.value);
|
||||
mColorName.setText(color.name);
|
||||
mColorValue.setText("#" + Integer.toHexString(color.value).toUpperCase());
|
||||
}
|
||||
}
|
||||
|
||||
public interface OnColorSelectListener {
|
||||
|
||||
void onColorSelected(ColorPickerAdapter adapter, ColorModel color);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
package ru.vtosters.lite.ui.adapters;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import com.vtosters.lite.R;
|
||||
|
||||
import ru.vtosters.lite.res.managers.PalettesManager;
|
||||
import ru.vtosters.lite.ui.dialogs.PalettesBottomSheetDialog;
|
||||
import ru.vtosters.lite.res.models.PaletteModel;
|
||||
import ru.vtosters.lite.utils.AndroidUtils;
|
||||
|
||||
public class PalettesAdapter extends RecyclerView.Adapter<PalettesAdapter.PaletteViewHolder> {
|
||||
|
||||
private PalettesManager manager = PalettesManager.getInstance();
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public PaletteViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
|
||||
var container = LayoutInflater.from(parent.getContext()).inflate(
|
||||
AndroidUtils.getIdentifier("palette_manager_item", "layout"), parent, false
|
||||
);
|
||||
return new PaletteViewHolder(container);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(@NonNull PaletteViewHolder holder, int pos) {
|
||||
holder.bind(manager.getPalette(pos));
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemCount() {
|
||||
return manager.getPalettesCount();
|
||||
}
|
||||
|
||||
public static class PaletteViewHolder extends RecyclerView.ViewHolder {
|
||||
private View mContainer;
|
||||
private TextView mName;
|
||||
private TextView mAuthor;
|
||||
|
||||
public PaletteViewHolder(@NonNull View view) {
|
||||
super(view);
|
||||
|
||||
mContainer = view;
|
||||
mName = view.findViewById(R.id.name);
|
||||
mAuthor = view.findViewById(AndroidUtils.getIdentifier("author", "id"));
|
||||
}
|
||||
|
||||
public void bind(PaletteModel palette) {
|
||||
mContainer.setOnClickListener(v -> {
|
||||
PalettesBottomSheetDialog.create((Activity) v.getContext(), palette);
|
||||
});
|
||||
mName.setText(palette.name);
|
||||
mAuthor.setText(palette.author);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,39 +1,38 @@
|
||||
package ru.vtosters.lite.ui.adapters;
|
||||
|
||||
import static ru.vtosters.lite.utils.AndroidUtils.dp2px;
|
||||
import static ru.vtosters.lite.utils.AndroidUtils.getGlobalContext;
|
||||
import static ru.vtosters.lite.utils.ThemesUtils.getAccentColor;
|
||||
import static ru.vtosters.lite.utils.ThemesUtils.getSTextAttr;
|
||||
import static ru.vtosters.lite.utils.ThemesUtils.getTextAttr;
|
||||
|
||||
import android.app.AlertDialog;
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.res.ColorStateList;
|
||||
import android.text.TextUtils;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.EditText;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.FrameLayout;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.constraintlayout.widget.ConstraintLayout;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import com.vk.core.dialogs.alert.VkAlertDialog;
|
||||
import com.vk.core.drawable.BorderDrawable;
|
||||
import com.vk.core.util.Screen;
|
||||
import com.vk.core.util.ToastUtils;
|
||||
import com.vtosters.lite.R;
|
||||
|
||||
import org.json.JSONException;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import ru.vtosters.lite.ui.components.ThemesManager;
|
||||
import ru.vtosters.lite.ui.items.Theme;
|
||||
import ru.vtosters.lite.res.managers.ThemesManager;
|
||||
import ru.vtosters.lite.ui.dialogs.EditTextDialog;
|
||||
import ru.vtosters.lite.ui.dialogs.ThemesEditorBottomSheetDialog;
|
||||
import ru.vtosters.lite.res.models.ThemeModel;
|
||||
import ru.vtosters.lite.ui.views.NoTouchRadioButton;
|
||||
import ru.vtosters.lite.ui.vkui.VKUIActionPopup;
|
||||
import ru.vtosters.lite.utils.AndroidUtils;
|
||||
import ru.vtosters.lite.utils.LifecycleUtils;
|
||||
import ru.vtosters.lite.utils.ThemesUtils;
|
||||
|
||||
public class ThemesAdapter extends RecyclerView.Adapter<ThemesAdapter.ThemeViewHolder> {
|
||||
|
||||
@@ -50,7 +49,7 @@ public class ThemesAdapter extends RecyclerView.Adapter<ThemesAdapter.ThemeViewH
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(@NonNull ThemeViewHolder holder, int pos) {
|
||||
holder.bind(mManager.getThemeByIndex(pos));
|
||||
holder.bind(mManager.getTheme(pos));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -61,6 +60,8 @@ public class ThemesAdapter extends RecyclerView.Adapter<ThemesAdapter.ThemeViewH
|
||||
public class ThemeViewHolder extends RecyclerView.ViewHolder {
|
||||
|
||||
private ConstraintLayout mContainer;
|
||||
private FrameLayout mThemeModePreviewBorder;
|
||||
private ImageView mThemeModePreview;
|
||||
private TextView mName;
|
||||
private TextView mAuthor;
|
||||
private NoTouchRadioButton mRadioButton;
|
||||
@@ -69,12 +70,20 @@ public class ThemesAdapter extends RecyclerView.Adapter<ThemesAdapter.ThemeViewH
|
||||
super(view);
|
||||
|
||||
mContainer = (ConstraintLayout) view;
|
||||
mName = view.findViewById(AndroidUtils.getIdentifier("name", "id"));
|
||||
mThemeModePreviewBorder = view.findViewById(AndroidUtils.getIdentifier("theme_mode_preview_border", "id"));
|
||||
mThemeModePreview = view.findViewById(AndroidUtils.getIdentifier("theme_mode_preview", "id"));
|
||||
mName = view.findViewById(R.id.name);
|
||||
mAuthor = view.findViewById(AndroidUtils.getIdentifier("author", "id"));
|
||||
mRadioButton = view.findViewById(AndroidUtils.getIdentifier("selection", "id"));
|
||||
}
|
||||
|
||||
public void bind(Theme theme) {
|
||||
public void bind(ThemeModel theme) {
|
||||
var accent = getAccentColorFromCustomTheme(theme);
|
||||
mThemeModePreviewBorder.setBackground(new BorderDrawable(accent, Screen.c(24.0f), Screen.c(1.0f)));
|
||||
mThemeModePreview.setImageResource(AndroidUtils.getIdentifier(
|
||||
"vk_icon_" + (theme.isDarkMode ? "moon" : "sun") + "_outline_28",
|
||||
"drawable"));
|
||||
mThemeModePreview.setImageTintList(ColorStateList.valueOf(accent));
|
||||
var isCurrentTheme = mManager.isCurrentTheme(theme);
|
||||
if (!isCurrentTheme)
|
||||
mContainer.setOnClickListener(v -> {
|
||||
@@ -83,60 +92,41 @@ public class ThemesAdapter extends RecyclerView.Adapter<ThemesAdapter.ThemeViewH
|
||||
});
|
||||
if (!"default".equals(theme.id))
|
||||
mContainer.setOnLongClickListener(v -> {
|
||||
var titles = new CharSequence[] { "Редактировать название", "Поделиться темой", "Удалить тему" };
|
||||
new VkAlertDialog.Builder(v.getContext())
|
||||
.setItems(titles, ((dialog, which) -> {
|
||||
switch (which) {
|
||||
case 0:
|
||||
showEditThemeNameDialog(v.getContext(), theme);
|
||||
break;
|
||||
case 1:
|
||||
try {
|
||||
mManager.share(v.getContext(), theme);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
ToastUtils.a("Не удалось поделиться темой");
|
||||
}
|
||||
break;
|
||||
case 2:
|
||||
mManager.delete(theme);
|
||||
if (isCurrentTheme)
|
||||
LifecycleUtils.restartApplication();
|
||||
else
|
||||
notifyDataSetChanged();
|
||||
break;
|
||||
}
|
||||
}))
|
||||
.create()
|
||||
.show();
|
||||
return true;
|
||||
});
|
||||
showActionPopup(v.getContext(), v, isCurrentTheme, theme);
|
||||
return true;
|
||||
});
|
||||
else
|
||||
mContainer.setOnLongClickListener(null);
|
||||
mRadioButton.setChecked(isCurrentTheme);
|
||||
mName.setText(theme.name);
|
||||
mAuthor.setText(theme.author);
|
||||
}
|
||||
|
||||
private void showEditThemeNameDialog(Context context, Theme theme) {
|
||||
LinearLayout linearLayout = new LinearLayout(getGlobalContext());
|
||||
private void showActionPopup(Context context, View anchorView, boolean isCurrentTheme, ThemeModel theme) {
|
||||
var builder = new VKUIActionPopup(context, anchorView, true, R.color.colorAccent)
|
||||
.setItem("Редактировать название",
|
||||
() -> showEditThemeNameDialog(context, theme))
|
||||
.setItem("Изменить цвета",
|
||||
() -> ThemesEditorBottomSheetDialog.create((Activity) context, theme, isCurrentTheme))
|
||||
.setItem("Поделиться темой", () -> {
|
||||
try {
|
||||
mManager.share(context, theme);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
ToastUtils.a("Не удалось поделиться темой");
|
||||
}})
|
||||
.setItem("Удалить тему", () -> {
|
||||
mManager.delete(theme);
|
||||
if (isCurrentTheme)
|
||||
LifecycleUtils.restartApplication();
|
||||
else
|
||||
notifyDataSetChanged();
|
||||
});
|
||||
builder.create().d();
|
||||
}
|
||||
|
||||
final EditText editText = new EditText(getGlobalContext());
|
||||
editText.setText(theme.name);
|
||||
editText.setHint("Название темы");
|
||||
editText.setTextColor(getTextAttr());
|
||||
editText.setHintTextColor(getSTextAttr());
|
||||
|
||||
editText.setBackgroundTintList(ColorStateList.valueOf(getAccentColor()));
|
||||
|
||||
linearLayout.addView(editText);
|
||||
editText.getLayoutParams().width = ViewGroup.LayoutParams.MATCH_PARENT;
|
||||
ViewGroup.MarginLayoutParams margin = ((ViewGroup.MarginLayoutParams) editText.getLayoutParams());
|
||||
margin.setMargins(dp2px(20f), 0, dp2px(20f), 0);
|
||||
editText.setLayoutParams(margin);
|
||||
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(context);
|
||||
builder.setTitle("Редактирование названия темы");
|
||||
builder.setView(linearLayout);
|
||||
builder.setPositiveButton("OK", (dialog, which) -> {
|
||||
private void showEditThemeNameDialog(Context context, ThemeModel theme) {
|
||||
EditTextDialog.create(context, "Редактирование названия темы", "Название темы", theme.name, (dialog, which, editText) -> {
|
||||
var newName = editText.getText().toString();
|
||||
if (TextUtils.isEmpty(newName)) {
|
||||
ToastUtils.a("Название не может быть пустым");
|
||||
@@ -155,10 +145,18 @@ public class ThemesAdapter extends RecyclerView.Adapter<ThemesAdapter.ThemeViewH
|
||||
ToastUtils.a("Не удалось изменить название темы");
|
||||
}
|
||||
});
|
||||
|
||||
var alert = builder.create();
|
||||
alert.show();
|
||||
alert.getButton(DialogInterface.BUTTON_POSITIVE).setTextColor(getAccentColor());
|
||||
}
|
||||
}
|
||||
|
||||
private int getAccentColorFromCustomTheme(ThemeModel theme) {
|
||||
if (!"default".equals(theme.id)) {
|
||||
if (theme.hasColor("accent"))
|
||||
return theme.getColor("accent");
|
||||
else if (theme.hasColor("colorAccent"))
|
||||
return theme.getColor("colorAccent");
|
||||
else if (theme.hasColor("vk_accent"))
|
||||
return theme.getColor("vk_accent");
|
||||
}
|
||||
return ThemesUtils.getColorFromAttr(R.attr.accent);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,228 +0,0 @@
|
||||
package ru.vtosters.lite.ui.components;
|
||||
|
||||
import android.content.ContentResolver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.os.Environment;
|
||||
import android.os.StrictMode;
|
||||
import android.util.Log;
|
||||
|
||||
import com.vtosters.lite.auth.VKAccountManager;
|
||||
|
||||
import org.json.JSONException;
|
||||
|
||||
import java.io.DataInputStream;
|
||||
import java.io.DataOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.zip.GZIPInputStream;
|
||||
import java.util.zip.GZIPOutputStream;
|
||||
|
||||
import ru.vtosters.lite.ui.items.Theme;
|
||||
import ru.vtosters.lite.utils.AndroidUtils;
|
||||
import ru.vtosters.lite.utils.IOUtils;
|
||||
|
||||
public class ThemesManager {
|
||||
|
||||
private List<Theme> themes = new ArrayList<>();
|
||||
private static File themesDir;
|
||||
|
||||
private static ThemesManager instance;
|
||||
|
||||
public static ThemesManager getInstance() {
|
||||
if (instance == null)
|
||||
instance = new ThemesManager();
|
||||
return instance;
|
||||
}
|
||||
|
||||
public ThemesManager() {
|
||||
init();
|
||||
}
|
||||
|
||||
private void init() {
|
||||
themes = new ArrayList<>();
|
||||
themes.add(new Theme().setName("Тема по умолчанию")
|
||||
.setAuthor("Разработчики ВК")
|
||||
.setDarkMode(false)
|
||||
.setId("default"));
|
||||
themesDir = new File(Environment.getExternalStorageDirectory(), "VTL/themes");
|
||||
if (!themesDir.exists()) {
|
||||
themesDir.mkdirs();
|
||||
return;
|
||||
}
|
||||
// Support themes from Sova V RE (.svt)
|
||||
File[] files = themesDir.listFiles((dir, name) -> name.endsWith(".vtlt") || name.endsWith(".svt"));
|
||||
if (files == null || files.length == 0)
|
||||
return;
|
||||
for (File file : files) {
|
||||
try {
|
||||
var theme = read(file);
|
||||
Log.d("ThemesManager", theme.toString());
|
||||
themes.add(theme);
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void reload() {
|
||||
init();
|
||||
}
|
||||
|
||||
/*
|
||||
* Theme file storing method from Sova V RE is storing json clearly or compressed in gzip.
|
||||
* If first byte byte is 0x1 or 0x2 json has been compressed with gzip,
|
||||
* but if first byte is 0x2 next int is length of json string. */
|
||||
|
||||
private Theme read(InputStream fis) throws IOException {
|
||||
InputStream is = fis;
|
||||
var first = is.read();
|
||||
if (first > 0x0 && first < 0x3) {
|
||||
var gzis = new GZIPInputStream(is);
|
||||
var dis = new DataInputStream(gzis);
|
||||
if (first == 0x2)
|
||||
dis.readInt();
|
||||
is = dis;
|
||||
}
|
||||
return Theme.fromJson(IOUtils.readAllLines(is).trim());
|
||||
}
|
||||
|
||||
private Theme read(File file) throws IOException {
|
||||
return read(new FileInputStream(file));
|
||||
}
|
||||
|
||||
public File getThemeFile(Theme theme) throws IOException {
|
||||
if (!themesDir.exists()) {
|
||||
themesDir.mkdirs();
|
||||
throw new FileNotFoundException("theme file not found");
|
||||
}
|
||||
|
||||
var themeFile = new File(themesDir, theme.id + ".svt");
|
||||
if (!themeFile.exists()) {
|
||||
themeFile = new File(themesDir, theme.id + ".vtlt");
|
||||
if (!themeFile.exists())
|
||||
themeFile.createNewFile();
|
||||
}
|
||||
|
||||
return themeFile;
|
||||
}
|
||||
|
||||
public void create(String name, boolean darkMode) throws JSONException, IOException {
|
||||
var id = name.toLowerCase().replace(" ", "-");
|
||||
if (hasTheme(id)) {
|
||||
int i = 1;
|
||||
while (hasTheme(id + "-" + i)) {
|
||||
i++;
|
||||
}
|
||||
id += "-" + i;
|
||||
}
|
||||
var userProfile = VKAccountManager.d().e1();
|
||||
Theme theme = new Theme()
|
||||
.setName(name)
|
||||
.setAuthor(userProfile.c + " " + userProfile.e)
|
||||
.setDarkMode(darkMode)
|
||||
.setId(id);
|
||||
themes.add(theme);
|
||||
save(theme);
|
||||
}
|
||||
|
||||
public void save(Theme theme) throws IOException, JSONException {
|
||||
var buffer = theme.toJson().toString().getBytes(StandardCharsets.UTF_8);
|
||||
var fos = new FileOutputStream(getThemeFile(theme));
|
||||
// writing with data length
|
||||
fos.write(0x2);
|
||||
var gzos = new GZIPOutputStream(fos);
|
||||
var dos = new DataOutputStream(gzos);
|
||||
dos.writeInt(buffer.length);
|
||||
dos.write(buffer);
|
||||
dos.close();
|
||||
}
|
||||
|
||||
public void delete(Theme theme) {
|
||||
try {
|
||||
getThemeFile(theme).delete();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
themes.remove(theme);
|
||||
}
|
||||
|
||||
public void copy(ContentResolver contentResolver, Uri uri) throws IOException, JSONException {
|
||||
var theme = read(contentResolver.openInputStream(uri));
|
||||
if (isCurrentTheme(theme)) {
|
||||
int i = 1;
|
||||
while (hasTheme(theme.id + "-" + i)) {
|
||||
i++;
|
||||
}
|
||||
theme.id += "-" + i;
|
||||
}
|
||||
themes.add(theme);
|
||||
save(theme);
|
||||
}
|
||||
|
||||
public void share(Context context, Theme theme) throws Exception {
|
||||
var vmPolicy = StrictMode.getVmPolicy();
|
||||
StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder().build());
|
||||
var intent = new Intent(Intent.ACTION_SEND)
|
||||
.setType("file/*")
|
||||
.putExtra(Intent.EXTRA_STREAM, Uri.fromFile(getThemeFile(theme)));
|
||||
context.startActivity(Intent.createChooser(intent, ""));
|
||||
StrictMode.setVmPolicy(vmPolicy);
|
||||
}
|
||||
|
||||
public void setTheme(Theme theme) {
|
||||
AndroidUtils.getPreferences()
|
||||
.edit()
|
||||
.putString("vtl_theme_id", theme.id)
|
||||
.commit();
|
||||
}
|
||||
|
||||
public Theme getThemeByIndex(int i) {
|
||||
return themes.get(i);
|
||||
}
|
||||
|
||||
public Theme getCurrentTheme() {
|
||||
var currentThemeId = AndroidUtils.getPreferences().getString("vtl_theme_id", "default");
|
||||
for (Theme theme : themes) {
|
||||
if (currentThemeId.equals(theme.id))
|
||||
return theme;
|
||||
}
|
||||
AndroidUtils.getPreferences()
|
||||
.edit()
|
||||
.putString("vtl_theme_id", "default")
|
||||
.commit();
|
||||
return getCurrentTheme();
|
||||
}
|
||||
|
||||
public int getThemesCount() {
|
||||
return themes.size();
|
||||
}
|
||||
|
||||
public boolean isCurrentTheme(Theme theme) {
|
||||
return isCurrentTheme(theme.id);
|
||||
}
|
||||
|
||||
public boolean isCurrentTheme(String id) {
|
||||
return id.equals(getCurrentTheme().id);
|
||||
}
|
||||
|
||||
public boolean hasTheme(Theme theme) {
|
||||
return hasTheme(theme.id);
|
||||
}
|
||||
|
||||
public boolean hasTheme(String id) {
|
||||
for (Theme theme : themes) {
|
||||
if (id.equals(theme.id))
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
package ru.vtosters.lite.ui.dialogs;
|
||||
|
||||
import static ru.vtosters.lite.res.VTLColors.getTextSecondaryColor;
|
||||
import static ru.vtosters.lite.res.VTLColors.getTextPrimaryColor;
|
||||
import static ru.vtosters.lite.utils.AndroidUtils.dp2px;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.text.TextUtils;
|
||||
import android.widget.EditText;
|
||||
import android.widget.LinearLayout;
|
||||
|
||||
import com.vk.core.dialogs.alert.VkAlertDialog;
|
||||
|
||||
import ru.vtosters.lite.ui.vkui.VKUIEditText;
|
||||
import ru.vtosters.lite.ui.vkui.VKUITextView;
|
||||
import ru.vtosters.lite.utils.LayoutUtils;
|
||||
|
||||
public class EditTextDialog {
|
||||
public static void create(Context context, String title, String hint, String text, OnClickListener clickListener) {
|
||||
var container = new LinearLayout(context);
|
||||
container.setOrientation(LinearLayout.VERTICAL);
|
||||
container.setPadding(
|
||||
dp2px(8), dp2px(12), dp2px(8), dp2px(12)
|
||||
);
|
||||
|
||||
if (!TextUtils.isEmpty(hint)) {
|
||||
var hitText = new VKUITextView(context, VKUITextView.STYLE_SUBHEAD_1);
|
||||
hitText.setText(hint);
|
||||
hitText.setPadding(dp2px(8), 0, 0, dp2px(8));
|
||||
container.addView(hitText, LayoutUtils.createLinear(-2, -2));
|
||||
}
|
||||
|
||||
var editText = new VKUIEditText(context);
|
||||
if (!TextUtils.isEmpty(text))
|
||||
editText.setText(text);
|
||||
editText.setTextColor(getTextPrimaryColor());
|
||||
editText.setHintTextColor(getTextSecondaryColor());
|
||||
editText.setPadding(dp2px(8), 0, dp2px(8), 0);
|
||||
|
||||
var editTextParams = LayoutUtils.createLinear(-1, dp2px(44));
|
||||
container.addView(editText, editTextParams);
|
||||
|
||||
VkAlertDialog.Builder builder = new VkAlertDialog.Builder(context);
|
||||
if (!TextUtils.isEmpty(title))
|
||||
builder.setTitle(title);
|
||||
builder.setPositiveButton("OK", (dialog, which) -> clickListener.onClick(dialog, which, editText));
|
||||
builder.setNegativeButton("Отмена", (dialog, which) -> dialog.cancel());
|
||||
|
||||
var alert = builder.create();
|
||||
alert.setView(container);
|
||||
alert.show();
|
||||
}
|
||||
|
||||
public interface OnClickListener {
|
||||
void onClick(DialogInterface dialog, int which, EditText editText);
|
||||
}
|
||||
}
|
||||
@@ -29,13 +29,13 @@ public class OTADialog implements OTAUtils.OTAListener {
|
||||
public void onUpdateApplied() {
|
||||
mActivity.runOnUiThread(() -> {
|
||||
// Toast.makeText(mActivity, "Обновления найдены", Toast.LENGTH_SHORT).show();
|
||||
new ModalBottomSheetWrapper(mActivity)
|
||||
.setTitle(getString("newversion") + " " + mHelper.getNewVersionName())
|
||||
.setUpdateInfoView(mHelper.getUpdateDescription())
|
||||
.setPositiveButton(getString("updateanddownload"), () -> {
|
||||
OTADownloader.downloadBuild(mHelper.getDownloadUrl());
|
||||
})
|
||||
.show();
|
||||
// new ModalBottomSheetWrapper(mActivity)
|
||||
// .setTitle(getString("newversion") + " " + mHelper.getNewVersionName())
|
||||
// // .setUpdateInfoView(mHelper.getUpdateDescription())
|
||||
// .setPositiveButton(getString("updateanddownload"), () -> {
|
||||
// OTADownloader.downloadBuild(mHelper.getDownloadUrl());
|
||||
// })
|
||||
// .show();
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
package ru.vtosters.lite.ui.dialogs;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.ClipData;
|
||||
import android.content.ClipboardManager;
|
||||
import android.content.Context;
|
||||
|
||||
import com.vk.core.util.ToastUtils;
|
||||
|
||||
import ru.vtosters.lite.ui.adapters.ColorPickerAdapter;
|
||||
import ru.vtosters.lite.res.models.PaletteModel;
|
||||
|
||||
public class PalettesBottomSheetDialog {
|
||||
|
||||
private static final String TAG = "palettes_copy_color";
|
||||
|
||||
public static void create(Activity activity, PaletteModel palette) {
|
||||
var instance = new VTColorPickerBottomSheetDialog(activity);
|
||||
var colorPickerAdapter = new ColorPickerAdapter(palette.colors, (adapter, color) -> {
|
||||
var manager = (ClipboardManager) activity.getSystemService(Context.CLIPBOARD_SERVICE);
|
||||
manager.setPrimaryClip(ClipData.newPlainText("color", Integer.toHexString(color.value).toUpperCase()));
|
||||
ToastUtils.a("Скопировано в буфер обмена");
|
||||
instance.dismiss();
|
||||
});
|
||||
instance.setTitle(palette.name)
|
||||
.setAdapter(colorPickerAdapter, true)
|
||||
.show(TAG, true);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
package ru.vtosters.lite.ui.dialogs;
|
||||
|
||||
import static ru.vtosters.lite.utils.AndroidUtils.dp2px;
|
||||
import static ru.vtosters.lite.utils.AndroidUtils.edit;
|
||||
import static ru.vtosters.lite.utils.AndroidUtils.getPreferences;
|
||||
import static ru.vtosters.lite.utils.LifecycleUtils.restartApplicationWithTimer;
|
||||
|
||||
import android.content.Context;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.SeekBar;
|
||||
|
||||
import com.vk.core.dialogs.alert.VkAlertDialog;
|
||||
|
||||
import ru.vtosters.lite.utils.LayoutUtils;
|
||||
|
||||
public class RoundingSeekbarDialog{
|
||||
public static void dialog(Context context) {
|
||||
LinearLayout mContainer = new LinearLayout(context);
|
||||
SeekBar seek = new SeekBar(context);
|
||||
seek.setMax(10);
|
||||
seek.setKeyProgressIncrement(0);
|
||||
seek.setProgress(getPreferences().getInt("pic_rounding", 0));
|
||||
seek.setPadding(dp2px(16f), dp2px(16f), dp2px(16f), dp2px(16f));
|
||||
|
||||
mContainer.addView(seek, LayoutUtils.createLinear(-1, -2));
|
||||
|
||||
VkAlertDialog.Builder builder = new VkAlertDialog.Builder(context);
|
||||
builder.setTitle("Выберите размер закругления углов");
|
||||
builder.setMessage("Для отключения функции закругления углов введите 0");
|
||||
builder.setCancelable(true);
|
||||
builder.setNegativeButton("Отмена", null);
|
||||
builder.setView(mContainer);
|
||||
builder.setPositiveButton("Применить", (dialog, which) -> {
|
||||
edit().putInt("pic_rounding", seek.getProgress()).commit();
|
||||
restartApplicationWithTimer();
|
||||
});
|
||||
|
||||
builder.show();
|
||||
}
|
||||
}
|
||||
@@ -97,18 +97,18 @@ public class ServerDialog {
|
||||
|
||||
if (activity != null) {
|
||||
activity.runOnUiThread(() -> {
|
||||
VkAlertDialog.Builder builder = new VkAlertDialog.Builder(activity);
|
||||
builder.setTitle(title);
|
||||
builder.setMessage(message);
|
||||
builder.setCancelable(cancelable);
|
||||
builder.setPositiveButton(positiveButton, (dialogInterface, i) -> edit().putBoolean(key, false).apply());
|
||||
builder.setNeutralButton(neutralButton, (dialogInterface, i) -> {
|
||||
edit().putBoolean(key, false).apply();
|
||||
activity.startActivity(new Intent("android.intent.action.VIEW").setData(Uri.parse(link)));
|
||||
});
|
||||
if (getBoolValue(key, true) && showForNotVerified() && showAlert) {
|
||||
builder.show();
|
||||
}
|
||||
// VkAlertDialog.Builder builder = new VkAlertDialog.Builder(activity);
|
||||
// builder.setTitle(title);
|
||||
// builder.setMessage(message);
|
||||
// builder.setCancelable(cancelable);
|
||||
// builder.setPositiveButton(positiveButton, (dialogInterface, i) -> edit().putBoolean(key, false).apply());
|
||||
// builder.setNeutralButton(neutralButton, (dialogInterface, i) -> {
|
||||
// edit().putBoolean(key, false).apply();
|
||||
// activity.startActivity(new Intent("android.intent.action.VIEW").setData(Uri.parse(link)));
|
||||
// });
|
||||
// if (getBoolValue(key, true) && showForNotVerified() && showAlert) {
|
||||
// builder.show();
|
||||
// }
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,218 @@
|
||||
package ru.vtosters.lite.ui.dialogs;
|
||||
|
||||
import static ru.vtosters.lite.utils.AndroidUtils.dp2px;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.view.Gravity;
|
||||
import android.widget.LinearLayout;
|
||||
|
||||
import androidx.appcompat.widget.SwitchCompat;
|
||||
|
||||
import com.vk.core.dialogs.alert.VkAlertDialog;
|
||||
import com.vk.core.util.ToastUtils;
|
||||
|
||||
import org.json.JSONException;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import ru.vtosters.lite.res.VTLColors;
|
||||
import ru.vtosters.lite.res.managers.PalettesManager;
|
||||
import ru.vtosters.lite.res.managers.ThemesManager;
|
||||
import ru.vtosters.lite.ui.adapters.ColorPickerAdapter;
|
||||
import ru.vtosters.lite.res.models.ColorModel;
|
||||
import ru.vtosters.lite.res.models.PaletteModel;
|
||||
import ru.vtosters.lite.res.models.ThemeModel;
|
||||
import ru.vtosters.lite.ui.views.flask.builder.ColorPickerDialogBuilder;
|
||||
import ru.vtosters.lite.ui.views.rarepebble.ColorPickerView;
|
||||
import ru.vtosters.lite.utils.LayoutUtils;
|
||||
import ru.vtosters.lite.utils.LifecycleUtils;
|
||||
|
||||
public class ThemesEditorBottomSheetDialog {
|
||||
|
||||
private static final String THEME_EDITOR_TAG = "theme_editor";
|
||||
private static final String PALETTES_EDITOR_TAG = "palettes_editor";
|
||||
private static final String[] COLOR_PICKERS = {
|
||||
"Wheel Color Picker by QuadFlask",
|
||||
"Palettes",
|
||||
"Advanced Color Picker by Martin Stone"
|
||||
};
|
||||
|
||||
public static void create(Activity activity, ThemeModel theme, boolean currentTheme) {
|
||||
var newTheme = ThemeModel.fromTheme(theme);
|
||||
List<ColorModel> newList = new ArrayList<>();
|
||||
for (ColorModel item : theme.colors)
|
||||
newList.add(ColorModel.valuesOf(item.name, item.value));
|
||||
newTheme.colors = newList;
|
||||
var colorPickerAdapter = new ColorPickerAdapter(newList, (adapter, color) -> {
|
||||
new VkAlertDialog.Builder(activity)
|
||||
.setItems(COLOR_PICKERS, (dialog, which) -> {
|
||||
if (which == 0) {
|
||||
showQuadFlashWheelColorPicker(activity, adapter, color);
|
||||
} else if (which == 1) {
|
||||
showPaletteChoiceDialog(activity, adapter, color);
|
||||
} else if (which == 2) {
|
||||
showMartinStoneColorPicker(activity, adapter, color);
|
||||
}
|
||||
})
|
||||
.create()
|
||||
.show();
|
||||
});
|
||||
var instance = new VTColorPickerBottomSheetDialog(activity);
|
||||
addSwitches(instance.getContainer(), newTheme, colorPickerAdapter);
|
||||
instance.setAdapter(colorPickerAdapter, false)
|
||||
.setTitle("Редактирование " + theme.name)
|
||||
.setPositiveButton("Применить", () -> {
|
||||
String message;
|
||||
try {
|
||||
ThemesManager.getInstance().save(newTheme);
|
||||
message = "Тема изменена";
|
||||
} catch (IOException | JSONException e) {
|
||||
e.printStackTrace();
|
||||
message = "Ошибка при изменении темы";
|
||||
}
|
||||
if (currentTheme) {
|
||||
LifecycleUtils.restartApplication();
|
||||
return;
|
||||
}
|
||||
ToastUtils.a(message);
|
||||
instance.dismiss();
|
||||
})
|
||||
.show(THEME_EDITOR_TAG, true);
|
||||
}
|
||||
|
||||
private static void addSwitches(LinearLayout container, ThemeModel theme, ColorPickerAdapter adapter) {
|
||||
var context = container.getContext();
|
||||
|
||||
var switchesContainer = new LinearLayout(context);
|
||||
switchesContainer.setOrientation(LinearLayout.HORIZONTAL);
|
||||
switchesContainer.setPadding(0, dp2px(5), 0, dp2px(5));
|
||||
container.addView(switchesContainer, LayoutUtils.createLinear(-1, -2));
|
||||
|
||||
var amoledTheme = new SwitchCompat(context);
|
||||
amoledTheme.setTextColor(VTLColors.getTextPrimaryColor());
|
||||
amoledTheme.setTextSize(16.0f);
|
||||
amoledTheme.setText("AMOLED-тема");
|
||||
amoledTheme.setEnabled(theme.isDarkMode);
|
||||
amoledTheme.setGravity(Gravity.CENTER);
|
||||
amoledTheme.setOnCheckedChangeListener((buttonView, isChecked) -> {
|
||||
theme.isDarkMode = isChecked;
|
||||
if (isChecked) {
|
||||
replaceToAmoled(theme, adapter);
|
||||
} else {
|
||||
replaceToLight(theme, adapter);
|
||||
}
|
||||
adapter.notifyDataSetChanged();
|
||||
});
|
||||
|
||||
var darkTheme = new SwitchCompat(context);
|
||||
darkTheme.setChecked(theme.isDarkMode);
|
||||
darkTheme.setTextColor(VTLColors.getTextPrimaryColor());
|
||||
darkTheme.setGravity(Gravity.CENTER);
|
||||
darkTheme.setTextSize(16.0f);
|
||||
darkTheme.setText("Темная тема");
|
||||
darkTheme.setOnCheckedChangeListener((buttonView, isChecked) -> {
|
||||
theme.isDarkMode = isChecked;
|
||||
if (isChecked) {
|
||||
amoledTheme.setEnabled(true);
|
||||
replaceToDark(theme, adapter);
|
||||
} else {
|
||||
amoledTheme.setChecked(false);
|
||||
amoledTheme.setEnabled(false);
|
||||
replaceToLight(theme, adapter);
|
||||
}
|
||||
adapter.notifyDataSetChanged();
|
||||
});
|
||||
var darkThemeParams = LayoutUtils.createLinear(-1, -2);
|
||||
darkThemeParams.weight = 1.0f;
|
||||
switchesContainer.addView(darkTheme, darkThemeParams);
|
||||
var amoledThemeParams = LayoutUtils.createLinear(-1, -2);
|
||||
amoledThemeParams.weight = 1.0f;
|
||||
switchesContainer.addView(amoledTheme, amoledThemeParams);
|
||||
}
|
||||
|
||||
private static void replaceToLight(ThemeModel theme, ColorPickerAdapter adapter) {
|
||||
|
||||
}
|
||||
|
||||
private static void replaceToDark(ThemeModel theme, ColorPickerAdapter adapter) {
|
||||
|
||||
}
|
||||
|
||||
private static void replaceToAmoled(ThemeModel theme, ColorPickerAdapter adapter) {
|
||||
|
||||
}
|
||||
|
||||
private static void showMartinStoneColorPicker(
|
||||
Activity activity,
|
||||
ColorPickerAdapter adapter,
|
||||
ColorModel dest) {
|
||||
var colorPickerView = new ColorPickerView(activity);
|
||||
colorPickerView.setColor(dest.value);
|
||||
var alertDialog = new VkAlertDialog.Builder(activity)
|
||||
.setTitle("Выберите цвет")
|
||||
.setPositiveButton("Выбрать", (dialog, which) -> {
|
||||
dest.setValue(colorPickerView.getColor());
|
||||
adapter.notifyDataSetChanged();
|
||||
dialog.dismiss();
|
||||
})
|
||||
.create();
|
||||
alertDialog.setView(colorPickerView);
|
||||
alertDialog.show();
|
||||
}
|
||||
|
||||
private static void showQuadFlashWheelColorPicker(
|
||||
Activity activity,
|
||||
ColorPickerAdapter adapter,
|
||||
ColorModel dest) {
|
||||
ColorPickerDialogBuilder
|
||||
.with(activity)
|
||||
.setTitle("Выберите цвет")
|
||||
.showBorder(true)
|
||||
.setOnColorSelectedListener((dialog, selectedColor) -> {
|
||||
dest.setValue(selectedColor);
|
||||
adapter.notifyDataSetChanged();
|
||||
dialog.cancel();
|
||||
})
|
||||
.build()
|
||||
.show();
|
||||
}
|
||||
|
||||
private static void showPaletteChoiceDialog(
|
||||
Activity activity,
|
||||
ColorPickerAdapter adapter,
|
||||
ColorModel dest
|
||||
) {
|
||||
var palettesManager = PalettesManager.getInstance();
|
||||
var titles = new String[palettesManager.getPalettesCount()];
|
||||
for (int i = 0; i < titles.length; i++)
|
||||
titles[i] = palettesManager.getPalette(i).name;
|
||||
new VkAlertDialog.Builder(activity)
|
||||
.setItems(titles, (dialog, which) -> createPaletteColorPicker(
|
||||
activity,
|
||||
adapter,
|
||||
dest,
|
||||
palettesManager.getPalette(which)))
|
||||
.create()
|
||||
.show();
|
||||
}
|
||||
|
||||
private static void createPaletteColorPicker(
|
||||
Activity activity,
|
||||
ColorPickerAdapter themeEditAdapter,
|
||||
ColorModel dest,
|
||||
PaletteModel palette
|
||||
) {
|
||||
var instance = new VTColorPickerBottomSheetDialog(activity);
|
||||
var colorPickerAdapter = new ColorPickerAdapter(palette.colors, (adapter, color) -> {
|
||||
dest.setValue(color.value);
|
||||
ToastUtils.a("Цвет был изменён");
|
||||
themeEditAdapter.notifyDataSetChanged();
|
||||
instance.dismiss();
|
||||
});
|
||||
instance.setTitle(palette.name)
|
||||
.setAdapter(colorPickerAdapter, true)
|
||||
.show(PALETTES_EDITOR_TAG, true);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
package ru.vtosters.lite.ui.dialogs;
|
||||
|
||||
import static ru.vtosters.lite.res.VTLColors.getTextPrimaryColor;
|
||||
import static ru.vtosters.lite.utils.AndroidUtils.dp2px;
|
||||
import static ru.vtosters.lite.utils.AndroidUtils.getString;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.TextView;
|
||||
|
||||
import ru.vtosters.lite.downloaders.OTADownloader;
|
||||
import ru.vtosters.lite.ui.vkui.ModalBottomSheetWrapper;
|
||||
import ru.vtosters.lite.utils.OTAUtils;
|
||||
|
||||
public class UpdateBottomSheetDialog extends ModalBottomSheetWrapper<UpdateBottomSheetDialog> {
|
||||
|
||||
private static final String TAG = "ota";
|
||||
|
||||
private Activity mActivity;
|
||||
|
||||
public UpdateBottomSheetDialog(Activity activity) {
|
||||
super(activity);
|
||||
this.mActivity = activity;
|
||||
}
|
||||
|
||||
public UpdateBottomSheetDialog setUpdateDescriptionText(String changelog) {
|
||||
var container = new LinearLayout(mActivity);
|
||||
var changelogView = new TextView(mActivity);
|
||||
|
||||
container.setOrientation(LinearLayout.VERTICAL);
|
||||
|
||||
changelogView.setPadding(0, dp2px(8), 0, 0);
|
||||
changelogView.setText(getString("changelog") + ": \n" + changelog);
|
||||
changelogView.setTextColor(getTextPrimaryColor());
|
||||
container.addView(changelogView, new LinearLayout.LayoutParams(-1, -2));
|
||||
|
||||
return setView(container);
|
||||
}
|
||||
|
||||
public static void create(Activity activity, OTAUtils helper) {
|
||||
new UpdateBottomSheetDialog(activity)
|
||||
.setTitle(getString("newversion") + " " + helper.getNewVersionName())
|
||||
.setUpdateDescriptionText(helper.getUpdateDescription())
|
||||
.setPositiveButton(getString("updateanddownload"), () -> {
|
||||
OTADownloader.downloadBuild(helper.getDownloadUrl());
|
||||
})
|
||||
.show(TAG);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
package ru.vtosters.lite.ui.dialogs;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.widget.LinearLayout;
|
||||
|
||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import ru.vtosters.lite.ui.adapters.ColorPickerAdapter;
|
||||
import ru.vtosters.lite.ui.vkui.ModalBottomSheetWrapper;
|
||||
import ru.vtosters.lite.utils.LayoutUtils;
|
||||
|
||||
public class VTColorPickerBottomSheetDialog extends ModalBottomSheetWrapper<VTColorPickerBottomSheetDialog> {
|
||||
|
||||
private Activity mActivity;
|
||||
private LinearLayout mContainer;
|
||||
|
||||
public VTColorPickerBottomSheetDialog(Activity activity) {
|
||||
super(activity);
|
||||
this.mActivity = activity;
|
||||
mContainer = new LinearLayout(mActivity);
|
||||
mContainer.setLayoutParams(LayoutUtils.createLinear(-1, -1));
|
||||
mContainer.setOrientation(LinearLayout.VERTICAL);
|
||||
}
|
||||
|
||||
public VTColorPickerBottomSheetDialog setAdapter(ColorPickerAdapter adapter, boolean searchMode) {
|
||||
var recyclerView = new RecyclerView(mActivity);
|
||||
recyclerView.setAdapter(adapter);
|
||||
recyclerView.setHasFixedSize(true);
|
||||
recyclerView.setLayoutManager(new LinearLayoutManager(mActivity));
|
||||
var recyclerViewParams = LayoutUtils.createLinear(-1, -1);
|
||||
mContainer.addView(recyclerView, recyclerViewParams);
|
||||
|
||||
mBuilder.a(mContainer);
|
||||
return setView(mContainer);
|
||||
}
|
||||
|
||||
public LinearLayout getContainer() {
|
||||
return mContainer;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
package ru.vtosters.lite.ui.fragments;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.widget.FrameLayout;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import ru.vtosters.lite.res.managers.PalettesManager;
|
||||
import ru.vtosters.lite.ui.adapters.PalettesAdapter;
|
||||
import ru.vtosters.lite.utils.LayoutUtils;
|
||||
|
||||
public class PalettesManagerFragment extends BaseToolbarFragment {
|
||||
|
||||
private PalettesAdapter mAdapter;
|
||||
private PalettesManager mManager = PalettesManager.getInstance();
|
||||
|
||||
@Override
|
||||
public View onCreateContent(@NonNull LayoutInflater inflater, @Nullable Bundle bundle) {
|
||||
setTitle("Палитры");
|
||||
|
||||
var container = new FrameLayout(getContext());
|
||||
|
||||
mAdapter = new PalettesAdapter();
|
||||
|
||||
var recyclerView = new RecyclerView(getContext());
|
||||
recyclerView.setAdapter(mAdapter);
|
||||
recyclerView.setLayoutManager(new LinearLayoutManager(getContext()));
|
||||
container.addView(recyclerView, LayoutUtils.createFrame(-1, -1));
|
||||
|
||||
return container;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
mManager.reload();
|
||||
}
|
||||
}
|
||||
@@ -12,10 +12,9 @@ import android.os.Bundle;
|
||||
import androidx.preference.Preference;
|
||||
|
||||
import com.vk.navigation.Navigator;
|
||||
import com.vk.newsfeed.NewsfeedSettingsFragment;
|
||||
import com.vtosters.lite.general.fragments.MaterialPreferenceToolbarFragment;
|
||||
|
||||
import ru.vtosters.lite.ui.components.ThemesManager;
|
||||
import ru.vtosters.lite.res.managers.ThemesManager;
|
||||
import ru.vtosters.lite.utils.LifecycleUtils;
|
||||
|
||||
public class ThemesFragment extends MaterialPreferenceToolbarFragment {
|
||||
@@ -30,8 +29,8 @@ public class ThemesFragment extends MaterialPreferenceToolbarFragment {
|
||||
private void prefs() {
|
||||
var themesPreference = findPreference("themes");
|
||||
var themesManager = ThemesManager.getInstance();
|
||||
if (themesManager.getThemesCount() > 0)
|
||||
themesPreference.setSummary(themesManager.getCurrentTheme().name);
|
||||
// if (themesManager.getThemesCount() > 0)
|
||||
// themesPreference.setSummary(themesManager.getCurrentTheme().name);
|
||||
themesPreference.setOnPreferenceClickListener(preference -> {
|
||||
var intent = new Navigator(ThemesManagerFragment.class).b(getContext());
|
||||
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
|
||||
@@ -1,15 +1,10 @@
|
||||
package ru.vtosters.lite.ui.fragments;
|
||||
|
||||
import static ru.vtosters.lite.utils.AndroidUtils.dp2px;
|
||||
import static ru.vtosters.lite.utils.AndroidUtils.getGlobalContext;
|
||||
import static ru.vtosters.lite.utils.AndroidUtils.getIdentifier;
|
||||
import static ru.vtosters.lite.utils.ThemesUtils.getAccentColor;
|
||||
import static ru.vtosters.lite.utils.ThemesUtils.getSTextAttr;
|
||||
import static ru.vtosters.lite.utils.ThemesUtils.getTextAttr;
|
||||
import static ru.vtosters.lite.res.VTLColors.getAccentColor;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.AlertDialog;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.content.res.ColorStateList;
|
||||
import android.graphics.Color;
|
||||
@@ -18,8 +13,6 @@ import android.text.TextUtils;
|
||||
import android.view.Gravity;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.EditText;
|
||||
import android.widget.FrameLayout;
|
||||
import android.widget.LinearLayout;
|
||||
|
||||
@@ -30,13 +23,16 @@ import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import com.google.android.material.floatingactionbutton.FloatingActionButton;
|
||||
import com.vk.core.util.ToastUtils;
|
||||
import com.vk.navigation.Navigator;
|
||||
import com.vtosters.lite.R;
|
||||
|
||||
import org.json.JSONException;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import ru.vtosters.lite.res.managers.ThemesManager;
|
||||
import ru.vtosters.lite.ui.adapters.ThemesAdapter;
|
||||
import ru.vtosters.lite.ui.components.ThemesManager;
|
||||
import ru.vtosters.lite.ui.dialogs.EditTextDialog;
|
||||
import ru.vtosters.lite.utils.LayoutUtils;
|
||||
|
||||
public class ThemesManagerFragment extends BaseToolbarFragment {
|
||||
@@ -52,10 +48,8 @@ public class ThemesManagerFragment extends BaseToolbarFragment {
|
||||
|
||||
switch (requestCode) {
|
||||
case IMPORT_REQUEST_CODE:
|
||||
if (resultCode != Activity.RESULT_OK) {
|
||||
ToastUtils.a("Не удалось импортировать тему");
|
||||
if (resultCode != Activity.RESULT_OK)
|
||||
return;
|
||||
}
|
||||
try {
|
||||
var data = intent.getData();
|
||||
if (data != null) {
|
||||
@@ -98,45 +92,22 @@ public class ThemesManagerFragment extends BaseToolbarFragment {
|
||||
addNewTheme.setImageResource(getIdentifier("ic_add_24", "drawable"));
|
||||
addNewTheme.setBackgroundTintList(ColorStateList.valueOf(getAccentColor()));
|
||||
addNewTheme.setOnClickListener(v -> {
|
||||
LinearLayout linearLayout = new LinearLayout(getGlobalContext());
|
||||
|
||||
final EditText editText = new EditText(getGlobalContext());
|
||||
editText.setHint("Название новой темы");
|
||||
editText.setTextColor(getTextAttr());
|
||||
editText.setHintTextColor(getSTextAttr());
|
||||
|
||||
editText.setBackgroundTintList(ColorStateList.valueOf(getAccentColor()));
|
||||
|
||||
linearLayout.addView(editText);
|
||||
editText.getLayoutParams().width = ViewGroup.LayoutParams.MATCH_PARENT;
|
||||
ViewGroup.MarginLayoutParams margin = ((ViewGroup.MarginLayoutParams) editText.getLayoutParams());
|
||||
margin.setMargins(dp2px(20f), 0, dp2px(20f), 0);
|
||||
editText.setLayoutParams(margin);
|
||||
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(getContext());
|
||||
builder.setTitle("Новая тема");
|
||||
builder.setView(linearLayout);
|
||||
builder.setPositiveButton("OK", (dialog, which) -> {
|
||||
EditTextDialog.create(v.getContext(), "Новая тема", "Название новой темы", null, (dialog, which, editText) -> {
|
||||
var name = editText.getText().toString();
|
||||
if (TextUtils.isEmpty(name)) {
|
||||
ToastUtils.a("Название не может быть пустым");
|
||||
return;
|
||||
}
|
||||
try {
|
||||
mManager.create(name, false);
|
||||
mManager.create(requireActivity(), name);
|
||||
mAdapter.notifyDataSetChanged();
|
||||
} catch (IOException | JSONException e) {
|
||||
e.printStackTrace();
|
||||
ToastUtils.a("Не удалось изменить название темы");
|
||||
}
|
||||
});
|
||||
|
||||
var alert = builder.create();
|
||||
alert.show();
|
||||
alert.getButton(DialogInterface.BUTTON_POSITIVE).setTextColor(getAccentColor());
|
||||
|
||||
mAdapter.notifyDataSetChanged();
|
||||
});
|
||||
|
||||
buttonsContainer.addView(addNewTheme, LayoutUtils.createLinear(-2, -2));
|
||||
|
||||
buttonsContainer.addView(new View(getContext()), LayoutUtils.createLinear(dp2px(5), 0));
|
||||
@@ -146,25 +117,26 @@ public class ThemesManagerFragment extends BaseToolbarFragment {
|
||||
importTheme.setBackgroundTintList(ColorStateList.valueOf(getAccentColor()));
|
||||
importTheme.setImageTintList(ColorStateList.valueOf(Color.WHITE));
|
||||
importTheme.setOnClickListener(v -> {
|
||||
|
||||
var intent = new Intent(Intent.ACTION_GET_CONTENT)
|
||||
.setType("*/*")
|
||||
.addCategory(Intent.CATEGORY_OPENABLE);
|
||||
|
||||
startActivityForResult(Intent.createChooser(intent, "Выберите тему (.vtlt, .svt)"), IMPORT_REQUEST_CODE);
|
||||
|
||||
ToastUtils.a("Возможен импорт тем из стороннего мода Sova V RE");
|
||||
ToastUtils.a("Возможен импорт тем из сторонней модификации ВК Sova V RE");
|
||||
});
|
||||
buttonsContainer.addView(importTheme, LayoutUtils.createLinear(-2, -2));
|
||||
|
||||
buttonsContainer.addView(new View(getContext()), LayoutUtils.createLinear(dp2px(5), 0));
|
||||
|
||||
var openPalettes = new FloatingActionButton(getContext());
|
||||
openPalettes.setImageResource(R.drawable.ic_palette_outline_28);
|
||||
openPalettes.setBackgroundTintList(ColorStateList.valueOf(getAccentColor()));
|
||||
openPalettes.setOnClickListener(v -> {
|
||||
var intent = new Navigator(PalettesManagerFragment.class, new Bundle()).b(getContext());
|
||||
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
getContext().startActivity(intent);
|
||||
});
|
||||
buttonsContainer.addView(openPalettes, LayoutUtils.createLinear(-2, -2));
|
||||
|
||||
return container;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
|
||||
mManager.reload();
|
||||
mAdapter.notifyDataSetChanged();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,115 +0,0 @@
|
||||
package ru.vtosters.lite.ui.items;
|
||||
|
||||
import android.graphics.Color;
|
||||
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
public class Theme {
|
||||
|
||||
public String author;
|
||||
public String name;
|
||||
public boolean isDarkMode;
|
||||
public String id;
|
||||
private Map<String, Integer> colors = new HashMap<>();
|
||||
|
||||
public Theme setAuthor(String author) {
|
||||
this.author = author;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Theme setName(String name) {
|
||||
this.name = name;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Theme setDarkMode(boolean b) {
|
||||
this.isDarkMode = b;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Theme setId(String id) {
|
||||
this.id = id;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Theme setColors(Map<String, Integer> colors) {
|
||||
this.colors.putAll(colors);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Theme setColor(String colorName, int color) {
|
||||
this.colors.put(colorName, color);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Theme setColor(String colorName, String color) {
|
||||
this.colors.put(colorName, Color.parseColor(color));
|
||||
return this;
|
||||
}
|
||||
|
||||
public static Theme valuesOf(String author, String name, boolean darkMode, String id, Map<String, Integer> colors) {
|
||||
return new Theme()
|
||||
.setAuthor(author)
|
||||
.setName(name)
|
||||
.setDarkMode(darkMode)
|
||||
.setId(id)
|
||||
.setColors(colors);
|
||||
}
|
||||
|
||||
public static Theme fromJson(String json) {
|
||||
try {
|
||||
return fromJson(new JSONObject(json));
|
||||
} catch (JSONException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static Theme fromJson(JSONObject json) {
|
||||
var colors = new HashMap<String, Integer>();
|
||||
var colorsJson = json.optJSONObject("colors");
|
||||
var names = colorsJson.names();
|
||||
for (int i = 0; i < colorsJson.length(); i++) {
|
||||
var name = names.optString(i);
|
||||
var color = Color.parseColor("#" + colorsJson.optString(name));
|
||||
colors.put(name, color);
|
||||
}
|
||||
return valuesOf(
|
||||
json.optString("author"),
|
||||
json.optString("name"),
|
||||
json.optBoolean("dark"),
|
||||
json.optString("id"),
|
||||
colors
|
||||
);
|
||||
}
|
||||
|
||||
public JSONObject toJson() throws JSONException {
|
||||
var colorsJson = new JSONObject();
|
||||
for (var entry : colors.entrySet())
|
||||
colorsJson.put(entry.getKey(), Integer.toHexString(entry.getValue()));
|
||||
return new JSONObject()
|
||||
.put("author", this.author)
|
||||
.put("name", this.name)
|
||||
.put("dark", this.isDarkMode)
|
||||
.put("id", this.id)
|
||||
.put("colors", colorsJson);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Theme{" +
|
||||
"author='" + this.author + '\'' +
|
||||
", name='" + this.name + '\'' +
|
||||
", isDarkMode=" + this.isDarkMode +
|
||||
", colors=" + this.colors +
|
||||
'}';
|
||||
}
|
||||
|
||||
public int getColor(String name) {
|
||||
return colors.get(name);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
package ru.vtosters.lite.ui.views.flask;
|
||||
|
||||
import android.graphics.Color;
|
||||
|
||||
public class ColorCircle {
|
||||
private float x, y;
|
||||
private float[] hsv = new float[3];
|
||||
private float[] hsvClone;
|
||||
private int color;
|
||||
|
||||
public ColorCircle(float x, float y, float[] hsv) {
|
||||
set(x, y, hsv);
|
||||
}
|
||||
|
||||
public double sqDist(float x, float y) {
|
||||
double dx = this.x - x;
|
||||
double dy = this.y - y;
|
||||
return dx * dx + dy * dy;
|
||||
}
|
||||
|
||||
public float getX() {
|
||||
return x;
|
||||
}
|
||||
|
||||
public float getY() {
|
||||
return y;
|
||||
}
|
||||
|
||||
public float[] getHsv() {
|
||||
return hsv;
|
||||
}
|
||||
|
||||
public float[] getHsvWithLightness(float lightness) {
|
||||
if (hsvClone == null)
|
||||
hsvClone = hsv.clone();
|
||||
hsvClone[0] = hsv[0];
|
||||
hsvClone[1] = hsv[1];
|
||||
hsvClone[2] = lightness;
|
||||
return hsvClone;
|
||||
}
|
||||
|
||||
public void set(float x, float y, float[] hsv) {
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
this.hsv[0] = hsv[0];
|
||||
this.hsv[1] = hsv[1];
|
||||
this.hsv[2] = hsv[2];
|
||||
this.color = Color.HSVToColor(this.hsv);
|
||||
}
|
||||
|
||||
public int getColor() {
|
||||
return color;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
package ru.vtosters.lite.ui.views.flask;
|
||||
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.drawable.ColorDrawable;
|
||||
|
||||
import ru.vtosters.lite.ui.views.flask.builder.PaintBuilder;
|
||||
|
||||
public class ColorCircleDrawable extends ColorDrawable {
|
||||
private float strokeWidth;
|
||||
private Paint strokePaint = PaintBuilder.newPaint().style(Paint.Style.STROKE).stroke(strokeWidth).color(0xff9e9e9e).build();
|
||||
private Paint fillPaint = PaintBuilder.newPaint().style(Paint.Style.FILL).color(0).build();
|
||||
private Paint fillBackPaint = PaintBuilder.newPaint().shader(PaintBuilder.createAlphaPatternShader(26)).build();
|
||||
|
||||
public ColorCircleDrawable(int color) {
|
||||
super(color);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void draw(Canvas canvas) {
|
||||
canvas.drawColor(0);
|
||||
|
||||
int width = canvas.getWidth();
|
||||
float radius = width / 2f;
|
||||
strokeWidth = radius / 8f;
|
||||
|
||||
this.strokePaint.setStrokeWidth(strokeWidth);
|
||||
this.fillPaint.setColor(getColor());
|
||||
canvas.drawCircle(radius, radius, radius - strokeWidth, fillBackPaint);
|
||||
canvas.drawCircle(radius, radius, radius - strokeWidth, fillPaint);
|
||||
canvas.drawCircle(radius, radius, radius - strokeWidth, strokePaint);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setColor(int color) {
|
||||
super.setColor(color);
|
||||
invalidateSelf();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,433 @@
|
||||
package ru.vtosters.lite.ui.views.flask;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.PorterDuff;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.LinearLayout;
|
||||
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
import ru.vtosters.lite.ui.views.flask.builder.PaintBuilder;
|
||||
import ru.vtosters.lite.ui.views.flask.render.ColorWheelRenderOption;
|
||||
import ru.vtosters.lite.ui.views.flask.render.ColorWheelRenderer;
|
||||
import ru.vtosters.lite.ui.views.flask.render.SimpleColorWheelRenderer;
|
||||
|
||||
public class ColorPickerView extends View {
|
||||
private static final float STROKE_RATIO = 1.5f;
|
||||
|
||||
private AlertDialog dialog;
|
||||
private Bitmap colorWheel;
|
||||
private Canvas colorWheelCanvas;
|
||||
private Bitmap currentColor;
|
||||
private Canvas currentColorCanvas;
|
||||
private boolean showBorder;
|
||||
private int density = 8;
|
||||
|
||||
private float lightness = 1;
|
||||
private float alpha = 1;
|
||||
private int backgroundColor = 0x00000000;
|
||||
|
||||
private Integer initialColors[] = new Integer[]{null, null, null, null, null};
|
||||
private int colorSelection = 0;
|
||||
private Integer initialColor;
|
||||
private Paint colorWheelFill = PaintBuilder.newPaint().color(0).build();
|
||||
private Paint selectorStroke = PaintBuilder.newPaint().color(0).build();
|
||||
private Paint alphaPatternPaint = PaintBuilder.newPaint().build();
|
||||
private ColorCircle currentColorCircle;
|
||||
|
||||
private ArrayList<OnColorChangedListener> colorChangedListeners = new ArrayList<>();
|
||||
private ArrayList<OnColorSelectedListener> listeners = new ArrayList<>();
|
||||
|
||||
|
||||
private LinearLayout colorPreview;
|
||||
|
||||
private ColorWheelRenderer renderer;
|
||||
|
||||
public ColorPickerView(Context context) {
|
||||
super(context);
|
||||
initWith();
|
||||
}
|
||||
|
||||
private void initWith() {
|
||||
density = 10;
|
||||
initialColor = 0xffffffff;
|
||||
|
||||
renderer = new SimpleColorWheelRenderer();
|
||||
|
||||
setDensity(density);
|
||||
setInitialColor(initialColor, true);
|
||||
}
|
||||
|
||||
public void setDialog(AlertDialog dialog) {
|
||||
this.dialog = dialog;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onWindowFocusChanged(boolean hasWindowFocus) {
|
||||
super.onWindowFocusChanged(hasWindowFocus);
|
||||
updateColorWheel();
|
||||
currentColorCircle = findNearestByColor(initialColor);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
|
||||
super.onLayout(changed, left, top, right, bottom);
|
||||
|
||||
updateColorWheel();
|
||||
currentColorCircle = findNearestByColor(initialColor);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
|
||||
super.onSizeChanged(w, h, oldw, oldh);
|
||||
updateColorWheel();
|
||||
}
|
||||
|
||||
private void updateColorWheel() {
|
||||
int width = getMeasuredWidth();
|
||||
int height = getMeasuredHeight();
|
||||
|
||||
if (height < width)
|
||||
width = height;
|
||||
if (width <= 0)
|
||||
return;
|
||||
if (colorWheel == null || colorWheel.getWidth() != width) {
|
||||
colorWheel = Bitmap.createBitmap(width, width, Bitmap.Config.ARGB_8888);
|
||||
colorWheelCanvas = new Canvas(colorWheel);
|
||||
alphaPatternPaint.setShader(PaintBuilder.createAlphaPatternShader(26));
|
||||
}
|
||||
if (currentColor == null || currentColor.getWidth() != width) {
|
||||
currentColor = Bitmap.createBitmap(width, width, Bitmap.Config.ARGB_8888);
|
||||
currentColorCanvas = new Canvas(currentColor);
|
||||
}
|
||||
drawColorWheel();
|
||||
invalidate();
|
||||
}
|
||||
|
||||
private void drawColorWheel() {
|
||||
colorWheelCanvas.drawColor(0, PorterDuff.Mode.CLEAR);
|
||||
currentColorCanvas.drawColor(0, PorterDuff.Mode.CLEAR);
|
||||
|
||||
if (renderer == null) return;
|
||||
|
||||
float half = colorWheelCanvas.getWidth() / 2f;
|
||||
float strokeWidth = STROKE_RATIO * (1f + ColorWheelRenderer.GAP_PERCENTAGE);
|
||||
float maxRadius = half - strokeWidth - half / density;
|
||||
float cSize = maxRadius / (density - 1) / 2;
|
||||
|
||||
ColorWheelRenderOption colorWheelRenderOption = renderer.getRenderOption();
|
||||
colorWheelRenderOption.density = this.density;
|
||||
colorWheelRenderOption.maxRadius = maxRadius;
|
||||
colorWheelRenderOption.cSize = cSize;
|
||||
colorWheelRenderOption.strokeWidth = strokeWidth;
|
||||
colorWheelRenderOption.alpha = alpha;
|
||||
colorWheelRenderOption.lightness = lightness;
|
||||
colorWheelRenderOption.targetCanvas = colorWheelCanvas;
|
||||
|
||||
renderer.initWith(colorWheelRenderOption);
|
||||
renderer.draw();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
|
||||
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
|
||||
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
|
||||
int width = 0;
|
||||
if (widthMode == MeasureSpec.UNSPECIFIED)
|
||||
width = widthMeasureSpec;
|
||||
else if (widthMode == MeasureSpec.AT_MOST)
|
||||
width = MeasureSpec.getSize(widthMeasureSpec);
|
||||
else if (widthMode == MeasureSpec.EXACTLY)
|
||||
width = MeasureSpec.getSize(widthMeasureSpec);
|
||||
|
||||
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
|
||||
int height = 0;
|
||||
if (heightMode == MeasureSpec.UNSPECIFIED)
|
||||
height = heightMeasureSpec;
|
||||
else if (heightMode == MeasureSpec.AT_MOST)
|
||||
height = MeasureSpec.getSize(heightMeasureSpec);
|
||||
else if (heightMode == MeasureSpec.EXACTLY)
|
||||
height = MeasureSpec.getSize(heightMeasureSpec);
|
||||
int squareDimen = width;
|
||||
if (height < width)
|
||||
squareDimen = height;
|
||||
setMeasuredDimension(squareDimen, squareDimen);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onTouchEvent(MotionEvent event) {
|
||||
switch (event.getAction()) {
|
||||
case MotionEvent.ACTION_DOWN:
|
||||
case MotionEvent.ACTION_MOVE: {
|
||||
int lastSelectedColor = getSelectedColor();
|
||||
currentColorCircle = findNearestByPosition(event.getX(), event.getY());
|
||||
int selectedColor = getSelectedColor();
|
||||
|
||||
callOnColorChangedListeners(lastSelectedColor, selectedColor);
|
||||
|
||||
initialColor = selectedColor;
|
||||
updateColorWheel();
|
||||
invalidate();
|
||||
break;
|
||||
}
|
||||
case MotionEvent.ACTION_UP: {
|
||||
int selectedColor = getSelectedColor();
|
||||
if (listeners != null) {
|
||||
for (OnColorSelectedListener listener : listeners) {
|
||||
try {
|
||||
listener.onColorSelected(dialog, selectedColor);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
setColorPreviewColor(selectedColor);
|
||||
invalidate();
|
||||
break;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
protected void callOnColorChangedListeners(int oldColor, int newColor) {
|
||||
if (colorChangedListeners != null && oldColor != newColor) {
|
||||
for (OnColorChangedListener listener : colorChangedListeners) {
|
||||
try {
|
||||
listener.onColorChanged(newColor);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDraw(Canvas canvas) {
|
||||
super.onDraw(canvas);
|
||||
canvas.drawColor(backgroundColor);
|
||||
|
||||
float maxRadius = canvas.getWidth() / (1f + ColorWheelRenderer.GAP_PERCENTAGE);
|
||||
float size = maxRadius / density / 2;
|
||||
if (colorWheel != null && currentColorCircle != null) {
|
||||
colorWheelFill.setColor(Color.HSVToColor(currentColorCircle.getHsvWithLightness(this.lightness)));
|
||||
colorWheelFill.setAlpha((int) (alpha * 0xff));
|
||||
|
||||
// a separate canvas is used to erase an issue with the alpha pattern around the edges
|
||||
// draw circle slightly larger than it needs to be, then erase edges to proper dimensions
|
||||
currentColorCanvas.drawCircle(currentColorCircle.getX(), currentColorCircle.getY(), size + 4, alphaPatternPaint);
|
||||
currentColorCanvas.drawCircle(currentColorCircle.getX(), currentColorCircle.getY(), size + 4, colorWheelFill);
|
||||
|
||||
selectorStroke = PaintBuilder.newPaint().color(0xffffffff).style(Paint.Style.STROKE).stroke(size * (STROKE_RATIO - 1)).xPerMode(PorterDuff.Mode.CLEAR).build();
|
||||
|
||||
if (showBorder) colorWheelCanvas.drawCircle(currentColorCircle.getX(), currentColorCircle.getY(), size + (selectorStroke.getStrokeWidth() / 2f), selectorStroke);
|
||||
canvas.drawBitmap(colorWheel, 0, 0, null);
|
||||
|
||||
currentColorCanvas.drawCircle(currentColorCircle.getX(), currentColorCircle.getY(), size + (selectorStroke.getStrokeWidth() / 2f), selectorStroke);
|
||||
canvas.drawBitmap(currentColor, 0, 0, null);
|
||||
}
|
||||
}
|
||||
|
||||
private ColorCircle findNearestByPosition(float x, float y) {
|
||||
ColorCircle near = null;
|
||||
double minDist = Double.MAX_VALUE;
|
||||
|
||||
for (ColorCircle colorCircle : renderer.getColorCircleList()) {
|
||||
double dist = colorCircle.sqDist(x, y);
|
||||
if (minDist > dist) {
|
||||
minDist = dist;
|
||||
near = colorCircle;
|
||||
}
|
||||
}
|
||||
|
||||
return near;
|
||||
}
|
||||
|
||||
private ColorCircle findNearestByColor(int color) {
|
||||
float[] hsv = new float[3];
|
||||
Color.colorToHSV(color, hsv);
|
||||
ColorCircle near = null;
|
||||
double minDiff = Double.MAX_VALUE;
|
||||
double x = hsv[1] * Math.cos(hsv[0] * Math.PI / 180);
|
||||
double y = hsv[1] * Math.sin(hsv[0] * Math.PI / 180);
|
||||
|
||||
for (ColorCircle colorCircle : renderer.getColorCircleList()) {
|
||||
float[] hsv1 = colorCircle.getHsv();
|
||||
double x1 = hsv1[1] * Math.cos(hsv1[0] * Math.PI / 180);
|
||||
double y1 = hsv1[1] * Math.sin(hsv1[0] * Math.PI / 180);
|
||||
double dx = x - x1;
|
||||
double dy = y - y1;
|
||||
double dist = dx * dx + dy * dy;
|
||||
if (dist < minDiff) {
|
||||
minDiff = dist;
|
||||
near = colorCircle;
|
||||
}
|
||||
}
|
||||
|
||||
return near;
|
||||
}
|
||||
|
||||
public int getSelectedColor() {
|
||||
int color = 0;
|
||||
if (currentColorCircle != null)
|
||||
color = Utils.colorAtLightness(currentColorCircle.getColor(), this.lightness);
|
||||
return Utils.adjustAlpha(this.alpha, color);
|
||||
}
|
||||
|
||||
public Integer[] getAllColors() {
|
||||
return initialColors;
|
||||
}
|
||||
|
||||
public void setInitialColors(Integer[] colors, int selectedColor) {
|
||||
this.initialColors = colors;
|
||||
this.colorSelection = selectedColor;
|
||||
Integer initialColor = this.initialColors[this.colorSelection];
|
||||
if (initialColor == null) initialColor = 0xffffffff;
|
||||
setInitialColor(initialColor, true);
|
||||
}
|
||||
|
||||
public void setInitialColor(int color, boolean updateText) {
|
||||
float[] hsv = new float[3];
|
||||
Color.colorToHSV(color, hsv);
|
||||
|
||||
this.alpha = Utils.getAlphaPercent(color);
|
||||
this.lightness = hsv[2];
|
||||
this.initialColors[this.colorSelection] = color;
|
||||
this.initialColor = color;
|
||||
setColorPreviewColor(color);
|
||||
currentColorCircle = findNearestByColor(color);
|
||||
}
|
||||
|
||||
public void setLightness(float lightness) {
|
||||
int lastSelectedColor = getSelectedColor();
|
||||
|
||||
this.lightness = lightness;
|
||||
if (currentColorCircle != null) {
|
||||
this.initialColor = Color.HSVToColor(Utils.alphaValueAsInt(this.alpha), currentColorCircle.getHsvWithLightness(lightness));
|
||||
|
||||
callOnColorChangedListeners(lastSelectedColor, this.initialColor);
|
||||
|
||||
updateColorWheel();
|
||||
invalidate();
|
||||
}
|
||||
}
|
||||
|
||||
public void setColor(int color, boolean updateText) {
|
||||
setInitialColor(color, updateText);
|
||||
updateColorWheel();
|
||||
invalidate();
|
||||
}
|
||||
|
||||
public void setAlphaValue(float alpha) {
|
||||
int lastSelectedColor = getSelectedColor();
|
||||
|
||||
this.alpha = alpha;
|
||||
this.initialColor = Color.HSVToColor(Utils.alphaValueAsInt(this.alpha), currentColorCircle.getHsvWithLightness(this.lightness));
|
||||
|
||||
callOnColorChangedListeners(lastSelectedColor, this.initialColor);
|
||||
|
||||
updateColorWheel();
|
||||
invalidate();
|
||||
}
|
||||
|
||||
public void addOnColorChangedListener(OnColorChangedListener listener) {
|
||||
this.colorChangedListeners.add(listener);
|
||||
}
|
||||
|
||||
public void addOnColorSelectedListener(OnColorSelectedListener listener) {
|
||||
this.listeners.add(listener);
|
||||
}
|
||||
|
||||
public void setDensity(int density) {
|
||||
this.density = Math.max(2, density);
|
||||
invalidate();
|
||||
}
|
||||
|
||||
public void setColorPreview(LinearLayout colorPreview, Integer selectedColor) {
|
||||
if (colorPreview == null)
|
||||
return;
|
||||
this.colorPreview = colorPreview;
|
||||
if (selectedColor == null)
|
||||
selectedColor = 0;
|
||||
int children = colorPreview.getChildCount();
|
||||
if (children == 0 || colorPreview.getVisibility() != View.VISIBLE)
|
||||
return;
|
||||
|
||||
for (int i = 0; i < children; i++) {
|
||||
View childView = colorPreview.getChildAt(i);
|
||||
if (!(childView instanceof LinearLayout))
|
||||
continue;
|
||||
LinearLayout childLayout = (LinearLayout) childView;
|
||||
if (i == selectedColor) {
|
||||
childLayout.setBackgroundColor(Color.WHITE);
|
||||
}
|
||||
ImageView childImage = childLayout.findViewWithTag("image_preview");
|
||||
childImage.setClickable(true);
|
||||
childImage.setTag(i);
|
||||
childImage.setOnClickListener(v -> {
|
||||
if (v == null)
|
||||
return;
|
||||
Object tag = v.getTag();
|
||||
if (tag == null || !(tag instanceof Integer))
|
||||
return;
|
||||
setSelectedColor((int) tag);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public void setSelectedColor(int previewNumber) {
|
||||
if (initialColors == null || initialColors.length < previewNumber)
|
||||
return;
|
||||
this.colorSelection = previewNumber;
|
||||
setHighlightedColor(previewNumber);
|
||||
Integer color = initialColors[previewNumber];
|
||||
if (color == null)
|
||||
return;
|
||||
setColor(color, true);
|
||||
}
|
||||
|
||||
public void setShowBorder(boolean showBorder) {
|
||||
this.showBorder = showBorder;
|
||||
}
|
||||
|
||||
private void setHighlightedColor(int previewNumber) {
|
||||
int children = colorPreview.getChildCount();
|
||||
if (children == 0 || colorPreview.getVisibility() != View.VISIBLE)
|
||||
return;
|
||||
|
||||
for (int i = 0; i < children; i++) {
|
||||
View childView = colorPreview.getChildAt(i);
|
||||
if (!(childView instanceof LinearLayout))
|
||||
continue;
|
||||
LinearLayout childLayout = (LinearLayout) childView;
|
||||
if (i == previewNumber) {
|
||||
childLayout.setBackgroundColor(Color.WHITE);
|
||||
} else {
|
||||
childLayout.setBackgroundColor(Color.TRANSPARENT);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void setColorPreviewColor(int newColor) {
|
||||
if (colorPreview == null || initialColors == null || colorSelection > initialColors.length || initialColors[colorSelection] == null)
|
||||
return;
|
||||
|
||||
int children = colorPreview.getChildCount();
|
||||
if (children == 0 || colorPreview.getVisibility() != View.VISIBLE)
|
||||
return;
|
||||
|
||||
View childView = colorPreview.getChildAt(colorSelection);
|
||||
if (!(childView instanceof LinearLayout))
|
||||
return;
|
||||
LinearLayout childLayout = (LinearLayout) childView;
|
||||
ImageView childImage = childLayout.findViewWithTag("image_preview");
|
||||
childImage.setImageDrawable(new ColorCircleDrawable(newColor));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
package ru.vtosters.lite.ui.views.flask;
|
||||
|
||||
public interface OnColorChangedListener {
|
||||
void onColorChanged(int selectedColor);
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
package ru.vtosters.lite.ui.views.flask;
|
||||
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
|
||||
public interface OnColorSelectedListener {
|
||||
void onColorSelected(AlertDialog dialog, int selectedColor);
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
package ru.vtosters.lite.ui.views.flask;
|
||||
|
||||
import android.graphics.Color;
|
||||
|
||||
/**
|
||||
* Created by Charles Andersons on 4/17/15.
|
||||
*/
|
||||
public class Utils {
|
||||
public static float getAlphaPercent(int argb) {
|
||||
return Color.alpha(argb) / 255f;
|
||||
}
|
||||
|
||||
public static int alphaValueAsInt(float alpha) {
|
||||
return Math.round(alpha * 255);
|
||||
}
|
||||
|
||||
public static int adjustAlpha(float alpha, int color) {
|
||||
return alphaValueAsInt(alpha) << 24 | (0x00ffffff & color);
|
||||
}
|
||||
|
||||
public static int colorAtLightness(int color, float lightness) {
|
||||
float[] hsv = new float[3];
|
||||
Color.colorToHSV(color, hsv);
|
||||
hsv[2] = lightness;
|
||||
return Color.HSVToColor(hsv);
|
||||
}
|
||||
|
||||
public static float lightnessOfColor(int color) {
|
||||
float[] hsv = new float[3];
|
||||
Color.colorToHSV(color, hsv);
|
||||
return hsv[2];
|
||||
}
|
||||
|
||||
public static String getHexString(int color, boolean showAlpha) {
|
||||
int base = showAlpha ? 0xFFFFFFFF : 0xFFFFFF;
|
||||
String format = showAlpha ? "#%08X" : "#%06X";
|
||||
return String.format(format, (base & color)).toUpperCase();
|
||||
}
|
||||
|
||||
}
|
||||
+105
@@ -0,0 +1,105 @@
|
||||
package ru.vtosters.lite.ui.views.flask.builder;
|
||||
|
||||
import android.content.Context;
|
||||
import android.view.Gravity;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.LinearLayout;
|
||||
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
|
||||
import com.vk.core.dialogs.alert.VkAlertDialog;
|
||||
|
||||
import ru.vtosters.lite.ui.views.flask.ColorPickerView;
|
||||
import ru.vtosters.lite.ui.views.flask.OnColorChangedListener;
|
||||
import ru.vtosters.lite.ui.views.flask.OnColorSelectedListener;
|
||||
import ru.vtosters.lite.utils.AndroidUtils;
|
||||
import ru.vtosters.lite.utils.LayoutUtils;
|
||||
|
||||
public class ColorPickerDialogBuilder {
|
||||
private Context context;
|
||||
private VkAlertDialog.Builder builder;
|
||||
private LinearLayout pickerContainer;
|
||||
private ColorPickerView colorPickerView;
|
||||
private LinearLayout colorPreview;
|
||||
|
||||
private boolean isBorderEnabled = true;
|
||||
private Integer[] initialColor = new Integer[]{null, null, null, null, null};
|
||||
|
||||
private ColorPickerDialogBuilder(Context context) {
|
||||
this.context = context;
|
||||
builder = new VkAlertDialog.Builder(context);
|
||||
pickerContainer = new LinearLayout(context);
|
||||
pickerContainer.setOrientation(LinearLayout.VERTICAL);
|
||||
pickerContainer.setGravity(Gravity.CENTER_HORIZONTAL);
|
||||
|
||||
LinearLayout.LayoutParams layoutParamsForColorPickerView = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 0);
|
||||
layoutParamsForColorPickerView.weight = 1;
|
||||
colorPickerView = new ColorPickerView(context);
|
||||
|
||||
pickerContainer.addView(colorPickerView, layoutParamsForColorPickerView);
|
||||
}
|
||||
|
||||
public static ColorPickerDialogBuilder with(Context context) {
|
||||
return new ColorPickerDialogBuilder(context);
|
||||
}
|
||||
|
||||
public ColorPickerDialogBuilder setTitle(String title) {
|
||||
builder.setTitle(title);
|
||||
return this;
|
||||
}
|
||||
|
||||
public ColorPickerDialogBuilder setTitle(int titleId) {
|
||||
builder.setTitle(titleId);
|
||||
return this;
|
||||
}
|
||||
|
||||
public ColorPickerDialogBuilder density(int density) {
|
||||
colorPickerView.setDensity(density);
|
||||
return this;
|
||||
}
|
||||
|
||||
public ColorPickerDialogBuilder setOnColorChangedListener(OnColorChangedListener onColorChangedListener) {
|
||||
colorPickerView.addOnColorChangedListener(onColorChangedListener);
|
||||
return this;
|
||||
}
|
||||
|
||||
public ColorPickerDialogBuilder setOnColorSelectedListener(OnColorSelectedListener onColorSelectedListener) {
|
||||
colorPickerView.addOnColorSelectedListener(onColorSelectedListener);
|
||||
return this;
|
||||
}
|
||||
|
||||
public ColorPickerDialogBuilder showBorder(boolean showBorder) {
|
||||
isBorderEnabled = showBorder;
|
||||
return this;
|
||||
}
|
||||
|
||||
public AlertDialog build() {
|
||||
colorPickerView.setInitialColors(initialColor, getStartOffset(initialColor));
|
||||
colorPickerView.setShowBorder(isBorderEnabled);
|
||||
|
||||
colorPreview = new LinearLayout(context);
|
||||
colorPreview.setLayoutParams(LayoutUtils.createLinear(-2, AndroidUtils.dp2px(40)));
|
||||
colorPreview.setOrientation(LinearLayout.HORIZONTAL);
|
||||
colorPreview.setGravity(Gravity.CENTER);
|
||||
pickerContainer.addView(colorPreview);
|
||||
|
||||
colorPickerView.setColorPreview(colorPreview, getStartOffset(initialColor));
|
||||
|
||||
var dialog = builder.create();
|
||||
dialog.setView(pickerContainer);
|
||||
colorPickerView.setDialog(dialog);
|
||||
|
||||
return dialog;
|
||||
}
|
||||
|
||||
private Integer getStartOffset(Integer[] colors) {
|
||||
Integer start = 0;
|
||||
for (int i = 0; i < colors.length; i++) {
|
||||
if (colors[i] == null) {
|
||||
return start;
|
||||
}
|
||||
start = (i + 1) / 2;
|
||||
}
|
||||
return start;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
package ru.vtosters.lite.ui.views.flask.builder;
|
||||
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.BitmapShader;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.PorterDuff;
|
||||
import android.graphics.PorterDuffXfermode;
|
||||
import android.graphics.Shader;
|
||||
|
||||
public class PaintBuilder {
|
||||
public static PaintHolder newPaint() {
|
||||
return new PaintHolder();
|
||||
}
|
||||
|
||||
public static class PaintHolder {
|
||||
private Paint paint;
|
||||
|
||||
private PaintHolder() {
|
||||
this.paint = new Paint(Paint.ANTI_ALIAS_FLAG);
|
||||
}
|
||||
|
||||
public PaintHolder color(int color) {
|
||||
this.paint.setColor(color);
|
||||
return this;
|
||||
}
|
||||
|
||||
public PaintHolder antiAlias(boolean flag) {
|
||||
this.paint.setAntiAlias(flag);
|
||||
return this;
|
||||
}
|
||||
|
||||
public PaintHolder style(Paint.Style style) {
|
||||
this.paint.setStyle(style);
|
||||
return this;
|
||||
}
|
||||
|
||||
public PaintHolder mode(PorterDuff.Mode mode) {
|
||||
this.paint.setXfermode(new PorterDuffXfermode(mode));
|
||||
return this;
|
||||
}
|
||||
|
||||
public PaintHolder stroke(float width) {
|
||||
this.paint.setStrokeWidth(width);
|
||||
return this;
|
||||
}
|
||||
|
||||
public PaintHolder xPerMode(PorterDuff.Mode mode) {
|
||||
this.paint.setXfermode(new PorterDuffXfermode(mode));
|
||||
return this;
|
||||
}
|
||||
|
||||
public PaintHolder shader(Shader shader) {
|
||||
this.paint.setShader(shader);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Paint build() {
|
||||
return this.paint;
|
||||
}
|
||||
}
|
||||
|
||||
public static Shader createAlphaPatternShader(int size) {
|
||||
size /= 2;
|
||||
size = Math.max(8, size * 2);
|
||||
return new BitmapShader(createAlphaBackgroundPattern(size), Shader.TileMode.REPEAT, Shader.TileMode.REPEAT);
|
||||
}
|
||||
|
||||
private static Bitmap createAlphaBackgroundPattern(int size) {
|
||||
Paint alphaPatternPaint = PaintBuilder.newPaint().build();
|
||||
Bitmap bm = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888);
|
||||
Canvas c = new Canvas(bm);
|
||||
int s = Math.round(size / 2f);
|
||||
for (int i = 0; i < 2; i++)
|
||||
for (int j = 0; j < 2; j++) {
|
||||
if ((i + j) % 2 == 0) alphaPatternPaint.setColor(0xffffffff);
|
||||
else alphaPatternPaint.setColor(0xffd0d0d0);
|
||||
c.drawRect(i * s, j * s, (i + 1) * s, (j + 1) * s, alphaPatternPaint);
|
||||
}
|
||||
return bm;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
package ru.vtosters.lite.ui.views.flask.render;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import ru.vtosters.lite.ui.views.flask.ColorCircle;
|
||||
|
||||
public abstract class AbsColorWheelRenderer implements ColorWheelRenderer {
|
||||
protected ColorWheelRenderOption colorWheelRenderOption;
|
||||
protected List<ColorCircle> colorCircleList = new ArrayList<>();
|
||||
|
||||
public void initWith(ColorWheelRenderOption colorWheelRenderOption) {
|
||||
this.colorWheelRenderOption = colorWheelRenderOption;
|
||||
this.colorCircleList.clear();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ColorWheelRenderOption getRenderOption() {
|
||||
if (colorWheelRenderOption == null) colorWheelRenderOption = new ColorWheelRenderOption();
|
||||
return colorWheelRenderOption;
|
||||
}
|
||||
|
||||
public List<ColorCircle> getColorCircleList() {
|
||||
return colorCircleList;
|
||||
}
|
||||
|
||||
protected int getAlphaValueAsInt() {
|
||||
return Math.round(colorWheelRenderOption.alpha * 255);
|
||||
}
|
||||
|
||||
protected int calcTotalCount(float radius, float size) {
|
||||
return Math.max(1, (int) ((1f - GAP_PERCENTAGE) * Math.PI / (Math.asin(size / radius)) + 0.5f));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
package ru.vtosters.lite.ui.views.flask.render;
|
||||
|
||||
import android.graphics.Canvas;
|
||||
|
||||
public class ColorWheelRenderOption {
|
||||
public int density;
|
||||
public float maxRadius;
|
||||
public float cSize, strokeWidth, alpha, lightness;
|
||||
public Canvas targetCanvas;
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package ru.vtosters.lite.ui.views.flask.render;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import ru.vtosters.lite.ui.views.flask.ColorCircle;
|
||||
|
||||
public interface ColorWheelRenderer {
|
||||
float GAP_PERCENTAGE = 0.025f;
|
||||
|
||||
void draw();
|
||||
|
||||
ColorWheelRenderOption getRenderOption();
|
||||
|
||||
void initWith(ColorWheelRenderOption colorWheelRenderOption);
|
||||
|
||||
List<ColorCircle> getColorCircleList();
|
||||
}
|
||||
+46
@@ -0,0 +1,46 @@
|
||||
package ru.vtosters.lite.ui.views.flask.render;
|
||||
|
||||
import android.graphics.Color;
|
||||
import android.graphics.Paint;
|
||||
|
||||
import ru.vtosters.lite.ui.views.flask.ColorCircle;
|
||||
import ru.vtosters.lite.ui.views.flask.builder.PaintBuilder;
|
||||
|
||||
public class SimpleColorWheelRenderer extends AbsColorWheelRenderer {
|
||||
private Paint selectorFill = PaintBuilder.newPaint().build();
|
||||
private float[] hsv = new float[3];
|
||||
|
||||
@Override
|
||||
public void draw() {
|
||||
final int setSize = colorCircleList.size();
|
||||
int currentCount = 0;
|
||||
float half = colorWheelRenderOption.targetCanvas.getWidth() / 2f;
|
||||
int density = colorWheelRenderOption.density;
|
||||
float maxRadius = colorWheelRenderOption.maxRadius;
|
||||
|
||||
for (int i = 0; i < density; i++) {
|
||||
float p = (float) i / (density - 1); // 0~1
|
||||
float radius = maxRadius * p;
|
||||
float size = colorWheelRenderOption.cSize;
|
||||
int total = calcTotalCount(radius, size);
|
||||
|
||||
for (int j = 0; j < total; j++) {
|
||||
double angle = Math.PI * 2 * j / total + (Math.PI / total) * ((i + 1) % 2);
|
||||
float x = half + (float) (radius * Math.cos(angle));
|
||||
float y = half + (float) (radius * Math.sin(angle));
|
||||
hsv[0] = (float) (angle * 180 / Math.PI);
|
||||
hsv[1] = radius / maxRadius;
|
||||
hsv[2] = colorWheelRenderOption.lightness;
|
||||
selectorFill.setColor(Color.HSVToColor(hsv));
|
||||
selectorFill.setAlpha(getAlphaValueAsInt());
|
||||
|
||||
colorWheelRenderOption.targetCanvas.drawCircle(x, y, size - colorWheelRenderOption.strokeWidth, selectorFill);
|
||||
|
||||
if (currentCount >= setSize)
|
||||
colorCircleList.add(new ColorCircle(x, y, hsv));
|
||||
else colorCircleList.get(currentCount).set(x, y, hsv);
|
||||
currentCount++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
package ru.vtosters.lite.ui.views.rarepebble;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Bitmap;
|
||||
|
||||
public class AlphaView extends SliderViewBase implements ColorObserver {
|
||||
|
||||
private ObservableColor observableColor = new ObservableColor(0);
|
||||
|
||||
public AlphaView(Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
public void observeColor(ObservableColor observableColor) {
|
||||
this.observableColor = observableColor;
|
||||
observableColor.addObserver(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateColor(ObservableColor observableColor) {
|
||||
setPos((float)observableColor.getAlpha()/0xff);
|
||||
updateBitmap();
|
||||
invalidate();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void notifyListener(float currentPos) {
|
||||
observableColor.updateAlpha((int)(currentPos * 0xff), this);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int getPointerColor(float currentPos) {
|
||||
float solidColorLightness = observableColor.getLightness();
|
||||
float posLightness = 1 + currentPos * (solidColorLightness - 1);
|
||||
return posLightness > 0.5f ? 0xff000000 : 0xffffffff;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Bitmap makeBitmap(int w, int h) {
|
||||
final boolean isWide = w > h;
|
||||
final int n = Math.max(w, h);
|
||||
int color = observableColor.getColor();
|
||||
int[] colors = new int[n];
|
||||
for (int i = 0; i < n; ++i) {
|
||||
float alpha = isWide ? (float)i / n : 1 - (float)i / n;
|
||||
colors[i] = color & 0xffffff | (int)(alpha * 0xff) << 24;
|
||||
}
|
||||
final int bmpWidth = isWide ? w : 1;
|
||||
final int bmpHeight = isWide ? 1 : h;
|
||||
return Bitmap.createBitmap(colors, bmpWidth, bmpHeight, Bitmap.Config.ARGB_8888);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
package ru.vtosters.lite.ui.views.rarepebble;
|
||||
|
||||
public interface ColorObserver {
|
||||
|
||||
void updateColor(ObservableColor observableColor);
|
||||
}
|
||||
@@ -0,0 +1,148 @@
|
||||
package ru.vtosters.lite.ui.views.rarepebble;
|
||||
|
||||
import static ru.vtosters.lite.utils.AndroidUtils.dp2px;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.Typeface;
|
||||
import android.text.InputFilter;
|
||||
import android.text.InputType;
|
||||
import android.text.method.DigitsKeyListener;
|
||||
import android.view.Gravity;
|
||||
import android.view.View;
|
||||
import android.view.inputmethod.EditorInfo;
|
||||
import android.widget.EditText;
|
||||
import android.widget.FrameLayout;
|
||||
import android.widget.LinearLayout;
|
||||
|
||||
import ru.vtosters.lite.utils.LayoutUtils;
|
||||
|
||||
public class ColorPickerView extends FrameLayout {
|
||||
|
||||
private AlphaView alphaView;
|
||||
private EditText hexEdit;
|
||||
private ObservableColor observableColor = new ObservableColor(0);
|
||||
private SwatchView swatchView;
|
||||
|
||||
public ColorPickerView(Context context) {
|
||||
super(context);
|
||||
|
||||
var container = new LinearLayout(context);
|
||||
container.setOrientation(LinearLayout.VERTICAL);
|
||||
container.setPadding(dp2px(16), dp2px(16), dp2px(16), dp2px(16));
|
||||
container.setGravity(Gravity.CENTER);
|
||||
this.addView(container, LayoutUtils.createFrame(-2, -2));
|
||||
|
||||
var colorPickerFrame = new FrameLayout(context);
|
||||
var colorPickerFrameParams = LayoutUtils.createLinear(-2, -2);
|
||||
colorPickerFrameParams.weight = 1.0f;
|
||||
container.addView(colorPickerFrame, colorPickerFrameParams);
|
||||
|
||||
swatchView = new SwatchView(context);
|
||||
colorPickerFrame.addView(swatchView, LayoutUtils.createFrame(-2, -2));
|
||||
|
||||
HueSatView hueSatView = new HueSatView(context);
|
||||
colorPickerFrame.addView(hueSatView, LayoutUtils.createFrame(-2, -2));
|
||||
|
||||
ValueView valueView = new ValueView(context);
|
||||
var valueViewParams = LayoutUtils.createLinear(-1, dp2px(36));
|
||||
valueViewParams.topMargin = dp2px(16);
|
||||
container.addView(valueView, valueViewParams);
|
||||
|
||||
alphaView = new AlphaView(context);
|
||||
var alphaViewParams = LayoutUtils.createLinear(-1, dp2px(36));
|
||||
valueViewParams.topMargin = dp2px(16);
|
||||
container.addView(alphaView, alphaViewParams);
|
||||
|
||||
hexEdit = new EditText(context);
|
||||
hexEdit.setText("00000000");
|
||||
hexEdit.setKeyListener(DigitsKeyListener.getInstance("0123456789ABCDEFabcdef"));
|
||||
hexEdit.setGravity(Gravity.CENTER_HORIZONTAL);
|
||||
hexEdit.setFilters(new InputFilter[]{ new InputFilter.LengthFilter(4) });
|
||||
hexEdit.setBackground(null);
|
||||
hexEdit.setTypeface(Typeface.MONOSPACE);
|
||||
hexEdit.setInputType(InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS);
|
||||
hexEdit.setImeOptions(EditorInfo.IME_FLAG_NO_EXTRACT_UI);
|
||||
container.addView(hexEdit, LayoutUtils.createLinear(-1, -2));
|
||||
|
||||
swatchView.observeColor(observableColor);
|
||||
hueSatView.observeColor(observableColor);
|
||||
valueView.observeColor(observableColor);
|
||||
alphaView.observeColor(observableColor);
|
||||
HexEdit.setUpListeners(hexEdit, observableColor);
|
||||
|
||||
showAlpha(true);
|
||||
showHex(true);
|
||||
showPreview(true);
|
||||
|
||||
// We get all our state saved and restored for free,
|
||||
// thanks to the EditText and its listeners!
|
||||
}
|
||||
|
||||
/** Returns the color selected by the user */
|
||||
public int getColor() {
|
||||
return observableColor.getColor();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the original color swatch and the current color to the specified value.<br>
|
||||
* Using dark colors may lead to a loss of color information caused by the ARGB color space.
|
||||
* Use {@link #setColor(int, float, float, float)} instead to avoid this.
|
||||
*/
|
||||
public void setColor(int color) {
|
||||
setOriginalColor(color);
|
||||
setCurrentColor(color);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the original color swatch and the current color to the specified value.
|
||||
*
|
||||
* <ul>
|
||||
* <li>Alpha [0...255]</li>
|
||||
* <li>Hue [0...360)</li>
|
||||
* <li>Saturation [0...1]</li>
|
||||
* <li>Brightness [0...1]</li>
|
||||
* </ul>
|
||||
*/ public void setColor(int alpha, float hue, float sat, float bri) {
|
||||
setOriginalColor(alpha, hue, sat, bri);
|
||||
setCurrentColor(alpha, hue, sat, bri);
|
||||
}
|
||||
|
||||
/** Sets the original color swatch without changing the current color. */
|
||||
public void setOriginalColor(int color) {
|
||||
swatchView.setOriginalColor(color);
|
||||
}
|
||||
|
||||
/** Sets the original color swatch without changing the current color. */
|
||||
public void setOriginalColor(int alpha, float hue, float sat, float bri) {
|
||||
int color = Color.HSVToColor(alpha, new float[]{hue, sat, bri});
|
||||
swatchView.setOriginalColor(color);
|
||||
}
|
||||
|
||||
/** Updates the current color without changing the original color swatch. */
|
||||
public void setCurrentColor(int color) {
|
||||
observableColor.updateColor(color, null);
|
||||
}
|
||||
|
||||
/** Updates the current color without changing the original color swatch. */
|
||||
public void setCurrentColor(int alpha, float hue, float sat, float bri) {
|
||||
observableColor.updateColor(alpha, hue, sat, bri, null);
|
||||
}
|
||||
|
||||
public void showAlpha(boolean showAlpha) {
|
||||
alphaView.setVisibility(showAlpha ? View.VISIBLE : View.GONE);
|
||||
HexEdit.setShowAlphaDigits(hexEdit, showAlpha);
|
||||
}
|
||||
|
||||
public void addColorObserver(ColorObserver observer) {
|
||||
observableColor.addObserver(observer);
|
||||
}
|
||||
|
||||
public void showHex(boolean showHex) {
|
||||
hexEdit.setVisibility(showHex ? View.VISIBLE : View.GONE);
|
||||
}
|
||||
|
||||
public void showPreview(boolean showPreview) {
|
||||
swatchView.setVisibility(showPreview ? View.VISIBLE : View.GONE);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
package ru.vtosters.lite.ui.views.rarepebble;
|
||||
|
||||
import android.graphics.Color;
|
||||
import android.text.Editable;
|
||||
import android.text.InputFilter;
|
||||
import android.text.Spanned;
|
||||
import android.text.TextWatcher;
|
||||
import android.widget.EditText;
|
||||
|
||||
class HexEdit {
|
||||
|
||||
private static InputFilter[] withoutAlphaDigits = {new ColorPasteLengthFilter()};
|
||||
private static InputFilter[] withAlphaDigits = {new InputFilter.LengthFilter(8)};
|
||||
|
||||
public static void setUpListeners(final EditText hexEdit, final ObservableColor observableColor) {
|
||||
|
||||
class MultiObserver implements ColorObserver, TextWatcher {
|
||||
|
||||
@Override
|
||||
public void updateColor(ObservableColor observableColor) {
|
||||
final String colorString = formatColor(observableColor.getColor());
|
||||
// Prevent onTextChanged getting called when we update text programmatically
|
||||
hexEdit.removeTextChangedListener(this);
|
||||
hexEdit.setText(colorString);
|
||||
hexEdit.addTextChangedListener(this);
|
||||
}
|
||||
|
||||
private String formatColor(int color) {
|
||||
return shouldTrimAlphaDigits()
|
||||
? String.format("%06x", color & 0x00ffffff)
|
||||
: String.format("%08x", color);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTextChanged(CharSequence s, int start, int before, int count) {
|
||||
int color = parseHexColor(s);
|
||||
if (shouldTrimAlphaDigits()) color = color | 0xff000000;
|
||||
observableColor.updateColor(color, this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterTextChanged(Editable s) {
|
||||
}
|
||||
|
||||
private boolean shouldTrimAlphaDigits() {
|
||||
return hexEdit.getFilters() == withoutAlphaDigits;
|
||||
}
|
||||
}
|
||||
|
||||
final MultiObserver multiObserver = new MultiObserver();
|
||||
hexEdit.addTextChangedListener(multiObserver);
|
||||
observableColor.addObserver(multiObserver);
|
||||
setShowAlphaDigits(hexEdit, true);
|
||||
}
|
||||
|
||||
private static int parseHexColor(CharSequence s) {
|
||||
try {
|
||||
return (int)(Long.parseLong(s.toString(), 16) & 0xffffffffL);
|
||||
}
|
||||
catch (NumberFormatException e) {
|
||||
return Color.GRAY;
|
||||
}
|
||||
}
|
||||
|
||||
public static void setShowAlphaDigits(final EditText hexEdit, boolean showAlphaDigits) {
|
||||
hexEdit.setFilters(showAlphaDigits ? withAlphaDigits : withoutAlphaDigits);
|
||||
hexEdit.setText(hexEdit.getText()); // trigger a reformat of text
|
||||
}
|
||||
|
||||
|
||||
private static class ColorPasteLengthFilter implements InputFilter {
|
||||
|
||||
private static final int MAX_LENGTH = 6;
|
||||
private static final int PASTED_LEN = 8;
|
||||
private final InputFilter sixDigitFilter = new InputFilter.LengthFilter(MAX_LENGTH);
|
||||
|
||||
public CharSequence filter(CharSequence source, int start, int end, Spanned dest, int dstart, int dend) {
|
||||
// If 8 digits have been pasted, replacing all source, trim alpha digits.
|
||||
// Otherwise standard LengthFilter behavior.
|
||||
final int srcLength = end - start;
|
||||
final int dstSelLength = dend - dstart;
|
||||
if (srcLength == PASTED_LEN && dstSelLength == dest.length()) {
|
||||
// Discard alpha digits:
|
||||
return source.subSequence(PASTED_LEN - MAX_LENGTH, PASTED_LEN);
|
||||
}
|
||||
else {
|
||||
return sixDigitFilter.filter(source, start, end, dest, dstart, dend);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,205 @@
|
||||
package ru.vtosters.lite.ui.views.rarepebble;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.Path;
|
||||
import android.graphics.PointF;
|
||||
import android.graphics.Rect;
|
||||
import android.graphics.RectF;
|
||||
import android.os.Build;
|
||||
import android.util.DisplayMetrics;
|
||||
import android.view.MotionEvent;
|
||||
|
||||
public class HueSatView extends SquareView implements ColorObserver {
|
||||
|
||||
private Paint borderPaint;
|
||||
private Paint pointerPaint;
|
||||
private Path pointerPath;
|
||||
private Path borderPath;
|
||||
private Rect viewRect = new Rect();
|
||||
private int w;
|
||||
private int h;
|
||||
|
||||
// Bitmap is generated once and shared across instances.
|
||||
private static Bitmap bitmap;
|
||||
|
||||
private final PointF pointer = new PointF();
|
||||
private ObservableColor observableColor = new ObservableColor(0);
|
||||
|
||||
public HueSatView(Context context) {
|
||||
super(context);
|
||||
|
||||
borderPaint = Resources.makeLinePaint();
|
||||
pointerPaint = Resources.makeLinePaint();
|
||||
pointerPaint.setColor(0xff000000);
|
||||
pointerPath = Resources.makePointerPath();
|
||||
borderPath = new Path();
|
||||
if (bitmap == null) {
|
||||
bitmap = makeBitmap(optimalBitmapSize());
|
||||
}
|
||||
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) {
|
||||
// Need clipPath() and setLayerType()...
|
||||
throw new UnsupportedOperationException("Android API 10 and below is not supported.");
|
||||
}
|
||||
else if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR2) {
|
||||
// clipPath() is only supported on a software layer.
|
||||
setLayerType(LAYER_TYPE_SOFTWARE, null);
|
||||
}
|
||||
}
|
||||
|
||||
private int optimalBitmapSize() {
|
||||
final int scale = 2;
|
||||
final int maxBitmapSize = 128;
|
||||
final DisplayMetrics dm = getResources().getDisplayMetrics();
|
||||
return Math.min(maxBitmapSize, Math.min(dm.widthPixels, dm.heightPixels) / scale);
|
||||
}
|
||||
|
||||
public void observeColor(ObservableColor observableColor) {
|
||||
this.observableColor = observableColor;
|
||||
observableColor.addObserver(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateColor(ObservableColor observableColor) {
|
||||
setPointer(pointer, observableColor.getHue(), observableColor.getSat(), w);
|
||||
optimisePointerColor();
|
||||
invalidate();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
|
||||
this.w = w;
|
||||
this.h = h;
|
||||
viewRect.set(0, 0, w, h);
|
||||
|
||||
float inset = borderPaint.getStrokeWidth() / 2;
|
||||
makeBorderPath(borderPath, w, h, inset);
|
||||
|
||||
// Sets pointer position
|
||||
updateColor(observableColor);
|
||||
}
|
||||
|
||||
private static void makeBorderPath(Path borderPath, int w, int h, float inset) {
|
||||
w -= inset;
|
||||
h -= inset;
|
||||
borderPath.reset();
|
||||
borderPath.moveTo(w, inset);
|
||||
borderPath.lineTo(w, h);
|
||||
borderPath.lineTo(inset, h);
|
||||
borderPath.addArc(new RectF(inset, inset, 2 * w, 2 * h), 180, 270);
|
||||
borderPath.close();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onTouchEvent(MotionEvent event) {
|
||||
int action = event.getActionMasked();
|
||||
switch (action) {
|
||||
case MotionEvent.ACTION_DOWN:
|
||||
boolean withinPicker = clamp(pointer, event.getX(), event.getY(), true);
|
||||
if (withinPicker) update();
|
||||
return withinPicker;
|
||||
case MotionEvent.ACTION_MOVE:
|
||||
clamp(pointer, event.getX(), event.getY(), false);
|
||||
update();
|
||||
getParent().requestDisallowInterceptTouchEvent(true);
|
||||
return true;
|
||||
}
|
||||
return super.onTouchEvent(event);
|
||||
}
|
||||
|
||||
private void update() {
|
||||
observableColor.updateHueSat(
|
||||
hueForPos(pointer.x, pointer.y, w),
|
||||
satForPos(pointer.x, pointer.y, w),
|
||||
this);
|
||||
optimisePointerColor();
|
||||
invalidate();
|
||||
}
|
||||
|
||||
private boolean clamp(PointF pointer, float x, float y, boolean rejectOutside) {
|
||||
x = Math.min(x, w);
|
||||
y = Math.min(y, h);
|
||||
final float dx = w - x;
|
||||
final float dy = h - y;
|
||||
final float r = (float)Math.sqrt(dx * dx + dy * dy);
|
||||
boolean outside = r > w;
|
||||
if (!outside || !rejectOutside) {
|
||||
if (outside) {
|
||||
x = w - dx * w / r;
|
||||
y = w - dy * w / r;
|
||||
}
|
||||
pointer.set(x, y);
|
||||
}
|
||||
return !outside;
|
||||
}
|
||||
|
||||
private void optimisePointerColor() {
|
||||
pointerPaint.setColor(
|
||||
observableColor.getLightnessWithValue(1) > 0.5
|
||||
? 0xff000000 : 0xffffffff);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDraw(Canvas canvas) {
|
||||
super.onDraw(canvas);
|
||||
|
||||
canvas.save();
|
||||
|
||||
canvas.clipPath(borderPath);
|
||||
canvas.drawBitmap(bitmap, null, viewRect, null);
|
||||
canvas.translate(pointer.x, pointer.y);
|
||||
canvas.drawPath(pointerPath, pointerPaint);
|
||||
|
||||
canvas.restore();
|
||||
|
||||
canvas.drawPath(borderPath, borderPaint);
|
||||
}
|
||||
|
||||
private static Bitmap makeBitmap(int radiusPx) {
|
||||
int[] colors = new int[radiusPx * radiusPx];
|
||||
float[] hsv = new float[]{0f, 0f, 1f};
|
||||
for (int y = 0; y < radiusPx; ++y) {
|
||||
for (int x = 0; x < radiusPx; ++x) {
|
||||
final int i = x + y * radiusPx;
|
||||
final float sat = satForPos(x, y, radiusPx);
|
||||
final float arcBleed = 2f / radiusPx; // extend curved edge pixels just outside clip area.
|
||||
if (sat <= 1 + arcBleed) {
|
||||
hsv[0] = hueForPos(x, y, radiusPx);
|
||||
hsv[1] = sat;
|
||||
colors[i] = Color.HSVToColor(0xff, hsv);
|
||||
}
|
||||
}
|
||||
}
|
||||
return Bitmap.createBitmap(colors, radiusPx, radiusPx, Bitmap.Config.ARGB_8888);
|
||||
}
|
||||
|
||||
private static float hueForPos(float x, float y, float radiusPx) {
|
||||
final double r = radiusPx - 1; // gives values 0...1 inclusive
|
||||
final double dx = (r - x) / r;
|
||||
final double dy = (r - y) / r;
|
||||
final double angle = Math.atan2(dy, dx);
|
||||
final double hue = 360 * angle / (Math.PI / 2);
|
||||
return (float)hue;
|
||||
}
|
||||
|
||||
private static float satForPos(float x, float y, float radiusPx) {
|
||||
final double r = radiusPx - 1; // gives values 0...1 inclusive
|
||||
final double dx = (r - x) / r;
|
||||
final double dy = (r - y) / r;
|
||||
final double sat = dx * dx + dy * dy; // leave it squared -- exaggerates pale colors
|
||||
return (float)sat;
|
||||
}
|
||||
|
||||
private static void setPointer(PointF pointer, float hue, float sat, float radiusPx) {
|
||||
final float r = radiusPx - 1; // for values 0...1 inclusive
|
||||
final double distance = r * Math.sqrt(sat);
|
||||
final double angle = hue / 360 * Math.PI / 2;
|
||||
final double dx = distance * Math.cos(angle);
|
||||
final double dy = distance * Math.sin(angle);
|
||||
pointer.set(r - (float)dx, r - (float)dy);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
package ru.vtosters.lite.ui.views.rarepebble;
|
||||
|
||||
import android.graphics.Color;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class ObservableColor {
|
||||
|
||||
// Store as HSV & A, otherwise round-trip to int causes color drift.
|
||||
private final float[] hsv = {0, 0, 0};
|
||||
private int alpha;
|
||||
private final List<ColorObserver> observers = new ArrayList<ColorObserver>();
|
||||
|
||||
public ObservableColor(int color) {
|
||||
Color.colorToHSV(color, hsv);
|
||||
alpha = Color.alpha(color);
|
||||
}
|
||||
|
||||
public void getHsv(float hsvOut[]) {
|
||||
hsvOut[0] = hsv[0];
|
||||
hsvOut[1] = hsv[1];
|
||||
hsvOut[2] = hsv[2];
|
||||
}
|
||||
|
||||
public int getColor() {
|
||||
return Color.HSVToColor(alpha, hsv);
|
||||
}
|
||||
|
||||
public float getHue() {
|
||||
return hsv[0];
|
||||
}
|
||||
|
||||
public float getSat() {
|
||||
return hsv[1];
|
||||
}
|
||||
|
||||
public float getValue() {
|
||||
return hsv[2];
|
||||
}
|
||||
|
||||
public int getAlpha() {
|
||||
return alpha;
|
||||
}
|
||||
|
||||
public float getLightness() {
|
||||
return getLightnessWithValue(hsv[2]);
|
||||
}
|
||||
|
||||
public float getLightnessWithValue(float value) {
|
||||
float[] hsV = {hsv[0], hsv[1], value};
|
||||
final int color = Color.HSVToColor(hsV);
|
||||
return (Color.red(color) * 0.2126f + Color.green(color) * 0.7152f + Color.blue(color) * 0.0722f)/0xff;
|
||||
}
|
||||
|
||||
public void addObserver(ColorObserver observer) {
|
||||
observers.add(observer);
|
||||
}
|
||||
|
||||
public void updateHueSat(float hue, float sat, ColorObserver sender) {
|
||||
hsv[0] = hue;
|
||||
hsv[1] = sat;
|
||||
notifyOtherObservers(sender);
|
||||
}
|
||||
|
||||
public void updateValue(float value, ColorObserver sender) {
|
||||
hsv[2] = value;
|
||||
notifyOtherObservers(sender);
|
||||
}
|
||||
|
||||
public void updateAlpha(int alpha, ColorObserver sender) {
|
||||
this.alpha = alpha;
|
||||
notifyOtherObservers(sender);
|
||||
}
|
||||
|
||||
public void updateColor(int color, ColorObserver sender) {
|
||||
Color.colorToHSV(color, hsv);
|
||||
alpha = Color.alpha(color);
|
||||
notifyOtherObservers(sender);
|
||||
}
|
||||
|
||||
public void updateColor(int alpha, float hue, float sat, float bri, ColorObserver sender) {
|
||||
this.alpha = alpha;
|
||||
hsv[0] = hue;
|
||||
hsv[1] = sat;
|
||||
hsv[2] = bri;
|
||||
notifyOtherObservers(sender);
|
||||
}
|
||||
|
||||
private void notifyOtherObservers(ColorObserver sender) {
|
||||
for (ColorObserver observer : observers) {
|
||||
if (observer != sender) {
|
||||
observer.updateColor(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
package ru.vtosters.lite.ui.views.rarepebble;
|
||||
|
||||
import static ru.vtosters.lite.utils.AndroidUtils.dp2px;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.BitmapFactory;
|
||||
import android.graphics.BitmapShader;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.Path;
|
||||
import android.graphics.Shader;
|
||||
|
||||
import ru.vtosters.lite.utils.AndroidUtils;
|
||||
|
||||
class Resources {
|
||||
|
||||
private static final float LINE_WIDTH_DIP = 1.5f;
|
||||
private static final float POINTER_RADIUS_DIP = 7;
|
||||
private static final int VIEW_OUTLINE_COLOR = 0xff808080;
|
||||
|
||||
public static Paint makeLinePaint() {
|
||||
Paint paint = new Paint();
|
||||
paint.setColor(VIEW_OUTLINE_COLOR);
|
||||
paint.setStrokeWidth(dp2px(LINE_WIDTH_DIP));
|
||||
paint.setStyle(Paint.Style.STROKE);
|
||||
paint.setAntiAlias(true);
|
||||
return paint;
|
||||
}
|
||||
|
||||
public static Paint makeCheckerPaint(Context context) {
|
||||
Paint paint = new Paint();
|
||||
final Bitmap checkerBmp = BitmapFactory.decodeResource(context.getResources(), AndroidUtils.getIdentifier("checker_background", "drawable"));
|
||||
paint.setShader(new BitmapShader(checkerBmp, Shader.TileMode.REPEAT, Shader.TileMode.REPEAT));
|
||||
paint.setStrokeWidth(dp2px(LINE_WIDTH_DIP));
|
||||
paint.setStyle(Paint.Style.FILL);
|
||||
paint.setAntiAlias(true);
|
||||
return paint;
|
||||
}
|
||||
|
||||
public static Path makePointerPath() {
|
||||
Path pointerPath = new Path();
|
||||
final float radiusPx = dp2px(POINTER_RADIUS_DIP);
|
||||
pointerPath.addCircle(0, 0, radiusPx, Path.Direction.CW);
|
||||
return pointerPath;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
package ru.vtosters.lite.ui.views.rarepebble;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.Path;
|
||||
import android.graphics.Rect;
|
||||
import android.graphics.RectF;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
|
||||
public abstract class SliderViewBase extends View {
|
||||
|
||||
private final Paint borderPaint;
|
||||
private final Paint checkerPaint;
|
||||
private final Rect viewRect = new Rect();
|
||||
private int w;
|
||||
private int h;
|
||||
private final Path borderPath;
|
||||
private Bitmap bitmap;
|
||||
private final Path pointerPath;
|
||||
private final Paint pointerPaint;
|
||||
|
||||
private float currentPos;
|
||||
|
||||
public SliderViewBase(Context context) {
|
||||
super(context);
|
||||
checkerPaint = Resources.makeCheckerPaint(context);
|
||||
borderPaint = Resources.makeLinePaint();
|
||||
pointerPaint = Resources.makeLinePaint();
|
||||
pointerPath = Resources.makePointerPath();
|
||||
borderPath = new Path();
|
||||
}
|
||||
|
||||
protected abstract void notifyListener(float currentPos);
|
||||
|
||||
protected abstract Bitmap makeBitmap(int w, int h);
|
||||
|
||||
protected abstract int getPointerColor(float currentPos);
|
||||
|
||||
@Override
|
||||
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
|
||||
this.w = w;
|
||||
this.h = h;
|
||||
viewRect.set(0, 0, w, h);
|
||||
float inset = borderPaint.getStrokeWidth() / 2;
|
||||
borderPath.reset();
|
||||
borderPath.addRect(new RectF(inset, inset, w - inset, h - inset), Path.Direction.CW);
|
||||
updateBitmap();
|
||||
}
|
||||
|
||||
protected void setPos(float pos) {
|
||||
currentPos = pos;
|
||||
optimisePointerColor();
|
||||
}
|
||||
|
||||
protected void updateBitmap() {
|
||||
if (w > 0 && h > 0) {
|
||||
bitmap = makeBitmap(w, h);
|
||||
optimisePointerColor();
|
||||
}
|
||||
// else not ready yet
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onTouchEvent(MotionEvent event) {
|
||||
int action = event.getActionMasked();
|
||||
switch (action) {
|
||||
case MotionEvent.ACTION_DOWN:
|
||||
case MotionEvent.ACTION_MOVE:
|
||||
currentPos = valueForTouchPos(event.getX(), event.getY());
|
||||
optimisePointerColor();
|
||||
notifyListener(currentPos);
|
||||
invalidate();
|
||||
getParent().requestDisallowInterceptTouchEvent(true);
|
||||
return true;
|
||||
}
|
||||
return super.onTouchEvent(event);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDraw(Canvas canvas) {
|
||||
super.onDraw(canvas);
|
||||
canvas.drawPath(borderPath, checkerPaint);
|
||||
canvas.drawBitmap(bitmap, null, viewRect, null);
|
||||
canvas.drawPath(borderPath, borderPaint);
|
||||
|
||||
canvas.save();
|
||||
if (isWide()) {
|
||||
canvas.translate(w * currentPos, h / 2);
|
||||
}
|
||||
else {
|
||||
canvas.translate(w / 2, h * (1 - currentPos));
|
||||
}
|
||||
canvas.drawPath(pointerPath, pointerPaint);
|
||||
canvas.restore();
|
||||
}
|
||||
|
||||
private boolean isWide() {
|
||||
return w > h;
|
||||
}
|
||||
|
||||
private float valueForTouchPos(float x, float y) {
|
||||
final float val = isWide() ? x / w : 1 - y / h;
|
||||
return Math.max(0, Math.min(1, val));
|
||||
}
|
||||
|
||||
private void optimisePointerColor() {
|
||||
pointerPaint.setColor(getPointerColor(currentPos));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
package ru.vtosters.lite.ui.views.rarepebble;
|
||||
|
||||
import static ru.vtosters.lite.utils.AndroidUtils.dp2px;
|
||||
|
||||
import android.content.Context;
|
||||
import android.view.View;
|
||||
|
||||
class SquareView extends View {
|
||||
private static final int MIN_SIZE_DIP = 200;
|
||||
|
||||
private final int minSizePx;
|
||||
|
||||
public SquareView(Context context) {
|
||||
super(context);
|
||||
minSizePx = dp2px(MIN_SIZE_DIP);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
|
||||
// Constrain to square
|
||||
final int w = MeasureSpec.getSize(widthMeasureSpec);
|
||||
final int h = MeasureSpec.getSize(heightMeasureSpec);
|
||||
final int modeW = MeasureSpec.getMode(widthMeasureSpec);
|
||||
final int modeH = MeasureSpec.getMode(heightMeasureSpec);
|
||||
int size = minSizePx;
|
||||
if (modeW == MeasureSpec.UNSPECIFIED) {
|
||||
size = h;
|
||||
}
|
||||
else if (modeH == MeasureSpec.UNSPECIFIED) {
|
||||
size = w;
|
||||
}
|
||||
else {
|
||||
size = Math.min(w, h);
|
||||
}
|
||||
size = Math.max(size, minSizePx);
|
||||
setMeasuredDimension(size, size);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,125 @@
|
||||
package ru.vtosters.lite.ui.views.rarepebble;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.Path;
|
||||
import android.graphics.RectF;
|
||||
|
||||
import ru.vtosters.lite.utils.AndroidUtils;
|
||||
|
||||
public class SwatchView extends SquareView implements ColorObserver {
|
||||
|
||||
private final Paint borderPaint;
|
||||
private final Path borderPath;
|
||||
private final Paint checkerPaint;
|
||||
private final Path oldFillPath;
|
||||
private final Path newFillPath;
|
||||
private final Paint oldFillPaint;
|
||||
private final Paint newFillPaint;
|
||||
private final float radialMarginPx;
|
||||
|
||||
public SwatchView(Context context) {
|
||||
super(context);
|
||||
|
||||
radialMarginPx = AndroidUtils.dp2px(16);
|
||||
|
||||
borderPaint = Resources.makeLinePaint();
|
||||
checkerPaint = Resources.makeCheckerPaint(context);
|
||||
oldFillPaint = new Paint();
|
||||
newFillPaint = new Paint();
|
||||
|
||||
borderPath = new Path();
|
||||
oldFillPath = new Path();
|
||||
newFillPath = new Path();
|
||||
}
|
||||
|
||||
void setOriginalColor(int color) {
|
||||
oldFillPaint.setColor(color);
|
||||
invalidate();
|
||||
}
|
||||
|
||||
void observeColor(ObservableColor observableColor) {
|
||||
observableColor.addObserver(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateColor(ObservableColor observableColor) {
|
||||
newFillPaint.setColor(observableColor.getColor());
|
||||
invalidate();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
|
||||
// We expect to be stacked behind a square HueSatView and fill the top left corner.
|
||||
|
||||
final float inset = borderPaint.getStrokeWidth() / 2;
|
||||
final float r = Math.min(w, h);
|
||||
|
||||
// Trim the corners of the swatch is where it is radialMarginPx thick.
|
||||
// find how long the outeredges are:
|
||||
final float margin = radialMarginPx;
|
||||
final float diagonal = r + 2 * margin;
|
||||
final float opp = (float)Math.sqrt(diagonal * diagonal - r * r);
|
||||
final float edgeLen = r - opp;
|
||||
|
||||
// Arc angles for drawing CCW
|
||||
final float outerAngle = (float)Math.toDegrees(Math.atan2(opp, r));
|
||||
final float startAngle = 270 - outerAngle;
|
||||
// Sweep angle for each half of the swatch:
|
||||
final float sweepAngle = outerAngle - 45;
|
||||
// Sweep angle for the smooth corners:
|
||||
final float cornerSweepAngle = 90 - outerAngle;
|
||||
|
||||
// Outer border:
|
||||
beginBorder(borderPath, inset, edgeLen, margin, cornerSweepAngle);
|
||||
mainArc(borderPath, r, margin, startAngle, 2 * sweepAngle);
|
||||
endBorder(borderPath, inset, edgeLen, margin, cornerSweepAngle);
|
||||
|
||||
// Old fill shape:
|
||||
oldFillPath.reset();
|
||||
oldFillPath.moveTo(inset, inset);
|
||||
mainArc(oldFillPath, r, margin, 225, sweepAngle);
|
||||
endBorder(oldFillPath, inset, edgeLen, margin, cornerSweepAngle);
|
||||
|
||||
// New fill shape:
|
||||
beginBorder(newFillPath, inset, edgeLen, margin, cornerSweepAngle);
|
||||
mainArc(newFillPath, r, margin, startAngle, sweepAngle);
|
||||
newFillPath.lineTo(inset, inset);
|
||||
newFillPath.close();
|
||||
}
|
||||
|
||||
private static void beginBorder(Path path, float inset, float edgeLen, float cornerRadius, float cornerSweepAngle) {
|
||||
path.reset();
|
||||
path.moveTo(inset, inset);
|
||||
cornerArc(path, edgeLen, inset, cornerRadius-inset, 0, cornerSweepAngle);
|
||||
}
|
||||
|
||||
private static void endBorder(Path path, float inset, float edgeLen, float cornerRadius, float cornerSweepAngle) {
|
||||
cornerArc(path, inset, edgeLen, cornerRadius - inset, 90 - cornerSweepAngle, cornerSweepAngle);
|
||||
path.lineTo(inset, inset);
|
||||
path.close();
|
||||
}
|
||||
|
||||
private static void cornerArc(Path path, float cx, float cy, float r, float startAngle, float sweepAngle) {
|
||||
final RectF ovalRect = new RectF(-r, -r, r, r);
|
||||
ovalRect.offset(cx, cy);
|
||||
path.arcTo(ovalRect, startAngle, sweepAngle);
|
||||
}
|
||||
|
||||
private static void mainArc(Path path, float viewSize, float margin, float startAngle, float sweepAngle) {
|
||||
float r = viewSize + margin;
|
||||
final RectF ovalRect = new RectF(-r, -r, r, r);
|
||||
ovalRect.offset(viewSize, viewSize);
|
||||
path.arcTo(ovalRect, startAngle, sweepAngle);
|
||||
}
|
||||
|
||||
protected void onDraw(Canvas canvas) {
|
||||
super.onDraw(canvas);
|
||||
canvas.drawPath(borderPath, checkerPaint);
|
||||
canvas.drawPath(oldFillPath, oldFillPaint);
|
||||
canvas.drawPath(newFillPath, newFillPaint);
|
||||
canvas.drawPath(borderPath, borderPaint);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
package ru.vtosters.lite.ui.views.rarepebble;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.Color;
|
||||
|
||||
public class ValueView extends SliderViewBase implements ColorObserver {
|
||||
|
||||
private ObservableColor observableColor = new ObservableColor(0);
|
||||
|
||||
public ValueView(Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
public void observeColor(ObservableColor observableColor) {
|
||||
this.observableColor = observableColor;
|
||||
observableColor.addObserver(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateColor(ObservableColor observableColor) {
|
||||
setPos(this.observableColor.getValue());
|
||||
updateBitmap();
|
||||
invalidate();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void notifyListener(float currentPos) {
|
||||
observableColor.updateValue(currentPos, this);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int getPointerColor(float currentPos) {
|
||||
float brightColorLightness = observableColor.getLightness();
|
||||
float posLightness = currentPos * brightColorLightness;
|
||||
return posLightness > 0.5f ? 0xff000000 : 0xffffffff;
|
||||
}
|
||||
|
||||
protected Bitmap makeBitmap(int w, int h) {
|
||||
final boolean isWide = w > h;
|
||||
final int n = Math.max(w, h);
|
||||
int[] colors = new int[n];
|
||||
|
||||
float[] hsv = new float[]{0, 0, 0};
|
||||
observableColor.getHsv(hsv);
|
||||
|
||||
for (int i = 0; i < n; ++i) {
|
||||
hsv[2] = isWide ? (float)i / n : 1 - (float)i / n;
|
||||
colors[i] = Color.HSVToColor(hsv);
|
||||
}
|
||||
final int bmpWidth = isWide ? w : 1;
|
||||
final int bmpHeight = isWide ? 1 : h;
|
||||
return Bitmap.createBitmap(colors, bmpWidth, bmpHeight, Bitmap.Config.ARGB_8888);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,76 +1,55 @@
|
||||
package ru.vtosters.lite.ui.vkui;
|
||||
|
||||
import static ru.vtosters.lite.utils.AndroidUtils.dp2px;
|
||||
import static ru.vtosters.lite.utils.AndroidUtils.getGlobalContext;
|
||||
import static ru.vtosters.lite.utils.AndroidUtils.getString;
|
||||
import static ru.vtosters.lite.utils.ThemesUtils.getTextAttr;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.app.Activity;
|
||||
import android.view.View;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.vk.core.dialogs.bottomsheet.ModalBottomSheet;
|
||||
|
||||
public class ModalBottomSheetWrapper {
|
||||
public final ModalBottomSheet.a builder;
|
||||
public class ModalBottomSheetWrapper<T> {
|
||||
public final ModalBottomSheet.a mBuilder;
|
||||
public ModalBottomSheet mBottomSheet;
|
||||
|
||||
public ModalBottomSheetWrapper(Activity activity) {
|
||||
builder = new ModalBottomSheet.a(activity);
|
||||
mBuilder = new ModalBottomSheet.a(activity);
|
||||
}
|
||||
|
||||
public ModalBottomSheetWrapper setTitle(String title) {
|
||||
builder.d(title);
|
||||
return this;
|
||||
public T setTitle(String title) {
|
||||
mBuilder.d(title);
|
||||
return (T) this;
|
||||
}
|
||||
|
||||
@SuppressLint("SetTextI18n")
|
||||
public ModalBottomSheetWrapper setUpdateInfoView(String changelog) {
|
||||
var context = getGlobalContext();
|
||||
var container = new LinearLayout(context);
|
||||
var changelogView = new TextView(context);
|
||||
|
||||
container.setOrientation(LinearLayout.VERTICAL);
|
||||
|
||||
changelogView.setPadding(0, dp2px(8), 0, 0);
|
||||
changelogView.setText(getString("changelog") + ": \n" + changelog);
|
||||
changelogView.setTextColor(getTextAttr());
|
||||
container.addView(changelogView, new LinearLayout.LayoutParams(-1, -2));
|
||||
|
||||
builder.a(container);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public ModalBottomSheetWrapper setClickableMessage(
|
||||
public T setClickableMessage(
|
||||
String text,
|
||||
Runnable callback
|
||||
) {
|
||||
builder.a(text, listener -> callback.run());
|
||||
return this;
|
||||
mBuilder.a(text, listener -> callback.run());
|
||||
return (T) this;
|
||||
}
|
||||
|
||||
public ModalBottomSheetWrapper setPositiveButton(
|
||||
public T setPositiveButton(
|
||||
String text,
|
||||
Runnable callback
|
||||
) {
|
||||
builder.a(text, kostil -> callback.run());
|
||||
return this;
|
||||
mBuilder.a(text, kostil -> callback.run());
|
||||
return (T) this;
|
||||
}
|
||||
|
||||
public ModalBottomSheetWrapper setView(View view) {
|
||||
builder.a(view);
|
||||
return this;
|
||||
public T setView(View view) {
|
||||
mBuilder.a(view);
|
||||
return (T) this;
|
||||
}
|
||||
|
||||
public ModalBottomSheet show() {
|
||||
return builder.a("");
|
||||
public ModalBottomSheet show(String tag) {
|
||||
return mBuilder.a(tag);
|
||||
}
|
||||
|
||||
public ModalBottomSheet show(boolean isCancellable) {
|
||||
var sheet = builder.a("");
|
||||
sheet.setCancelable(isCancellable);
|
||||
return sheet;
|
||||
public ModalBottomSheet show(String tag, boolean isCancellable) {
|
||||
mBottomSheet = mBuilder.a(tag);
|
||||
mBottomSheet.setCancelable(isCancellable);
|
||||
return mBottomSheet;
|
||||
}
|
||||
|
||||
public void dismiss() {
|
||||
mBottomSheet.dismiss();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ import android.view.View;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
public class VBottomSheetBuilder {
|
||||
public static void show(Activity activity, VBSContent content) {
|
||||
public static void show(Activity activity, VBSContent content, String tag) {
|
||||
ModalBottomSheetWrapper builder = new ModalBottomSheetWrapper(activity);
|
||||
|
||||
builder.setView(content.view);
|
||||
@@ -20,7 +20,7 @@ public class VBottomSheetBuilder {
|
||||
new Handler(Looper.getMainLooper()).post(content.button.action);
|
||||
}); // Positive
|
||||
|
||||
builder.show(); // Build+Show
|
||||
builder.show(tag); // Build+Show
|
||||
}
|
||||
|
||||
public static class VBSContent {
|
||||
|
||||
@@ -17,40 +17,12 @@ import java.util.List;
|
||||
public class LifecycleUtils {
|
||||
private static final List<Activity> activities = new ArrayList<>();
|
||||
|
||||
public static void registerActivities(Application application) {
|
||||
application.registerActivityLifecycleCallbacks(new Application.ActivityLifecycleCallbacks() {
|
||||
@Override // android.app.Application.ActivityLifecycleCallbacks
|
||||
public void onActivityPaused(Activity activity) {
|
||||
}
|
||||
public static void addActivity(Activity activity) {
|
||||
activities.add(activity);
|
||||
}
|
||||
|
||||
@Override // android.app.Application.ActivityLifecycleCallbacks
|
||||
public void onActivityResumed(Activity activity) {
|
||||
}
|
||||
|
||||
@Override // android.app.Application.ActivityLifecycleCallbacks
|
||||
public void onActivitySaveInstanceState(Activity activity, Bundle bundle) {
|
||||
}
|
||||
|
||||
@Override // android.app.Application.ActivityLifecycleCallbacks
|
||||
public void onActivityStarted(Activity activity) {
|
||||
}
|
||||
|
||||
@Override // android.app.Application.ActivityLifecycleCallbacks
|
||||
public void onActivityStopped(Activity activity) {
|
||||
}
|
||||
|
||||
@Override // android.app.Application.ActivityLifecycleCallbacks
|
||||
public void onActivityCreated(Activity activity, Bundle bundle) {
|
||||
if (activity instanceof VKActivity) {
|
||||
activities.add(activity);
|
||||
}
|
||||
}
|
||||
|
||||
@Override // android.app.Application.ActivityLifecycleCallbacks
|
||||
public void onActivityDestroyed(Activity activity) {
|
||||
activities.remove(activity);
|
||||
}
|
||||
});
|
||||
public static void removeActivity(Activity activity) {
|
||||
activities.remove(activity);
|
||||
}
|
||||
|
||||
public static Activity getCurrentActivity() {
|
||||
|
||||
@@ -32,7 +32,6 @@ public class Preferences {
|
||||
GmsUtils.fixGapps();
|
||||
setProxy();
|
||||
reloadMessagesList();
|
||||
LifecycleUtils.registerActivities(application);
|
||||
getInstance().autoCleaningCache();
|
||||
} // VK Init
|
||||
|
||||
|
||||
@@ -11,46 +11,83 @@ public class ReflectionUtils {
|
||||
* Gets a class with specific name.
|
||||
* Supports smali type (Lpackage/Class;)
|
||||
*/
|
||||
public static Class<?> getClass(String name) throws ClassNotFoundException {
|
||||
public static Class<?> getClass(String name)
|
||||
throws ClassNotFoundException {
|
||||
if (name.contains(";"))
|
||||
name = name.substring(1, name.length() - 1).replace("/", ".");
|
||||
return Class.forName(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a declared field with specific name.
|
||||
**/
|
||||
public static Field getDeclaredField(Class<?> clz, String fieldName) throws NoSuchFieldException {
|
||||
public static Field getDeclaredField(Class<?> clz, String fieldName)
|
||||
throws NoSuchFieldException {
|
||||
Field field = clz.getDeclaredField(fieldName);
|
||||
field.setAccessible(true);
|
||||
return field;
|
||||
}
|
||||
|
||||
public static Field getDeclaredField(String className, String fieldName) throws NoSuchFieldException, ClassNotFoundException {
|
||||
Field field = getClass(className).getDeclaredField(fieldName);
|
||||
field.setAccessible(true);
|
||||
return field;
|
||||
public static Field getDeclaredField(String className, String fieldName)
|
||||
throws NoSuchFieldException, ClassNotFoundException {
|
||||
return getDeclaredField(getClass(className), fieldName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a field with specific name.
|
||||
**/
|
||||
public static Field getField(Class<?> clz, String fieldName) throws NoSuchFieldException {
|
||||
public static Field getDeclaredFieldRecursive(Class<?> clz, String fieldName)
|
||||
throws NoSuchFieldException {
|
||||
Field field = null;
|
||||
Class<?> clazz = clz;
|
||||
while (clazz != null) {
|
||||
try {
|
||||
field = getDeclaredField(clazz, fieldName);
|
||||
break;
|
||||
} catch (NoSuchFieldException e) {
|
||||
clazz = clz.getSuperclass();
|
||||
}
|
||||
}
|
||||
if (field != null)
|
||||
return field;
|
||||
throw new NoSuchFieldException(fieldName);
|
||||
}
|
||||
|
||||
public static Field getDeclaredFieldRecursive(String className, String fieldName)
|
||||
throws ClassNotFoundException, NoSuchFieldException {
|
||||
return getDeclaredFieldRecursive(getClass(className), fieldName);
|
||||
}
|
||||
|
||||
public static Field getField(Class<?> clz, String fieldName)
|
||||
throws NoSuchFieldException {
|
||||
Field field = clz.getField(fieldName);
|
||||
field.setAccessible(true);
|
||||
return field;
|
||||
}
|
||||
|
||||
public static Field getField(String className, String fieldName) throws NoSuchFieldException, ClassNotFoundException {
|
||||
Field field = getClass(className).getField(fieldName);
|
||||
field.setAccessible(true);
|
||||
return field;
|
||||
public static Field getField(String className, String fieldName)
|
||||
throws NoSuchFieldException, ClassNotFoundException {
|
||||
return getField(getClass(className), fieldName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a non-static field value
|
||||
*/
|
||||
public static <T> T getObjectField(Class<?> clz, String fieldName, Object instance) throws NoSuchFieldException, IllegalAccessException {
|
||||
public static Field getFieldRecursive(Class<?> clz, String fieldName)
|
||||
throws NoSuchFieldException {
|
||||
Field field = null;
|
||||
Class<?> clazz = clz;
|
||||
while (clazz != null) {
|
||||
try {
|
||||
field = getField(clazz, fieldName);
|
||||
break;
|
||||
} catch (NoSuchFieldException e) {
|
||||
clazz = clz.getSuperclass();
|
||||
}
|
||||
}
|
||||
if (field != null)
|
||||
return field;
|
||||
throw new NoSuchFieldException(fieldName);
|
||||
}
|
||||
|
||||
public static Field getFieldRecursive(String className, String fieldName)
|
||||
throws ClassNotFoundException, NoSuchFieldException {
|
||||
return getFieldRecursive(getClass(className), fieldName);
|
||||
}
|
||||
|
||||
public static <T> T getObjectField(Class<?> clz, String fieldName, Object instance)
|
||||
throws NoSuchFieldException, IllegalAccessException {
|
||||
Field field;
|
||||
try {
|
||||
field = getField(clz, fieldName);
|
||||
@@ -60,43 +97,50 @@ public class ReflectionUtils {
|
||||
return (T) field.get(instance);
|
||||
}
|
||||
|
||||
public static <T> T getObjectField(String className, String fieldName, Object instance) throws NoSuchFieldException, IllegalAccessException, ClassNotFoundException {
|
||||
Field field;
|
||||
try {
|
||||
field = getField(className, fieldName);
|
||||
} catch (NoSuchFieldException e) {
|
||||
field = getDeclaredField(className, fieldName);
|
||||
}
|
||||
return (T) field.get(instance);
|
||||
public static <T> T getObjectField(String className, String fieldName, Object instance)
|
||||
throws NoSuchFieldException, IllegalAccessException, ClassNotFoundException {
|
||||
return getObjectField(getClass(className), fieldName, instance);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a value for non-static field
|
||||
*/
|
||||
public static void setObjectField(Class<?> clz, String fieldName, Object instance, Object value) throws NoSuchFieldException, IllegalAccessException {
|
||||
public static void setObjectField(Class<?> clz, String fieldName, Object instance, Object value)
|
||||
throws NoSuchFieldException, IllegalAccessException {
|
||||
Field field;
|
||||
try {
|
||||
field = getField(clz, fieldName);
|
||||
} catch (NoSuchFieldException e) {
|
||||
field = getDeclaredField(clz, fieldName);
|
||||
}
|
||||
unfinalField(field);
|
||||
field.set(instance, value);
|
||||
}
|
||||
|
||||
public static void setObjectField(String className, String fieldName, Object instance, Object value) throws NoSuchFieldException, IllegalAccessException, ClassNotFoundException {
|
||||
Field field;
|
||||
try {
|
||||
field = getField(className, fieldName);
|
||||
} catch (NoSuchFieldException e) {
|
||||
field = getDeclaredField(className, fieldName);
|
||||
}
|
||||
field.set(instance, value);
|
||||
public static void setObjectField(String className, String fieldName, Object instance, Object value)
|
||||
throws NoSuchFieldException, IllegalAccessException, ClassNotFoundException {
|
||||
setObjectField(getClass(className), fieldName, instance, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Unlocks modifying of field
|
||||
*/
|
||||
private static void unlockField(Field field) throws NoSuchFieldException {
|
||||
public static <T> T getObjectStaticField(Class<?> clz, String fieldName)
|
||||
throws NoSuchFieldException, IllegalAccessException {
|
||||
return getObjectField(clz, fieldName, null);
|
||||
}
|
||||
|
||||
public static <T> T getObjectStaticField(String className, String fieldName)
|
||||
throws NoSuchFieldException, IllegalAccessException, ClassNotFoundException {
|
||||
return getObjectField(getClass(className), fieldName, null);
|
||||
}
|
||||
|
||||
public static void setObjectStaticField(Class<?> clz, String fieldName, Object value)
|
||||
throws NoSuchFieldException, IllegalAccessException {
|
||||
setObjectField(clz, fieldName, null, value);
|
||||
}
|
||||
|
||||
public static void setObjectStaticField(String className, String fieldName, Object value)
|
||||
throws NoSuchFieldException, IllegalAccessException, ClassNotFoundException {
|
||||
setObjectStaticField(getClass(className), fieldName, value);
|
||||
}
|
||||
|
||||
public static void unfinalField(Field field) throws NoSuchFieldException {
|
||||
if (!Modifier.isFinal(field.getModifiers())) return;
|
||||
Field accessFlagsFld = getDeclaredField(Field.class, "accessFlags");
|
||||
try {
|
||||
int accessFlags = accessFlagsFld.getInt(field);
|
||||
@@ -106,178 +150,60 @@ public class ReflectionUtils {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Unlocks modifying of non-static field and sets a value for it
|
||||
*/
|
||||
public static void setObjectFinalField(Class<?> clz, String fieldName, Object instance, Object value) throws NoSuchFieldException, IllegalAccessException {
|
||||
Field field;
|
||||
try {
|
||||
field = getField(clz, fieldName);
|
||||
} catch (NoSuchFieldException e) {
|
||||
field = getDeclaredField(clz, fieldName);
|
||||
}
|
||||
|
||||
if (!Modifier.isFinal(field.getModifiers())) return;
|
||||
|
||||
unlockField(field);
|
||||
field.set(instance, value);
|
||||
}
|
||||
|
||||
public static void setObjectFinalField(String className, String fieldName, Object instance, Object value) throws NoSuchFieldException, IllegalAccessException, ClassNotFoundException {
|
||||
Field field;
|
||||
try {
|
||||
field = getField(className, fieldName);
|
||||
} catch (NoSuchFieldException e) {
|
||||
field = getDeclaredField(className, fieldName);
|
||||
}
|
||||
|
||||
if (!Modifier.isFinal(field.getModifiers())) return;
|
||||
|
||||
unlockField(field);
|
||||
field.set(instance, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a value for static field
|
||||
*/
|
||||
public static void setObjectStaticField(Class<?> clz, String fieldName, Object value) throws NoSuchFieldException, IllegalAccessException {
|
||||
Field field;
|
||||
try {
|
||||
field = getField(clz, fieldName);
|
||||
} catch (NoSuchFieldException e) {
|
||||
field = getDeclaredField(clz, fieldName);
|
||||
}
|
||||
field.set(null, value);
|
||||
}
|
||||
|
||||
public static void setObjectStaticField(String className, String fieldName, Object value) throws NoSuchFieldException, IllegalAccessException, ClassNotFoundException {
|
||||
Field field;
|
||||
try {
|
||||
field = getField(className, fieldName);
|
||||
} catch (NoSuchFieldException e) {
|
||||
field = getDeclaredField(className, fieldName);
|
||||
}
|
||||
field.set(null, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Unlocks modifying of non-static field and sets a value for it
|
||||
*/
|
||||
public static void setObjectFinalStaticField(Class<?> clz, String fieldName, Object value) throws NoSuchFieldException, IllegalAccessException {
|
||||
Field field;
|
||||
try {
|
||||
field = getField(clz, fieldName);
|
||||
} catch (NoSuchFieldException e) {
|
||||
field = getDeclaredField(clz, fieldName);
|
||||
}
|
||||
|
||||
if (!Modifier.isFinal(field.getModifiers())) return;
|
||||
|
||||
unlockField(field);
|
||||
field.set(null, value);
|
||||
}
|
||||
|
||||
public static void setObjectFinalStaticField(String className, String fieldName, Object value) throws NoSuchFieldException, IllegalAccessException, ClassNotFoundException {
|
||||
Field field;
|
||||
try {
|
||||
field = getField(className, fieldName);
|
||||
} catch (NoSuchFieldException e) {
|
||||
field = getDeclaredField(className, fieldName);
|
||||
}
|
||||
|
||||
if (!Modifier.isFinal(field.getModifiers())) return;
|
||||
|
||||
unlockField(field);
|
||||
field.set(null, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a static field value
|
||||
*/
|
||||
public static <T> T getObjectField(Class<?> clz, String fieldName) throws NoSuchFieldException, IllegalAccessException {
|
||||
Field field;
|
||||
try {
|
||||
field = getField(clz, fieldName);
|
||||
} catch (NoSuchFieldException e) {
|
||||
field = getDeclaredField(clz, fieldName);
|
||||
}
|
||||
return (T) field.get(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a method with specific name.
|
||||
**/
|
||||
public static Method getMethod(Class<?> clz, String methodName, Class<?>... parameterTypes) throws NoSuchMethodException {
|
||||
public static Method getMethod(Class<?> clz, String methodName, Class<?>... parameterTypes)
|
||||
throws NoSuchMethodException {
|
||||
Method method = clz.getMethod(methodName, parameterTypes);
|
||||
method.setAccessible(true);
|
||||
return method;
|
||||
}
|
||||
|
||||
public static Method getMethod(String className, String methodName, Class<?>... parameterTypes) throws ClassNotFoundException, NoSuchMethodException {
|
||||
Method method = getClass(className).getMethod(methodName, parameterTypes);
|
||||
method.setAccessible(true);
|
||||
return method;
|
||||
public static Method getMethod(String className, String methodName, Class<?>... parameterTypes)
|
||||
throws ClassNotFoundException, NoSuchMethodException {
|
||||
return getMethod(getClass(className), methodName, parameterTypes);
|
||||
}
|
||||
|
||||
public static Method getMethod(Class<?> clz, String methodName, Object... parameters) throws NoSuchMethodException {
|
||||
public static Method getMethod(Class<?> clz, String methodName, Object... parameters)
|
||||
throws NoSuchMethodException {
|
||||
Class<?>[] parameterTypes = new Class<?>[parameters.length];
|
||||
for (int i = 0; i < parameters.length; i++) {
|
||||
parameterTypes[i] = parameters[i].getClass();
|
||||
}
|
||||
Method method = getMethod(clz, methodName, parameterTypes);
|
||||
method.setAccessible(true);
|
||||
return method;
|
||||
return getMethod(clz, methodName, parameterTypes);
|
||||
}
|
||||
|
||||
public static Method getMethod(String className, String methodName, Object... parameters) throws ClassNotFoundException, NoSuchMethodException {
|
||||
Class<?>[] parameterTypes = new Class<?>[parameters.length];
|
||||
for (int i = 0; i < parameters.length; i++) {
|
||||
parameterTypes[i] = parameters[i].getClass();
|
||||
}
|
||||
Method method = getMethod(className, methodName, parameterTypes);
|
||||
method.setAccessible(true);
|
||||
return method;
|
||||
public static Method getMethod(String className, String methodName, Object... parameters)
|
||||
throws ClassNotFoundException, NoSuchMethodException {
|
||||
return getMethod(getClass(className), methodName, parameters);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a declared method with specific name.
|
||||
**/
|
||||
public static Method getDeclaredMethod(Class<?> clz, String methodName, Class<?>... parameterTypes) throws NoSuchMethodException {
|
||||
public static Method getDeclaredMethod(Class<?> clz, String methodName, Class<?>... parameterTypes)
|
||||
throws NoSuchMethodException {
|
||||
Method method = clz.getDeclaredMethod(methodName, parameterTypes);
|
||||
method.setAccessible(true);
|
||||
return method;
|
||||
}
|
||||
|
||||
public static Method getDeclaredMethod(String className, String methodName, Class<?>... parameterTypes) throws ClassNotFoundException, NoSuchMethodException {
|
||||
Method method = getClass(className).getDeclaredMethod(methodName, parameterTypes);
|
||||
method.setAccessible(true);
|
||||
return method;
|
||||
public static Method getDeclaredMethod(String className, String methodName, Class<?>... parameterTypes)
|
||||
throws ClassNotFoundException, NoSuchMethodException {
|
||||
return getDeclaredMethod(getClass(className), methodName, parameterTypes);
|
||||
}
|
||||
|
||||
public static Method getDeclaredMethod(Class<?> clz, String methodName, Object... parameters) throws NoSuchMethodException {
|
||||
public static Method getDeclaredMethod(Class<?> clz, String methodName, Object... parameters)
|
||||
throws NoSuchMethodException {
|
||||
Class<?>[] parameterTypes = new Class<?>[parameters.length];
|
||||
for (int i = 0; i < parameters.length; i++) {
|
||||
parameterTypes[i] = parameters[i].getClass();
|
||||
}
|
||||
Method method = getDeclaredMethod(clz, methodName, parameterTypes);
|
||||
method.setAccessible(true);
|
||||
return method;
|
||||
return getDeclaredMethod(clz, methodName, parameterTypes);
|
||||
}
|
||||
|
||||
public static Method getDeclaredMethod(String className, String methodName, Object... parameters) throws ClassNotFoundException, NoSuchMethodException {
|
||||
Class<?>[] parameterTypes = new Class<?>[parameters.length];
|
||||
for (int i = 0; i < parameters.length; i++) {
|
||||
parameterTypes[i] = parameters[i].getClass();
|
||||
}
|
||||
Method method = getDeclaredMethod(className, methodName, parameterTypes);
|
||||
method.setAccessible(true);
|
||||
return method;
|
||||
public static Method getDeclaredMethod(String className, String methodName, Object... parameters)
|
||||
throws ClassNotFoundException, NoSuchMethodException {
|
||||
return getDeclaredMethod(getClass(className), methodName, parameters);
|
||||
}
|
||||
|
||||
/**
|
||||
* Invokes a non-static method
|
||||
*/
|
||||
public static <T> T invokeMethod(Class<?> clz, String methodName, Object instance, Class<?>[] parameterTypes, Object... parameters) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
|
||||
public static <T> T invokeMethod(Class<?> clz, String methodName, Object instance, Class<?>[] parameterTypes, Object... parameters)
|
||||
throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
|
||||
Method method;
|
||||
try {
|
||||
method = getMethod(clz, methodName, parameterTypes);
|
||||
@@ -287,92 +213,42 @@ public class ReflectionUtils {
|
||||
return (T) method.invoke(instance, parameters);
|
||||
}
|
||||
|
||||
public static <T> T invokeMethod(String className, String methodName, Object instance, Class<?>[] parameterTypes, Object... parameters) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException, ClassNotFoundException {
|
||||
Method method;
|
||||
try {
|
||||
method = getMethod(className, methodName, parameterTypes);
|
||||
} catch (NoSuchMethodException e) {
|
||||
method = getDeclaredMethod(className, methodName, parameters);
|
||||
}
|
||||
return (T) method.invoke(instance, parameters);
|
||||
public static <T> T invokeMethod(String className, String methodName, Object instance, Class<?>[] parameterTypes, Object... parameters)
|
||||
throws NoSuchMethodException, InvocationTargetException, IllegalAccessException, ClassNotFoundException {
|
||||
return invokeMethod(getClass(className), methodName, instance, parameterTypes, parameters);
|
||||
}
|
||||
|
||||
public static <T> T invokeMethod(Class<?> clz, String methodName, Object instance, Object... parameters) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
|
||||
public static <T> T invokeMethod(Class<?> clz, String methodName, Object instance, Object... parameters)
|
||||
throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
|
||||
Class<?>[] parameterTypes = new Class<?>[parameters.length];
|
||||
for (int i = 0; i < parameters.length; i++) {
|
||||
parameterTypes[i] = parameters[i].getClass();
|
||||
}
|
||||
Method method;
|
||||
try {
|
||||
method = getMethod(clz, methodName, parameterTypes);
|
||||
} catch (NoSuchMethodException e) {
|
||||
method = getDeclaredMethod(clz, methodName, parameters);
|
||||
}
|
||||
return (T) method.invoke(instance, parameters);
|
||||
return invokeMethod(clz, methodName, instance, parameterTypes, parameters);
|
||||
}
|
||||
|
||||
public static <T> T invokeMethod(String className, String methodName, Object instance, Object... parameters) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException, ClassNotFoundException {
|
||||
Class<?>[] parameterTypes = new Class<?>[parameters.length];
|
||||
for (int i = 0; i < parameters.length; i++) {
|
||||
parameterTypes[i] = parameters[i].getClass();
|
||||
}
|
||||
Method method;
|
||||
try {
|
||||
method = getMethod(className, methodName, parameterTypes);
|
||||
} catch (NoSuchMethodException e) {
|
||||
method = getDeclaredMethod(className, methodName, parameters);
|
||||
}
|
||||
return (T) method.invoke(instance, parameters);
|
||||
public static <T> T invokeMethod(String className, String methodName, Object instance, Object... parameters)
|
||||
throws NoSuchMethodException, InvocationTargetException, IllegalAccessException, ClassNotFoundException {
|
||||
return invokeMethod(getClass(className), methodName, instance, parameters);
|
||||
}
|
||||
|
||||
/**
|
||||
* Invokes a static method
|
||||
*/
|
||||
public static <T> T invokeStaticMethod(Class<?> clz, String methodName, Class<?>[] parameterTypes, Object... parameters) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
|
||||
Method method;
|
||||
try {
|
||||
method = getMethod(clz, methodName, parameterTypes);
|
||||
} catch (NoSuchMethodException e) {
|
||||
method = getDeclaredMethod(clz, methodName, parameters);
|
||||
}
|
||||
return (T) method.invoke(null, parameters);
|
||||
public static <T> T invokeStaticMethod(Class<?> clz, String methodName, Class<?>[] parameterTypes, Object... parameters)
|
||||
throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
|
||||
return invokeMethod(clz, methodName, null, parameterTypes, parameters);
|
||||
}
|
||||
|
||||
public static <T> T invokeStaticMethod(String className, String methodName, Class<?>[] parameterTypes, Object... parameters) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException, ClassNotFoundException {
|
||||
Method method;
|
||||
try {
|
||||
method = getMethod(className, methodName, parameterTypes);
|
||||
} catch (NoSuchMethodException e) {
|
||||
method = getDeclaredMethod(className, methodName, parameters);
|
||||
}
|
||||
return (T) method.invoke(null, parameters);
|
||||
public static <T> T invokeStaticMethod(String className, String methodName, Class<?>[] parameterTypes, Object... parameters)
|
||||
throws NoSuchMethodException, InvocationTargetException, IllegalAccessException, ClassNotFoundException {
|
||||
return invokeStaticMethod(getClass(className), methodName, parameterTypes, parameters);
|
||||
}
|
||||
|
||||
public static <T> T invokeStaticMethod(Class<?> clz, String methodName, Object... parameters) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
|
||||
Class<?>[] parameterTypes = new Class<?>[parameters.length];
|
||||
for (int i = 0; i < parameters.length; i++) {
|
||||
parameterTypes[i] = parameters[i].getClass();
|
||||
}
|
||||
Method method;
|
||||
try {
|
||||
method = getMethod(clz, methodName, parameterTypes);
|
||||
} catch (NoSuchMethodException e) {
|
||||
method = getDeclaredMethod(clz, methodName, parameters);
|
||||
}
|
||||
return (T) method.invoke(null, parameters);
|
||||
public static <T> T invokeStaticMethod(Class<?> clz, String methodName, Object... parameters)
|
||||
throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
|
||||
return invokeMethod(clz, methodName, null, parameters);
|
||||
}
|
||||
|
||||
public static <T> T invokeStaticMethod(String className, String methodName, Object... parameters) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException, ClassNotFoundException {
|
||||
Class<?>[] parameterTypes = new Class<?>[parameters.length];
|
||||
for (int i = 0; i < parameters.length; i++) {
|
||||
parameterTypes[i] = parameters[i].getClass();
|
||||
}
|
||||
Method method;
|
||||
try {
|
||||
method = getMethod(className, methodName, parameterTypes);
|
||||
} catch (NoSuchMethodException e) {
|
||||
method = getDeclaredMethod(className, methodName, parameters);
|
||||
}
|
||||
return (T) method.invoke(null, parameters);
|
||||
public static <T> T invokeStaticMethod(String className, String methodName, Object... parameters)
|
||||
throws NoSuchMethodException, InvocationTargetException, IllegalAccessException, ClassNotFoundException {
|
||||
return invokeMethod(getClass(className), methodName, null, parameters);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -45,10 +45,13 @@ import java.util.List;
|
||||
|
||||
public class ThemesUtils {
|
||||
public static List<String> accentColors = Arrays.asList(
|
||||
"3f8ae0",
|
||||
"5692d7", "528bcc", "7aa0cc", "518bcc", "6296d0", "2f68aa", "638ebf", "5181b8", "71aaeb", "4774a8", "5baaf4", "4186c8", "add3ff", "718198", "5a9eff", "99a2ad", "74a2d6", "e9eef3", "dfe3e7", "eff1f3", "5c9ce6", "4986cc", "4680c2"
|
||||
"2688eb", "529ef4", "2d81e0", "2975cc", "3f8ae0", "5692d7", "528bcc", "7aa0cc", "518bcc", "6296d0", "2f68aa", "638ebf", "5181b8", "71aaeb", "4774a8", "5baaf4", "4186c8", "add3ff", "718198", "5a9eff", "99a2ad", "74a2d6", "e9eef3", "dfe3e7", "eff1f3", "5c9ce6", "4986cc", "4680c2"
|
||||
);
|
||||
|
||||
public static boolean isCustomThemeApplied() {
|
||||
return !"default".equals(AndroidUtils.getPreferences().getString("vtl_theme_id", "default"));
|
||||
}
|
||||
|
||||
public static void applyTheme(VKTheme theme) {
|
||||
VKThemeHelper.theme(theme, LifecycleUtils.getCurrentActivity(), getCenterScreenCoords());
|
||||
} // Apply VKTheme and ImTheme (hard applying without dynamic theme changing)
|
||||
@@ -76,8 +79,7 @@ public class ThemesUtils {
|
||||
}
|
||||
|
||||
public static int getAccentColor() {
|
||||
return getResources().getColor(R.color.red);
|
||||
//return getColorFromAttr(getAttrId("accent"));
|
||||
return getColorFromAttr(getAttrId("accent"));
|
||||
} // Color accent
|
||||
|
||||
public static int getTextAttr() {
|
||||
@@ -315,13 +317,6 @@ public class ThemesUtils {
|
||||
return context.getColor(i);
|
||||
} // Android Support color injector + accent color checker
|
||||
|
||||
public static int getColor2(int i) {
|
||||
if (isColorRefAccented(i)) {
|
||||
return getAccentColor();
|
||||
}
|
||||
return getResources().getColor(i);
|
||||
} // Android Support color injector + accent color checker
|
||||
|
||||
public static int getAlertStyle() {
|
||||
if (isDarkTheme()) return R.style.Theme_MaterialComponents_Dialog_Alert;
|
||||
return R.style.Theme_MaterialComponents_Light_Dialog_Alert;
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 326 B |
Binary file not shown.
|
After Width: | Height: | Size: 238 B |
Binary file not shown.
|
After Width: | Height: | Size: 397 B |
Binary file not shown.
|
After Width: | Height: | Size: 397 B |
@@ -0,0 +1,48 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:orientation="horizontal"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center_vertical"
|
||||
android:background="?attr/selectableItemBackground"
|
||||
android:paddingStart="12dp"
|
||||
android:paddingTop="5dp"
|
||||
android:paddingEnd="12dp"
|
||||
android:paddingBottom="5dp">
|
||||
|
||||
<FrameLayout
|
||||
android:id="@id/color_preview_border"
|
||||
android:layout_width="48dp"
|
||||
android:layout_height="48dp">
|
||||
|
||||
<com.vk.stories.view.StoryCircleImageView
|
||||
android:id="@id/color_preview"
|
||||
android:layout_width="40dp"
|
||||
android:layout_height="40dp"
|
||||
android:layout_gravity="center" />
|
||||
|
||||
</FrameLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="16dp"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:id="@id/color_name"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:textColor="?attr/text_primary"
|
||||
android:textSize="16sp"
|
||||
android:maxLines="1" />
|
||||
|
||||
<TextView
|
||||
android:id="@id/color_value"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:textColor="?attr/text_secondary" />
|
||||
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
@@ -0,0 +1,32 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="?attr/selectableItemBackground"
|
||||
android:paddingLeft="12dp"
|
||||
android:paddingTop="8dp"
|
||||
android:paddingRight="12dp"
|
||||
android:paddingBottom="8dp">
|
||||
|
||||
<TextView
|
||||
android:id="@id/name"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:textSize="16sp"
|
||||
android:textColor="?attr/text_primary"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintBottom_toTopOf="@id/author" />
|
||||
|
||||
<TextView
|
||||
android:id="@id/author"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:textColor="?attr/text_secondary"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/name"
|
||||
app:layout_constraintBottom_toBottomOf="parent" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
@@ -1,40 +1,56 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:background="?attr/selectableItemBackground"
|
||||
android:paddingTop="5dp"
|
||||
android:paddingBottom="5dp"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="?attr/selectableItemBackground"
|
||||
android:paddingLeft="12dp"
|
||||
android:paddingTop="8dp"
|
||||
android:paddingRight="12dp"
|
||||
android:paddingBottom="8dp">
|
||||
android:paddingStart="8dp"
|
||||
android:paddingEnd="8dp">
|
||||
|
||||
<FrameLayout
|
||||
android:id="@id/theme_mode_preview_border"
|
||||
android:layout_width="48dp"
|
||||
android:layout_height="48dp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent">
|
||||
|
||||
<ImageView
|
||||
android:layout_gravity="center"
|
||||
android:id="@id/theme_mode_preview"
|
||||
android:layout_width="48dp"
|
||||
android:layout_height="48dp"
|
||||
android:scaleType="centerInside" />
|
||||
</FrameLayout>
|
||||
|
||||
<TextView
|
||||
android:textSize="16sp"
|
||||
android:textColor="?attr/text_primary"
|
||||
android:id="@id/name"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:textSize="16sp"
|
||||
android:textColor="?attr/text_primary"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintBottom_toTopOf="@id/author" />
|
||||
android:layout_marginLeft="16dp"
|
||||
app:layout_constraintBottom_toTopOf="@id/author"
|
||||
app:layout_constraintStart_toEndOf="@id/theme_mode_preview_border"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<TextView
|
||||
android:textColor="?attr/text_secondary"
|
||||
android:id="@id/author"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:textColor="?attr/text_secondary"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/name"
|
||||
app:layout_constraintBottom_toBottomOf="parent" />
|
||||
android:layout_marginLeft="16dp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintStart_toEndOf="@id/theme_mode_preview_border"
|
||||
app:layout_constraintTop_toBottomOf="@id/name" />
|
||||
|
||||
<ru.vtosters.lite.ui.views.NoTouchRadioButton
|
||||
android:id="@id/selection"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent" />
|
||||
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
@@ -223,10 +223,14 @@
|
||||
const/high16 p3, 0x43a00000 # 320.0f
|
||||
|
||||
.line 21
|
||||
invoke-static {p1}, Lru/vtosters/lite/res/proxy/ProxyResources;->addProxyResources(Ljava/lang/Object;)V
|
||||
|
||||
invoke-virtual {p1}, Landroid/content/Context;->getResources()Landroid/content/res/Resources;
|
||||
|
||||
move-result-object p1
|
||||
|
||||
|
||||
|
||||
invoke-virtual {p1}, Landroid/content/res/Resources;->getDisplayMetrics()Landroid/util/DisplayMetrics;
|
||||
|
||||
move-result-object p1
|
||||
|
||||
@@ -86,7 +86,7 @@
|
||||
.line 3
|
||||
sget-object v1, Landroidx/appcompat/R$styleable;->ActionBarLayout:[I
|
||||
|
||||
invoke-virtual {p1, p2, v1}, Landroid/content/Context;->obtainStyledAttributes(Landroid/util/AttributeSet;[I)Landroid/content/res/TypedArray;
|
||||
invoke-static {p1, p2, v1}, Lru/vtosters/lite/res/VTLResources;->obtainStyledAttributes(Landroid/content/Context;Landroid/util/AttributeSet;[I)Landroid/content/res/TypedArray;
|
||||
|
||||
move-result-object p1
|
||||
|
||||
|
||||
+1
-1
@@ -93,7 +93,7 @@
|
||||
|
||||
const v3, 0x10102ce
|
||||
|
||||
invoke-virtual {v0, v1, v2, v3, v4}, Landroid/content/Context;->obtainStyledAttributes(Landroid/util/AttributeSet;[III)Landroid/content/res/TypedArray;
|
||||
invoke-static {v0, v1, v2, v3, v4}, Lru/vtosters/lite/res/VTLResources;->obtainStyledAttributes(Landroid/content/Context;Landroid/util/AttributeSet;[III)Landroid/content/res/TypedArray;
|
||||
|
||||
move-result-object v0
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user