added themes manager ui (beta)
This commit is contained in:
@@ -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<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.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());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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<Theme> themes = new ArrayList<>();
|
||||
private static File themesDir;
|
||||
|
||||
private static ThemesManager instance;
|
||||
|
||||
public static ThemesManager getInstance() {
|
||||
if (instance == null)
|
||||
instance = new ThemesManager();
|
||||
return instance;
|
||||
}
|
||||
|
||||
public ThemesManager() {
|
||||
init();
|
||||
}
|
||||
|
||||
private void init() {
|
||||
themes = new ArrayList<>();
|
||||
themes.add(new Theme().setName("Тема по умолчанию")
|
||||
.setAuthor("Разработчики ВК")
|
||||
.setDarkMode(false)
|
||||
.setId("default"));
|
||||
themesDir = new File(Environment.getExternalStorageDirectory(), "VTL/themes");
|
||||
if (!themesDir.exists()) {
|
||||
themesDir.mkdirs();
|
||||
return;
|
||||
}
|
||||
// Support themes from Sova V RE (.svt)
|
||||
File[] files = themesDir.listFiles((dir, name) -> name.endsWith(".vtlt") || name.endsWith(".svt"));
|
||||
if (files == null || files.length == 0)
|
||||
return;
|
||||
for (File file : files) {
|
||||
try {
|
||||
var theme = read(file);
|
||||
Log.d("ThemesManager", theme.toString());
|
||||
themes.add(theme);
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void reload() {
|
||||
init();
|
||||
}
|
||||
|
||||
/*
|
||||
* Theme file storing method from Sova V RE is storing json clearly or compressed in gzip.
|
||||
* If first byte byte is 0x1 or 0x2 json has been compressed with gzip,
|
||||
* but if first byte is 0x2 next int is length of json string. */
|
||||
|
||||
private Theme read(InputStream fis) throws IOException {
|
||||
InputStream is = fis;
|
||||
var first = is.read();
|
||||
if (first > 0x0 && first < 0x3) {
|
||||
var gzis = new GZIPInputStream(is);
|
||||
var dis = new DataInputStream(gzis);
|
||||
if (first == 0x2)
|
||||
dis.readInt();
|
||||
is = dis;
|
||||
}
|
||||
return Theme.fromJson(IOUtils.readAllLines(is).trim());
|
||||
}
|
||||
|
||||
private Theme read(File file) throws IOException {
|
||||
return read(new FileInputStream(file));
|
||||
}
|
||||
|
||||
public File getThemeFile(Theme theme) throws IOException {
|
||||
if (!themesDir.exists()) {
|
||||
themesDir.mkdirs();
|
||||
throw new FileNotFoundException("theme file not found");
|
||||
}
|
||||
|
||||
var themeFile = new File(themesDir, theme.id + ".svt");
|
||||
if (!themeFile.exists()) {
|
||||
themeFile = new File(themesDir, theme.id + ".vtlt");
|
||||
if (!themeFile.exists())
|
||||
themeFile.createNewFile();
|
||||
}
|
||||
|
||||
return themeFile;
|
||||
}
|
||||
|
||||
public void create(String name, boolean darkMode) throws JSONException, IOException {
|
||||
var id = name.toLowerCase().replace(" ", "-");
|
||||
if (hasTheme(id)) {
|
||||
int i = 1;
|
||||
while (hasTheme(id + "-" + i)) {
|
||||
i++;
|
||||
}
|
||||
id += "-" + i;
|
||||
}
|
||||
var userProfile = VKAccountManager.d().e1();
|
||||
Theme theme = new Theme()
|
||||
.setName(name)
|
||||
.setAuthor(userProfile.c + " " + userProfile.e)
|
||||
.setDarkMode(darkMode)
|
||||
.setId(id);
|
||||
themes.add(theme);
|
||||
save(theme);
|
||||
}
|
||||
|
||||
public void save(Theme theme) throws IOException, JSONException {
|
||||
var buffer = theme.toJson().toString().getBytes(StandardCharsets.UTF_8);
|
||||
var fos = new FileOutputStream(getThemeFile(theme));
|
||||
// writing with data length
|
||||
fos.write(0x2);
|
||||
var gzos = new GZIPOutputStream(fos);
|
||||
var dos = new DataOutputStream(gzos);
|
||||
dos.writeInt(buffer.length);
|
||||
dos.write(buffer);
|
||||
dos.close();
|
||||
}
|
||||
|
||||
public void delete(Theme theme) {
|
||||
try {
|
||||
getThemeFile(theme).delete();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
themes.remove(theme);
|
||||
}
|
||||
|
||||
public void copy(ContentResolver contentResolver, Uri uri) throws IOException, JSONException {
|
||||
var theme = read(contentResolver.openInputStream(uri));
|
||||
if (isCurrentTheme(theme)) {
|
||||
int i = 1;
|
||||
while (hasTheme(theme.id + "-" + i)) {
|
||||
i++;
|
||||
}
|
||||
theme.id += "-" + i;
|
||||
}
|
||||
themes.add(theme);
|
||||
save(theme);
|
||||
}
|
||||
|
||||
public void share(Context context, Theme theme) throws Exception {
|
||||
var vmPolicy = StrictMode.getVmPolicy();
|
||||
StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder().build());
|
||||
var intent = new Intent(Intent.ACTION_SEND)
|
||||
.setType("file/*")
|
||||
.putExtra(Intent.EXTRA_STREAM, Uri.fromFile(getThemeFile(theme)));
|
||||
context.startActivity(Intent.createChooser(intent, ""));
|
||||
StrictMode.setVmPolicy(vmPolicy);
|
||||
}
|
||||
|
||||
public void setTheme(Theme theme) {
|
||||
AndroidUtils.getPreferences()
|
||||
.edit()
|
||||
.putString("vtl_theme_id", theme.id)
|
||||
.commit();
|
||||
}
|
||||
|
||||
public Theme getThemeByIndex(int i) {
|
||||
return themes.get(i);
|
||||
}
|
||||
|
||||
public Theme getCurrentTheme() {
|
||||
var currentThemeId = AndroidUtils.getPreferences().getString("vtl_theme_id", "default");
|
||||
for (Theme theme : themes) {
|
||||
if (currentThemeId.equals(theme.id))
|
||||
return theme;
|
||||
}
|
||||
AndroidUtils.getPreferences()
|
||||
.edit()
|
||||
.putString("vtl_theme_id", "default")
|
||||
.commit();
|
||||
return getCurrentTheme();
|
||||
}
|
||||
|
||||
public int getThemesCount() {
|
||||
return themes.size();
|
||||
}
|
||||
|
||||
public boolean isCurrentTheme(Theme theme) {
|
||||
return isCurrentTheme(theme.id);
|
||||
}
|
||||
|
||||
public boolean isCurrentTheme(String id) {
|
||||
return id.equals(getCurrentTheme().id);
|
||||
}
|
||||
|
||||
public boolean hasTheme(Theme theme) {
|
||||
return hasTheme(theme.id);
|
||||
}
|
||||
|
||||
public boolean hasTheme(String id) {
|
||||
for (Theme theme : themes) {
|
||||
if (id.equals(theme.id))
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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<String, Integer> colors = new HashMap<>();
|
||||
|
||||
public Theme setAuthor(String author) {
|
||||
this.author = author;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Theme setName(String name) {
|
||||
this.name = name;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Theme setDarkMode(boolean b) {
|
||||
this.isDarkMode = b;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Theme setId(String id) {
|
||||
this.id = id;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Theme setColors(Map<String, Integer> colors) {
|
||||
this.colors.putAll(colors);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Theme setColor(String colorName, int color) {
|
||||
this.colors.put(colorName, color);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Theme setColor(String colorName, String color) {
|
||||
this.colors.put(colorName, Color.parseColor(color));
|
||||
return this;
|
||||
}
|
||||
|
||||
public static Theme valuesOf(String author, String name, boolean darkMode, String id, Map<String, Integer> colors) {
|
||||
return new Theme()
|
||||
.setAuthor(author)
|
||||
.setName(name)
|
||||
.setDarkMode(darkMode)
|
||||
.setId(id)
|
||||
.setColors(colors);
|
||||
}
|
||||
|
||||
public static Theme fromJson(String json) {
|
||||
try {
|
||||
return fromJson(new JSONObject(json));
|
||||
} catch (JSONException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static Theme fromJson(JSONObject json) {
|
||||
var colors = new HashMap<String, Integer>();
|
||||
var colorsJson = json.optJSONObject("colors");
|
||||
var names = colorsJson.names();
|
||||
for (int i = 0; i < colorsJson.length(); i++) {
|
||||
var name = names.optString(i);
|
||||
var color = Color.parseColor("#" + colorsJson.optString(name));
|
||||
colors.put(name, color);
|
||||
}
|
||||
return valuesOf(
|
||||
json.optString("author"),
|
||||
json.optString("name"),
|
||||
json.optBoolean("dark"),
|
||||
json.optString("id"),
|
||||
colors
|
||||
);
|
||||
}
|
||||
|
||||
public JSONObject toJson() throws JSONException {
|
||||
var colorsJson = new JSONObject();
|
||||
for (var entry : colors.entrySet())
|
||||
colorsJson.put(entry.getKey(), Integer.toHexString(entry.getValue()));
|
||||
return new JSONObject()
|
||||
.put("author", this.author)
|
||||
.put("name", this.name)
|
||||
.put("dark", this.isDarkMode)
|
||||
.put("id", this.id)
|
||||
.put("colors", colorsJson);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Theme{" +
|
||||
"author='" + this.author + '\'' +
|
||||
", name='" + this.name + '\'' +
|
||||
", isDarkMode=" + this.isDarkMode +
|
||||
", colors=" + this.colors +
|
||||
'}';
|
||||
}
|
||||
|
||||
public int getColor(String name) {
|
||||
return colors.get(name);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,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;
|
||||
}
|
||||
}
|
||||
@@ -71,10 +71,34 @@ public class IOUtils {
|
||||
}
|
||||
|
||||
public static void writeToFile(File file, byte[] content) throws IOException {
|
||||
if (!file.exists())
|
||||
file.createNewFile();
|
||||
FileOutputStream fos = new FileOutputStream(file);
|
||||
fos.write(content);
|
||||
}
|
||||
|
||||
public static void copyFileToDirectory(File dest, File targetDir) throws IOException {
|
||||
if (!targetDir.exists())
|
||||
targetDir.mkdirs();
|
||||
var path = dest.getAbsolutePath();
|
||||
var target = new File(targetDir, path.substring(path.lastIndexOf("/")));
|
||||
copyFile(dest, target);
|
||||
}
|
||||
|
||||
public static void copyFile(File dest, File target) throws IOException {
|
||||
var parent = target.getParentFile();
|
||||
if (!parent.exists())
|
||||
parent.getParentFile().mkdirs();
|
||||
writeToFile(target, readAllBytes(dest));
|
||||
}
|
||||
|
||||
public static void copyFile(InputStream is, File target) throws IOException {
|
||||
var parent = target.getParentFile();
|
||||
if (!parent.exists())
|
||||
parent.getParentFile().mkdirs();
|
||||
writeToFile(target, readAllBytes(is));
|
||||
}
|
||||
|
||||
public static void deleteRecursive(File f) {
|
||||
deleteRecursive(f, true);
|
||||
}
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
package ru.vtosters.lite.utils;
|
||||
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.FrameLayout;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.RelativeLayout;
|
||||
|
||||
import androidx.constraintlayout.widget.ConstraintLayout;
|
||||
|
||||
public class LayoutUtils {
|
||||
|
||||
public static int MATCH_PARENT = -1;
|
||||
@@ -21,4 +24,12 @@ public class LayoutUtils {
|
||||
public static FrameLayout.LayoutParams createFrame(int w, int h) {
|
||||
return new FrameLayout.LayoutParams(w, h);
|
||||
}
|
||||
|
||||
public static ViewGroup.LayoutParams createViewGroup(int w, int h) {
|
||||
return new ViewGroup.LayoutParams(w, h);
|
||||
}
|
||||
|
||||
public static ConstraintLayout.LayoutParams createConstraint(int w, int h) {
|
||||
return new ConstraintLayout.LayoutParams(w, h);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
<vector android:height="24dp"
|
||||
android:viewportHeight="17"
|
||||
android:viewportWidth="17"
|
||||
android:width="24dp"
|
||||
xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<path
|
||||
android:fillColor="#ff000000"
|
||||
android:pathData="M2.8,0.7c-0.4,0.4 -0.4,1 0,1.4l2.7,2.7l1.4,-1.4L4.2,0.7C3.8,0.3 3.2,0.3 2.8,0.7zM11.8,12.4H9.5V6.8l4.7,-4.7c0.4,-0.4 0.4,-1 0,-1.4c-0.4,-0.4 -1,-0.4 -1.4,0L8.1,5.4C7.7,5.8 7.5,6.3 7.5,6.8v5.6H5.2c-0.4,0 -0.7,0.5 -0.4,0.8l3.3,3.3c0.2,0.2 0.5,0.2 0.7,0l3.3,-3.3C12.5,12.9 12.3,12.4 11.8,12.4z" />
|
||||
</vector>
|
||||
@@ -0,0 +1,40 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="?attr/selectableItemBackground"
|
||||
android:paddingLeft="12dp"
|
||||
android:paddingTop="8dp"
|
||||
android:paddingRight="12dp"
|
||||
android:paddingBottom="8dp">
|
||||
|
||||
<TextView
|
||||
android:id="@id/name"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:textSize="16sp"
|
||||
android:textColor="?attr/text_primary"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintBottom_toTopOf="@id/author" />
|
||||
|
||||
<TextView
|
||||
android:id="@id/author"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:textColor="?attr/text_secondary"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/name"
|
||||
app:layout_constraintBottom_toBottomOf="parent" />
|
||||
|
||||
<ru.vtosters.lite.ui.views.NoTouchRadioButton
|
||||
android:id="@id/selection"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
Reference in New Issue
Block a user