Compare commits

...
58 Commits
Author SHA1 Message Date
Vologhat ff404b21b5 override obtainAttributes 2022-11-29 11:07:07 +03:00
Vologhat aeed9efaeb remove redundant code 2022-11-29 11:04:03 +03:00
Vologhat 29b9251763 refactor 2022-11-22 16:53:34 +03:00
Vologhat 91e6c3185f refactored color attributes storing and color value replacement 2022-11-22 16:52:00 +03:00
Vologhat 7169e3ab8b refactored color attributes storing and color value replacement 2022-11-22 16:48:59 +03:00
Vologhat 97a0ae4b95 refactored color attributes storing and color value replacement 2022-11-22 11:49:00 +03:00
Vologhat 5d5ad70e7e converted color strings to integers and added copies with prefix "ff" 2022-11-22 11:47:20 +03:00
Vologhat 752c59e2a2 removed old ui hooks for recoloring and added some new 2022-11-22 11:45:52 +03:00
Vologhat e3b33a88c7 added try-catch block for ignoring unknown resource ids 2022-11-22 11:43:37 +03:00
aliveoutside b35d5c428f get rid of static imports in themes 2022-11-21 23:03:00 +03:00
aliveoutside c796006b94 fix access flag in VKLayoutInflater 2022-11-12 16:18:39 +03:00
Vologhat 70d3b8f8c0 convert VKLayoutInflater and VKUILayoutFactory to java 2022-10-30 18:35:12 +03:00
Vologhat 15f678e20d added recolor to accent methods 2022-10-22 19:35:25 +03:00
Vologhat 011698a722 fix 3 2022-10-13 22:45:19 +03:00
Vologhat d127fbbd22 fix 2 2022-10-07 12:48:43 +03:00
Vologhat eb167be1c0 renamed package name, temporarily disabled theme creating, test fixes 2022-10-04 16:54:21 +03:00
aliveoutside 19fa48a1f6 add AccentAttrs 2022-08-28 20:08:33 +03:00
aliveoutside e96ba600cb uncomments 2022-08-28 20:08:02 +03:00
aliveoutside 70c75edacc hz 2022-08-28 20:07:44 +03:00
aliveoutside 5a048eff92 add attr replacer 2022-08-28 20:07:26 +03:00
aliveoutside 9abed48ec0 get color from theme by attr 2022-08-28 20:07:04 +03:00
aliveoutside 6758c8735c get color from theme by attr 2022-08-28 20:06:57 +03:00
aliveoutside b20fdee8da and some more junk code removed 2022-08-28 18:30:49 +03:00
aliveoutside dffa4ff7c4 remove junk code 2022-08-28 18:29:49 +03:00
Vologhat 6dd715af0b added palettes 2022-08-25 22:06:58 +03:00
Vologhat 10de24f119 added resources hooks, palettes manager and new color pickers 2022-08-25 21:47:34 +03:00
Vologhat 2cd856e574 made hook and added images for theme toggle animation 2022-08-25 19:03:29 +03:00
aliveoutside 9e9617fd16 Merge pull request #555 from iNotFlying/vtl-remaster && update from vtl-remaster branch
Remove dead VK code, update colors.xml
2022-08-01 19:09:53 +03:00
iNotFlying 3ae2637da0 Update colors.xml 2022-08-01 16:45:30 +04:00
iNotFlying 94438662cb Update colors.xml 2022-08-01 16:31:46 +04:00
iNotFlying aa3d4df40a Merge branch 'vtosters:vtl-remaster' into vtl-remaster 2022-08-01 16:29:57 +04:00
iNotFlying 1e5fdde641 Remove dead VK code 2022-07-31 22:44:07 +04:00
Vologhat b37b41ddd3 added ui 2022-07-31 21:05:39 +03:00
Vologhat 25d6231ae3 Merge branch 'theming' of https://github.com/vtosters/lite into theming 2022-07-31 20:22:19 +03:00
Vologhat db83fbff52 added themes manager ui (beta) 2022-07-31 20:22:06 +03:00
aliveoutside 48e6b4f54d [accents] add exceptions to attr hooker 2022-07-31 16:58:18 +03:00
aliveoutside 937c7efb03 [accents] use sparseintarray instead of hashmap for hooked attributes 2022-07-29 17:46:33 +03:00
aliveoutside 86c513ed58 [accents] fix header background in newsfeed 2022-07-29 17:44:53 +03:00
aliveoutside e701d03233 [accents] fix tinttextview 2022-07-29 17:44:09 +03:00
aliveoutside 097faf6b38 [accents] hook linkedtextview 2022-07-28 20:15:57 +03:00
aliveoutside 4f4b167d99 [accents] return hex2 back
(Влад спасибо)
2022-07-28 18:57:35 +03:00
aliveoutside d886e43f3d Merge branch 'vtl-remaster' into theming 2022-07-28 18:50:35 +03:00
aliveoutside fcf1ee608d [accents] remove a12 detection from themes 2022-07-28 18:47:05 +03:00
aliveoutside cd15541583 [accents] fix attr injector 2022-07-28 18:25:20 +03:00
aliveoutside 7bdaa736ef [accents] force red accent until color picker will be implemented 2022-07-28 17:44:37 +03:00
aliveoutside 74f10e6ab2 [accents] resolve merge issues 2022-07-28 17:40:54 +03:00
aliveoutside 678a398612 [accents] return themeCSL method back to ThemesUtils 2022-07-28 17:40:26 +03:00
aliveoutside 7e1c3d62cf Merge branch 'vtl-remaster' into theming
# Conflicts:
#	app/src/main/java/ru/vtosters/lite/utils/Globals.java
#	app/src/main/java/ru/vtosters/lite/utils/ThemesUtils.java
2022-07-28 17:09:52 +03:00
aliveoutside c0ce22565e [accents] add tablayouthook to ViewInjector 2022-07-28 17:09:02 +03:00
aliveoutside 217a376f5b [accents] fix dropdown color in newsfeed 2022-07-28 17:08:43 +03:00
aliveoutside 35f486dc86 [accents] add some dirty hacks 2022-07-28 17:08:07 +03:00
aliveoutside 252d761b9e [accents] hook VKTabLayout instead of TabLayout 2022-07-28 17:07:06 +03:00
aliveoutside 237be0cc88 [accents] fix crash on theme switching 2022-07-28 17:06:36 +03:00
aliveoutside 2717fd5736 [accents] remove ToolbarHook for now 2022-07-28 17:05:20 +03:00
aliveoutside 81cf7ad694 Merge branch 'vtl-remaster' into theming 2022-07-26 19:46:54 +03:00
aliveoutside 34007658d5 implement attributes resolver 2022-07-26 19:46:28 +03:00
aliveoutside 3d1ca8d36a Merge branch 'vtl-remaster' into theming 2022-07-25 03:55:55 +03:00
aliveoutside 99f8e4f896 init theming stuff 2022-07-25 03:55:33 +03:00
1286 changed files with 15872 additions and 5347 deletions
Binary file not shown.
@@ -0,0 +1,51 @@
package com.vk.core.ui;
import android.content.Context;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.View;
public class VKLayoutInflater extends LayoutInflater {
private final static String[] DEFAULT_PACKAGES = {
"android.widget.", "android.webkit.", "android.app."
};
public VKLayoutInflater(LayoutInflater original, Context context) {
super(original, context);
}
@Override
public LayoutInflater cloneInContext(Context newContext) {
return new VKLayoutInflater(this, newContext);
}
public final View createView(View parent, String name, Context context, AttributeSet attrs) {
if (!name.startsWith("android.")) {
try {
return onCreateView(parent, name, attrs);
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
try {
return createView(name, null, attrs);
} catch (ClassNotFoundException ex) {
ex.printStackTrace();
}
return null;
}
@Override
protected View onCreateView(String name, AttributeSet attrs) throws ClassNotFoundException {
for (var defPackage : DEFAULT_PACKAGES) {
try {
View view = createView(name, defPackage, attrs);
if (view != null) return view;
} catch (ClassCastException | ClassNotFoundException ignored) {
}
}
return super.onCreateView(name, attrs);
}
}
@@ -0,0 +1,119 @@
package com.vk.core.ui;
import android.content.Context;
import android.content.res.TypedArray;
import android.os.Build;
import android.util.AttributeSet;
import android.view.InflateException;
import android.view.LayoutInflater;
import android.view.View;
import android.webkit.WebView;
import android.widget.CheckBox;
import android.widget.RadioButton;
import android.widget.Switch;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatDelegate;
import androidx.appcompat.widget.SwitchCompat;
import com.vk.core.ui.themes.VKThemeHelper;
import com.vk.core.util.OsUtil;
import com.vk.core.view.NavigationSpinner;
import com.vk.core.view.TextViewColorStateListAndAlphaSupportPreV23;
import com.vk.core.view.VKAppBarLayout;
public class VKUILayoutFactory implements LayoutInflater.Factory2 {
private final static int[] FONT_ATTR = { android.R.attr.font };
private AppCompatDelegate mDelegate;
private VKLayoutInflater mInflater;
public VKUILayoutFactory(AppCompatDelegate delegate, VKLayoutInflater inflater) {
mDelegate = delegate;
mInflater = inflater;
}
private void setTypeface(TextView textView, Context context, AttributeSet attrs) {
TypedArray ta = context.obtainStyledAttributes(attrs, FONT_ATTR, android.R.attr.textViewStyle, 0);
if (ta == null) return;
Font font = Font.Companion.a(ta.getString(0));
if (font != null && Build.VERSION.SDK_INT < font.a())
textView.setTypeface(font.c());
ta.recycle();
}
@Nullable
@Override
public View onCreateView(@Nullable View parent, @NonNull String name, @NonNull Context context, @NonNull AttributeSet attrs) {
View view = null;
try {
if (!(parent instanceof NavigationSpinner) && !OsUtil.b())
view = new TextViewColorStateListAndAlphaSupportPreV23(context, attrs, 0);
else if (mDelegate != null)
view = mDelegate.createView(parent, name, context, attrs);
else if (mInflater != null)
view = mInflater.createView(parent, name, context, attrs);
} catch (Throwable unused) {
throw new InflateException(
"parent=" + parent.getClass().getName()
+ ":" + getResourceIdName(view)
+ ", view=" + name
+ ":" + getResourceIdName(context, attrs));
}
if (view == null &&
("com.google.android.material.appbar.AppBarLayout".equals(name)
|| "android.support.design.widget.AppBarLayout".equals(name)))
view = new VKAppBarLayout(context, attrs);
if (view != null) {
if (!(view.getTag() instanceof String)) {
if (view instanceof TextView) {
setTypeface((TextView) view, context, attrs);
if (view instanceof CheckBox)
VKThemeHelper.k.a((CheckBox) view);
else if (view instanceof RadioButton)
VKThemeHelper.k.a((RadioButton) view);
else if (view instanceof SwitchCompat)
VKThemeHelper.k.a((SwitchCompat) view);
else if (view instanceof Switch)
VKThemeHelper.k.a((Switch) view);
} else if (view instanceof WebView) {
VKThemeHelper.a((WebView) view);
}
VKThemeHelper.k.a(view, attrs);
}
}
return view;
}
private String getResourceIdName(View view) {
try {
if (view != null) {
if (view.getId() != -1)
return view.getResources().getResourceName(view.getId());
return "NO_ID";
}
return "null";
} catch (Throwable unused) {
return "FAILED_TO_GET_ID";
}
}
private String getResourceIdName(Context context, AttributeSet attrs) {
TypedArray ta = context.obtainStyledAttributes(attrs, new int[] { android.R.attr.id });
int resId = ta.getResourceId(0, -1);
ta.recycle();
return resId != -1
? context.getResources().getResourceName(resId)
: "NO_ID";
}
@Nullable
@Override
public View onCreateView(@NonNull String name, @NonNull Context context, @NonNull AttributeSet attrs) {
return onCreateView(null, name, context, attrs);
}
}
@@ -0,0 +1,17 @@
package ru.vtosters.lite.hooks;
import com.vk.extensions.ViewExtKt;
import com.vk.navigation.right.RightMenuPresenter;
import ru.vtosters.lite.ui.views.VTLCheckableButton;
public class DayNightButtonHook {
public static void injectOnClick(VTLCheckableButton button, RightMenuPresenter presenter) {
button.postOnAnimationDelayed(() -> {
var rect = ViewExtKt.e(button);
presenter.a(new float[] { rect.centerX(), rect.centerY() });
}, 550); // minimal delay for fix toggle animation
button.toggle();
}
}
@@ -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();
@@ -67,6 +68,7 @@ public class DockBarInjector {
@SuppressLint("RestrictedApi")
public static void inject(BottomNavigationView navigationView) {
navigationView.setBackgroundColor(ThemesUtils.getBackgroundContent());
Menu menu = navigationView.getMenu();
menu.clear();
try {
@@ -86,14 +88,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,10 +17,14 @@ 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;
import android.graphics.Color;
import android.util.Log;
import com.vk.core.network.Network;
@@ -33,6 +37,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 +68,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(Color.parseColor(textColor.startsWith("#") ? textColor : "#" + textColor))) {
item.put("text_color", hex2(getAccentColor()));
}
}
}
orig.put("buttons", newItem);
return orig;
@@ -18,7 +18,6 @@ import ru.vtosters.lite.ui.dialogs.ServerDialog;
import ru.vtosters.lite.ui.dialogs.Start;
public class MainActivityInjector {
public static void inject(Activity activity) {
setNeededTheme(activity);
ServerDialog.activity = activity;
@@ -35,7 +34,6 @@ public class MainActivityInjector {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
NotificationChannels.createChannels();
}
// VKIDProtection.alert(activity);
}
}
@@ -5,7 +5,6 @@ import static ru.vtosters.lite.utils.AndroidUtils.getPrefsValue;
import static ru.vtosters.lite.utils.Preferences.getBoolValue;
import static ru.vtosters.lite.utils.Preferences.milkshake;
import static ru.vtosters.lite.utils.Preferences.superapp;
import static ru.vtosters.lite.utils.Preferences.useNewSettings;
import static ru.vtosters.lite.utils.Preferences.videonewcatalog;
import static ru.vtosters.lite.utils.Preferences.vkme;
@@ -34,6 +33,8 @@ import com.vtosters.lite.fragments.y2.VideosFragment;
import com.vtosters.lite.general.fragments.GamesFragment;
import com.vtosters.lite.general.fragments.PhotosFragment;
import ru.vtosters.lite.ui.fragments.VTSettings;
public class StartFragmentHook {
public static Class getStartFragment() {
if (vkme()) {
@@ -55,7 +56,7 @@ public class StartFragmentHook {
case "videos":
return videonewcatalog() ? VideoCatalogFragment.class : VideosFragment.class;
case "settings":
return useNewSettings();
return VTSettings.class;
case "apps":
return AppsFragment.class;
case "discover":
@@ -0,0 +1,38 @@
package ru.vtosters.lite.themes;
import android.content.res.ColorStateList;
import android.widget.ImageView;
import android.widget.TextView;
import androidx.appcompat.widget.Toolbar;
import com.vk.core.view.TintTextView;
import ru.vtosters.lite.utils.ThemesUtils;
public class Recolor {
private static int getColor(int original) {
var color = VTLColors.getColor(original);
if (color != -1) return color;
return original;
}
public static void recolorToolbar(Toolbar toolbar) {
if (ThemesUtils.isCustomThemeApplied())
toolbar.setBackgroundColor(ThemesUtils.getHeaderBackground());
}
public static void recolorIconToAccent(ImageView iv) {
if (ThemesUtils.isCustomThemeApplied())
iv.setImageTintList(ColorStateList.valueOf(VTLColors.getAccentColor()));
}
public static void recolorTextToAccent(TextView iv) {
iv.setTextColor(VTLColors.getAccentColor());
}
public static void recolorTintTextViewToAccent(TintTextView ttv) {
ttv.setDrawableTint(VTLColors.getAccentColor());
}
}
@@ -0,0 +1,94 @@
package ru.vtosters.lite.themes;
import android.content.Context;
import android.util.Log;
import com.vk.core.ui.themes.VKThemeHelper;
import com.vk.core.util.ContextExtKt;
import com.vtosters.lite.R;
import ru.vtosters.lite.themes.managers.ThemesManager;
import ru.vtosters.lite.themes.models.ThemeModel;
import ru.vtosters.lite.utils.AndroidUtils;
import ru.vtosters.lite.utils.Preferences;
import ru.vtosters.lite.utils.ThemesUtils;
public class VTLColors {
private static final ThemesManager themesManager = ThemesManager.getInstance();
private static final ThemeModel currentTheme = themesManager.getCurrentTheme();
public static int getAccentColor() {
return getColor(R.attr.accent, R.attr.accentColor, R.attr.colorAccent, R.attr.vk_accent);
}
public static int getTextPrimaryColor() {
return getColor(R.attr.text_primary);
}
public static int getTextSecondaryColor() {
return getColor(R.attr.text_secondary);
}
public static int getTabbarBackground() {
return getColor(R.attr.tabbar_background);
}
public static int getTabbarActiveColor() {
if (ThemesUtils.isCustomThemeApplied())
return getColor(R.attr.tabbar_active_icon);
return Preferences.dockbar_accent()
? getAccentColor()
: AndroidUtils.getResources().getColor(ThemesUtils.isDarkTheme() ? R.color.white : R.color.gray_700);
}
public static int getTabbarInactiveColor() {
if (ThemesUtils.isCustomThemeApplied())
return getColor(R.attr.tabbar_inactive_icon);
return AndroidUtils.getResources().getColor(ThemesUtils.isDarkTheme() ? R.color.gray_500 : R.color.vk_steel_gray_300);
}
public static int getBackgroundContent() {
return getColor(R.attr.background_content);
}
public static int getBackgroundPage() {
return getColor(R.attr.background_page);
}
public static int getHeaderBackground() {
return getColor(R.attr.header_background);
}
public static int getHeaderText() {
return getColor(R.attr.header_text);
}
//region Hooks
public static int getColor(int... attrs) {
return getColor(attrs[0]);
}
public static int getColor(int attr) {
if (ThemesUtils.isCustomThemeApplied()) {
int color = currentTheme.getColor(attr);
if (color != -1)
return color;
}
return VKThemeHelper.d(attr);
}
public static int getColor(Context context, int colorId) {
try {
Log.d("VTLColors", "getColor(Landroid/content/Context;I): " + AndroidUtils.getResources().getResourceEntryName(colorId));
} catch (Throwable ignored) {
}
if (ThemesUtils.isCustomThemeApplied()) {
int color = currentTheme.getColor(colorId);
if (color != -1)
return color;
}
return ContextExtKt.h(context, colorId);
}
//endregion
}
@@ -0,0 +1,140 @@
package ru.vtosters.lite.themes;
import android.annotation.SuppressLint;
import android.content.Context;
import android.content.res.ColorStateList;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.os.Build;
import android.util.AttributeSet;
import android.util.SparseIntArray;
import android.util.TypedValue;
import com.vtosters.lite.R;
import java.lang.reflect.Field;
import ru.vtosters.lite.themes.managers.ThemesManager;
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);
// android attributes
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);
}
public static void prepareTypedArray(TypedArray target, int[] attrs) {
if (!ThemesUtils.isCustomThemeApplied() || attrs == null) return;
var theme = ThemesManager.getInstance().getCurrentTheme();
var data = getData(target);
for (int i = 0; i < attrs.length; i++) {
//var attr = attrs[i];
var off = STYLE_NUM_ENTRIES * i;
var type = data[off];
var replacement = -1;
if (type == TypedValue.TYPE_ATTRIBUTE) {
replacement = theme.getColor(data[off + 0x1]);
} else if (type == TypedValue.TYPE_INT_COLOR_ARGB8 || type == TypedValue.TYPE_INT_COLOR_RGB8) {
replacement = forcedAttrs.get(data[off + 0x1], -1);
if (replacement == -1
&& ThemesUtils.accentColors.contains(data[off + 0x1]))
replacement = ThemesUtils.getAccentColor();
}
if (replacement == -1) continue;
try {
data[off] = TypedValue.TYPE_INT_COLOR_RGB8; // type
data[off + 0x1] = replacement; // data
data[off + 0x2] = 0x0; // asset cookie
data[off + 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 VTLColors.getColor(colorId);//res.getColor(colorId);
}
@SuppressLint("NewApi")
public static int getColor(Resources res, int colorId, Resources.Theme theme) {
return VTLColors.getColor(colorId);
}
@SuppressLint("UseCompatLoadingForColorStateLists")
public static ColorStateList getColorStateList(Resources res, int colorId) {
if (ThemesUtils.isCustomThemeApplied()) {
int color = VTLColors.getColor(colorId);
if (color != -1)
return ColorStateList.valueOf(color);
}
return res.getColorStateList(colorId);
}
@SuppressLint("NewApi")
public static ColorStateList getColorStateList(Resources res, int colorId, Resources.Theme theme) {
if (ThemesUtils.isCustomThemeApplied()) {
int color = VTLColors.getColor(colorId);
if (color != -1)
return ColorStateList.valueOf(color);
}
return res.getColorStateList(colorId, theme);
}
//endregion
}
@@ -0,0 +1,97 @@
package ru.vtosters.lite.themes.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.List;
import ru.vtosters.lite.themes.models.PaletteModel;
import ru.vtosters.lite.utils.AndroidUtils;
import ru.vtosters.lite.utils.IOUtils;
public class PalettesManager {
private static PalettesManager instance;
private static File palettesDir = new File(Environment.getExternalStorageDirectory(), "VTL/themes/palettes");
private List<PaletteModel> palettes = new ArrayList<>();
public static PalettesManager getInstance() {
if (instance == null)
instance = new PalettesManager();
return instance;
}
public PalettesManager() {
load();
}
public void load() {
palettes.clear();
if (!palettesDir.exists())
palettesDir.mkdirs();
final var palettes = palettesDir.listFiles((dir, name) -> name.endsWith(".json"));
var check = true;
if (palettes == null || palettes.length == 0)
check = copyPaletteFromAssets();
if (check)
parsePalettes();
}
private boolean copyPaletteFromAssets() {
try {
final var manager = AndroidUtils.getGlobalContext().getAssets();
final var assets = manager.list("vtl_themes/palettes/");
if (assets == null || assets.length == 0) return false;
var i = 0;
for (var asset : assets) {
try {
IOUtils.copyFile(manager.open("vtl_themes/palettes/" + asset), new File(palettesDir, asset));
i++;
} catch (IOException e) {
e.printStackTrace();
}
}
return i > 0;
} catch (IOException e) {
e.printStackTrace();
}
return false;
}
private void parsePalettes() {
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 PaletteModel getPalette(String id) {
for (PaletteModel palette : palettes) {
if (id.equals(palette.id))
return palette;
}
return null;
}
public PaletteModel getPalette(int i) {
return palettes.get(i);
}
public int getPalettesCount() {
return palettes.size();
}
}
@@ -0,0 +1,247 @@
package ru.vtosters.lite.themes.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.List;
import java.util.zip.GZIPInputStream;
import java.util.zip.GZIPOutputStream;
import ru.vtosters.lite.ui.dialogs.ThemesEditorBottomSheetDialog;
import ru.vtosters.lite.themes.models.ThemeModel;
import ru.vtosters.lite.utils.AndroidUtils;
import ru.vtosters.lite.utils.IOUtils;
import ru.vtosters.lite.utils.ThemesUtils;
public class ThemesManager {
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);
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,26 @@
package ru.vtosters.lite.themes.models;
public class ColorModel {
public String resName;
public int id;
public int color;
public ColorModel(int id, int color) {
this.id = id;
this.color = color;
}
public ColorModel(String resName, int id, int color) {
this.resName = resName;
this.id = id;
this.color = color;
}
public static ColorModel create(int id, int color) {
return new ColorModel(id, color);
}
public static ColorModel create(String resName, int id, int color) {
return new ColorModel(resName, id, color);
}
}
@@ -0,0 +1,94 @@
package ru.vtosters.lite.themes.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, int color) {
this.colors.add(new ColorModel(name, 0, color));
return this;
}
public int getColor(String name) {
for (var item : colors)
if (name.equals(item.resName))
return item.color;
return -1;
}
public static PaletteModel fromJson(JSONObject json) throws JSONException {
var palette = new PaletteModel()
.setId(json.optString("palette_id"))
.setName(json.optString("name"))
.setAuthor(json.optString("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");
var subColor = subColorNode.getString("color");
if (!subColor.startsWith("#")) subColor = "#" + subColor;
palette.setColor(name + " " + index, Color.parseColor(subColor));
}
} else {
var color = colorNode.getString("color");
if (!color.startsWith("#")) color = "#" + color;
palette.setColor(name, Color.parseColor(color));
}
}
return palette;
}
public static PaletteModel valuesOf(String id, String name, String author, List<ColorModel> colors) {
return new PaletteModel()
.setId(id)
.setName(name)
.setAuthor(author)
.setColors(colors);
}
}
@@ -0,0 +1,152 @@
package ru.vtosters.lite.themes.models;
import android.content.res.Resources;
import android.graphics.Color;
import android.util.Pair;
import org.json.JSONException;
import org.json.JSONObject;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
import ru.vtosters.lite.themes.managers.PalettesManager;
import ru.vtosters.lite.utils.AndroidUtils;
public class ThemeModel {
public String author;
public String name;
public boolean isDarkMode;
public String id;
public List<ColorModel> colors = new ArrayList<>();
public ThemeModel() {
}
public ThemeModel(String author, String name, boolean darkMode, String id, List<ColorModel> colors) {
this.author = author;
this.name = name;
this.isDarkMode = darkMode;
this.id = id;
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 setColors(List<ColorModel> colors) {
this.colors = colors;
return this;
}
public ThemeModel setColor(int attrId, int color) {
this.colors.add(new ColorModel(attrId, color));
return this;
}
public int getColor(int attrId) {
for (var item : colors)
if (attrId == item.id)
return item.color;
return -1;
}
public static ThemeModel valuesOf(String author, String name, boolean darkMode, String id, List<ColorModel> colors) {
var theme = new ThemeModel()
.setAuthor(author)
.setName(name)
.setDarkMode(darkMode)
.setId(id)
.setColors(colors);
return theme;
}
public static ThemeModel fromJson(String json) throws JSONException {
return fromJson(new JSONObject(json));
}
public static ThemeModel fromJson(JSONObject json) {
List<String> palettes;
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 attrId;
try {
attrId = AndroidUtils.getIdentifier(name, "attr");
} catch (Resources.NotFoundException e) {
attrId = AndroidUtils.getIdentifier(name, "color");
}
final 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(new ColorModel(name, attrId, 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 item : colors)
colorsJson.put(item.resName, Integer.toHexString(item.color).toUpperCase());
return new JSONObject()
.put("author", this.author)
.put("name", this.name)
.put("dark", this.isDarkMode)
.put("id", this.id)
.put("colors", colorsJson);
}
public ThemeModel clone() {
return new ThemeModel(
author,
name,
isDarkMode,
id,
colors.stream()
.map(colorModel -> new ColorModel(colorModel.resName, colorModel.id, colorModel.color))
.collect(Collectors.toList())
);
}
}
@@ -0,0 +1,14 @@
package ru.vtosters.lite.themes.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,67 @@
package ru.vtosters.lite.themes.proxy;
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 androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import androidx.annotation.XmlRes;
import ru.vtosters.lite.themes.VTLResources;
import ru.vtosters.lite.themes.managers.ThemesManager;
import ru.vtosters.lite.utils.ThemesUtils;
public class ProxyResources extends Resources {
private final Resources mOriginalResource;
public ProxyResources(Context context, Resources original) {
this(original);
}
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 {
if (ThemesUtils.isCustomThemeApplied()) {
var color = ThemesManager.getInstance().getCurrentTheme().getColor(id);
if (color != -1)
return color;
}
return mOriginalResource.getColor(id, theme);
}
@RequiresApi(api = Build.VERSION_CODES.M)
@NonNull
@Override
public ColorStateList getColorStateList(@XmlRes int id, @Nullable Resources.Theme theme)
throws Resources.NotFoundException {
var target = mOriginalResource.getColorStateList(id, theme);
if (ThemesUtils.isCustomThemeApplied() && ThemesUtils.isAccentedColor(target)) {
return ColorStateList.valueOf(ThemesUtils.getAccentColor());
}
return target;
}
@Nullable
@Override
public Drawable getDrawableForDensity(int id, int density, @Nullable Resources.Theme theme) {
return mOriginalResource.getDrawableForDensity(id, density, theme);
}
@Override
public TypedArray obtainAttributes(AttributeSet set, int[] attrs) {
var ta = super.obtainAttributes(set, attrs);
VTLResources.prepareTypedArray(ta, attrs);
return ta;
}
}
@@ -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.themes.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 item) {
mContainer.setOnClickListener(v -> mSelectListener.onColorSelected(ColorPickerAdapter.this, item));
mColorPreviewBorder.setBackground(new BorderDrawable(ThemesUtils.isDarkTheme() ? Color.WHITE : Color.BLACK, Screen.c(24.0f), Screen.c(1.0f)));
mColorPreview.setPlaceholderColor(item.color);
mColorName.setText(item.resName);
mColorValue.setText("#" + Integer.toHexString(item.color).toUpperCase());
}
}
public interface OnColorSelectListener {
void onColorSelected(ColorPickerAdapter adapter, ColorModel colorModel);
}
}
@@ -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.themes.managers.PalettesManager;
import ru.vtosters.lite.ui.dialogs.PalettesBottomSheetDialog;
import ru.vtosters.lite.themes.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);
}
}
}
@@ -0,0 +1,166 @@
package ru.vtosters.lite.ui.adapters;
import android.app.Activity;
import android.content.Context;
import android.content.res.ColorStateList;
import android.text.TextUtils;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
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.drawable.BorderDrawable;
import com.vk.core.ui.themes.VKThemeHelper;
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.themes.managers.ThemesManager;
import ru.vtosters.lite.ui.dialogs.EditTextDialog;
import ru.vtosters.lite.ui.dialogs.ThemesEditorBottomSheetDialog;
import ru.vtosters.lite.themes.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> {
public ThemesManager mManager = ThemesManager.getInstance();
@NonNull
@Override
public ThemeViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
var container = LayoutInflater.from(parent.getContext()).inflate(
AndroidUtils.getIdentifier("theme_manager_item", "layout"), parent, false
);
return new ThemeViewHolder(container);
}
@Override
public void onBindViewHolder(@NonNull ThemeViewHolder holder, int pos) {
holder.bind(mManager.getTheme(pos));
}
@Override
public int getItemCount() {
return mManager.getThemesCount();
}
public class ThemeViewHolder extends RecyclerView.ViewHolder {
private ConstraintLayout mContainer;
private FrameLayout mThemeModePreviewBorder;
private ImageView mThemeModePreview;
private TextView mName;
private TextView mAuthor;
private NoTouchRadioButton mRadioButton;
public ThemeViewHolder(@NonNull View view) {
super(view);
mContainer = (ConstraintLayout) view;
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(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 -> {
mManager.setTheme(theme);
LifecycleUtils.restartApplication();
});
if (!"default".equals(theme.id))
mContainer.setOnLongClickListener(v -> {
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 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();
}
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("Название не может быть пустым");
return;
}
if (newName.equals(theme.name)) {
ToastUtils.a("Вы не изменили название");
return;
}
theme.setName(newName);
try {
mManager.save(theme);
notifyDataSetChanged();
} catch (IOException | JSONException e) {
e.printStackTrace();
ToastUtils.a("Не удалось изменить название темы");
}
});
}
}
private int getAccentColorFromCustomTheme(ThemeModel theme) {
if (!"default".equals(theme.id)) {
int color = theme.getColor(R.attr.accent);
if (color != -1)
return color;
color = theme.getColor(R.attr.colorAccent);
if (color != -1)
return color;
color = theme.getColor(R.attr.vk_accent);
if (color != -1)
return color;
}
return VKThemeHelper.d((R.attr.accent));
}
}
@@ -5,7 +5,6 @@ import static ru.vtosters.lite.utils.AndroidUtils.getPreferences;
import static ru.vtosters.lite.utils.Preferences.getBoolValue;
import static ru.vtosters.lite.utils.Preferences.milkshake;
import static ru.vtosters.lite.utils.Preferences.superapp;
import static ru.vtosters.lite.utils.Preferences.useNewSettings;
import static ru.vtosters.lite.utils.Preferences.videonewcatalog;
import static ru.vtosters.lite.utils.Preferences.vkme;
import static ru.vtosters.lite.utils.Preferences.vkme_notifs;
@@ -43,6 +42,7 @@ import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
import ru.vtosters.lite.ui.fragments.VTSettings;
import ru.vtosters.lite.ui.items.DockBarTab;
import ru.vtosters.lite.utils.AndroidUtils;
@@ -261,7 +261,7 @@ public class DockBarEditorManager {
R.drawable.ic_settings_outline_28,
R.string.menu_settings,
R.id.menu_settings,
useNewSettings());
VTSettings.class);
}
}
}
@@ -0,0 +1,58 @@
package ru.vtosters.lite.ui.dialogs;
import static ru.vtosters.lite.themes.VTLColors.getTextSecondaryColor;
import static ru.vtosters.lite.themes.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.themes.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.color).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,212 @@
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 ru.vtosters.lite.themes.VTLColors;
import ru.vtosters.lite.themes.managers.PalettesManager;
import ru.vtosters.lite.themes.managers.ThemesManager;
import ru.vtosters.lite.themes.models.ColorModel;
import ru.vtosters.lite.ui.adapters.ColorPickerAdapter;
import ru.vtosters.lite.themes.models.PaletteModel;
import ru.vtosters.lite.themes.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 = theme.clone();
var colorPickerAdapter = new ColorPickerAdapter(newTheme.colors, (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.color);
var alertDialog = new VkAlertDialog.Builder(activity)
.setTitle("Выберите цвет")
.setPositiveButton("Выбрать", (dialog, which) -> {
dest.color = 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.color = 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.color = color.color;
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.themes.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,38 @@
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.themes.managers.PalettesManager;
import ru.vtosters.lite.ui.adapters.PalettesAdapter;
import ru.vtosters.lite.utils.LayoutUtils;
public class PalettesManagerFragment extends BaseToolbarFragment {
private final PalettesManager mManager = PalettesManager.getInstance();
private PalettesAdapter mAdapter;
@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;
}
}
@@ -14,9 +14,9 @@ import androidx.preference.Preference;
import com.vk.navigation.Navigator;
import com.vtosters.lite.general.fragments.MaterialPreferenceToolbarFragment;
import ru.vtosters.lite.themes.managers.ThemesManager;
import ru.vtosters.lite.utils.LifecycleUtils;
public class ThemesFragment extends MaterialPreferenceToolbarFragment {
@Override
@@ -27,6 +27,17 @@ 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);
themesPreference.setOnPreferenceClickListener(preference -> {
var intent = new Navigator(ThemesManagerFragment.class).b(getContext());
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
getContext().startActivity(intent);
return false;
});
findPreference("navbar").setOnPreferenceClickListener(new restart());
findPreference("milkshake").setOnPreferenceClickListener(new restart());
findPreference("darktheme").setOnPreferenceChangeListener(new restartdark());
@@ -0,0 +1,142 @@
package ru.vtosters.lite.ui.fragments;
import static ru.vtosters.lite.utils.AndroidUtils.dp2px;
import static ru.vtosters.lite.utils.AndroidUtils.getIdentifier;
import static ru.vtosters.lite.themes.VTLColors.getAccentColor;
import android.app.Activity;
import android.content.Intent;
import android.content.res.ColorStateList;
import android.graphics.Color;
import android.os.Bundle;
import android.text.TextUtils;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.FrameLayout;
import android.widget.LinearLayout;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.recyclerview.widget.LinearLayoutManager;
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.themes.managers.ThemesManager;
import ru.vtosters.lite.ui.adapters.ThemesAdapter;
import ru.vtosters.lite.ui.dialogs.EditTextDialog;
import ru.vtosters.lite.utils.LayoutUtils;
public class ThemesManagerFragment extends BaseToolbarFragment {
private static final int IMPORT_REQUEST_CODE = 0x1;
private ThemesAdapter mAdapter;
private ThemesManager mManager = ThemesManager.getInstance();
@Override
public void onActivityResult(int requestCode, int resultCode, Intent intent) {
super.onActivityResult(requestCode, resultCode, intent);
switch (requestCode) {
case IMPORT_REQUEST_CODE:
if (resultCode != Activity.RESULT_OK)
return;
try {
var data = intent.getData();
if (data != null) {
mManager.copy(getContext().getContentResolver(), data);
mAdapter.notifyDataSetChanged();
} else {
throw new IOException("Error while getting file path");
}
} catch (IOException | JSONException e) {
e.printStackTrace();
ToastUtils.a("Не удалось импортировать тему");
}
break;
}
}
@Override
public View onCreateContent(@NonNull LayoutInflater inflater, @Nullable Bundle bundle) {
setTitle("Темы");
var container = new FrameLayout(getContext());
mAdapter = new ThemesAdapter();
var recyclerView = new RecyclerView(getContext());
recyclerView.setAdapter(mAdapter);
recyclerView.setLayoutManager(new LinearLayoutManager(getContext()));
container.addView(recyclerView, LayoutUtils.createFrame(-1, -1));
var buttonsContainer = new LinearLayout(getContext());
buttonsContainer.setOrientation(LinearLayout.HORIZONTAL);
var params = LayoutUtils.createFrame(-2, -2);
params.gravity = Gravity.BOTTOM | Gravity.CENTER_HORIZONTAL;
params.setMargins(0, 0, 0, dp2px(12f));
container.addView(buttonsContainer, params);
var addNewTheme = new FloatingActionButton(getContext());
addNewTheme.setImageResource(getIdentifier("ic_add_24", "drawable"));
addNewTheme.setBackgroundTintList(ColorStateList.valueOf(getAccentColor()));
addNewTheme.setOnClickListener(v -> {
EditTextDialog.create(v.getContext(), "Новая тема", "Название новой темы", null, (dialog, which, editText) -> {
var name = editText.getText().toString();
if (TextUtils.isEmpty(name)) {
ToastUtils.a("Название не может быть пустым");
return;
}
try {
mManager.create(requireActivity(), name);
mAdapter.notifyDataSetChanged();
} catch (IOException | JSONException e) {
e.printStackTrace();
ToastUtils.a("Не удалось изменить название темы");
}
});
});
buttonsContainer.addView(addNewTheme, LayoutUtils.createLinear(-2, -2));
buttonsContainer.addView(new View(getContext()), LayoutUtils.createLinear(dp2px(5), 0));
var importTheme = new FloatingActionButton(getContext());
importTheme.setImageResource(getIdentifier("ic_import", "drawable"));
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");
});
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;
}
}
@@ -0,0 +1,26 @@
package ru.vtosters.lite.ui.views;
import android.content.Context;
import android.util.AttributeSet;
import android.view.MotionEvent;
import androidx.appcompat.widget.AppCompatRadioButton;
public class NoTouchRadioButton extends AppCompatRadioButton {
public NoTouchRadioButton(Context context) {
super(context);
}
public NoTouchRadioButton(Context context, AttributeSet attrs) {
super(context, attrs);
}
public NoTouchRadioButton(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override
public boolean dispatchTouchEvent(MotionEvent event) {
return false;
}
}
@@ -0,0 +1,95 @@
package ru.vtosters.lite.ui.views;
import android.content.Context;
import android.os.Parcel;
import android.os.Parcelable;
import android.util.AttributeSet;
import android.view.View;
import android.widget.Checkable;
import androidx.appcompat.widget.AppCompatImageButton;
public class VTLCheckableButton extends AppCompatImageButton implements Checkable {
private boolean mIsChecked;
public VTLCheckableButton(Context context) {
super(context);
}
public VTLCheckableButton(Context context, AttributeSet attributeSet) {
super(context, attributeSet);
}
public VTLCheckableButton(Context context, AttributeSet attributeSet, int i) {
super(context, attributeSet, i);
}
@Override
public Parcelable onSaveInstanceState() {
var state = new CustomState(super.onSaveInstanceState());
state.setChecked(mIsChecked);
return state;
}
@Override
public void onRestoreInstanceState(Parcelable state) {
if (state instanceof CustomState) {
var customState = ((CustomState) state);
super.onRestoreInstanceState(customState.getSuperState());
mIsChecked = customState.isChecked();
return;
}
super.onRestoreInstanceState(state);
}
@Override
public boolean isChecked() {
return this.mIsChecked;
}
@Override
public void setChecked(boolean checked) {
mIsChecked = checked;
refreshDrawableState();
}
@Override
public int[] onCreateDrawableState(int extraSpace) {
return mIsChecked
? View.mergeDrawableStates(super.onCreateDrawableState(extraSpace + 1), new int[] { android.R.attr.state_checked })
: super.onCreateDrawableState(extraSpace);
}
@Override
public void toggle() {
setChecked(!mIsChecked);
}
static class CustomState extends View.BaseSavedState {
private boolean checked;
public CustomState(Parcelable source) {
super(source);
}
public CustomState(Parcel source) {
super(source);
checked = source.readInt() != 0;
}
public boolean isChecked() {
return checked;
}
public void setChecked(boolean checked) {
this.checked = checked;
}
@Override
public void writeToParcel(Parcel out, int flags) {
super.writeToParcel(out, flags);
out.writeInt(checked ? 1 : 0);
}
}
}
@@ -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();
}
}
@@ -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();
}
@@ -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 {
@@ -0,0 +1,70 @@
package ru.vtosters.lite.ui.vkui;
import android.content.Context;
import android.graphics.drawable.Drawable;
import android.view.View;
import androidx.annotation.ColorRes;
import androidx.annotation.Nullable;
import androidx.annotation.Px;
import androidx.recyclerview.widget.RecyclerView;
import com.vk.core.dialogs.actionspopup.ActionsPopup;
import com.vk.core.dialogs.adapter.ModalAdapter;
public class VKUIActionPopup {
private final ActionsPopup.b mLocalBuilder;
public VKUIActionPopup(Context context, View anchorView, boolean anchor, @ColorRes int color) {
this.mLocalBuilder = new ActionsPopup.b(anchorView, anchor, color).a(context);
}
public VKUIActionPopup setItem(String title, @Nullable Drawable icon, boolean z, onClickListener clickListener) {
mLocalBuilder.a(title, icon, z, () -> {
clickListener.onClick();
return null;
});
return this;
}
public VKUIActionPopup setItem(String title, @Nullable Drawable icon, onClickListener clickListener) {
return setItem(title, icon, false, clickListener);
}
public VKUIActionPopup setItem(String title, onClickListener clickListener) {
return setItem(title, null, false, clickListener);
}
public VKUIActionPopup setActionCheckIcon(Drawable actionCheckItem) {
mLocalBuilder.a(actionCheckItem);
return this;
}
public VKUIActionPopup setMaxWidth(@Px int maxWidth) {
mLocalBuilder.a(Integer.valueOf(maxWidth));
return this;
}
public VKUIActionPopup setAdapter(ModalAdapter adapter) {
mLocalBuilder.a((ModalAdapter<Object>) adapter);
return this;
}
public VKUIActionPopup setAdapter(RecyclerView.Adapter adapter) {
mLocalBuilder.a(adapter);
return this;
}
public ActionsPopup create() {
return mLocalBuilder.a();
}
public ActionsPopup create(boolean cancel) {
return mLocalBuilder.a(cancel);
}
public interface onClickListener {
void onClick();
}
}
@@ -0,0 +1,55 @@
package ru.vtosters.lite.ui.vkui;
import android.content.Context;
import android.util.AttributeSet;
import android.view.ContextThemeWrapper;
import android.widget.TextView;
import androidx.annotation.Nullable;
import com.vtosters.lite.R;
public class VKUIButton extends TextView /*Yes, yes, it's TextView*/ {
public static final int STYLE_PRIMARY = R.style.VKUIButton_Primary;
public static final int STYLE_PRIMARY_ROUNDED = R.style.VKUIButton_Primary_Rounded;
public static final int STYLE_PRIMARY_SMALL = R.style.VKUIButton_Primary_Small;
public static final int STYLE_PRIMARY_SMALL_FLAT = R.style.VKUIButton_Primary_Small_Flat;
public static final int STYLE_PRIMARY_MEDIUM = R.style.VKUIButton_Primary_Medium;
public static final int STYLE_PRIMARY_BIG = R.style.VKUIButton_Primary_Big;
public static final int STYLE_SECONDARY = R.style.VKUIButton_Secondary;
public static final int STYLE_SECONDARY_ROUNDED = R.style.VKUIButton_Secondary_Rounded;
public static final int STYLE_SECONDARY_SMALL = R.style.VKUIButton_Secondary_Small;
public static final int STYLE_SECONDARY_SMALL_FLAT = R.style.VKUIButton_Secondary_Small_Flat;
public static final int STYLE_SECONDARY_MEDIUM = R.style.VKUIButton_Secondary_Medium;
public static final int STYLE_SECONDARY_BIG = R.style.VKUIButton_Secondary_Big;
public static final int STYLE_MUTED = R.style.VKUIButton_Muted;
public static final int STYLE_MUTED_ROUNDED = R.style.VKUIButton_Muted_Rounded;
public static final int STYLE_TERTIARY = R.style.VKUIButton_Tertiary;
public static final int STYLE_TERTIARY_ROUNDED = R.style.VKUIButton_Tertiary_Rounded;
public static final int STYLE_OUTLINE = R.style.VKUIButton_Outline;
public static final int STYLE_OUTLINE_WHITE = R.style.VKUIButton_Outline_White;
public static final int STYLE_GREEN = R.style.VKUIButton_Green;
public static final int STYLE_COMMERCE = STYLE_GREEN;
public static final int STYLE_FLAT = R.style.VKUIButton_Flat;
public static final int STYLE_WHITE = R.style.VKUIButton_White;
public static final int STYLE_MEDIA_OVERLAY = R.style.VKUIButton_MediaOverlay;
public static final int STYLE_SELECTABLE = R.style.VKUIButton_Selectable;
public static final int STYLE_SELECTABLE_FLAT = R.style.VKUIButton_Selectable_Flat;
public VKUIButton(Context context) {
this(context, STYLE_PRIMARY);
}
public VKUIButton(Context context, int style) {
super(new ContextThemeWrapper(context, style));
}
public VKUIButton(Context context, @Nullable AttributeSet attrs) {
super(new ContextThemeWrapper(context, STYLE_PRIMARY), attrs);
}
public VKUIButton(Context context, @Nullable AttributeSet attrs, int style) {
super(new ContextThemeWrapper(context, style), attrs);
}
}
@@ -0,0 +1,24 @@
package ru.vtosters.lite.ui.vkui;
import android.content.Context;
import android.util.AttributeSet;
import android.widget.CheckBox;
public class VKUICheckBox extends CheckBox {
public VKUICheckBox(Context context) {
super(context);
}
public VKUICheckBox(Context context, AttributeSet attrs) {
super(context, attrs);
}
public VKUICheckBox(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
public VKUICheckBox(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
}
}
@@ -0,0 +1,77 @@
package ru.vtosters.lite.ui.vkui;
import android.content.Context;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.widget.EditText;
import androidx.annotation.Nullable;
import com.vk.core.drawable.VkUiDrawableHelper;
import com.vk.core.ui.themes.VKThemeHelper;
import com.vtosters.lite.R;
import ru.vtosters.lite.themes.VTLColors;
public class VKUIEditText extends EditText {
public VKUIEditText(Context context) {
super(context);
init();
}
public VKUIEditText(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public VKUIEditText(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
public VKUIEditText(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
init();
}
private void init() {
var fieldBackground = VKThemeHelper.d(R.attr.field_background);
var drawable = VkUiDrawableHelper.c.a(getContext(), fieldBackground, fieldBackground, VTLColors.getAccentColor(), fieldBackground);
setBackground(drawable);
}
@Override
public final void setBackground(Drawable background) {
super.setBackground(background);
}
@Override
public final void setBackgroundColor(int color) {
}
@Override
public final void setBackgroundResource(int resid) {
}
@Override
public void setCursorVisible(boolean visible) {
super.setCursorVisible(visible);
}
@Nullable
@Override
public Drawable getTextCursorDrawable() {
return super.getTextCursorDrawable();
}
@Override
public void setTextCursorDrawable(int textCursorDrawable) {
super.setTextCursorDrawable(textCursorDrawable);
}
@Override
public void setTextCursorDrawable(@Nullable Drawable textCursorDrawable) {
super.setTextCursorDrawable(textCursorDrawable);
}
}
@@ -0,0 +1,18 @@
package ru.vtosters.lite.ui.vkui;
import android.content.Context;
import android.util.AttributeSet;
import android.view.ContextThemeWrapper;
import androidx.appcompat.widget.AppCompatRadioButton;
public class VKUIRadioCheckbox extends AppCompatRadioButton {
public VKUIRadioCheckbox(Context context) {
super(new ContextThemeWrapper(context, android.R.style.Widget_CompoundButton_RadioButton));
}
public VKUIRadioCheckbox(Context context, AttributeSet attributeSet) {
super(new ContextThemeWrapper(context, android.R.style.Widget_CompoundButton_RadioButton), attributeSet);
}
}
@@ -0,0 +1,83 @@
package ru.vtosters.lite.ui.vkui;
import android.content.Context;
import android.graphics.drawable.Drawable;
import android.view.View;
import android.view.Window;
import androidx.annotation.DrawableRes;
import androidx.annotation.StringRes;
import androidx.fragment.app.Fragment;
import com.vk.core.dialogs.snackbar.VkSnackbar;
public class VKUISnackbar {
private VkSnackbar.a mLocalBuilder;
public VKUISnackbar(Context context, boolean darkMode) {
this.mLocalBuilder = new VkSnackbar.a(context, darkMode);
}
public VKUISnackbar setIcon(Drawable icon) {
mLocalBuilder.a(icon);
return this;
}
public VKUISnackbar setIcon(@DrawableRes int iconId) {
mLocalBuilder.b(iconId);
return this;
}
public VKUISnackbar setMessage(String message) {
mLocalBuilder.a((CharSequence) message);
return this;
}
public VKUISnackbar setMessage(@StringRes int resId) {
mLocalBuilder.a(resId);
return this;
}
public VKUISnackbar setShowTime(long time) {
mLocalBuilder.a(time);
return this;
}
public VKUISnackbar setView(View anchorView) {
mLocalBuilder.a(anchorView);
return this;
}
public VKUISnackbar setFragment(Fragment fragment) {
mLocalBuilder.a(fragment);
return this;
}
public VKUISnackbar setActionButton(String text, OnClickListener clickListener) {
mLocalBuilder.a(text, vkSnackbar -> {
clickListener.onClick(vkSnackbar);
return null;
});
return this;
}
public VKUISnackbar setActionButton(@StringRes int textId, OnClickListener clickListener) {
mLocalBuilder.a(textId, vkSnackbar -> {
clickListener.onClick(vkSnackbar);
return null;
});
return this;
}
public VkSnackbar create() {
return mLocalBuilder.d();
}
public VkSnackbar create(Window window) {
return mLocalBuilder.a(window);
}
public interface OnClickListener {
void onClick(VkSnackbar snackbar);
}
}
@@ -0,0 +1,38 @@
package ru.vtosters.lite.ui.vkui;
import android.content.Context;
import android.util.AttributeSet;
import android.view.ContextThemeWrapper;
import android.widget.TextView;
import androidx.annotation.Nullable;
import com.vtosters.lite.R;
public class VKUITextView extends TextView {
public static final int STYLE_TITLE_1 = R.style.VKUIText_Title1;
public static final int STYLE_TITLE_2 = R.style.VKUIText_Title2;
public static final int STYLE_HEADLINE = R.style.VKUIText_Headline;
public static final int STYLE_DEFAULT = R.style.VKUIText_Text;
public static final int STYLE_SUBHEAD_1 = R.style.VKUIText_Subhead1;
public static final int STYLE_SUBHEAD_2 = R.style.VKUIText_Subhead2;
public static final int STYLE_CAPTION_1 = R.style.VKUIText_Caption1;
public static final int STYLE_CAPTION_2 = R.style.VKUIText_Caption2;
public VKUITextView(Context context) {
this(context, STYLE_DEFAULT);
}
public VKUITextView(Context context, int style) {
super(new ContextThemeWrapper(context, style));
}
public VKUITextView(Context context, @Nullable AttributeSet attrs) {
super(new ContextThemeWrapper(context, STYLE_DEFAULT), attrs);
}
public VKUITextView(Context context, @Nullable AttributeSet attrs, int style) {
super(new ContextThemeWrapper(context, style), attrs);
}
}
@@ -0,0 +1,23 @@
package ru.vtosters.lite.ui.vkui;
import android.content.Context;
import android.text.TextUtils;
import androidx.appcompat.widget.SwitchCompat;
import ru.vtosters.lite.utils.AndroidUtils;
public class VKUiSwitch extends SwitchCompat {
public VKUiSwitch(Context context) {
super(context);
setEllipsize(TextUtils.TruncateAt.END);
setPadding(AndroidUtils.dp2px(8), AndroidUtils.dp2px(8), AndroidUtils.dp2px(8), AndroidUtils.dp2px(8));
setTextSize(16.0f);
}
@Override
public final void setEllipsize(TextUtils.TruncateAt where) {
super.setEllipsize(where);
}
}
@@ -71,10 +71,34 @@ public class IOUtils {
}
public static void writeToFile(File file, byte[] content) throws IOException {
if (!file.exists())
file.createNewFile();
FileOutputStream fos = new FileOutputStream(file);
fos.write(content);
}
public static void copyFileToDirectory(File dest, File targetDir) throws IOException {
if (!targetDir.exists())
targetDir.mkdirs();
var path = dest.getAbsolutePath();
var target = new File(targetDir, path.substring(path.lastIndexOf("/")));
copyFile(dest, target);
}
public static void copyFile(File dest, File target) throws IOException {
var parent = target.getParentFile();
if (!parent.exists())
parent.getParentFile().mkdirs();
writeToFile(target, readAllBytes(dest));
}
public static void copyFile(InputStream is, File target) throws IOException {
var parent = target.getParentFile();
if (!parent.exists())
parent.getParentFile().mkdirs();
writeToFile(target, readAllBytes(is));
}
public static void deleteRecursive(File f) {
deleteRecursive(f, true);
}
@@ -1,9 +1,12 @@
package ru.vtosters.lite.utils;
import android.view.ViewGroup;
import android.widget.FrameLayout;
import android.widget.LinearLayout;
import android.widget.RelativeLayout;
import androidx.constraintlayout.widget.ConstraintLayout;
public class LayoutUtils {
public static int MATCH_PARENT = -1;
@@ -21,4 +24,12 @@ public class LayoutUtils {
public static FrameLayout.LayoutParams createFrame(int w, int h) {
return new FrameLayout.LayoutParams(w, h);
}
public static ViewGroup.LayoutParams createViewGroup(int w, int h) {
return new ViewGroup.LayoutParams(w, h);
}
public static ConstraintLayout.LayoutParams createConstraint(int w, int h) {
return new ConstraintLayout.LayoutParams(w, h);
}
}
@@ -1,15 +1,12 @@
package ru.vtosters.lite.utils;
import android.app.Activity;
import android.app.Application;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.os.Handler;
import com.vk.navigation.Navigator;
import com.vtosters.lite.VKActivity;
import java.util.ArrayList;
import java.util.List;
@@ -17,40 +14,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() {
@@ -20,12 +20,9 @@ import android.content.pm.PackageManager;
import android.preference.PreferenceManager;
import com.vtosters.lite.data.Users;
import com.vtosters.lite.fragments.SettingsListFragment;
import java.security.NoSuchAlgorithmException;
import ru.vtosters.lite.ui.fragments.VTSettings;
public class Preferences {
public static SharedPreferences preferences = getGlobalContext().getSharedPreferences("com.vtosters.lite_preferences", Context.MODE_PRIVATE);
public static String VERSIONNAME = "Beta";
@@ -35,7 +32,6 @@ public class Preferences {
GmsUtils.fixGapps();
setProxy();
reloadMessagesList();
LifecycleUtils.registerActivities(application);
getInstance().autoCleaningCache();
} // VK Init
@@ -136,11 +132,6 @@ public class Preferences {
return getBoolValue("foaf", false);
}
public static Class useNewSettings() {
boolean bool = getBoolValue("useNewSettings", true);
return bool ? VTSettings.class : SettingsListFragment.class;
}
public static boolean VKUI_INJ() {
return getBoolValue("VKUI_INJ", true);
}
@@ -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);
}
}
@@ -8,7 +8,6 @@ import static ru.vtosters.lite.utils.AndroidUtils.getCenterScreenCoords;
import static ru.vtosters.lite.utils.AndroidUtils.getIdentifier;
import static ru.vtosters.lite.utils.AndroidUtils.getPrefsValue;
import static ru.vtosters.lite.utils.AndroidUtils.getResources;
import static ru.vtosters.lite.utils.Preferences.color_grishka;
import static ru.vtosters.lite.utils.Preferences.dockbar_accent;
import static ru.vtosters.lite.utils.Preferences.milkshake;
import static ru.vtosters.lite.utils.Preferences.navbar;
@@ -43,11 +42,73 @@ import com.vtosters.lite.data.ThemeTracker;
import java.util.Arrays;
import java.util.List;
import ru.vtosters.lite.themes.managers.ThemesManager;
public class ThemesUtils {
public static List<String> accentColors = Arrays.asList(
"5692d7", "528bcc", "7aa0cc", "518bcc", "6296d0", "2f68aa", "638ebf", "5181b8", "71aaeb", "4774a8", "5baaf4", "4186c8", "add3ff", "4774a8", "718198", "5a9eff", "99a2ad", "74a2d6", "e9eef3", "dfe3e7", "eff1f3", "5c9ce6", "4986cc", "4680c2"
// temporary added colors with prefix "ff" for testing
public static List<Integer> accentColors = Arrays.asList(
Color.parseColor("#2688eb"),
Color.parseColor("#ff2688eb"),
Color.parseColor("#529ef4"),
Color.parseColor("#ff529ef4"),
Color.parseColor("#2d81e0"),
Color.parseColor("#ff2d81e0"),
Color.parseColor("#2975cc"),
Color.parseColor("#ff2975cc"),
Color.parseColor("#3f8ae0"),
Color.parseColor("#ff3f8ae0"),
Color.parseColor("#5692d7"),
Color.parseColor("#ff5692d7"),
Color.parseColor("#528bcc"),
Color.parseColor("#ff528bcc"),
Color.parseColor("#7aa0cc"),
Color.parseColor("#ff7aa0cc"),
Color.parseColor("#518bcc"),
Color.parseColor("#ff518bcc"),
Color.parseColor("#6296d0"),
Color.parseColor("#ff6296d0"),
Color.parseColor("#2f68aa"),
Color.parseColor("#ff2f68aa"),
Color.parseColor("#638ebf"),
Color.parseColor("#ff638ebf"),
Color.parseColor("#5181b8"),
Color.parseColor("#ff5181b8"),
Color.parseColor("#71aaeb"),
Color.parseColor("#ff71aaeb"),
Color.parseColor("#4774a8"),
Color.parseColor("#ff4774a8"),
Color.parseColor("#5baaf4"),
Color.parseColor("#ff5baaf4"),
Color.parseColor("#4186c8"),
Color.parseColor("#ff4186c8"),
Color.parseColor("#add3ff"),
Color.parseColor("#ffadd3ff"),
Color.parseColor("#718198"),
Color.parseColor("#ff718198"),
Color.parseColor("#5a9eff"),
Color.parseColor("#ff5a9eff"),
Color.parseColor("#99a2ad"),
Color.parseColor("#ff99a2ad"),
Color.parseColor("#74a2d6"),
Color.parseColor("#ff74a2d6"),
Color.parseColor("#e9eef3"),
Color.parseColor("#ffe9eef3"),
Color.parseColor("#dfe3e7"),
Color.parseColor("#ffdfe3e7"),
Color.parseColor("#eff1f3"),
Color.parseColor("#ffeff1f3"),
Color.parseColor("#5c9ce6"),
Color.parseColor("#ff5c9ce6"),
Color.parseColor("#4986cc"),
Color.parseColor("#ff4986cc"),
Color.parseColor("#4680c2"),
Color.parseColor("#ff4680c2")
);
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)
@@ -156,12 +217,14 @@ public class ThemesUtils {
} // Return needed theme for theme changer
public static int getColorFromAttr(int attr) {
if (isCustomThemeApplied()) {
var color = ThemesManager.getInstance().getCurrentTheme().getColor(attr);
if (color != -1)
return color;
}
return VKThemeHelper.d(attr);
} // Get needed attr color
public static boolean isAndroidMonet() {
return color_grishka() && Build.VERSION.SDK_INT >= 31;
}
public static void colorWriteBar(View view) {
if (!navbar()) return;
@@ -194,7 +257,7 @@ public class ThemesUtils {
if (drawable == null) return null;
if (isAndroidMonet() || isDarkTheme()) accent = getAccentColor();
if (isDarkTheme()) accent = getAccentColor();
return new RecoloredDrawable(drawable, accent);
} // Recolor vk icon to accent color
@@ -203,14 +266,11 @@ public class ThemesUtils {
public static Drawable recolorDrawableToolbar(Drawable drawable) {
if (drawable == null) return null;
if (!isAndroidMonet()) return drawable;
return new RecoloredDrawable(drawable, getHeaderText());
} // Recolor toolbar drawable to accent color
@SuppressLint("UseCompatLoadingForDrawables")
public static Drawable recolorDrawableInt(int drawable) {
if (!isAndroidMonet()) return getResources().getDrawable(drawable);
@SuppressLint("UseCompatLoadingForDrawables") Drawable res = getResources().getDrawable(drawable);
return new RecoloredDrawable(res, getAccentColor());
@@ -219,14 +279,12 @@ public class ThemesUtils {
public static ColorStateList recolorCSL(ColorStateList colorStateList) {
if (colorStateList == null) return null;
if (!isAndroidMonet()) return colorStateList;
return ColorStateList.valueOf(getAccentColor());
} // Recolor ColorStateList to accent color
@SuppressLint("UseCompatLoadingForColorStateLists")
public static ColorStateList themeCSL(Context context, int color) {
if (isColorRefAccented(color) && isAndroidMonet()) {
if (isColorRefAccented(color)) {
return ColorStateList.valueOf(getAccentColor());
}
@@ -260,6 +318,26 @@ public class ThemesUtils {
}
} // Recolor ColorStateList
public static ColorStateList themeCSL(ColorStateList csl){
try {
int unsel = csl.getColorForState(new int[] {-android.R.attr.state_selected}, Color.BLACK);
int sel = csl.getColorForState(new int[] {android.R.attr.state_selected}, Color.BLACK);
boolean isUnselAccent = isAccentedColor(unsel);
boolean isSelAccent = isAccentedColor(sel);
if (isUnselAccent || isSelAccent) {
return new ColorStateList(new int[][] {
new int[] {android.R.attr.state_selected}, new int[] {-android.R.attr.state_selected}
}, new int[] {isSelAccent ? getAccentColor() : sel, isUnselAccent ? getAccentColor() : unsel});
}
return csl;
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
public static int darken(int color, float by) {
float[] hsl = new float[3];
ColorUtils.colorToHSL(color, hsl);
@@ -291,23 +369,17 @@ public class ThemesUtils {
}
public static boolean isAccentedColor(int target) {
return accentColors.contains(hex(target).toLowerCase());
return accentColors.contains(target);
}
@SuppressLint("NewApi")
public static int getColor(Context context, int i) {
if (isColorRefAccented(i) && isAndroidMonet()) {
if (isColorRefAccented(i)) {
return getAccentColor();
}
return context.getColor(i);
} // Android Support color injector + accent color checker
public static int getColor2(int i) {
if (isColorRefAccented(i) && isAndroidMonet()) {
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;
@@ -355,6 +427,10 @@ public class ThemesUtils {
return String.format("#%06X", Integer.valueOf(i & 16777215));
} // Get color as hex string
public static String hex2(int i) {
return String.format("%06X", Integer.valueOf(i & 16777215));
}
public static int getNavigationHeight(int Default) {
int VKME = R.dimen.design_bottom_sheet_peek_height_min;
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:height="28dp"
android:width="28dp"
android:viewportWidth="28.0"
android:viewportHeight="28.0">
<path
android:fillColor="#FF000000"
android:pathData="M24.1662,15.6852C24.9586,15.4046 25.7226,16.1675 25.4431,16.9603C24.8744,18.5739 23.9978,20.0062 22.8112,21.1886C18.393,25.6068 11.2319,25.6061 6.8145,21.1888C2.3952,16.7721 2.3952,9.6093 6.8143,5.1906C7.9987,4.0061 9.4314,3.1289 11.0417,2.5592C11.8346,2.2787 12.5985,3.0428 12.3179,3.8357C11.1241,7.2082 11.9241,10.9693 14.4782,13.5252C17.0322,16.0787 20.7953,16.879 24.1662,15.6852ZM13.064,14.9394C10.4879,12.3616 9.3792,8.777 9.9012,5.2958C9.2915,5.6653 8.7316,6.1016 8.2284,6.6048C4.5906,10.2424 4.5906,16.1386 8.2285,19.7744C11.8651,23.4109 17.7599,23.4115 21.3982,19.7731C21.9025,19.2706 22.339,18.7114 22.7083,18.1016C19.227,18.6242 15.6403,17.5154 13.064,14.9394Z"
android:strokeColor="#00000000"
android:strokeWidth="1.0"
android:fillType="nonZero" />
</vector>
@@ -0,0 +1,29 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:height="28dp"
android:width="28dp"
android:viewportWidth="28.0"
android:viewportHeight="28.0">
<path
android:fillColor="#FF000000"
android:pathData="m15,2c0,-0.5523 -0.4477,-1 -1,-1s-1,0.4477 -1,1v3c0,0.5523 0.4477,1 1,1s1,-0.4477 1,-1z" />
<path
android:fillColor="#FF000000"
android:pathData="m15,23c0,-0.5523 -0.4477,-1 -1,-1s-1,0.4477 -1,1v3c0,0.5523 0.4477,1 1,1s1,-0.4477 1,-1z" />
<path
android:fillColor="#FF000000"
android:pathData="m23.8923,20.866c0.4783,0.2762 1.0899,0.1123 1.366,-0.366 0.2762,-0.4783 0.1123,-1.0899 -0.366,-1.366l-2.5981,-1.5c-0.4782,-0.2762 -1.0898,-0.1123 -1.366,0.366 -0.2761,0.4783 -0.1122,1.0899 0.366,1.366z" />
<path
android:fillColor="#FF000000"
android:pathData="m5.7058,10.366c0.4783,0.2762 1.0899,0.1123 1.366,-0.366 0.2761,-0.4783 0.1123,-1.0899 -0.366,-1.366l-2.5981,-1.5c-0.4783,-0.2761 -1.0899,-0.1123 -1.366,0.366s-0.1123,1.0899 0.366,1.366z" />
<path
android:fillColor="#FF000000"
android:pathData="m2.7417,20.5c-0.2761,-0.4783 -0.1123,-1.0899 0.366,-1.366l2.5981,-1.5c0.4783,-0.2762 1.0899,-0.1123 1.366,0.366s0.1123,1.0899 -0.366,1.366l-2.5981,1.5c-0.4783,0.2762 -1.0899,0.1123 -1.366,-0.366z" />
<path
android:fillColor="#FF000000"
android:pathData="m20.9282,10c-0.2761,-0.4783 -0.1122,-1.0899 0.366,-1.366l2.5981,-1.5c0.4783,-0.2761 1.0899,-0.1123 1.366,0.366 0.2762,0.4783 0.1123,1.0899 -0.366,1.366l-2.5981,1.5c-0.4782,0.2762 -1.0898,0.1123 -1.366,-0.366z" />
<path
android:fillColor="#FF000000"
android:pathData="m14,8.0313c-3.2964,0 -5.9688,2.6723 -5.9688,5.9688s2.6723,5.9688 5.9688,5.9688 5.9688,-2.6724 5.9688,-5.9688 -2.6724,-5.9688 -5.9688,-5.9688zM9.9922,14c0,-2.3546 1.6532,-4.0078 4.0078,-4.0078s4.0156,1.6532 4.0156,4.0078 -1.661,4 -4.0156,4 -4.0078,-1.6454 -4.0078,-4z"
android:fillType="evenOdd" />
</vector>
Binary file not shown.

After

Width:  |  Height:  |  Size: 326 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 578 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 549 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 356 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 238 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 727 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 369 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 397 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 732 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

@@ -0,0 +1,76 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:aapt="http://schemas.android.com/aapt"
android:name="moon_to_sun_animation"
android:height="28dp"
android:width="28dp"
android:viewportWidth="28.0"
android:viewportHeight="28.0">
<group
android:name="corepath"
android:pivotX="14.0"
android:pivotY="14.0"
android:rotation="-180.0">
<path
android:name="1"
android:pathData="M 14 5 L 14 2"
android:strokeColor="#FF979797"
android:trimPathStart="0.5"
android:trimPathEnd="0.5"
android:strokeLineCap="round"
android:strokeAlpha="0.0"
android:fillType="evenOdd" />
<path
android:name="2"
android:pathData="M 21.794 9.5 L 24.392 8"
android:strokeColor="#FF979797"
android:trimPathStart="0.5"
android:trimPathEnd="0.5"
android:strokeLineCap="round"
android:strokeAlpha="0.0"
android:fillType="evenOdd" />
<path
android:name="3"
android:pathData="M 21.794 18.5 L 24.392 20"
android:strokeColor="#FF979797"
android:trimPathStart="0.5"
android:trimPathEnd="0.5"
android:strokeLineCap="round"
android:strokeAlpha="0.0"
android:fillType="evenOdd" />
<path
android:name="4"
android:pathData="M 14 26 L 14 23"
android:strokeColor="#FF979797"
android:trimPathStart="0.5"
android:trimPathEnd="0.5"
android:strokeLineCap="round"
android:strokeAlpha="0.0"
android:fillType="evenOdd" />
<path
android:name="5"
android:pathData="M 3.608 20 L 6.206 18.5"
android:strokeColor="#FF979797"
android:trimPathStart="0.5"
android:trimPathEnd="0.5"
android:strokeLineCap="round"
android:strokeAlpha="0.0"
android:fillType="evenOdd" />
<path
android:name="6"
android:pathData="M 3.608 8 L 6.206 9.5"
android:strokeColor="#FF979797"
android:trimPathStart="0.5"
android:trimPathEnd="0.5"
android:strokeLineCap="round"
android:strokeAlpha="0.0"
android:fillType="evenOdd" />
<path
android:name="mainpath"
android:pathData="M 16.624 24.497 C 21 23 23.5 19 23.5 14.75 C 23.5 9.089 18.911 4.5 13.25 4.5 C 9 4.5 5 7 3.5 11.376 C 5.314 10.762 7.113 10.625 8.898 10.964 C 11.575 11.472 13.232 12.769 14.23 13.771 C 15.227 14.773 16.486 16.343 17.029 18.996 C 17.391 20.764 17.256 22.597 16.624 24.497 Z"
android:strokeColor="#FF979797"
android:strokeWidth="2.0"
android:strokeLineJoin="round"
android:fillType="evenOdd" />
</group>
</vector>
@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<objectAnimator xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:aapt="http://schemas.android.com/aapt"
android:interpolator="@android:interpolator/fast_out_slow_in"
android:duration="400"
android:valueFrom="M 16.624 24.497 C 21 23 23.5 19 23.5 14.75 C 23.5 9.089 18.911 4.5 13.25 4.5 C 9 4.5 5 7 3.5 11.376 C 5.314 10.762 7.113 10.625 8.898 10.964 C 11.575 11.472 13.232 12.769 14.23 13.771 C 15.227 14.773 16.486 16.343 17.029 18.996 C 17.391 20.764 17.256 22.597 16.624 24.497 Z"
android:valueTo="M 14 19 C 16.761 19 19 16.761 19 14 C 19 11.239 16.761 9 14 9 C 11.239 9 9 11.239 9 14 C 9 14.724 9.154 15.413 9.431 16.034 C 9.682 16.597 10.034 17.105 10.465 17.536 C 10.895 17.966 11.403 18.318 11.965 18.569 C 12.587 18.846 13.275 19 14 19 Z"
android:valueType="pathType"
android:propertyName="pathData" />
@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<objectAnimator xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:aapt="http://schemas.android.com/aapt"
android:interpolator="@android:interpolator/fast_out_slow_in"
android:duration="500"
android:valueFrom="-180"
android:valueTo="-300"
android:valueType="floatType"
android:propertyName="rotation" />
@@ -0,0 +1,32 @@
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:aapt="http://schemas.android.com/aapt">
<objectAnimator
android:interpolator="@android:interpolator/fast_out_slow_in"
android:duration="500"
android:valueFrom="0.5"
android:valueTo="1"
android:valueType="floatType"
android:propertyName="trimPathEnd" />
<objectAnimator
android:interpolator="@android:interpolator/fast_out_slow_in"
android:duration="500"
android:valueFrom="0.5"
android:valueTo="0"
android:valueType="floatType"
android:propertyName="trimPathStart" />
<objectAnimator
android:interpolator="@android:interpolator/fast_out_slow_in"
android:duration="200"
android:valueFrom="0"
android:valueTo="2"
android:valueType="floatType"
android:propertyName="strokeWidth" />
<objectAnimator
android:interpolator="@android:interpolator/fast_out_slow_in"
android:duration="200"
android:valueFrom="0"
android:valueTo="1"
android:valueType="floatType"
android:propertyName="strokeAlpha" />
</set>
@@ -0,0 +1,32 @@
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:aapt="http://schemas.android.com/aapt">
<objectAnimator
android:interpolator="@android:interpolator/fast_out_slow_in"
android:duration="500"
android:valueFrom="0.5"
android:valueTo="0"
android:valueType="floatType"
android:propertyName="trimPathStart" />
<objectAnimator
android:interpolator="@android:interpolator/fast_out_slow_in"
android:duration="500"
android:valueFrom="0.5"
android:valueTo="1"
android:valueType="floatType"
android:propertyName="trimPathEnd" />
<objectAnimator
android:interpolator="@android:interpolator/fast_out_slow_in"
android:duration="200"
android:valueFrom="0"
android:valueTo="2"
android:valueType="floatType"
android:propertyName="strokeWidth" />
<objectAnimator
android:interpolator="@android:interpolator/fast_out_slow_in"
android:duration="200"
android:valueFrom="0"
android:valueTo="1"
android:valueType="floatType"
android:propertyName="strokeAlpha" />
</set>
@@ -0,0 +1,32 @@
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:aapt="http://schemas.android.com/aapt">
<objectAnimator
android:interpolator="@android:interpolator/fast_out_slow_in"
android:duration="500"
android:valueFrom="0.5"
android:valueTo="0"
android:valueType="floatType"
android:propertyName="trimPathStart" />
<objectAnimator
android:interpolator="@android:interpolator/fast_out_slow_in"
android:duration="500"
android:valueFrom="0.5"
android:valueTo="1"
android:valueType="floatType"
android:propertyName="trimPathEnd" />
<objectAnimator
android:interpolator="@android:interpolator/fast_out_slow_in"
android:duration="200"
android:valueFrom="0"
android:valueTo="2"
android:valueType="floatType"
android:propertyName="strokeWidth" />
<objectAnimator
android:interpolator="@android:interpolator/fast_out_slow_in"
android:duration="200"
android:valueFrom="0"
android:valueTo="1"
android:valueType="floatType"
android:propertyName="strokeAlpha" />
</set>

Some files were not shown because too many files have changed in this diff Show More