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.components.DockBarEditorManager;
import ru.vtosters.lite.ui.items.DockBarTab; import ru.vtosters.lite.ui.items.DockBarTab;
import ru.vtosters.lite.utils.ThemesUtils;
public class DockBarInjector { public class DockBarInjector {
private static final DockBarEditorManager sManager = DockBarEditorManager.getInstance(); private static final DockBarEditorManager sManager = DockBarEditorManager.getInstance();
@@ -67,6 +68,7 @@ public class DockBarInjector {
@SuppressLint("RestrictedApi") @SuppressLint("RestrictedApi")
public static void inject(BottomNavigationView navigationView) { public static void inject(BottomNavigationView navigationView) {
navigationView.setBackgroundColor(ThemesUtils.getBackgroundContent());
Menu menu = navigationView.getMenu(); Menu menu = navigationView.getMenu();
menu.clear(); menu.clear();
try { try {
@@ -86,14 +88,11 @@ public class DockBarInjector {
e.printStackTrace(); e.printStackTrace();
} }
for (DockBarTab tab : sManager.getSelectedTabs()) { for (DockBarTab tab : sManager.getSelectedTabs())
MenuItem add = menu.add(0, tab.id, 0, tab.titleID); menu.add(0, tab.id, 0, tab.titleID)
Drawable drawable = getResources().getDrawable(tab.iconID); .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)) { if (getBoolValue("dockbar_tab_titles", true)) {
var menuView = (BottomNavigationMenuView) navigationView.getChildAt(0); var menuView = (BottomNavigationMenuView) navigationView.getChildAt(0);
for (int i = 0; i < menuView.getChildCount(); i++) { 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.hasVerification;
import static ru.vtosters.lite.utils.Preferences.podcastcatalog; import static ru.vtosters.lite.utils.Preferences.podcastcatalog;
import static ru.vtosters.lite.utils.Preferences.postsrecomm; 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.isDeveloper;
import static ru.vtosters.lite.utils.VTVerifications.isPrometheus; import static ru.vtosters.lite.utils.VTVerifications.isPrometheus;
import static ru.vtosters.lite.utils.VTVerifications.isVerified; import static ru.vtosters.lite.utils.VTVerifications.isVerified;
import android.graphics.Color;
import android.util.Log; import android.util.Log;
import com.vk.core.network.Network; import com.vk.core.network.Network;
@@ -33,6 +37,7 @@ import org.json.JSONObject;
import java.io.IOException; import java.io.IOException;
import java.text.ParseException; import java.text.ParseException;
import java.util.Locale;
import java.util.Objects; import java.util.Objects;
import java.util.Random; import java.util.Random;
@@ -63,6 +68,17 @@ public class JsonInjectors {
newItem.put(vktesters(id)); 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); orig.put("buttons", newItem);
return orig; return orig;
@@ -18,7 +18,6 @@ import ru.vtosters.lite.ui.dialogs.ServerDialog;
import ru.vtosters.lite.ui.dialogs.Start; import ru.vtosters.lite.ui.dialogs.Start;
public class MainActivityInjector { public class MainActivityInjector {
public static void inject(Activity activity) { public static void inject(Activity activity) {
setNeededTheme(activity); setNeededTheme(activity);
ServerDialog.activity = activity; ServerDialog.activity = activity;
@@ -35,7 +34,6 @@ public class MainActivityInjector {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
NotificationChannels.createChannels(); NotificationChannels.createChannels();
} }
// VKIDProtection.alert(activity); // 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.getBoolValue;
import static ru.vtosters.lite.utils.Preferences.milkshake; import static ru.vtosters.lite.utils.Preferences.milkshake;
import static ru.vtosters.lite.utils.Preferences.superapp; 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.videonewcatalog;
import static ru.vtosters.lite.utils.Preferences.vkme; 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.GamesFragment;
import com.vtosters.lite.general.fragments.PhotosFragment; import com.vtosters.lite.general.fragments.PhotosFragment;
import ru.vtosters.lite.ui.fragments.VTSettings;
public class StartFragmentHook { public class StartFragmentHook {
public static Class getStartFragment() { public static Class getStartFragment() {
if (vkme()) { if (vkme()) {
@@ -55,7 +56,7 @@ public class StartFragmentHook {
case "videos": case "videos":
return videonewcatalog() ? VideoCatalogFragment.class : VideosFragment.class; return videonewcatalog() ? VideoCatalogFragment.class : VideosFragment.class;
case "settings": case "settings":
return useNewSettings(); return VTSettings.class;
case "apps": case "apps":
return AppsFragment.class; return AppsFragment.class;
case "discover": 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.getBoolValue;
import static ru.vtosters.lite.utils.Preferences.milkshake; import static ru.vtosters.lite.utils.Preferences.milkshake;
import static ru.vtosters.lite.utils.Preferences.superapp; 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.videonewcatalog;
import static ru.vtosters.lite.utils.Preferences.vkme; import static ru.vtosters.lite.utils.Preferences.vkme;
import static ru.vtosters.lite.utils.Preferences.vkme_notifs; import static ru.vtosters.lite.utils.Preferences.vkme_notifs;
@@ -43,6 +42,7 @@ import java.util.Arrays;
import java.util.List; import java.util.List;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import ru.vtosters.lite.ui.fragments.VTSettings;
import ru.vtosters.lite.ui.items.DockBarTab; import ru.vtosters.lite.ui.items.DockBarTab;
import ru.vtosters.lite.utils.AndroidUtils; import ru.vtosters.lite.utils.AndroidUtils;
@@ -261,7 +261,7 @@ public class DockBarEditorManager {
R.drawable.ic_settings_outline_28, R.drawable.ic_settings_outline_28,
R.string.menu_settings, R.string.menu_settings,
R.id.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() { public void onUpdateApplied() {
mActivity.runOnUiThread(() -> { mActivity.runOnUiThread(() -> {
// Toast.makeText(mActivity, "Обновления найдены", Toast.LENGTH_SHORT).show(); // Toast.makeText(mActivity, "Обновления найдены", Toast.LENGTH_SHORT).show();
new ModalBottomSheetWrapper(mActivity) // new ModalBottomSheetWrapper(mActivity)
.setTitle(getString("newversion") + " " + mHelper.getNewVersionName()) // .setTitle(getString("newversion") + " " + mHelper.getNewVersionName())
.setUpdateInfoView(mHelper.getUpdateDescription()) // // .setUpdateInfoView(mHelper.getUpdateDescription())
.setPositiveButton(getString("updateanddownload"), () -> { // .setPositiveButton(getString("updateanddownload"), () -> {
OTADownloader.downloadBuild(mHelper.getDownloadUrl()); // OTADownloader.downloadBuild(mHelper.getDownloadUrl());
}) // })
.show(); // .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) { if (activity != null) {
activity.runOnUiThread(() -> { activity.runOnUiThread(() -> {
VkAlertDialog.Builder builder = new VkAlertDialog.Builder(activity); // VkAlertDialog.Builder builder = new VkAlertDialog.Builder(activity);
builder.setTitle(title); // builder.setTitle(title);
builder.setMessage(message); // builder.setMessage(message);
builder.setCancelable(cancelable); // builder.setCancelable(cancelable);
builder.setPositiveButton(positiveButton, (dialogInterface, i) -> edit().putBoolean(key, false).apply()); // builder.setPositiveButton(positiveButton, (dialogInterface, i) -> edit().putBoolean(key, false).apply());
builder.setNeutralButton(neutralButton, (dialogInterface, i) -> { // builder.setNeutralButton(neutralButton, (dialogInterface, i) -> {
edit().putBoolean(key, false).apply(); // edit().putBoolean(key, false).apply();
activity.startActivity(new Intent("android.intent.action.VIEW").setData(Uri.parse(link))); // activity.startActivity(new Intent("android.intent.action.VIEW").setData(Uri.parse(link)));
}); // });
if (getBoolValue(key, true) && showForNotVerified() && showAlert) { // if (getBoolValue(key, true) && showForNotVerified() && showAlert) {
builder.show(); // 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.vk.navigation.Navigator;
import com.vtosters.lite.general.fragments.MaterialPreferenceToolbarFragment; import com.vtosters.lite.general.fragments.MaterialPreferenceToolbarFragment;
import ru.vtosters.lite.themes.managers.ThemesManager;
import ru.vtosters.lite.utils.LifecycleUtils; import ru.vtosters.lite.utils.LifecycleUtils;
public class ThemesFragment extends MaterialPreferenceToolbarFragment { public class ThemesFragment extends MaterialPreferenceToolbarFragment {
@Override @Override
@@ -27,6 +27,17 @@ public class ThemesFragment extends MaterialPreferenceToolbarFragment {
} }
private void prefs() { 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("navbar").setOnPreferenceClickListener(new restart());
findPreference("milkshake").setOnPreferenceClickListener(new restart()); findPreference("milkshake").setOnPreferenceClickListener(new restart());
findPreference("darktheme").setOnPreferenceChangeListener(new restartdark()); 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; 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.app.Activity;
import android.view.View; import android.view.View;
import android.widget.LinearLayout;
import android.widget.TextView;
import com.vk.core.dialogs.bottomsheet.ModalBottomSheet; import com.vk.core.dialogs.bottomsheet.ModalBottomSheet;
public class ModalBottomSheetWrapper { public class ModalBottomSheetWrapper<T> {
public final ModalBottomSheet.a builder; public final ModalBottomSheet.a mBuilder;
public ModalBottomSheet mBottomSheet;
public ModalBottomSheetWrapper(Activity activity) { public ModalBottomSheetWrapper(Activity activity) {
builder = new ModalBottomSheet.a(activity); mBuilder = new ModalBottomSheet.a(activity);
} }
public ModalBottomSheetWrapper setTitle(String title) { public T setTitle(String title) {
builder.d(title); mBuilder.d(title);
return this; return (T) this;
} }
@SuppressLint("SetTextI18n") public T setClickableMessage(
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(
String text, String text,
Runnable callback Runnable callback
) { ) {
builder.a(text, listener -> callback.run()); mBuilder.a(text, listener -> callback.run());
return this; return (T) this;
} }
public ModalBottomSheetWrapper setPositiveButton( public T setPositiveButton(
String text, String text,
Runnable callback Runnable callback
) { ) {
builder.a(text, kostil -> callback.run()); mBuilder.a(text, kostil -> callback.run());
return this; return (T) this;
} }
public ModalBottomSheetWrapper setView(View view) { public T setView(View view) {
builder.a(view); mBuilder.a(view);
return this; return (T) this;
} }
public ModalBottomSheet show() { public ModalBottomSheet show(String tag) {
return builder.a(""); return mBuilder.a(tag);
} }
public ModalBottomSheet show(boolean isCancellable) { public ModalBottomSheet show(String tag, boolean isCancellable) {
var sheet = builder.a(""); mBottomSheet = mBuilder.a(tag);
sheet.setCancelable(isCancellable); mBottomSheet.setCancelable(isCancellable);
return sheet; return mBottomSheet;
}
public void dismiss() {
mBottomSheet.dismiss();
} }
} }
@@ -8,7 +8,7 @@ import android.view.View;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
public class VBottomSheetBuilder { 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); ModalBottomSheetWrapper builder = new ModalBottomSheetWrapper(activity);
builder.setView(content.view); builder.setView(content.view);
@@ -20,7 +20,7 @@ public class VBottomSheetBuilder {
new Handler(Looper.getMainLooper()).post(content.button.action); new Handler(Looper.getMainLooper()).post(content.button.action);
}); // Positive }); // Positive
builder.show(); // Build+Show builder.show(tag); // Build+Show
} }
public static class VBSContent { 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 { public static void writeToFile(File file, byte[] content) throws IOException {
if (!file.exists())
file.createNewFile();
FileOutputStream fos = new FileOutputStream(file); FileOutputStream fos = new FileOutputStream(file);
fos.write(content); 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) { public static void deleteRecursive(File f) {
deleteRecursive(f, true); deleteRecursive(f, true);
} }
@@ -1,9 +1,12 @@
package ru.vtosters.lite.utils; package ru.vtosters.lite.utils;
import android.view.ViewGroup;
import android.widget.FrameLayout; import android.widget.FrameLayout;
import android.widget.LinearLayout; import android.widget.LinearLayout;
import android.widget.RelativeLayout; import android.widget.RelativeLayout;
import androidx.constraintlayout.widget.ConstraintLayout;
public class LayoutUtils { public class LayoutUtils {
public static int MATCH_PARENT = -1; public static int MATCH_PARENT = -1;
@@ -21,4 +24,12 @@ public class LayoutUtils {
public static FrameLayout.LayoutParams createFrame(int w, int h) { public static FrameLayout.LayoutParams createFrame(int w, int h) {
return new FrameLayout.LayoutParams(w, 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; package ru.vtosters.lite.utils;
import android.app.Activity; import android.app.Activity;
import android.app.Application;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.content.pm.PackageManager; import android.content.pm.PackageManager;
import android.os.Bundle;
import android.os.Handler; import android.os.Handler;
import com.vk.navigation.Navigator; import com.vk.navigation.Navigator;
import com.vtosters.lite.VKActivity;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
@@ -17,41 +14,13 @@ import java.util.List;
public class LifecycleUtils { public class LifecycleUtils {
private static final List<Activity> activities = new ArrayList<>(); private static final List<Activity> activities = new ArrayList<>();
public static void registerActivities(Application application) { public static void addActivity(Activity activity) {
application.registerActivityLifecycleCallbacks(new Application.ActivityLifecycleCallbacks() {
@Override // android.app.Application.ActivityLifecycleCallbacks
public void onActivityPaused(Activity 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); activities.add(activity);
} }
}
@Override // android.app.Application.ActivityLifecycleCallbacks public static void removeActivity(Activity activity) {
public void onActivityDestroyed(Activity activity) {
activities.remove(activity); activities.remove(activity);
} }
});
}
public static Activity getCurrentActivity() { public static Activity getCurrentActivity() {
return activities.size() > 0 ? activities.get(0) : null; return activities.size() > 0 ? activities.get(0) : null;
@@ -20,12 +20,9 @@ import android.content.pm.PackageManager;
import android.preference.PreferenceManager; import android.preference.PreferenceManager;
import com.vtosters.lite.data.Users; import com.vtosters.lite.data.Users;
import com.vtosters.lite.fragments.SettingsListFragment;
import java.security.NoSuchAlgorithmException; import java.security.NoSuchAlgorithmException;
import ru.vtosters.lite.ui.fragments.VTSettings;
public class Preferences { public class Preferences {
public static SharedPreferences preferences = getGlobalContext().getSharedPreferences("com.vtosters.lite_preferences", Context.MODE_PRIVATE); public static SharedPreferences preferences = getGlobalContext().getSharedPreferences("com.vtosters.lite_preferences", Context.MODE_PRIVATE);
public static String VERSIONNAME = "Beta"; public static String VERSIONNAME = "Beta";
@@ -35,7 +32,6 @@ public class Preferences {
GmsUtils.fixGapps(); GmsUtils.fixGapps();
setProxy(); setProxy();
reloadMessagesList(); reloadMessagesList();
LifecycleUtils.registerActivities(application);
getInstance().autoCleaningCache(); getInstance().autoCleaningCache();
} // VK Init } // VK Init
@@ -136,11 +132,6 @@ public class Preferences {
return getBoolValue("foaf", false); return getBoolValue("foaf", false);
} }
public static Class useNewSettings() {
boolean bool = getBoolValue("useNewSettings", true);
return bool ? VTSettings.class : SettingsListFragment.class;
}
public static boolean VKUI_INJ() { public static boolean VKUI_INJ() {
return getBoolValue("VKUI_INJ", true); return getBoolValue("VKUI_INJ", true);
} }
@@ -11,46 +11,83 @@ public class ReflectionUtils {
* Gets a class with specific name. * Gets a class with specific name.
* Supports smali type (Lpackage/Class;) * Supports smali type (Lpackage/Class;)
*/ */
public static Class<?> getClass(String name) throws ClassNotFoundException { public static Class<?> getClass(String name)
throws ClassNotFoundException {
if (name.contains(";")) if (name.contains(";"))
name = name.substring(1, name.length() - 1).replace("/", "."); name = name.substring(1, name.length() - 1).replace("/", ".");
return Class.forName(name); return Class.forName(name);
} }
/** public static Field getDeclaredField(Class<?> clz, String fieldName)
* Gets a declared field with specific name. throws NoSuchFieldException {
**/
public static Field getDeclaredField(Class<?> clz, String fieldName) throws NoSuchFieldException {
Field field = clz.getDeclaredField(fieldName); Field field = clz.getDeclaredField(fieldName);
field.setAccessible(true); field.setAccessible(true);
return field; return field;
} }
public static Field getDeclaredField(String className, String fieldName) throws NoSuchFieldException, ClassNotFoundException { public static Field getDeclaredField(String className, String fieldName)
Field field = getClass(className).getDeclaredField(fieldName); throws NoSuchFieldException, ClassNotFoundException {
field.setAccessible(true); return getDeclaredField(getClass(className), fieldName);
return field;
} }
/** public static Field getDeclaredFieldRecursive(Class<?> clz, String fieldName)
* Gets a field with specific name. throws NoSuchFieldException {
**/ Field field = null;
public static Field getField(Class<?> clz, String fieldName) throws NoSuchFieldException { 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 field = clz.getField(fieldName);
field.setAccessible(true); field.setAccessible(true);
return field; return field;
} }
public static Field getField(String className, String fieldName) throws NoSuchFieldException, ClassNotFoundException { public static Field getField(String className, String fieldName)
Field field = getClass(className).getField(fieldName); throws NoSuchFieldException, ClassNotFoundException {
field.setAccessible(true); return getField(getClass(className), fieldName);
}
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; return field;
throw new NoSuchFieldException(fieldName);
} }
/** public static Field getFieldRecursive(String className, String fieldName)
* Gets a non-static field value throws ClassNotFoundException, NoSuchFieldException {
*/ return getFieldRecursive(getClass(className), fieldName);
public static <T> T getObjectField(Class<?> clz, String fieldName, Object instance) throws NoSuchFieldException, IllegalAccessException { }
public static <T> T getObjectField(Class<?> clz, String fieldName, Object instance)
throws NoSuchFieldException, IllegalAccessException {
Field field; Field field;
try { try {
field = getField(clz, fieldName); field = getField(clz, fieldName);
@@ -60,43 +97,50 @@ public class ReflectionUtils {
return (T) field.get(instance); return (T) field.get(instance);
} }
public static <T> T getObjectField(String className, String fieldName, Object instance) throws NoSuchFieldException, IllegalAccessException, ClassNotFoundException { public static <T> T getObjectField(String className, String fieldName, Object instance)
Field field; throws NoSuchFieldException, IllegalAccessException, ClassNotFoundException {
try { return getObjectField(getClass(className), fieldName, instance);
field = getField(className, fieldName);
} catch (NoSuchFieldException e) {
field = getDeclaredField(className, fieldName);
}
return (T) field.get(instance);
} }
/** public static void setObjectField(Class<?> clz, String fieldName, Object instance, Object value)
* Sets a value for non-static field throws NoSuchFieldException, IllegalAccessException {
*/
public static void setObjectField(Class<?> clz, String fieldName, Object instance, Object value) throws NoSuchFieldException, IllegalAccessException {
Field field; Field field;
try { try {
field = getField(clz, fieldName); field = getField(clz, fieldName);
} catch (NoSuchFieldException e) { } catch (NoSuchFieldException e) {
field = getDeclaredField(clz, fieldName); field = getDeclaredField(clz, fieldName);
} }
unfinalField(field);
field.set(instance, value); field.set(instance, value);
} }
public static void setObjectField(String className, String fieldName, Object instance, Object value) throws NoSuchFieldException, IllegalAccessException, ClassNotFoundException { public static void setObjectField(String className, String fieldName, Object instance, Object value)
Field field; throws NoSuchFieldException, IllegalAccessException, ClassNotFoundException {
try { setObjectField(getClass(className), fieldName, instance, value);
field = getField(className, fieldName);
} catch (NoSuchFieldException e) {
field = getDeclaredField(className, fieldName);
}
field.set(instance, value);
} }
/** public static <T> T getObjectStaticField(Class<?> clz, String fieldName)
* Unlocks modifying of field throws NoSuchFieldException, IllegalAccessException {
*/ return getObjectField(clz, fieldName, null);
private static void unlockField(Field field) throws NoSuchFieldException { }
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"); Field accessFlagsFld = getDeclaredField(Field.class, "accessFlags");
try { try {
int accessFlags = accessFlagsFld.getInt(field); int accessFlags = accessFlagsFld.getInt(field);
@@ -106,178 +150,60 @@ public class ReflectionUtils {
} }
} }
/** public static Method getMethod(Class<?> clz, String methodName, Class<?>... parameterTypes)
* Unlocks modifying of non-static field and sets a value for it throws NoSuchMethodException {
*/
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 {
Method method = clz.getMethod(methodName, parameterTypes); Method method = clz.getMethod(methodName, parameterTypes);
method.setAccessible(true); method.setAccessible(true);
return method; return method;
} }
public static Method getMethod(String className, String methodName, Class<?>... parameterTypes) throws ClassNotFoundException, NoSuchMethodException { public static Method getMethod(String className, String methodName, Class<?>... parameterTypes)
Method method = getClass(className).getMethod(methodName, parameterTypes); throws ClassNotFoundException, NoSuchMethodException {
method.setAccessible(true); return getMethod(getClass(className), methodName, parameterTypes);
return method;
} }
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]; Class<?>[] parameterTypes = new Class<?>[parameters.length];
for (int i = 0; i < parameters.length; i++) { for (int i = 0; i < parameters.length; i++) {
parameterTypes[i] = parameters[i].getClass(); parameterTypes[i] = parameters[i].getClass();
} }
Method method = getMethod(clz, methodName, parameterTypes); return getMethod(clz, methodName, parameterTypes);
method.setAccessible(true);
return method;
} }
public static Method getMethod(String className, String methodName, Object... parameters) throws ClassNotFoundException, NoSuchMethodException { public static Method getMethod(String className, String methodName, Object... parameters)
Class<?>[] parameterTypes = new Class<?>[parameters.length]; throws ClassNotFoundException, NoSuchMethodException {
for (int i = 0; i < parameters.length; i++) { return getMethod(getClass(className), methodName, parameters);
parameterTypes[i] = parameters[i].getClass();
}
Method method = getMethod(className, methodName, parameterTypes);
method.setAccessible(true);
return method;
} }
/** public static Method getDeclaredMethod(Class<?> clz, String methodName, Class<?>... parameterTypes)
* Gets a declared method with specific name. throws NoSuchMethodException {
**/
public static Method getDeclaredMethod(Class<?> clz, String methodName, Class<?>... parameterTypes) throws NoSuchMethodException {
Method method = clz.getDeclaredMethod(methodName, parameterTypes); Method method = clz.getDeclaredMethod(methodName, parameterTypes);
method.setAccessible(true); method.setAccessible(true);
return method; return method;
} }
public static Method getDeclaredMethod(String className, String methodName, Class<?>... parameterTypes) throws ClassNotFoundException, NoSuchMethodException { public static Method getDeclaredMethod(String className, String methodName, Class<?>... parameterTypes)
Method method = getClass(className).getDeclaredMethod(methodName, parameterTypes); throws ClassNotFoundException, NoSuchMethodException {
method.setAccessible(true); return getDeclaredMethod(getClass(className), methodName, parameterTypes);
return method;
} }
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]; Class<?>[] parameterTypes = new Class<?>[parameters.length];
for (int i = 0; i < parameters.length; i++) { for (int i = 0; i < parameters.length; i++) {
parameterTypes[i] = parameters[i].getClass(); parameterTypes[i] = parameters[i].getClass();
} }
Method method = getDeclaredMethod(clz, methodName, parameterTypes); return getDeclaredMethod(clz, methodName, parameterTypes);
method.setAccessible(true);
return method;
} }
public static Method getDeclaredMethod(String className, String methodName, Object... parameters) throws ClassNotFoundException, NoSuchMethodException { public static Method getDeclaredMethod(String className, String methodName, Object... parameters)
Class<?>[] parameterTypes = new Class<?>[parameters.length]; throws ClassNotFoundException, NoSuchMethodException {
for (int i = 0; i < parameters.length; i++) { return getDeclaredMethod(getClass(className), methodName, parameters);
parameterTypes[i] = parameters[i].getClass();
}
Method method = getDeclaredMethod(className, methodName, parameterTypes);
method.setAccessible(true);
return method;
} }
/** public static <T> T invokeMethod(Class<?> clz, String methodName, Object instance, Class<?>[] parameterTypes, Object... parameters)
* Invokes a non-static method 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; Method method;
try { try {
method = getMethod(clz, methodName, parameterTypes); method = getMethod(clz, methodName, parameterTypes);
@@ -287,92 +213,42 @@ public class ReflectionUtils {
return (T) method.invoke(instance, 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 { public static <T> T invokeMethod(String className, String methodName, Object instance, Class<?>[] parameterTypes, Object... parameters)
Method method; throws NoSuchMethodException, InvocationTargetException, IllegalAccessException, ClassNotFoundException {
try { return invokeMethod(getClass(className), methodName, instance, parameterTypes, parameters);
method = getMethod(className, methodName, parameterTypes);
} catch (NoSuchMethodException e) {
method = getDeclaredMethod(className, methodName, parameters);
}
return (T) method.invoke(instance, 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]; Class<?>[] parameterTypes = new Class<?>[parameters.length];
for (int i = 0; i < parameters.length; i++) { for (int i = 0; i < parameters.length; i++) {
parameterTypes[i] = parameters[i].getClass(); parameterTypes[i] = parameters[i].getClass();
} }
Method method; return invokeMethod(clz, methodName, instance, parameterTypes, parameters);
try {
method = getMethod(clz, methodName, parameterTypes);
} catch (NoSuchMethodException e) {
method = getDeclaredMethod(clz, 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 { public static <T> T invokeMethod(String className, String methodName, Object instance, Object... parameters)
Class<?>[] parameterTypes = new Class<?>[parameters.length]; throws NoSuchMethodException, InvocationTargetException, IllegalAccessException, ClassNotFoundException {
for (int i = 0; i < parameters.length; i++) { return invokeMethod(getClass(className), methodName, instance, parameters);
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 invokeStaticMethod(Class<?> clz, String methodName, Class<?>[] parameterTypes, Object... parameters)
* Invokes a static method throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
*/ return invokeMethod(clz, methodName, null, parameterTypes, parameters);
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(String className, String methodName, Class<?>[] parameterTypes, Object... parameters) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException, ClassNotFoundException { public static <T> T invokeStaticMethod(String className, String methodName, Class<?>[] parameterTypes, Object... parameters)
Method method; throws NoSuchMethodException, InvocationTargetException, IllegalAccessException, ClassNotFoundException {
try { return invokeStaticMethod(getClass(className), methodName, parameterTypes, parameters);
method = getMethod(className, methodName, parameterTypes);
} catch (NoSuchMethodException e) {
method = getDeclaredMethod(className, methodName, parameters);
}
return (T) method.invoke(null, parameters);
} }
public static <T> T invokeStaticMethod(Class<?> clz, String methodName, Object... parameters) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException { public static <T> T invokeStaticMethod(Class<?> clz, String methodName, Object... parameters)
Class<?>[] parameterTypes = new Class<?>[parameters.length]; throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
for (int i = 0; i < parameters.length; i++) { return invokeMethod(clz, methodName, null, parameters);
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(String className, String methodName, Object... parameters) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException, ClassNotFoundException { public static <T> T invokeStaticMethod(String className, String methodName, Object... parameters)
Class<?>[] parameterTypes = new Class<?>[parameters.length]; throws NoSuchMethodException, InvocationTargetException, IllegalAccessException, ClassNotFoundException {
for (int i = 0; i < parameters.length; i++) { return invokeMethod(getClass(className), methodName, null, parameters);
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);
} }
} }
@@ -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.getIdentifier;
import static ru.vtosters.lite.utils.AndroidUtils.getPrefsValue; import static ru.vtosters.lite.utils.AndroidUtils.getPrefsValue;
import static ru.vtosters.lite.utils.AndroidUtils.getResources; 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.dockbar_accent;
import static ru.vtosters.lite.utils.Preferences.milkshake; import static ru.vtosters.lite.utils.Preferences.milkshake;
import static ru.vtosters.lite.utils.Preferences.navbar; 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.Arrays;
import java.util.List; import java.util.List;
import ru.vtosters.lite.themes.managers.ThemesManager;
public class ThemesUtils { public class ThemesUtils {
public static List<String> accentColors = Arrays.asList( // temporary added colors with prefix "ff" for testing
"5692d7", "528bcc", "7aa0cc", "518bcc", "6296d0", "2f68aa", "638ebf", "5181b8", "71aaeb", "4774a8", "5baaf4", "4186c8", "add3ff", "4774a8", "718198", "5a9eff", "99a2ad", "74a2d6", "e9eef3", "dfe3e7", "eff1f3", "5c9ce6", "4986cc", "4680c2" 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) { public static void applyTheme(VKTheme theme) {
VKThemeHelper.theme(theme, LifecycleUtils.getCurrentActivity(), getCenterScreenCoords()); VKThemeHelper.theme(theme, LifecycleUtils.getCurrentActivity(), getCenterScreenCoords());
} // Apply VKTheme and ImTheme (hard applying without dynamic theme changing) } // Apply VKTheme and ImTheme (hard applying without dynamic theme changing)
@@ -156,12 +217,14 @@ public class ThemesUtils {
} // Return needed theme for theme changer } // Return needed theme for theme changer
public static int getColorFromAttr(int attr) { public static int getColorFromAttr(int attr) {
if (isCustomThemeApplied()) {
var color = ThemesManager.getInstance().getCurrentTheme().getColor(attr);
if (color != -1)
return color;
}
return VKThemeHelper.d(attr); return VKThemeHelper.d(attr);
} // Get needed attr color } // Get needed attr color
public static boolean isAndroidMonet() {
return color_grishka() && Build.VERSION.SDK_INT >= 31;
}
public static void colorWriteBar(View view) { public static void colorWriteBar(View view) {
if (!navbar()) return; if (!navbar()) return;
@@ -194,7 +257,7 @@ public class ThemesUtils {
if (drawable == null) return null; if (drawable == null) return null;
if (isAndroidMonet() || isDarkTheme()) accent = getAccentColor(); if (isDarkTheme()) accent = getAccentColor();
return new RecoloredDrawable(drawable, accent); return new RecoloredDrawable(drawable, accent);
} // Recolor vk icon to accent color } // Recolor vk icon to accent color
@@ -203,14 +266,11 @@ public class ThemesUtils {
public static Drawable recolorDrawableToolbar(Drawable drawable) { public static Drawable recolorDrawableToolbar(Drawable drawable) {
if (drawable == null) return null; if (drawable == null) return null;
if (!isAndroidMonet()) return drawable;
return new RecoloredDrawable(drawable, getHeaderText()); return new RecoloredDrawable(drawable, getHeaderText());
} // Recolor toolbar drawable to accent color } // Recolor toolbar drawable to accent color
@SuppressLint("UseCompatLoadingForDrawables") @SuppressLint("UseCompatLoadingForDrawables")
public static Drawable recolorDrawableInt(int drawable) { public static Drawable recolorDrawableInt(int drawable) {
if (!isAndroidMonet()) return getResources().getDrawable(drawable);
@SuppressLint("UseCompatLoadingForDrawables") Drawable res = getResources().getDrawable(drawable); @SuppressLint("UseCompatLoadingForDrawables") Drawable res = getResources().getDrawable(drawable);
return new RecoloredDrawable(res, getAccentColor()); return new RecoloredDrawable(res, getAccentColor());
@@ -219,14 +279,12 @@ public class ThemesUtils {
public static ColorStateList recolorCSL(ColorStateList colorStateList) { public static ColorStateList recolorCSL(ColorStateList colorStateList) {
if (colorStateList == null) return null; if (colorStateList == null) return null;
if (!isAndroidMonet()) return colorStateList;
return ColorStateList.valueOf(getAccentColor()); return ColorStateList.valueOf(getAccentColor());
} // Recolor ColorStateList to accent color } // Recolor ColorStateList to accent color
@SuppressLint("UseCompatLoadingForColorStateLists") @SuppressLint("UseCompatLoadingForColorStateLists")
public static ColorStateList themeCSL(Context context, int color) { public static ColorStateList themeCSL(Context context, int color) {
if (isColorRefAccented(color) && isAndroidMonet()) { if (isColorRefAccented(color)) {
return ColorStateList.valueOf(getAccentColor()); return ColorStateList.valueOf(getAccentColor());
} }
@@ -260,6 +318,26 @@ public class ThemesUtils {
} }
} // Recolor ColorStateList } // 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) { public static int darken(int color, float by) {
float[] hsl = new float[3]; float[] hsl = new float[3];
ColorUtils.colorToHSL(color, hsl); ColorUtils.colorToHSL(color, hsl);
@@ -291,23 +369,17 @@ public class ThemesUtils {
} }
public static boolean isAccentedColor(int target) { 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) { public static int getColor(Context context, int i) {
if (isColorRefAccented(i) && isAndroidMonet()) { if (isColorRefAccented(i)) {
return getAccentColor(); return getAccentColor();
} }
return context.getColor(i); return context.getColor(i);
} // Android Support color injector + accent color checker } // 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() { public static int getAlertStyle() {
if (isDarkTheme()) return R.style.Theme_MaterialComponents_Dialog_Alert; if (isDarkTheme()) return R.style.Theme_MaterialComponents_Dialog_Alert;
return R.style.Theme_MaterialComponents_Light_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)); return String.format("#%06X", Integer.valueOf(i & 16777215));
} // Get color as hex string } // 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) { public static int getNavigationHeight(int Default) {
int VKME = R.dimen.design_bottom_sheet_peek_height_min; 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