From db83fbff5277a0189a32a94c4b39929b3c664ce3 Mon Sep 17 00:00:00 2001 From: Vologhat <47892605+Vologhat@users.noreply.github.com> Date: Sun, 31 Jul 2022 20:22:06 +0300 Subject: [PATCH] added themes manager ui (beta) --- .../lite/ui/adapters/ThemesAdapter.java | 164 +++++++++++++ .../lite/ui/components/ThemesManager.java | 228 ++++++++++++++++++ .../lite/ui/fragments/ThemesFragment.java | 14 +- .../ui/fragments/ThemesManagerFragment.java | 170 +++++++++++++ .../java/ru/vtosters/lite/ui/items/Theme.java | 115 +++++++++ .../lite/ui/views/NoTouchRadioButton.java | 26 ++ .../java/ru/vtosters/lite/utils/IOUtils.java | 24 ++ .../ru/vtosters/lite/utils/LayoutUtils.java | 11 + smali/res/drawable/ic_import.xml | 9 + smali/res/layout/theme_manager_item.xml | 40 +++ 10 files changed, 800 insertions(+), 1 deletion(-) create mode 100644 app/src/main/java/ru/vtosters/lite/ui/adapters/ThemesAdapter.java create mode 100644 app/src/main/java/ru/vtosters/lite/ui/components/ThemesManager.java create mode 100644 app/src/main/java/ru/vtosters/lite/ui/fragments/ThemesManagerFragment.java create mode 100644 app/src/main/java/ru/vtosters/lite/ui/items/Theme.java create mode 100644 app/src/main/java/ru/vtosters/lite/ui/views/NoTouchRadioButton.java create mode 100644 smali/res/drawable/ic_import.xml create mode 100644 smali/res/layout/theme_manager_item.xml diff --git a/app/src/main/java/ru/vtosters/lite/ui/adapters/ThemesAdapter.java b/app/src/main/java/ru/vtosters/lite/ui/adapters/ThemesAdapter.java new file mode 100644 index 000000000..7f160a9cf --- /dev/null +++ b/app/src/main/java/ru/vtosters/lite/ui/adapters/ThemesAdapter.java @@ -0,0 +1,164 @@ +package ru.vtosters.lite.ui.adapters; + +import static ru.vtosters.lite.utils.AndroidUtils.dp2px; +import static ru.vtosters.lite.utils.AndroidUtils.getGlobalContext; +import static ru.vtosters.lite.utils.ThemesUtils.getAccentColor; +import static ru.vtosters.lite.utils.ThemesUtils.getSTextAttr; +import static ru.vtosters.lite.utils.ThemesUtils.getTextAttr; + +import android.app.AlertDialog; +import android.content.Context; +import android.content.DialogInterface; +import android.content.res.ColorStateList; +import android.text.TextUtils; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.EditText; +import android.widget.LinearLayout; +import android.widget.TextView; + +import androidx.annotation.NonNull; +import androidx.constraintlayout.widget.ConstraintLayout; +import androidx.recyclerview.widget.RecyclerView; + +import com.vk.core.dialogs.alert.VkAlertDialog; +import com.vk.core.util.ToastUtils; + +import org.json.JSONException; + +import java.io.IOException; + +import ru.vtosters.lite.ui.components.ThemesManager; +import ru.vtosters.lite.ui.items.Theme; +import ru.vtosters.lite.ui.views.NoTouchRadioButton; +import ru.vtosters.lite.utils.AndroidUtils; +import ru.vtosters.lite.utils.LifecycleUtils; + +public class ThemesAdapter extends RecyclerView.Adapter { + + 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.getThemeByIndex(pos)); + } + + @Override + public int getItemCount() { + return mManager.getThemesCount(); + } + + public class ThemeViewHolder extends RecyclerView.ViewHolder { + + private ConstraintLayout mContainer; + private TextView mName; + private TextView mAuthor; + private NoTouchRadioButton mRadioButton; + + public ThemeViewHolder(@NonNull View view) { + super(view); + + mContainer = (ConstraintLayout) view; + mName = view.findViewById(AndroidUtils.getIdentifier("name", "id")); + mAuthor = view.findViewById(AndroidUtils.getIdentifier("author", "id")); + mRadioButton = view.findViewById(AndroidUtils.getIdentifier("selection", "id")); + } + + public void bind(Theme theme) { + var isCurrentTheme = mManager.isCurrentTheme(theme); + if (!isCurrentTheme) + mContainer.setOnClickListener(v -> { + mManager.setTheme(theme); + LifecycleUtils.restartApplication(); + }); + if (!"default".equals(theme.id)) + mContainer.setOnLongClickListener(v -> { + var titles = new CharSequence[] { "Редактировать название", "Поделиться темой", "Удалить тему" }; + new VkAlertDialog.Builder(v.getContext()) + .setItems(titles, ((dialog, which) -> { + switch (which) { + case 0: + showEditThemeNameDialog(v.getContext(), theme); + break; + case 1: + try { + mManager.share(v.getContext(), theme); + } catch (Exception e) { + e.printStackTrace(); + ToastUtils.a("Не удалось поделиться темой"); + } + break; + case 2: + mManager.delete(theme); + if (isCurrentTheme) + LifecycleUtils.restartApplication(); + else + notifyDataSetChanged(); + break; + } + })) + .create() + .show(); + return true; + }); + mRadioButton.setChecked(isCurrentTheme); + mName.setText(theme.name); + mAuthor.setText(theme.author); + } + + private void showEditThemeNameDialog(Context context, Theme theme) { + LinearLayout linearLayout = new LinearLayout(getGlobalContext()); + + final EditText editText = new EditText(getGlobalContext()); + editText.setText(theme.name); + editText.setHint("Название темы"); + editText.setTextColor(getTextAttr()); + editText.setHintTextColor(getSTextAttr()); + + editText.setBackgroundTintList(ColorStateList.valueOf(getAccentColor())); + + linearLayout.addView(editText); + editText.getLayoutParams().width = ViewGroup.LayoutParams.MATCH_PARENT; + ViewGroup.MarginLayoutParams margin = ((ViewGroup.MarginLayoutParams) editText.getLayoutParams()); + margin.setMargins(dp2px(20f), 0, dp2px(20f), 0); + editText.setLayoutParams(margin); + + AlertDialog.Builder builder = new AlertDialog.Builder(context); + builder.setTitle("Редактирование названия темы"); + builder.setView(linearLayout); + builder.setPositiveButton("OK", (dialog, which) -> { + 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("Не удалось изменить название темы"); + } + }); + + var alert = builder.create(); + alert.show(); + alert.getButton(DialogInterface.BUTTON_POSITIVE).setTextColor(getAccentColor()); + } + } +} diff --git a/app/src/main/java/ru/vtosters/lite/ui/components/ThemesManager.java b/app/src/main/java/ru/vtosters/lite/ui/components/ThemesManager.java new file mode 100644 index 000000000..c3e74387b --- /dev/null +++ b/app/src/main/java/ru/vtosters/lite/ui/components/ThemesManager.java @@ -0,0 +1,228 @@ +package ru.vtosters.lite.ui.components; + +import android.content.ContentResolver; +import android.content.Context; +import android.content.Intent; +import android.net.Uri; +import android.os.Environment; +import android.os.StrictMode; +import android.util.Log; + +import com.vtosters.lite.auth.VKAccountManager; + +import org.json.JSONException; + +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.List; +import java.util.zip.GZIPInputStream; +import java.util.zip.GZIPOutputStream; + +import ru.vtosters.lite.ui.items.Theme; +import ru.vtosters.lite.utils.AndroidUtils; +import ru.vtosters.lite.utils.IOUtils; + +public class ThemesManager { + + private List themes = new ArrayList<>(); + private static File themesDir; + + private static ThemesManager instance; + + public static ThemesManager getInstance() { + if (instance == null) + instance = new ThemesManager(); + return instance; + } + + public ThemesManager() { + init(); + } + + private void init() { + themes = new ArrayList<>(); + themes.add(new Theme().setName("Тема по умолчанию") + .setAuthor("Разработчики ВК") + .setDarkMode(false) + .setId("default")); + themesDir = new File(Environment.getExternalStorageDirectory(), "VTL/themes"); + if (!themesDir.exists()) { + themesDir.mkdirs(); + return; + } + // Support themes from Sova V RE (.svt) + File[] files = themesDir.listFiles((dir, name) -> name.endsWith(".vtlt") || name.endsWith(".svt")); + if (files == null || files.length == 0) + return; + for (File file : files) { + try { + var theme = read(file); + Log.d("ThemesManager", theme.toString()); + themes.add(theme); + } catch (IOException e) { + e.printStackTrace(); + } + } + } + + public void reload() { + init(); + } + + /* + * Theme file storing method from Sova V RE is storing json clearly or compressed in gzip. + * If first byte byte is 0x1 or 0x2 json has been compressed with gzip, + * but if first byte is 0x2 next int is length of json string. */ + + private Theme read(InputStream fis) throws IOException { + InputStream is = fis; + var first = is.read(); + if (first > 0x0 && first < 0x3) { + var gzis = new GZIPInputStream(is); + var dis = new DataInputStream(gzis); + if (first == 0x2) + dis.readInt(); + is = dis; + } + return Theme.fromJson(IOUtils.readAllLines(is).trim()); + } + + private Theme read(File file) throws IOException { + return read(new FileInputStream(file)); + } + + public File getThemeFile(Theme theme) throws IOException { + if (!themesDir.exists()) { + themesDir.mkdirs(); + throw new FileNotFoundException("theme file not found"); + } + + var themeFile = new File(themesDir, theme.id + ".svt"); + if (!themeFile.exists()) { + themeFile = new File(themesDir, theme.id + ".vtlt"); + if (!themeFile.exists()) + themeFile.createNewFile(); + } + + return themeFile; + } + + public void create(String name, boolean darkMode) throws JSONException, IOException { + var id = name.toLowerCase().replace(" ", "-"); + if (hasTheme(id)) { + int i = 1; + while (hasTheme(id + "-" + i)) { + i++; + } + id += "-" + i; + } + var userProfile = VKAccountManager.d().e1(); + Theme theme = new Theme() + .setName(name) + .setAuthor(userProfile.c + " " + userProfile.e) + .setDarkMode(darkMode) + .setId(id); + themes.add(theme); + save(theme); + } + + public void save(Theme theme) throws IOException, JSONException { + var buffer = theme.toJson().toString().getBytes(StandardCharsets.UTF_8); + var fos = new FileOutputStream(getThemeFile(theme)); + // writing with data length + fos.write(0x2); + var gzos = new GZIPOutputStream(fos); + var dos = new DataOutputStream(gzos); + dos.writeInt(buffer.length); + dos.write(buffer); + dos.close(); + } + + public void delete(Theme theme) { + try { + getThemeFile(theme).delete(); + } catch (IOException e) { + e.printStackTrace(); + } + themes.remove(theme); + } + + public void copy(ContentResolver contentResolver, Uri uri) throws IOException, JSONException { + var theme = read(contentResolver.openInputStream(uri)); + if (isCurrentTheme(theme)) { + int i = 1; + while (hasTheme(theme.id + "-" + i)) { + i++; + } + theme.id += "-" + i; + } + themes.add(theme); + save(theme); + } + + public void share(Context context, Theme theme) throws Exception { + var vmPolicy = StrictMode.getVmPolicy(); + StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder().build()); + var intent = new Intent(Intent.ACTION_SEND) + .setType("file/*") + .putExtra(Intent.EXTRA_STREAM, Uri.fromFile(getThemeFile(theme))); + context.startActivity(Intent.createChooser(intent, "")); + StrictMode.setVmPolicy(vmPolicy); + } + + public void setTheme(Theme theme) { + AndroidUtils.getPreferences() + .edit() + .putString("vtl_theme_id", theme.id) + .commit(); + } + + public Theme getThemeByIndex(int i) { + return themes.get(i); + } + + public Theme getCurrentTheme() { + var currentThemeId = AndroidUtils.getPreferences().getString("vtl_theme_id", "default"); + for (Theme theme : themes) { + if (currentThemeId.equals(theme.id)) + return theme; + } + AndroidUtils.getPreferences() + .edit() + .putString("vtl_theme_id", "default") + .commit(); + return getCurrentTheme(); + } + + public int getThemesCount() { + return themes.size(); + } + + public boolean isCurrentTheme(Theme theme) { + return isCurrentTheme(theme.id); + } + + public boolean isCurrentTheme(String id) { + return id.equals(getCurrentTheme().id); + } + + public boolean hasTheme(Theme theme) { + return hasTheme(theme.id); + } + + public boolean hasTheme(String id) { + for (Theme theme : themes) { + if (id.equals(theme.id)) + return true; + } + return false; + } +} diff --git a/app/src/main/java/ru/vtosters/lite/ui/fragments/ThemesFragment.java b/app/src/main/java/ru/vtosters/lite/ui/fragments/ThemesFragment.java index a952bffef..95918d8d9 100644 --- a/app/src/main/java/ru/vtosters/lite/ui/fragments/ThemesFragment.java +++ b/app/src/main/java/ru/vtosters/lite/ui/fragments/ThemesFragment.java @@ -12,11 +12,12 @@ import android.os.Bundle; import androidx.preference.Preference; import com.vk.navigation.Navigator; +import com.vk.newsfeed.NewsfeedSettingsFragment; import com.vtosters.lite.general.fragments.MaterialPreferenceToolbarFragment; +import ru.vtosters.lite.ui.components.ThemesManager; import ru.vtosters.lite.utils.LifecycleUtils; - public class ThemesFragment extends MaterialPreferenceToolbarFragment { @Override @@ -27,6 +28,17 @@ public class ThemesFragment extends MaterialPreferenceToolbarFragment { } private void prefs() { + var themesPreference = findPreference("themes"); + var themesManager = ThemesManager.getInstance(); + if (themesManager.getThemesCount() > 0) + themesPreference.setSummary(themesManager.getCurrentTheme().name); + themesPreference.setOnPreferenceClickListener(preference -> { + var intent = new Navigator(ThemesManagerFragment.class).b(getContext()); + intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + getContext().startActivity(intent); + return false; + }); + findPreference("navbar").setOnPreferenceClickListener(new restart()); findPreference("milkshake").setOnPreferenceClickListener(new restart()); findPreference("darktheme").setOnPreferenceChangeListener(new restartdark()); diff --git a/app/src/main/java/ru/vtosters/lite/ui/fragments/ThemesManagerFragment.java b/app/src/main/java/ru/vtosters/lite/ui/fragments/ThemesManagerFragment.java new file mode 100644 index 000000000..375f74a66 --- /dev/null +++ b/app/src/main/java/ru/vtosters/lite/ui/fragments/ThemesManagerFragment.java @@ -0,0 +1,170 @@ +package ru.vtosters.lite.ui.fragments; + +import static ru.vtosters.lite.utils.AndroidUtils.dp2px; +import static ru.vtosters.lite.utils.AndroidUtils.getGlobalContext; +import static ru.vtosters.lite.utils.AndroidUtils.getIdentifier; +import static ru.vtosters.lite.utils.ThemesUtils.getAccentColor; +import static ru.vtosters.lite.utils.ThemesUtils.getSTextAttr; +import static ru.vtosters.lite.utils.ThemesUtils.getTextAttr; + +import android.app.Activity; +import android.app.AlertDialog; +import android.content.DialogInterface; +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.view.ViewGroup; +import android.widget.EditText; +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 org.json.JSONException; + +import java.io.IOException; + +import ru.vtosters.lite.ui.adapters.ThemesAdapter; +import ru.vtosters.lite.ui.components.ThemesManager; +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) { + ToastUtils.a("Не удалось импортировать тему"); + 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 -> { + LinearLayout linearLayout = new LinearLayout(getGlobalContext()); + + final EditText editText = new EditText(getGlobalContext()); + editText.setHint("Название новой темы"); + editText.setTextColor(getTextAttr()); + editText.setHintTextColor(getSTextAttr()); + + editText.setBackgroundTintList(ColorStateList.valueOf(getAccentColor())); + + linearLayout.addView(editText); + editText.getLayoutParams().width = ViewGroup.LayoutParams.MATCH_PARENT; + ViewGroup.MarginLayoutParams margin = ((ViewGroup.MarginLayoutParams) editText.getLayoutParams()); + margin.setMargins(dp2px(20f), 0, dp2px(20f), 0); + editText.setLayoutParams(margin); + + AlertDialog.Builder builder = new AlertDialog.Builder(getContext()); + builder.setTitle("Новая тема"); + builder.setView(linearLayout); + builder.setPositiveButton("OK", (dialog, which) -> { + var name = editText.getText().toString(); + if (TextUtils.isEmpty(name)) { + ToastUtils.a("Название не может быть пустым"); + return; + } + try { + mManager.create(name, false); + mAdapter.notifyDataSetChanged(); + } catch (IOException | JSONException e) { + e.printStackTrace(); + ToastUtils.a("Не удалось изменить название темы"); + } + }); + + var alert = builder.create(); + alert.show(); + alert.getButton(DialogInterface.BUTTON_POSITIVE).setTextColor(getAccentColor()); + + mAdapter.notifyDataSetChanged(); + }); + buttonsContainer.addView(addNewTheme, LayoutUtils.createLinear(-2, -2)); + + buttonsContainer.addView(new View(getContext()), LayoutUtils.createLinear(dp2px(5), 0)); + + 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)); + + return container; + } + + @Override + public void onResume() { + super.onResume(); + + mManager.reload(); + mAdapter.notifyDataSetChanged(); + } +} diff --git a/app/src/main/java/ru/vtosters/lite/ui/items/Theme.java b/app/src/main/java/ru/vtosters/lite/ui/items/Theme.java new file mode 100644 index 000000000..30302e81f --- /dev/null +++ b/app/src/main/java/ru/vtosters/lite/ui/items/Theme.java @@ -0,0 +1,115 @@ +package ru.vtosters.lite.ui.items; + +import android.graphics.Color; + +import org.json.JSONException; +import org.json.JSONObject; + +import java.util.HashMap; +import java.util.Map; + +public class Theme { + + public String author; + public String name; + public boolean isDarkMode; + public String id; + private Map colors = new HashMap<>(); + + public Theme setAuthor(String author) { + this.author = author; + return this; + } + + public Theme setName(String name) { + this.name = name; + return this; + } + + public Theme setDarkMode(boolean b) { + this.isDarkMode = b; + return this; + } + + public Theme setId(String id) { + this.id = id; + return this; + } + + public Theme setColors(Map colors) { + this.colors.putAll(colors); + return this; + } + + public Theme setColor(String colorName, int color) { + this.colors.put(colorName, color); + return this; + } + + public Theme setColor(String colorName, String color) { + this.colors.put(colorName, Color.parseColor(color)); + return this; + } + + public static Theme valuesOf(String author, String name, boolean darkMode, String id, Map colors) { + return new Theme() + .setAuthor(author) + .setName(name) + .setDarkMode(darkMode) + .setId(id) + .setColors(colors); + } + + public static Theme fromJson(String json) { + try { + return fromJson(new JSONObject(json)); + } catch (JSONException e) { + e.printStackTrace(); + } + return null; + } + + public static Theme fromJson(JSONObject json) { + var colors = new HashMap(); + var colorsJson = json.optJSONObject("colors"); + var names = colorsJson.names(); + for (int i = 0; i < colorsJson.length(); i++) { + var name = names.optString(i); + var color = Color.parseColor("#" + colorsJson.optString(name)); + colors.put(name, color); + } + return valuesOf( + json.optString("author"), + json.optString("name"), + json.optBoolean("dark"), + json.optString("id"), + colors + ); + } + + public JSONObject toJson() throws JSONException { + var colorsJson = new JSONObject(); + for (var entry : colors.entrySet()) + colorsJson.put(entry.getKey(), Integer.toHexString(entry.getValue())); + return new JSONObject() + .put("author", this.author) + .put("name", this.name) + .put("dark", this.isDarkMode) + .put("id", this.id) + .put("colors", colorsJson); + } + + @Override + public String toString() { + return "Theme{" + + "author='" + this.author + '\'' + + ", name='" + this.name + '\'' + + ", isDarkMode=" + this.isDarkMode + + ", colors=" + this.colors + + '}'; + } + + public int getColor(String name) { + return colors.get(name); + } +} diff --git a/app/src/main/java/ru/vtosters/lite/ui/views/NoTouchRadioButton.java b/app/src/main/java/ru/vtosters/lite/ui/views/NoTouchRadioButton.java new file mode 100644 index 000000000..2b5237491 --- /dev/null +++ b/app/src/main/java/ru/vtosters/lite/ui/views/NoTouchRadioButton.java @@ -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; + } +} diff --git a/app/src/main/java/ru/vtosters/lite/utils/IOUtils.java b/app/src/main/java/ru/vtosters/lite/utils/IOUtils.java index a0b7db6e3..9195e72c7 100644 --- a/app/src/main/java/ru/vtosters/lite/utils/IOUtils.java +++ b/app/src/main/java/ru/vtosters/lite/utils/IOUtils.java @@ -71,10 +71,34 @@ public class IOUtils { } public static void writeToFile(File file, byte[] content) throws IOException { + if (!file.exists()) + file.createNewFile(); FileOutputStream fos = new FileOutputStream(file); fos.write(content); } + public static void copyFileToDirectory(File dest, File targetDir) throws IOException { + if (!targetDir.exists()) + targetDir.mkdirs(); + var path = dest.getAbsolutePath(); + var target = new File(targetDir, path.substring(path.lastIndexOf("/"))); + copyFile(dest, target); + } + + public static void copyFile(File dest, File target) throws IOException { + var parent = target.getParentFile(); + if (!parent.exists()) + parent.getParentFile().mkdirs(); + writeToFile(target, readAllBytes(dest)); + } + + public static void copyFile(InputStream is, File target) throws IOException { + var parent = target.getParentFile(); + if (!parent.exists()) + parent.getParentFile().mkdirs(); + writeToFile(target, readAllBytes(is)); + } + public static void deleteRecursive(File f) { deleteRecursive(f, true); } diff --git a/app/src/main/java/ru/vtosters/lite/utils/LayoutUtils.java b/app/src/main/java/ru/vtosters/lite/utils/LayoutUtils.java index 718b66fa1..b55aa679f 100644 --- a/app/src/main/java/ru/vtosters/lite/utils/LayoutUtils.java +++ b/app/src/main/java/ru/vtosters/lite/utils/LayoutUtils.java @@ -1,9 +1,12 @@ package ru.vtosters.lite.utils; +import android.view.ViewGroup; import android.widget.FrameLayout; import android.widget.LinearLayout; import android.widget.RelativeLayout; +import androidx.constraintlayout.widget.ConstraintLayout; + public class LayoutUtils { public static int MATCH_PARENT = -1; @@ -21,4 +24,12 @@ public class LayoutUtils { public static FrameLayout.LayoutParams createFrame(int w, int h) { return new FrameLayout.LayoutParams(w, h); } + + public static ViewGroup.LayoutParams createViewGroup(int w, int h) { + return new ViewGroup.LayoutParams(w, h); + } + + public static ConstraintLayout.LayoutParams createConstraint(int w, int h) { + return new ConstraintLayout.LayoutParams(w, h); + } } \ No newline at end of file diff --git a/smali/res/drawable/ic_import.xml b/smali/res/drawable/ic_import.xml new file mode 100644 index 000000000..80d4c13c5 --- /dev/null +++ b/smali/res/drawable/ic_import.xml @@ -0,0 +1,9 @@ + + + \ No newline at end of file diff --git a/smali/res/layout/theme_manager_item.xml b/smali/res/layout/theme_manager_item.xml new file mode 100644 index 000000000..7906b3fc4 --- /dev/null +++ b/smali/res/layout/theme_manager_item.xml @@ -0,0 +1,40 @@ + + + + + + + + + + \ No newline at end of file