diff --git a/app/build.gradle b/app/build.gradle index 7bacca8a6..3b3787e55 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -74,6 +74,7 @@ dependencies { // TODO: completion providers should not be included on the main module // alternate editor impl implementation 'com.blacksquircle.ui:editorkit:2.1.2' + implementation 'com.caverock:androidsvg-aar:1.4' implementation project(path: ':code-editor') implementation project(path: ':xml-completion') diff --git a/app/src/main/assets/Icons.zip b/app/src/main/assets/Icons.zip new file mode 100644 index 000000000..e262ca52d Binary files /dev/null and b/app/src/main/assets/Icons.zip differ diff --git a/app/src/main/java/com/tyron/code/ApplicationLoader.java b/app/src/main/java/com/tyron/code/ApplicationLoader.java index 8c2d380cd..410f333cb 100644 --- a/app/src/main/java/com/tyron/code/ApplicationLoader.java +++ b/app/src/main/java/com/tyron/code/ApplicationLoader.java @@ -27,6 +27,7 @@ import com.tyron.code.ui.main.action.debug.DebugActionGroup; import com.tyron.code.ui.main.action.other.FormatAction; import com.tyron.code.ui.main.action.other.OpenSettingsAction; +import com.tyron.code.ui.main.action.other.OpenIconManagerAction; import com.tyron.code.ui.main.action.project.ProjectActionGroup; import com.tyron.code.ui.settings.ApplicationSettingsFragment; import com.tyron.common.ApplicationProvider; @@ -145,6 +146,7 @@ private void runStartup() { manager.registerAction(CompileActionGroup.ID, new CompileActionGroup()); manager.registerAction(ProjectActionGroup.ID, new ProjectActionGroup()); manager.registerAction(PreviewLayoutAction.ID, new PreviewLayoutAction()); + manager.registerAction(OpenIconManagerAction.ID, new OpenIconManagerAction()); manager.registerAction(OpenSettingsAction.ID, new OpenSettingsAction()); manager.registerAction(FormatAction.ID, new FormatAction()); manager.registerAction(DebugActionGroup.ID, new DebugActionGroup()); @@ -190,4 +192,4 @@ public static void showToast(String message) { public static void setApplicationContext(Context context) { applicationContext = context; } -} \ No newline at end of file +} diff --git a/app/src/main/java/com/tyron/code/ui/iconmanager/EditVectorDialogFragment.java b/app/src/main/java/com/tyron/code/ui/iconmanager/EditVectorDialogFragment.java new file mode 100644 index 000000000..b7bc42a22 --- /dev/null +++ b/app/src/main/java/com/tyron/code/ui/iconmanager/EditVectorDialogFragment.java @@ -0,0 +1,197 @@ +package com.tyron.code.ui.iconmanager; + +import android.graphics.PorterDuff; +import android.graphics.Color; +import android.graphics.drawable.Drawable; +import android.graphics.drawable.PictureDrawable; + +import android.text.Editable; +import android.view.View; +import android.os.Bundle; +import android.app.Dialog; + +import android.widget.EditText; +import android.widget.ImageView; +import android.widget.Button; +import android.widget.LinearLayout; +import android.widget.Toast; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.appcompat.app.AlertDialog; + +import com.caverock.androidsvg.SVG; + +import com.google.android.material.dialog.MaterialAlertDialogBuilder; +import androidx.fragment.app.DialogFragment; +import com.google.android.material.textfield.TextInputEditText; +import com.google.android.material.textfield.TextInputLayout; + +import com.tyron.code.R; +import com.tyron.code.ui.project.ProjectManager; +import com.tyron.common.util.SingleTextWatcher; + +import java.io.File; +import java.io.FileInputStream; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.nio.file.StandardOpenOption; + +import org.w3c.dom.Element; +import org.w3c.dom.NodeList; +import org.w3c.dom.Document; + +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; + +public class EditVectorDialogFragment extends DialogFragment { + + public static final String TAG = EditVectorDialogFragment.class.getSimpleName(); + public static final String ADD_KEY = "addVector"; + private String iconPath, projectResourceDirectory; + + @SuppressWarnings("ConstantConditions") + @NonNull + @Override + public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) { + + Bundle bundle = this.getArguments(); + if (bundle != null) { + iconPath = bundle.getString("iconPath"); + projectResourceDirectory = ProjectManager.getInstance().getCurrentProject().getRootFile().getAbsolutePath() + "/app/src/main/res/drawable/"; + } + + MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(requireContext()); + View inflate = getLayoutInflater().inflate(R.layout.create_vector_dialog, null); + TextInputLayout textinput1 = (TextInputLayout) inflate.findViewById(R.id.textinputlayout1); + TextInputLayout textinput2 = (TextInputLayout) inflate.findViewById(R.id.textinputlayout2); + TextInputLayout textinput3 = (TextInputLayout) inflate.findViewById(R.id.textinputlayout3); + TextInputLayout textinput4 = (TextInputLayout) inflate.findViewById(R.id.textinputlayout4); + TextInputLayout textinput5 = (TextInputLayout) inflate.findViewById(R.id.textinputlayout5); + TextInputEditText name = (TextInputEditText) inflate.findViewById(R.id.name); + TextInputEditText height = (TextInputEditText) inflate.findViewById(R.id.height); + TextInputEditText width = (TextInputEditText) inflate.findViewById(R.id.width); + TextInputEditText color = (TextInputEditText) inflate.findViewById(R.id.color); + TextInputEditText path = (TextInputEditText) inflate.findViewById(R.id.path); + ImageView icon = (ImageView) inflate.findViewById(R.id.icon); + LinearLayout container = (LinearLayout) inflate.findViewById(R.id.container); + LinearLayout round = (LinearLayout) inflate.findViewById(R.id.round); + + round.setBackgroundColor(0XFF000000); + path.setEnabled(false); + path.setText(projectResourceDirectory); + + textinput4.setEndIconOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + if (!color.getText().toString().trim().startsWith("#")) { + Toast.makeText(requireContext(), "Invalid color code", 3000).show(); + } else { + try { + icon.setColorFilter(Color.parseColor(color.getText().toString().trim()), PorterDuff.Mode.MULTIPLY); + round.setBackgroundColor(Color.parseColor(color.getText().toString().trim())); + } catch (Exception e) { + Toast.makeText(requireContext(), e.toString(), 3000).show(); + } + } + } + }); + + builder.setView(inflate); + + if (iconPath.contains(".svg")) { + name.setText(new File(iconPath).getName().replace(".svg", "")); + } else if (iconPath.contains(".xml")) { + name.setText(new File(iconPath).getName().replace(".xml", "")); + } + + icon.setImageDrawable(loadSvg(iconPath)); + + builder.setPositiveButton("Create", (d, w) -> { + generateSvg2Vector(name.getText().toString().trim(), width.getText().toString().trim(), height.getText().toString().trim(), color.getText().toString().trim(), iconPath, projectResourceDirectory); + }); + + builder.setNegativeButton("Cancel", null); + + AlertDialog dialog = builder.create(); + dialog.setOnShowListener(d -> { + final Button positiveButton = dialog.getButton(AlertDialog.BUTTON_POSITIVE); + SingleTextWatcher textWatcher = new SingleTextWatcher() { + @Override + public void afterTextChanged(Editable editable) { + boolean valid = validate(name, height, width, color, path); + positiveButton.setEnabled(valid); + } + }; + + name.addTextChangedListener(textWatcher); + height.addTextChangedListener(textWatcher); + width.addTextChangedListener(textWatcher); + color.addTextChangedListener(textWatcher); + path.addTextChangedListener(textWatcher); + }); + + return dialog; + } + + private void generateSvg2Vector(String name, String width, String height, String color, String source, String destination) { + + File svgPath = new File(source); + DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance(); + + try { + DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder(); + Document document = documentBuilder.parse(svgPath); + NodeList nodeList = document.getElementsByTagName("path"); + if (nodeList.getLength() > 0) { + Element element = (Element) nodeList.item(0); + String a = "\n \n\n"; + + byte[] vectorText = a.getBytes(StandardCharsets.UTF_8); + + Files.write(Paths.get(new File(projectResourceDirectory + name + ".xml").toURI()), vectorText, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING); + + } + } catch (Exception e) { + Toast.makeText(requireContext(), e.toString(), 3000).show(); + } + } + + private boolean validate(EditText name, EditText height, EditText width, EditText color, EditText path) { + if (name.getText().toString().trim().isEmpty() && name.getText().toString().trim().endsWith(".xml") && name.getText().toString().endsWith(".svg")) { + return false; + } else if (height.getText().toString().trim().isEmpty()) { + return false; + } else if (width.getText().toString().trim().isEmpty()) { + return false; + } else if (color.getText().toString().trim().isEmpty()) { + return false; + } else if (path.getText().toString().trim().isEmpty()) { + return false; + } + return !name.getText().toString().contains(".xml") && !name.getText().toString().contains(".svg"); + } + + private Drawable loadSvg(String path) { + Drawable drawable = null; + try { + FileInputStream fileInputStream = new FileInputStream(new File(path)); + SVG svg = SVG.getFromInputStream(fileInputStream); + drawable = new PictureDrawable(svg.renderToPicture()); + } catch (Exception e) { + Toast.makeText(requireContext(), e.toString(), 3000).show(); + } + return drawable; + } +} diff --git a/app/src/main/java/com/tyron/code/ui/iconmanager/IconManagerFragment.java b/app/src/main/java/com/tyron/code/ui/iconmanager/IconManagerFragment.java new file mode 100644 index 000000000..8601f05db --- /dev/null +++ b/app/src/main/java/com/tyron/code/ui/iconmanager/IconManagerFragment.java @@ -0,0 +1,146 @@ +package com.tyron.code.ui.iconmanager; + +import android.app.ProgressDialog; +import android.os.Bundle; + +import android.view.ViewGroup; +import android.view.LayoutInflater; +import android.view.View; +import android.widget.TextView; +import android.widget.Toast; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.fragment.app.Fragment; +import androidx.appcompat.widget.Toolbar; +import androidx.recyclerview.widget.GridLayoutManager; +import androidx.recyclerview.widget.RecyclerView; + +import com.google.android.material.dialog.MaterialAlertDialogBuilder; +import com.google.android.material.transition.MaterialSharedAxis; + +import com.tyron.code.R; +import com.tyron.code.util.UiUtilsKt; +import com.tyron.code.ui.project.ProjectManager; +import com.tyron.completion.progress.ProgressManager; +import com.tyron.common.util.Decompress; +import com.tyron.code.ui.iconmanager.adapter.IconAdapter; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStream; + +import java.util.ArrayList; +import org.w3c.dom.Document; + +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; + +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; +import org.w3c.dom.Element; + +public class IconManagerFragment extends Fragment { + + public static String TAG = IconManagerFragment.class.getSimpleName(); + private String iconFolderDirectory, projectResourceDirectory; + private ArrayList iconList = new ArrayList<>(); + private ProgressDialog pDialog; + + @Override + public void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + setEnterTransition(new MaterialSharedAxis(MaterialSharedAxis.X, true)); + setExitTransition(new MaterialSharedAxis(MaterialSharedAxis.X, false)); + + iconFolderDirectory = getPackageDirectory() + "/Icons/"; + } + + @Nullable + + @Override + + public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { + + View view = inflater.inflate(R.layout.icon_manager_fragment, container, false); + + pDialog = new ProgressDialog(requireContext()); + RecyclerView recyclerView = view.findViewById(R.id.recyclerview); + Toolbar toolbar = view.findViewById(R.id.toolbar); + UiUtilsKt.addSystemWindowInsetToPadding(toolbar, false, true, false, false); + if (!new File(getPackageDirectory() + "/Icons/").exists()) { + showConfirmationDialog(recyclerView, pDialog); + } else { + loadIcons(iconFolderDirectory, iconList, recyclerView); + } + + return view; + } + + @Override + + public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { + + } + + private void showConfirmationDialog(RecyclerView recyclerView, final ProgressDialog progressDialog) { + MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(requireContext()); + builder.setTitle("Warning!"); + builder.setMessage("Do you want to extract all icons from CodeAssist?"); + builder.setPositiveButton("EXTRACT", (d, w) -> { + progressDialog.setMessage("Extracting icons"); + progressDialog.setCancelable(false); + progressDialog.show(); + ProgressManager.getInstance().runNonCancelableAsync(() ->startExtractingIcons(progressDialog, recyclerView)); + }); + builder.setNegativeButton("CANCEL", null); + builder.create().show(); + } + + private void startExtractingIcons(final ProgressDialog progressDialog, RecyclerView recyclerView) { + Decompress.unzipFromAssets(requireContext(), "Icons.zip", getPackageDirectory()); + + ProgressManager.getInstance().runLater(() -> { + if (progressDialog.isShowing()) { + progressDialog.dismiss(); + Toast.makeText(requireContext(), "Please restart Icon manager", 3000).show(); + } + }); + } + + private void loadIcons(String path, ArrayList list, RecyclerView recyclerView) { + list.clear(); + getFileList(path, list); + recyclerView.setNestedScrollingEnabled(false); + recyclerView.setAdapter(new IconAdapter(list, requireContext())); + recyclerView.setLayoutManager(new GridLayoutManager(requireContext(), 4)); + } + + private String getPackageDirectory() { + return requireContext().getExternalFilesDir(null).getAbsolutePath(); + } + + public static void makeDirs(String path) { + if (!new File(path).exists()) { + new File(path).mkdirs(); + } + } + + public static void getFileList(String source, ArrayList list) { + File dir = new File(source); + if (!dir.exists() || dir.isFile()) + return; + File[] listFiles = dir.listFiles(); + if (listFiles == null || listFiles.length <= 0) + return; + if (list == null) + return; + list.clear(); + for (File file : listFiles) { + list.add(file.getAbsolutePath()); + } + } +} diff --git a/app/src/main/java/com/tyron/code/ui/iconmanager/adapter/IconAdapter.java b/app/src/main/java/com/tyron/code/ui/iconmanager/adapter/IconAdapter.java new file mode 100644 index 000000000..f98513915 --- /dev/null +++ b/app/src/main/java/com/tyron/code/ui/iconmanager/adapter/IconAdapter.java @@ -0,0 +1,235 @@ +package com.tyron.code.ui.iconmanager.adapter; + +import android.content.Context; + +import android.graphics.Bitmap; + +import android.graphics.BitmapFactory; + +import android.graphics.PorterDuff; + +import android.graphics.drawable.Drawable; + +import android.graphics.drawable.PictureDrawable; + +import android.net.Uri; + +import android.os.Bundle; + +import android.view.LayoutInflater; + +import android.view.View; + +import android.view.ViewGroup; + +import android.widget.ImageView; + +import android.widget.LinearLayout; + +import android.widget.TextView; + +import android.widget.Toast; + +import androidx.fragment.app.Fragment; + +import androidx.fragment.app.FragmentActivity; + +import androidx.fragment.app.FragmentManager; + +import com.caverock.androidsvg.SVG; + +import com.caverock.androidsvg.SVGParseException; + +import com.tyron.code.ui.iconmanager.EditVectorDialogFragment; + +import com.tyron.code.ui.iconmanager.IconManagerFragment; + +import com.tyron.code.R; + +import androidx.recyclerview.widget.RecyclerView; + +import java.io.ByteArrayInputStream; + +import java.io.File; + +import java.io.FileNotFoundException; + +import java.io.FileInputStream; + +import java.io.InputStream; + +import java.nio.charset.StandardCharsets; + +import java.util.ArrayList; + +import java.util.HashMap; + +public class IconAdapter extends RecyclerView.Adapter { + + private ArrayList data; private Context c; + + private LinearLayout base; + + private ImageView icon; + + private TextView name; + + public IconAdapter(ArrayList arr, Context context) { + + data = arr; + + c = context; + + } + + @Override + + public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { + + View v = LayoutInflater.from(c).inflate(R.layout.icon_manager_item, null); + + RecyclerView.LayoutParams lp = new RecyclerView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, + + ViewGroup.LayoutParams.WRAP_CONTENT); + + v.setLayoutParams(lp); + + return new ViewHolder(v); + + } + + @Override + + public void onBindViewHolder(ViewHolder holder, final int position) { + + View view = holder.itemView; + + base = view.findViewById(R.id.linear1); + + icon = view.findViewById(R.id.imageview1); + + name = view.findViewById(R.id.textview1); + + if (!data.get(position).isEmpty()) { + + if (new File(data.get(position)).getName().endsWith(".svg")) { + + name.setText(new File(data.get(position)).getName().replace(".svg", "")); + + } else if (new File(data.get(position)).getName().endsWith(".xml")) { + + name.setText(new File(data.get(position)).getName().replace(".xml", "")); + + } + + } + + if (isFile(data.get(position))) { + + if (data.get(position).contains(".svg") || data.get(position).contains(".xml")) { + + try { + + icon.setImageDrawable(loadSvg(data.get(position))); + + } catch (Exception e) { + + Toast.makeText(c, e.toString(), 3000).show(); + + } + + } + + } + + icon.setColorFilter(0xFF000000, PorterDuff.Mode.MULTIPLY); + + name.setSelected(true); + + base.setOnClickListener(new View.OnClickListener() { + + @Override + + public void onClick(View view) { + + Bundle bundle = new Bundle(); + + bundle.putString("iconPath", data.get(position)); + + bundle.putInt("position", position); + + FragmentActivity activity = (FragmentActivity) (c); + + FragmentManager fm = activity.getSupportFragmentManager(); + + if (fm.findFragmentByTag(EditVectorDialogFragment.TAG) == null) { + + EditVectorDialogFragment fragment = new EditVectorDialogFragment(); + + fragment.setArguments(bundle); + + fragment.show(fm, EditVectorDialogFragment.TAG); + + } + + } + + }); + + } + + @Override + + public int getItemCount() { + + return data.size(); + + } + + public boolean isFile(String path) { + + if (!new File(path).exists()) + + return false; + + return new File(path).isFile(); + + } + + public Drawable loadSvg(String path) { + + Drawable drawable = null; + + try { + + FileInputStream fileInputStream = new FileInputStream(new File(path)); + + try { + + SVG svg = SVG.getFromInputStream(fileInputStream); + + drawable = new PictureDrawable(svg.renderToPicture()); + + } catch (SVGParseException e) { + + } + + } catch (FileNotFoundException e) { + + } + + return drawable; + + } + + public class ViewHolder extends RecyclerView.ViewHolder { + + public ViewHolder(View v) { + + super(v); + + } + + } + +} diff --git a/app/src/main/java/com/tyron/code/ui/main/action/other/OpenIconManagerAction.java b/app/src/main/java/com/tyron/code/ui/main/action/other/OpenIconManagerAction.java new file mode 100644 index 000000000..d22fa14cd --- /dev/null +++ b/app/src/main/java/com/tyron/code/ui/main/action/other/OpenIconManagerAction.java @@ -0,0 +1,136 @@ +package com.tyron.code.ui.main.action.other; + +import android.app.Activity; + +import android.content.Context; + +import android.content.ContextWrapper; + +import android.view.ContextThemeWrapper; + +import androidx.annotation.NonNull; + +import androidx.appcompat.app.AppCompatActivity; + +import androidx.fragment.app.Fragment; + +import androidx.fragment.app.FragmentManager; + +import com.tyron.actions.ActionPlaces; + +import com.tyron.actions.AnAction; + +import com.tyron.actions.AnActionEvent; + +import com.tyron.actions.CommonDataKeys; + +import com.tyron.actions.Presentation; + +import com.tyron.builder.project.Project; + +import com.tyron.builder.project.api.Module; + +import com.tyron.code.R; + +import com.tyron.code.ui.iconmanager.IconManagerFragment; + +public class OpenIconManagerAction extends AnAction { + + public static final String ID = "openIconManagerAction"; + +@Override + + public void update(@NonNull AnActionEvent event) { + + Presentation presentation = event.getPresentation(); + + Context context = event.getDataContext(); + + presentation.setVisible(false); + + if (!ActionPlaces.MAIN_TOOLBAR.equals(event.getPlace())) { + + return; + + } + + Project project = event.getData(CommonDataKeys.PROJECT); + + if (project == null) { + + return; + + } + + presentation.setText(context.getString(R.string.menu_icon_manager)); + + presentation.setVisible(true); + + presentation.setEnabled(true); + + } + + @Override + + public void actionPerformed(@NonNull AnActionEvent e) { + + Context context = e.getRequiredData(CommonDataKeys.CONTEXT); + + context = getActivityContext(context); + + Project project = e.getRequiredData(CommonDataKeys.PROJECT); + + Module mainModule = project.getMainModule(); + + if (context instanceof AppCompatActivity) { + + FragmentManager fragmentManager = + + ((AppCompatActivity) context).getSupportFragmentManager(); + + Fragment fragment = new IconManagerFragment(); + + fragmentManager.beginTransaction() + + .replace(R.id.fragment_container, fragment, IconManagerFragment.TAG) + + .addToBackStack(IconManagerFragment.TAG) + + .commit(); + + } + + } + + + + private Context getActivityContext(Context context) { + + Context current = context; + + while (current != null) { + + if (current instanceof Activity) { + + return current; + + } + + if (current instanceof ContextWrapper) { + + current = ((ContextWrapper) current).getBaseContext(); + + } else { + + current = null; + + } + + } + + return null; + + } + +} + diff --git a/app/src/main/res/drawable/outline_sync_24.xml b/app/src/main/res/drawable/outline_sync_24.xml new file mode 100644 index 000000000..74598aca2 --- /dev/null +++ b/app/src/main/res/drawable/outline_sync_24.xml @@ -0,0 +1,20 @@ + + + + + + diff --git a/app/src/main/res/layout/create_vector_dialog.xml b/app/src/main/res/layout/create_vector_dialog.xml new file mode 100644 index 000000000..b3a61b74b --- /dev/null +++ b/app/src/main/res/layout/create_vector_dialog.xml @@ -0,0 +1,164 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/icon_manager_fragment.xml b/app/src/main/res/layout/icon_manager_fragment.xml new file mode 100644 index 000000000..f0d234338 --- /dev/null +++ b/app/src/main/res/layout/icon_manager_fragment.xml @@ -0,0 +1,53 @@ + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/icon_manager_item.xml b/app/src/main/res/layout/icon_manager_item.xml new file mode 100644 index 000000000..fc709cf42 --- /dev/null +++ b/app/src/main/res/layout/icon_manager_item.xml @@ -0,0 +1,104 @@ + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/menu/code_editor_menu.xml b/app/src/main/res/menu/code_editor_menu.xml index 91b6fd1cb..cb33bc3fa 100644 --- a/app/src/main/res/menu/code_editor_menu.xml +++ b/app/src/main/res/menu/code_editor_menu.xml @@ -45,6 +45,10 @@ + + Library manager Format Settings + Icon Manager Application