Fixed merge conflict

This commit is contained in:
Akash Yadav
2022-03-04 16:39:38 +05:30
429 changed files with 63400 additions and 53996 deletions
+15
View File
@@ -0,0 +1,15 @@
name: Format Code
on:
push:
branches: [ main ]
jobs:
formatting:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: axel-op/googlejavaformat-action@v3
with:
commitMessage: Format code
args: "--aosp --replace"
@@ -5,12 +5,12 @@
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
*
* AndroidIDE is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
*
* You should have received a copy of the GNU General Public License
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
*
@@ -36,110 +36,169 @@ import java.util.List;
public class AboutActivity extends StudioActivity {
private ActivityAboutBinding binding;
public static final String JDK_SOURCE = "https://github.com/itsaky/OpenJDK-Android";
@Override
protected View bindLayout() {
binding = ActivityAboutBinding.inflate(getLayoutInflater());
return binding.getRoot();
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
binding.items.footerText.setText(getFooter());
setupTranslations ();
setupTranslations();
setupLicenses();
LayoutAboutItemsBinding items = binding.items;
TooltipCompat.setTooltipText(items.discuss, getString(R.string.discussions_on_telegram));
TooltipCompat.setTooltipText(items.github, getString(R.string.title_github));
TooltipCompat.setTooltipText(items.email, getString(R.string.about_option_email));
TooltipCompat.setTooltipText(items.website, getString(R.string.about_option_website));
items.github.setOnClickListener(v -> {
getApp().openGitHub ();
});
items.discuss.setOnClickListener(v -> {
getApp().openTelegramGroup();
});
items.email.setOnClickListener(v -> {
getApp().emailUs();
});
items.website.setOnClickListener(v -> {
getApp().openWebsite();
});
items.github.setOnClickListener(
v -> {
getApp().openGitHub();
});
items.discuss.setOnClickListener(
v -> {
getApp().openTelegramGroup();
});
items.email.setOnClickListener(
v -> {
getApp().emailUs();
});
items.website.setOnClickListener(
v -> {
getApp().openWebsite();
});
}
private void setupTranslations () {
final var translations = createTranslationsList ();
final var sb = new StringBuilder ();
private void setupTranslations() {
final var translations = createTranslationsList();
final var sb = new StringBuilder();
for (var translation : translations) {
final var author = translation.first;
final var language = translation.second;
sb.append (author);
sb.append (" - ");
sb.append (language);
sb.append ("\n");
sb.append(author);
sb.append(" - ");
sb.append(language);
sb.append("\n");
}
binding.items.translations.setText (sb);
binding.items.translations.setText(sb);
}
/**
* Create the list of translations.
* The list items are pairs.
* The first item of the pair is the name of the language.
* The second item is the name of the person who made the translations.
* Create the list of translations. The list items are pairs. The first item of the pair is the
* name of the language. The second item is the name of the person who made the translations.
*
* If you're adding translations, make sure you add an item to this list.
* You can choose to display the language in a localized version. For example :
* Language: हिन्दी - < name of author >
* <p>If you're adding translations, make sure you add an item to this list. You can choose to
* display the language in a localized version. For example : Language: हिन्दी - < name of
* author >
*
* Prefer using unicode escapes for language names. For example, हिन्दी can be written as :
* <p>Prefer using unicode escapes for language names. For example, हिन्दी can be written as :
* Language: \u0939\u093f\u0928\u094d\u0926\u0940 - < name of author >
*
* @return The translations list.
*/
private List<Pair<String, String>> createTranslationsList () {
final var list = new ArrayList<Pair<String, String>> ();
list.add (Pair.create ("Bahasa Indonesia", "Fitrah Nuno Syahbani"));
list.add (Pair.create ("\u4e2d\u6587", "Rosemoe")); // 中文
list.add (Pair.create ("Deutsch", "Marvin Stelter"));
list.add (Pair.create ("\u0939\u093f\u0928\u094d\u0926\u0940", "Premjit Chowdhury")); // हिन्दी
list.add (Pair.create ("Russian", "Smooth-E"));
list.add (Pair.create ("French", "Se-Lyan"));
private List<Pair<String, String>> createTranslationsList() {
final var list = new ArrayList<Pair<String, String>>();
list.add(Pair.create("Bahasa Indonesia", "Fitrah Nuno Syahbani"));
list.add(Pair.create("\u4e2d\u6587", "Rosemoe")); // 中文
list.add(Pair.create("Deutsch", "Marvin Stelter"));
list.add(
Pair.create("\u0939\u093f\u0928\u094d\u0926\u0940", "Premjit Chowdhury")); // हिन्दी
list.add(Pair.create("Russian", "Smooth-E"));
list.add(Pair.create("French", "Se-Lyan"));
return list;
}
private void setupLicenses() {
final var licenses = new ArrayList<License>();
licenses.add(new License("AndroidX Libraries", "Apache License 2.0", "https://github.com/androidx/androidx"));
licenses.add(new License("Gradle Build Tool", "Apache License 2.0", "https://github.com/gradle/gradle"));
licenses.add(
new License(
"AndroidX Libraries",
"Apache License 2.0",
"https://github.com/androidx/androidx"));
licenses.add(
new License(
"Gradle Build Tool",
"Apache License 2.0",
"https://github.com/gradle/gradle"));
licenses.add(new License("Gson", "Apache License 2.0", "https://github.com/google/gson"));
licenses.add(new License("Material Components for Android", "Apache License 2.0", "https://github.com/material-components/material-components-android"));
licenses.add(new License("ANTLR4 Runtime", "BSD 3-clause License", "https://github.com/antlr/antlr4"));
licenses.add(new License("Glide", "BSD, part MIT and Apache 2.0", "https://github.com/bumptech/glide"));
licenses.add(new License("Android UtilCode", "Apache License 2.0", "https://github.com/Blankj/AndroidUtilCode"));
licenses.add(new License("UnicornFilePicker", "Apache License 2.0", "https://github.com/abhishekti7/UnicornFilePicker"));
licenses.add(new License("QuickAction", "Apache License 2.0", "https://github.com/piruin/quickaction"));
licenses.add(new License("XmlToJson", "Apache License 2.0", "https://github.com/smart-fun/XmlToJson"));
licenses.add(new License("AndroidTreeView", "Apache License 2.0", "https://github.com/bmelnychuk/AndroidTreeView"));
licenses.add(new License("CodeEditor v0.5.2", "Apache License 2.0", "https://github.com/Rosemoe/CodeEditor"));
licenses.add(new License("Guava-Android", "Apache License 2.0", "https://github.com/google/guava"));
licenses.add(
new License(
"Material Components for Android",
"Apache License 2.0",
"https://github.com/material-components/material-components-android"));
licenses.add(
new License(
"ANTLR4 Runtime",
"BSD 3-clause License",
"https://github.com/antlr/antlr4"));
licenses.add(
new License(
"Glide",
"BSD, part MIT and Apache 2.0",
"https://github.com/bumptech/glide"));
licenses.add(
new License(
"Android UtilCode",
"Apache License 2.0",
"https://github.com/Blankj/AndroidUtilCode"));
licenses.add(
new License(
"UnicornFilePicker",
"Apache License 2.0",
"https://github.com/abhishekti7/UnicornFilePicker"));
licenses.add(
new License(
"QuickAction",
"Apache License 2.0",
"https://github.com/piruin/quickaction"));
licenses.add(
new License(
"XmlToJson",
"Apache License 2.0",
"https://github.com/smart-fun/XmlToJson"));
licenses.add(
new License(
"AndroidTreeView",
"Apache License 2.0",
"https://github.com/bmelnychuk/AndroidTreeView"));
licenses.add(
new License(
"CodeEditor v0.5.2",
"Apache License 2.0",
"https://github.com/Rosemoe/CodeEditor"));
licenses.add(
new License(
"Guava-Android", "Apache License 2.0", "https://github.com/google/guava"));
licenses.add(new License("Jsoup", "MIT License", "https://github.com/jhy/jsoup"));
licenses.add(new License("Termux [Terminal Emulator]", "GPL 3", "https://github.com/termux/termux-app"));
licenses.add(new License("JavaPoet", "Apache License 2.0", "https://github.com/square/JavaPoet"));
licenses.add(new License("Zip4j", "Apache License 2.0", "https://github.com/srikanth-lingala/zip4j"));
licenses.add(
new License(
"Termux [Terminal Emulator]",
"GPL 3",
"https://github.com/termux/termux-app"));
licenses.add(
new License(
"JavaPoet", "Apache License 2.0", "https://github.com/square/JavaPoet"));
licenses.add(
new License(
"Zip4j",
"Apache License 2.0",
"https://github.com/srikanth-lingala/zip4j"));
StringBuilder sb = new StringBuilder();
for(License license : licenses) {
for (License license : licenses) {
sb.append("\u2022 ");
sb.append(license.name);
sb.append("<br>");
@@ -158,11 +217,12 @@ public class AboutActivity extends StudioActivity {
binding.items.licenses.setText(Html.fromHtml(sb.toString()));
binding.items.licenses.setMovementMethod(LinkMovementMethod.getInstance());
}
private String getFooter() {
final String arch = android.os.Build.SUPPORTED_ABIS[0];
try {
final String version = getPackageManager().getPackageInfo("com.itsaky.androidide", 0).versionName;
final String version =
getPackageManager().getPackageInfo("com.itsaky.androidide", 0).versionName;
return getString(R.string.about_footer, version, arch);
} catch (Throwable th) {
return getString(R.string.about_footer_alternate, arch);
@@ -17,53 +17,43 @@
package com.itsaky.androidide;
import static com.blankj.utilcode.util.AppUtils.getAppVersionCode;
import static com.blankj.utilcode.util.AppUtils.getAppVersionName;
import static com.blankj.utilcode.util.DeviceUtils.getManufacturer;
import static com.blankj.utilcode.util.DeviceUtils.getModel;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.view.View;
import androidx.annotation.NonNull;
import com.blankj.utilcode.util.ClipboardUtils;
import com.itsaky.androidide.app.BaseApplication;
import com.itsaky.androidide.app.StudioActivity;
import com.itsaky.androidide.databinding.ActivityCrashHandlerBinding;
import com.itsaky.androidide.fragments.CrashReportFragment;
public class CrashHandlerActivity extends StudioActivity {
private ActivityCrashHandlerBinding binding;
public static final String REPORT_ACTION = "com.itsaky.androidide.REPORT_CRASH";
public static final String TRACE_KEY = "crash_trace";
@Override
protected View bindLayout () {
binding = ActivityCrashHandlerBinding.inflate (getLayoutInflater ());
return binding.getRoot ();
protected View bindLayout() {
binding = ActivityCrashHandlerBinding.inflate(getLayoutInflater());
return binding.getRoot();
}
@Override
protected void onCreate (Bundle savedInstanceState) {
super.onCreate (savedInstanceState);
final var extra = getIntent ().getExtras ();
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
final var extra = getIntent().getExtras();
if (extra == null) {
finishAffinity ();
finishAffinity();
return;
}
final var report = extra.getString (TRACE_KEY, "Unable to get logs.");
final var fragment = CrashReportFragment.newInstance (report);
getSupportFragmentManager ().beginTransaction ()
.replace (binding.getRoot ().getId (), fragment, "crash_report_fragment")
.addToBackStack (null)
.commit ();
final var report = extra.getString(TRACE_KEY, "Unable to get logs.");
final var fragment = CrashReportFragment.newInstance(report);
getSupportFragmentManager()
.beginTransaction()
.replace(binding.getRoot().getId(), fragment, "crash_report_fragment")
.addToBackStack(null)
.commit();
}
}
}
@@ -80,335 +80,399 @@ import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
public class DesignerActivity extends StudioActivity implements WidgetItemAdapter.OnDragStartListener {
public class DesignerActivity extends StudioActivity
implements WidgetItemAdapter.OnDragStartListener {
public static final String KEY_LAYOUT_PATH = "designer_layoutPath";
public static final String KEY_GENERATED_CODE = "designer_xmlCode";
public static final String DRAGGING_WIDGET_TAG = "DRAGGING_WIDGET";
public static final String DRAGGING_WIDGET_MIME = "application/ide_widget";
private static final Logger LOG = Logger.instance ("DesignerActivity");
private final boolean isTablet = DeviceUtils.isTablet ();
private final List<UIWidgetGroup> widgetGroups = new ArrayList<> ();
private static final Logger LOG = Logger.instance("DesignerActivity");
private final boolean isTablet = DeviceUtils.isTablet();
private final List<UIWidgetGroup> widgetGroups = new ArrayList<>();
private ActivityDesignerBinding mBinding;
private UIWidgetGroup checkedWidgetCategory;
private IViewGroup inflatedRoot;
private File layout;
private boolean inflationFailed = false;
private AttrEditorSheet mEditorSheet;
// This will make sure to apply listeners, background and data to view that are inflated from XML.
private final IInflateListener mInflateListener = new IInflateListener () {
@Override
public void onBeginInflate () {
}
@Override
public void onInflateView (IView view, IViewGroup parent) {
if (view instanceof IViewGroup) {
View v = view.asView ();
v.setBackgroundResource (R.drawable.bg_design_container_background);
v.setOnDragListener (getOnDragListener ((IViewGroup) view));
}
setupInflatedView (view);
setDragDataToInflatedView (view);
}
@Override
public void onApplyAttribute (IAttribute attr, IView view) {
}
@Override
public void onFinishInflate (IView rootView) {
if (rootView instanceof IViewGroup) {
inflatedRoot = (IViewGroup) rootView;
}
}
};
@Override
protected View bindLayout () {
this.mBinding = ActivityDesignerBinding.inflate (getLayoutInflater ());
return mBinding.getRoot ();
}
@Override
protected void onCreate (Bundle savedInstanceState) {
super.onCreate (savedInstanceState);
setSupportActionBar (mBinding.toolbar);
getSupportActionBar ().setDisplayHomeAsUpEnabled (true);
final var toggle = new ActionBarDrawerToggle (this, mBinding.getRoot (), mBinding.toolbar, R.string.app_name, R.string.app_name) {
@Override
public void onDrawerSlide (View drawerView, float slideOffset) {
super.onDrawerSlide (drawerView, slideOffset);
if (isTablet) {
mBinding.mainContainer.setTranslationX (drawerView.getWidth () * slideOffset);
// This will make sure to apply listeners, background and data to view that are inflated from
// XML.
private final IInflateListener mInflateListener =
new IInflateListener() {
@Override
public void onBeginInflate() {}
@Override
public void onInflateView(IView view, IViewGroup parent) {
if (view instanceof IViewGroup) {
View v = view.asView();
v.setBackgroundResource(R.drawable.bg_design_container_background);
v.setOnDragListener(getOnDragListener((IViewGroup) view));
}
setupInflatedView(view);
setDragDataToInflatedView(view);
}
}
};
mBinding.getRoot ().addDrawerListener (toggle);
toggle.syncState ();
final var extras = getIntent ().getExtras ();
final var path = extras.getString (KEY_LAYOUT_PATH, null);
final var name = path.substring (path.lastIndexOf (File.separator) + 1);
getSupportActionBar ().setTitle (name);
@Override
public void onApplyAttribute(IAttribute attr, IView view) {}
@Override
public void onFinishInflate(IView rootView) {
if (rootView instanceof IViewGroup) {
inflatedRoot = (IViewGroup) rootView;
}
}
};
@Override
protected View bindLayout() {
this.mBinding = ActivityDesignerBinding.inflate(getLayoutInflater());
return mBinding.getRoot();
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setSupportActionBar(mBinding.toolbar);
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
final var toggle =
new ActionBarDrawerToggle(
this,
mBinding.getRoot(),
mBinding.toolbar,
R.string.app_name,
R.string.app_name) {
@Override
public void onDrawerSlide(View drawerView, float slideOffset) {
super.onDrawerSlide(drawerView, slideOffset);
if (isTablet) {
mBinding.mainContainer.setTranslationX(
drawerView.getWidth() * slideOffset);
}
}
};
mBinding.getRoot().addDrawerListener(toggle);
toggle.syncState();
final var extras = getIntent().getExtras();
final var path = extras.getString(KEY_LAYOUT_PATH, null);
final var name = path.substring(path.lastIndexOf(File.separator) + 1);
getSupportActionBar().setTitle(name);
try {
this.layout = new File (path);
final ILayoutInflater inflater = getApp ().getLayoutInflater ();
inflater.resetContextProvider (newContextProvider ());
inflater.registerInflateListener (this.mInflateListener);
final IView view = inflater.inflatePath (path, mBinding.layoutContainer);
this.layout = new File(path);
final ILayoutInflater inflater = getApp().getLayoutInflater();
inflater.resetContextProvider(newContextProvider());
inflater.registerInflateListener(this.mInflateListener);
final IView view = inflater.inflatePath(path, mBinding.layoutContainer);
if (this.inflatedRoot != null) {
this.inflatedRoot.asView ().setOnDragListener (getOnDragListener (this.inflatedRoot));
this.inflatedRoot.asView().setOnDragListener(getOnDragListener(this.inflatedRoot));
}
mBinding.layoutContainer.addView (view.asView ());
mBinding.layoutContainer.setOnClickListener (v -> mBinding.getRoot ().openDrawer (GravityCompat.START));
mBinding.layoutContainer.addView(view.asView());
mBinding.layoutContainer.setOnClickListener(
v -> mBinding.getRoot().openDrawer(GravityCompat.START));
} catch (Throwable th) {
mBinding.layoutContainer.removeAllViews ();
mBinding.layoutContainer.addView (createErrorText (th));
LOG.error (getString (R.string.err_cannot_inflate_layout), th);
mBinding.layoutContainer.removeAllViews();
mBinding.layoutContainer.addView(createErrorText(th));
LOG.error(getString(R.string.err_cannot_inflate_layout), th);
inflationFailed = true;
}
setupWidgets ();
setupWidgets();
// Always open the drawer if this device is a tablet.
if (isTablet) {
mBinding.getRoot ().setScrimColor (Color.TRANSPARENT);
mBinding.getRoot ().openDrawer (GravityCompat.START);
mBinding.getRoot ().setDrawerLockMode (DrawerLayout.LOCK_MODE_LOCKED_OPEN);
mBinding.getRoot().setScrimColor(Color.TRANSPARENT);
mBinding.getRoot().openDrawer(GravityCompat.START);
mBinding.getRoot().setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_OPEN);
}
}
@Override
public void onBackPressed () {
public void onBackPressed() {
if (inflationFailed) {
super.onBackPressed ();
super.onBackPressed();
return;
}
// When the user presses the back button, set the activity result and finish this activity
final var progress = new ProgressSheet ();
progress.setSubMessageEnabled (true);
progress.setShowTitle (false);
progress.setMessage (getString (R.string.title_generating_xml));
progress.setSubMessage (getString (R.string.please_wait));
progress.show (getSupportFragmentManager (), "generate_code_progress");
final var future = CompletableFuture.supplyAsync (() -> BaseView.XML_HEADER.concat ("\n").concat (inflatedRoot.generateCode ()));
future.whenComplete ((s, throwable) -> {
progress.dismiss ();
if (throwable != null) {
notifyXmlGenerationFailed (throwable);
return;
}
try {
finishWithResult (future.get ());
} catch (ExecutionException | InterruptedException e) {
notifyXmlGenerationFailed (e);
}
});
final var progress = new ProgressSheet();
progress.setSubMessageEnabled(true);
progress.setShowTitle(false);
progress.setMessage(getString(R.string.title_generating_xml));
progress.setSubMessage(getString(R.string.please_wait));
progress.show(getSupportFragmentManager(), "generate_code_progress");
final var future =
CompletableFuture.supplyAsync(
() -> BaseView.XML_HEADER.concat("\n").concat(inflatedRoot.generateCode()));
future.whenComplete(
(s, throwable) -> {
progress.dismiss();
if (throwable != null) {
notifyXmlGenerationFailed(throwable);
return;
}
try {
finishWithResult(future.get());
} catch (ExecutionException | InterruptedException e) {
notifyXmlGenerationFailed(e);
}
});
}
private void notifyXmlGenerationFailed (Throwable error) {
final var errorMessage = getString (R.string.msg_generate_xml_failed);
LOG.error (errorMessage, error);
DialogUtils.newYesNoDialog (this,
getString (R.string.title_code_generation_failed),
getString (R.string.msg_code_generation_failed),
(dialog1, which) -> finishWithError (),
(dialog1, which) -> dialog1.dismiss ())
.show ();
private void notifyXmlGenerationFailed(Throwable error) {
final var errorMessage = getString(R.string.msg_generate_xml_failed);
LOG.error(errorMessage, error);
DialogUtils.newYesNoDialog(
this,
getString(R.string.title_code_generation_failed),
getString(R.string.msg_code_generation_failed),
(dialog1, which) -> finishWithError(),
(dialog1, which) -> dialog1.dismiss())
.show();
}
private void finishWithResult (String result) {
final var intent = new Intent ();
intent.putExtra (KEY_GENERATED_CODE, result);
setResult (RESULT_OK, intent);
finish ();
private void finishWithResult(String result) {
final var intent = new Intent();
intent.putExtra(KEY_GENERATED_CODE, result);
setResult(RESULT_OK, intent);
finish();
}
private void finishWithError () {
final var intent = new Intent ();
setResult (-123, intent);
finish ();
private void finishWithError() {
final var intent = new Intent();
setResult(-123, intent);
finish();
}
@Override
protected void onDestroy () {
if (isAttrEditorShowing ()) {
getAttrEditorSheet ().dismiss ();
protected void onDestroy() {
if (isAttrEditorShowing()) {
getAttrEditorSheet().dismiss();
}
this.mEditorSheet = null;
// Release the reference to inflate listener and context from the layout inflater
// Failing to do so will lead getSupportFragmentManager() to return a destroyed fragment manager
// Failing to do so will lead getSupportFragmentManager() to return a destroyed fragment
// manager
try {
final var layoutInflater = getApp ().getLayoutInflater ();
layoutInflater.unregisterListener (mInflateListener);
layoutInflater.resetContextProvider (null);
final var layoutInflater = getApp().getLayoutInflater();
layoutInflater.unregisterListener(mInflateListener);
layoutInflater.resetContextProvider(null);
} catch (Throwable e) {
e.printStackTrace ();
e.printStackTrace();
}
super.onDestroy ();
super.onDestroy();
}
private void setupInflatedView (@NonNull IView view) {
final var listener = new WidgetTouchListener (
view,
this::onLayoutViewClick,
this::onLayoutViewLongClick
);
view.asView ().setOnTouchListener (listener);
setDragDataToInflatedView (view);
private void setupInflatedView(@NonNull IView view) {
final var listener =
new WidgetTouchListener(view, this::onLayoutViewClick, this::onLayoutViewLongClick);
view.asView().setOnTouchListener(listener);
setDragDataToInflatedView(view);
}
private boolean onLayoutViewLongClick (@NonNull IView view) {
final var shadow = new WidgetDragShadowBuilder (view.asView ());
final var item = new ClipData.Item (DesignerActivity.DRAGGING_WIDGET_TAG);
final var clip = new ClipData (DesignerActivity.DRAGGING_WIDGET_TAG, new String[]{DesignerActivity.DRAGGING_WIDGET_MIME}, item);
final var data = view.getExtraData ();
view.asView ().startDragAndDrop (clip, shadow, data, 0);
view.removeFromParent ();
private boolean onLayoutViewLongClick(@NonNull IView view) {
final var shadow = new WidgetDragShadowBuilder(view.asView());
final var item = new ClipData.Item(DesignerActivity.DRAGGING_WIDGET_TAG);
final var clip =
new ClipData(
DesignerActivity.DRAGGING_WIDGET_TAG,
new String[] {DesignerActivity.DRAGGING_WIDGET_MIME},
item);
final var data = view.getExtraData();
view.asView().startDragAndDrop(clip, shadow, data, 0);
view.removeFromParent();
return true;
}
private void onLayoutViewClick (@NonNull IView view) {
if (isAttrEditorShowing ()) {
getAttrEditorSheet ().dismiss ();
private void onLayoutViewClick(@NonNull IView view) {
if (isAttrEditorShowing()) {
getAttrEditorSheet().dismiss();
}
getAttrEditorSheet ().setSelectedView (view);
if (!getSupportFragmentManager ().isDestroyed ()) {
getAttrEditorSheet ().show (getSupportFragmentManager (), "attribute_editor_dialog");
getAttrEditorSheet().setSelectedView(view);
if (!getSupportFragmentManager().isDestroyed()) {
getAttrEditorSheet().show(getSupportFragmentManager(), "attribute_editor_dialog");
}
}
private boolean isAttrEditorShowing () {
return getAttrEditorSheet ().getDialog () != null && getAttrEditorSheet ().getDialog ().isShowing ();
private boolean isAttrEditorShowing() {
return getAttrEditorSheet().getDialog() != null
&& getAttrEditorSheet().getDialog().isShowing();
}
private void setupWidgets () {
final var android = new UIWidgetGroup (getString (R.string.widget_group_android));
android.addChild (new UIWidget (getString (R.string.widget_button), R.drawable.ic_widget_button, Button.class));
android.addChild (new UIWidget (getString (R.string.widget_checkbox), R.drawable.ic_widget_checkbox, CheckBox.class));
android.addChild (new UIWidget (getString (R.string.widget_checked_textview), R.drawable.ic_widget_checked_textview, CheckedTextView.class));
android.addChild (new UIWidget (getString (R.string.widget_edittext), R.drawable.ic_widget_edittext, EditText.class));
android.addChild (new UIWidget (getString (R.string.widget_image_button), R.drawable.ic_widget_image, ImageButton.class));
android.addChild (new UIWidget (getString (R.string.widget_image_view), R.drawable.ic_widget_image, ImageView.class));
android.addChild (new UIWidget (getString (R.string.widget_progressbar), R.drawable.ic_widget_progress, ProgressBar.class));
android.addChild (new UIWidget (getString (R.string.widget_radio_button), R.drawable.ic_widget_radio_button, RadioButton.class));
android.addChild (new UIWidget (getString (R.string.widget_seekbar), R.drawable.ic_widget_seekbar, SeekBar.class));
android.addChild (new UIWidget (getString (R.string.widget_spinner), R.drawable.ic_widget_spinner, Spinner.class));
android.addChild (new UIWidget (getString (R.string.widget_textview), R.drawable.ic_widget_textview, TextView.class));
private void setupWidgets() {
final var android = new UIWidgetGroup(getString(R.string.widget_group_android));
android.addChild(
new UIWidget(
getString(R.string.widget_button),
R.drawable.ic_widget_button,
Button.class));
android.addChild(
new UIWidget(
getString(R.string.widget_checkbox),
R.drawable.ic_widget_checkbox,
CheckBox.class));
android.addChild(
new UIWidget(
getString(R.string.widget_checked_textview),
R.drawable.ic_widget_checked_textview,
CheckedTextView.class));
android.addChild(
new UIWidget(
getString(R.string.widget_edittext),
R.drawable.ic_widget_edittext,
EditText.class));
android.addChild(
new UIWidget(
getString(R.string.widget_image_button),
R.drawable.ic_widget_image,
ImageButton.class));
android.addChild(
new UIWidget(
getString(R.string.widget_image_view),
R.drawable.ic_widget_image,
ImageView.class));
android.addChild(
new UIWidget(
getString(R.string.widget_progressbar),
R.drawable.ic_widget_progress,
ProgressBar.class));
android.addChild(
new UIWidget(
getString(R.string.widget_radio_button),
R.drawable.ic_widget_radio_button,
RadioButton.class));
android.addChild(
new UIWidget(
getString(R.string.widget_seekbar),
R.drawable.ic_widget_seekbar,
SeekBar.class));
android.addChild(
new UIWidget(
getString(R.string.widget_spinner),
R.drawable.ic_widget_spinner,
Spinner.class));
android.addChild(
new UIWidget(
getString(R.string.widget_textview),
R.drawable.ic_widget_textview,
TextView.class));
// IMPORTANT: Do not select any other groups here
android.setSelected (true);
android.setSelected(true);
this.checkedWidgetCategory = android;
widgetGroups.add (android);
final var layouts = new UIWidgetGroup (getString (R.string.widget_group_layouts));
layouts.addChild (new UIWidget (getString (R.string.layout_linear), R.drawable.ic_widget_linear, LinearLayout.class));
layouts.addChild (new UIWidget (getString (R.string.layout_relative), R.drawable.ic_widget_relative, RelativeLayout.class));
widgetGroups.add (layouts);
final var adapter = new WidgetGroupItemAdapter (widgetGroups, this::showWidgetGroup);
mBinding.groupItems.setAdapter (adapter);
showWidgetGroup (android);
widgetGroups.add(android);
final var layouts = new UIWidgetGroup(getString(R.string.widget_group_layouts));
layouts.addChild(
new UIWidget(
getString(R.string.layout_linear),
R.drawable.ic_widget_linear,
LinearLayout.class));
layouts.addChild(
new UIWidget(
getString(R.string.layout_relative),
R.drawable.ic_widget_relative,
RelativeLayout.class));
widgetGroups.add(layouts);
final var adapter = new WidgetGroupItemAdapter(widgetGroups, this::showWidgetGroup);
mBinding.groupItems.setAdapter(adapter);
showWidgetGroup(android);
}
@SuppressLint("NotifyDataSetChanged")
private void showWidgetGroup (@NonNull UIWidgetGroup group) {
final var children = group.getChildren ();
this.mBinding.widgetItems.setAdapter (new WidgetItemAdapter (children, this));
this.checkedWidgetCategory.setSelected (false);
private void showWidgetGroup(@NonNull UIWidgetGroup group) {
final var children = group.getChildren();
this.mBinding.widgetItems.setAdapter(new WidgetItemAdapter(children, this));
this.checkedWidgetCategory.setSelected(false);
this.checkedWidgetCategory = group;
this.checkedWidgetCategory.setSelected (true);
TransitionManager.beginDelayedTransition (mBinding.navigation,
new TransitionSet ()
.addTransition (new Slide (Gravity.END))
.addTransition (new Fade ())
);
this.checkedWidgetCategory.setSelected(true);
TransitionManager.beginDelayedTransition(
mBinding.navigation,
new TransitionSet()
.addTransition(new Slide(Gravity.END))
.addTransition(new Fade()));
//noinspection ConstantConditions
this.mBinding.groupItems.getAdapter ().notifyDataSetChanged ();
this.mBinding.groupItems.getAdapter().notifyDataSetChanged();
}
@NonNull
@Contract(pure = true)
private ILayoutInflater.ContextProvider newContextProvider () {
private ILayoutInflater.ContextProvider newContextProvider() {
return () -> DesignerActivity.this;
}
@NonNull
private TextView createErrorText (Throwable th) {
final TextView error = new TextView (this);
error.setText (ThrowableUtils.getFullStackTrace (th));
error.setLayoutParams (new ViewGroup.LayoutParams (-2, -2));
error.setTextColor (ContextCompat.getColor (this, android.R.color.black));
error.setBackgroundColor (ContextCompat.getColor (this, android.R.color.white));
private TextView createErrorText(Throwable th) {
final TextView error = new TextView(this);
error.setText(ThrowableUtils.getFullStackTrace(th));
error.setLayoutParams(new ViewGroup.LayoutParams(-2, -2));
error.setTextColor(ContextCompat.getColor(this, android.R.color.black));
error.setBackgroundColor(ContextCompat.getColor(this, android.R.color.white));
return error;
}
@Override
public void onDragStarted (View view) {
mBinding.getRoot ().closeDrawer (GravityCompat.START);
public void onDragStarted(View view) {
mBinding.getRoot().closeDrawer(GravityCompat.START);
}
@NonNull
@Contract("_ -> new")
private View.OnDragListener getOnDragListener (IViewGroup group) {
private View.OnDragListener getOnDragListener(IViewGroup group) {
// WidgetDragListener cannot be reused
// This is because they keep a reference to the view group in which dragged view will be added
return new WidgetDragListener (
this,
group,
this::setupInflatedView);
// This is because they keep a reference to the view group in which dragged view will be
// added
return new WidgetDragListener(this, group, this::setupInflatedView);
}
private void setDragDataToInflatedView (@NonNull IView view) {
view.setExtraData (new WidgetDragData (true, view, null));
private void setDragDataToInflatedView(@NonNull IView view) {
view.setExtraData(new WidgetDragData(true, view, null));
}
private AttrEditorSheet getAttrEditorSheet () {
private AttrEditorSheet getAttrEditorSheet() {
return this.mEditorSheet == null
? mEditorSheet = new AttrEditorSheet ()
.setDeletionFailedListener (this::onViewDeletionFailed)
.setLayout (this.layout)
? mEditorSheet =
new AttrEditorSheet()
.setDeletionFailedListener(this::onViewDeletionFailed)
.setLayout(this.layout)
: mEditorSheet;
}
private boolean onViewDeletionFailed (@NonNull IView view) {
final var v = view.asView ();
if (mBinding.layoutContainer.indexOfChild (v) >= 0) {
mBinding.layoutContainer.removeView (v);
private boolean onViewDeletionFailed(@NonNull IView view) {
final var v = view.asView();
if (mBinding.layoutContainer.indexOfChild(v) >= 0) {
mBinding.layoutContainer.removeView(v);
return true;
}
return false;
@@ -17,8 +17,6 @@
*/
package com.itsaky.androidide;
import abhishekti7.unicorn.filepicker.UnicornFilePicker;
import android.content.Intent;
import android.content.res.ColorStateList;
import android.os.Bundle;
@@ -29,6 +27,8 @@ import android.view.View.OnClickListener;
import androidx.core.content.ContextCompat;
import androidx.transition.TransitionManager;
import abhishekti7.unicorn.filepicker.UnicornFilePicker;
import com.blankj.utilcode.util.FileIOUtils;
import com.blankj.utilcode.util.SizeUtils;
import com.blankj.utilcode.util.ThreadUtils;
@@ -55,252 +55,274 @@ import java.io.IOException;
import java.util.ArrayList;
public class DownloadActivity extends StudioActivity {
private ActivityDownloadBinding binding;
private ProgressSheet progressSheet;
private File choosenDir = null;
@Override
protected View bindLayout () {
binding = ActivityDownloadBinding.inflate (getLayoutInflater ());
return binding.getRoot ();
protected View bindLayout() {
binding = ActivityDownloadBinding.inflate(getLayoutInflater());
return binding.getRoot();
}
@Override
protected void onCreate (Bundle savedInstanceState) {
super.onCreate (savedInstanceState);
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
final OnClickListener clickListener = this::handleClick;
binding.dismiss.setOnClickListener (clickListener);
binding.choose.setOnClickListener (clickListener);
binding.install.setOnClickListener (clickListener);
binding.install.setEnabled (false);
binding.dismiss.setOnClickListener(clickListener);
binding.choose.setOnClickListener(clickListener);
binding.install.setOnClickListener(clickListener);
binding.install.setEnabled(false);
}
private void handleClick (View v) {
final int id = v.getId ();
if (id == binding.dismiss.getId ()) {
TransitionManager.beginDelayedTransition (binding.getRoot ());
binding.card.setVisibility (View.GONE);
} else if (id == binding.choose.getId ()) {
private void handleClick(View v) {
final int id = v.getId();
if (id == binding.dismiss.getId()) {
TransitionManager.beginDelayedTransition(binding.getRoot());
binding.card.setVisibility(View.GONE);
} else if (id == binding.choose.getId()) {
try {
binding.install.setEnabled (false);
UnicornFilePicker.from (this)
.addConfigBuilder ()
.addItemDivider (false)
.selectMultipleFiles (false)
.setRootDirectory (FileUtil.getExternalStorageDir ())
.showHiddenFiles (true)
.showOnlyDirectory (true)
.theme (R.style.AppTheme_FilePicker)
.build ()
.forResult (abhishekti7.unicorn.filepicker.utils.Constants.REQ_UNICORN_FILE);
binding.install.setEnabled(false);
UnicornFilePicker.from(this)
.addConfigBuilder()
.addItemDivider(false)
.selectMultipleFiles(false)
.setRootDirectory(FileUtil.getExternalStorageDir())
.showHiddenFiles(true)
.showOnlyDirectory(true)
.theme(R.style.AppTheme_FilePicker)
.build()
.forResult(abhishekti7.unicorn.filepicker.utils.Constants.REQ_UNICORN_FILE);
} catch (Throwable th) {
LOG.error (getString (R.string.err_cannot_start_file_picker), th);
LOG.error(getString(R.string.err_cannot_start_file_picker), th);
}
} else if (id == binding.install.getId ()) {
} else if (id == binding.install.getId()) {
if (choosenDir == null) {
getApp ().toast (R.string.msg_pick_files, Toaster.Type.ERROR);
getApp().toast(R.string.msg_pick_files, Toaster.Type.ERROR);
return;
}
if (!choosenDir.exists ()) {
getApp ().toast (R.string.msg_file_doesnt_exist, Toaster.Type.ERROR);
if (!choosenDir.exists()) {
getApp().toast(R.string.msg_file_doesnt_exist, Toaster.Type.ERROR);
return;
}
installAll ();
installAll();
}
}
private void installAll () {
showProgress ();
getApp ().getPrefManager ().putBoolean (PreferenceManager.KEY_FRAMEWORK_DOWNLOADED, true);
private void installAll() {
showProgress();
getApp().getPrefManager().putBoolean(PreferenceManager.KEY_FRAMEWORK_DOWNLOADED, true);
try {
final File script = createExtractScript ();
final ProcessStreamsHolder holder = new ProcessStreamsHolder ();
final IProcessExecutor executor = ProcessExecutorFactory.commonExecutor ();
executor.execAsync (holder,
final File script = createExtractScript();
final ProcessStreamsHolder holder = new ProcessStreamsHolder();
final IProcessExecutor executor = ProcessExecutorFactory.commonExecutor();
executor.execAsync(
holder,
this::onInstallProcessExit,
true,
Environment.BUSYBOX.getAbsolutePath (), "sh",// We use busybox's sh, because Environment.SHELL is not installed yet...
script.getAbsolutePath ()
);
this.output = new StringBuilder ();
final InputStreamLineReader reader = new InputStreamLineReader (holder.in, this::onInstallationOutput);
new Thread (reader).start ();
Environment.BUSYBOX.getAbsolutePath(),
"sh", // We use busybox's sh, because Environment.SHELL is not installed yet...
script.getAbsolutePath());
this.output = new StringBuilder();
final InputStreamLineReader reader =
new InputStreamLineReader(holder.in, this::onInstallationOutput);
new Thread(reader).start();
} catch (DownloadActivity.InstallationException e) {
LOG.error (getString (R.string.err_installation), e);
onInstallationFailed (e.exitCode);
LOG.error(getString(R.string.err_installation), e);
onInstallationFailed(e.exitCode);
} catch (IOException e) {
LOG.error (getString (R.string.err_installation), e);
onInstallationFailed (5); // Exit code 5 : I/O Error
LOG.error(getString(R.string.err_installation), e);
onInstallationFailed(5); // Exit code 5 : I/O Error
}
}
private void onInstallationOutput (final String line) {
ThreadUtils.runOnUiThread (() -> this.appendOut (line));
private void onInstallationOutput(final String line) {
ThreadUtils.runOnUiThread(() -> this.appendOut(line));
}
private void onInstallProcessExit (final int code) {
ThreadUtils.runOnUiThread (() -> {
if (code == 0) { // 0 = normal execution
getApp ().getPrefManager ().putBoolean (PreferenceManager.KEY_FRAMEWORK_INSTALLED, true);
showRestartNeeded ();
if (getProgressSheet ().isShowing ()) {
getProgressSheet ().dismiss ();
}
} else {
onInstallationFailed (code);
}
});
private void onInstallProcessExit(final int code) {
ThreadUtils.runOnUiThread(
() -> {
if (code == 0) { // 0 = normal execution
getApp().getPrefManager()
.putBoolean(PreferenceManager.KEY_FRAMEWORK_INSTALLED, true);
showRestartNeeded();
if (getProgressSheet().isShowing()) {
getProgressSheet().dismiss();
}
} else {
onInstallationFailed(code);
}
});
}
private void onInstallationFailed (int code) {
private void onInstallationFailed(int code) {
// TODO Can this be improved by adding some animations?
if (getProgressSheet ().isShowing ()) {
getProgressSheet ().dismiss ();
if (getProgressSheet().isShowing()) {
getProgressSheet().dismiss();
}
final MaterialAlertDialogBuilder builder = DialogUtils.newMaterialDialogBuilder (this);
builder.setTitle (R.string.title_installation_failed);
builder.setMessage (getString (R.string.msg_installation_failed, code, this.output.toString ()));
builder.setPositiveButton (android.R.string.ok, (d, w) -> {
d.dismiss ();
finishAffinity ();
});
builder.setNegativeButton (android.R.string.cancel, null);
builder.show ();
final MaterialAlertDialogBuilder builder = DialogUtils.newMaterialDialogBuilder(this);
builder.setTitle(R.string.title_installation_failed);
builder.setMessage(
getString(R.string.msg_installation_failed, code, this.output.toString()));
builder.setPositiveButton(
android.R.string.ok,
(d, w) -> {
d.dismiss();
finishAffinity();
});
builder.setNegativeButton(android.R.string.cancel, null);
builder.show();
}
private File createExtractScript () throws DownloadActivity.InstallationException {
final StringBuilder sb = new StringBuilder ();
sb.append ("cd");
joiner (sb);
sb.append ("echo 'Installing...'");
joiner (sb);
File[] files = choosenDir.listFiles (ARCHIVE_FILTER);
private File createExtractScript() throws DownloadActivity.InstallationException {
final StringBuilder sb = new StringBuilder();
sb.append("cd");
joiner(sb);
sb.append("echo 'Installing...'");
joiner(sb);
File[] files = choosenDir.listFiles(ARCHIVE_FILTER);
if (files == null || files.length <= 0) {
throw new InstallationException (2);
throw new InstallationException(2);
}
for (File f : files) {
if (f.getName ().endsWith (".tar.xz")) {
if (f.getName ().startsWith ("androidide-sysroot")) {
sb.append ("cd $SYSROOT/..");
joiner (sb);
if (f.getName().endsWith(".tar.xz")) {
if (f.getName().startsWith("androidide-sysroot")) {
sb.append("cd $SYSROOT/..");
joiner(sb);
}
sb.append ("$BUSYBOX tar xvJf '").append (f.getAbsolutePath ()).append ("'");
joiner (sb);
sb.append ("cd $HOME");
joiner (sb);
} else if (f.getName ().endsWith (".zip")) {
sb.append ("$BUSYBOX unzip '").append (f.getAbsolutePath ()).append ("'");
joiner (sb);
sb.append("$BUSYBOX tar xvJf '").append(f.getAbsolutePath()).append("'");
joiner(sb);
sb.append("cd $HOME");
joiner(sb);
} else if (f.getName().endsWith(".zip")) {
sb.append("$BUSYBOX unzip '").append(f.getAbsolutePath()).append("'");
joiner(sb);
}
}
sb.append ("echo 'Cleaning unsupported flags in binaries...'");
joiner (sb);
sb.append("echo 'Cleaning unsupported flags in binaries...'");
joiner(sb);
String DONE = "DONE";
sb.append ("echo ").append (DONE);
final File script = new File (Environment.TMP_DIR, "extract_tools.sh");
if (!FileIOUtils.writeFileFromString (script, sb.toString ())) {
throw new InstallationException (2);
sb.append("echo ").append(DONE);
final File script = new File(Environment.TMP_DIR, "extract_tools.sh");
if (!FileIOUtils.writeFileFromString(script, sb.toString())) {
throw new InstallationException(2);
}
return script;
}
private void joiner (StringBuilder sb) {
sb.append (" && ");
private void joiner(StringBuilder sb) {
sb.append(" && ");
}
private StringBuilder output = new StringBuilder ();
private void appendOut (String line) {
LOG.debug ("Installation:", line);
output.append(line.trim ());
output.append ("\n");
getProgressSheet ().setSubMessage (line);
private StringBuilder output = new StringBuilder();
private void appendOut(String line) {
LOG.debug("Installation:", line);
output.append(line.trim());
output.append("\n");
getProgressSheet().setSubMessage(line);
}
@Override
protected void onActivityResult (int requestCode, int resultCode, Intent data) {
super.onActivityResult (requestCode, resultCode, data);
if (requestCode == abhishekti7.unicorn.filepicker.utils.Constants.REQ_UNICORN_FILE && resultCode == RESULT_OK) {
ArrayList<String> files = data.getStringArrayListExtra ("filePaths");
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == abhishekti7.unicorn.filepicker.utils.Constants.REQ_UNICORN_FILE
&& resultCode == RESULT_OK) {
ArrayList<String> files = data.getStringArrayListExtra("filePaths");
if (files != null) {
if (files.size () == 1) {
File choosenDir = new File (files.get (0));
if (choosenDir.exists () && choosenDir.isDirectory ()) {
if (files.size() == 1) {
File choosenDir = new File(files.get(0));
if (choosenDir.exists() && choosenDir.isDirectory()) {
this.choosenDir = choosenDir;
setDone (binding.choose);
binding.install.setEnabled (true);
new TaskExecutor ().executeAsync (new ListDirectoryCallable (choosenDir), __ -> binding.filesText.setText (getString (R.string.msg_installable_files, __)));
setDone(binding.choose);
binding.install.setEnabled(true);
new TaskExecutor()
.executeAsync(
new ListDirectoryCallable(choosenDir),
__ ->
binding.filesText.setText(
getString(
R.string.msg_installable_files,
__)));
} else {
getApp ().toast (R.string.msg_picked_isnt_dir, Toaster.Type.ERROR);
getApp().toast(R.string.msg_picked_isnt_dir, Toaster.Type.ERROR);
}
} else {
getApp ().toast (R.string.msg_pick_single_file, Toaster.Type.ERROR);
getApp().toast(R.string.msg_pick_single_file, Toaster.Type.ERROR);
}
}
}
}
private void setDone (MaterialButton button) {
final int dp8 = SizeUtils.dp2px (8);
final int dp4 = SizeUtils.dp2px (4);
final int dp24 = SizeUtils.dp2px (24);
button.setText ("");
button.setIconResource (R.drawable.ic_ok);
button.setIconTint (ColorStateList.valueOf (ContextCompat.getColor (this, R.color.bg_green)));
button.setBackgroundColor (android.graphics.Color.TRANSPARENT);
button.setPaddingRelative (dp8, dp4, dp8, dp4);
button.setStrokeWidth (0);
button.setGravity (Gravity.CENTER);
button.setIconPadding (0);
button.setInsetTop (0);
button.setInsetBottom (0);
button.setPaddingRelative (dp24, dp4, dp24, dp4);
private void setDone(MaterialButton button) {
final int dp8 = SizeUtils.dp2px(8);
final int dp4 = SizeUtils.dp2px(4);
final int dp24 = SizeUtils.dp2px(24);
button.setText("");
button.setIconResource(R.drawable.ic_ok);
button.setIconTint(ColorStateList.valueOf(ContextCompat.getColor(this, R.color.bg_green)));
button.setBackgroundColor(android.graphics.Color.TRANSPARENT);
button.setPaddingRelative(dp8, dp4, dp8, dp4);
button.setStrokeWidth(0);
button.setGravity(Gravity.CENTER);
button.setIconPadding(0);
button.setInsetTop(0);
button.setInsetBottom(0);
button.setPaddingRelative(dp24, dp4, dp24, dp4);
}
private void showProgress () {
getProgressSheet ().setCancelable (false);
getProgressSheet ().setShowShadow (false);
getProgressSheet ().setSubMessageEnabled (true)
.setWelcomeTextEnabled (true)
.show (getSupportFragmentManager (), "progress_sheet");
private void showProgress() {
getProgressSheet().setCancelable(false);
getProgressSheet().setShowShadow(false);
getProgressSheet()
.setSubMessageEnabled(true)
.setWelcomeTextEnabled(true)
.show(getSupportFragmentManager(), "progress_sheet");
}
private void showRestartNeeded () {
setDone (binding.install);
final MaterialAlertDialogBuilder builder = DialogUtils.newMaterialDialogBuilder (this);
builder.setTitle (R.string.title_restart);
builder.setMessage (R.string.msg_restart);
builder.setCancelable (false);
builder.setPositiveButton (android.R.string.ok, (p1, p2) -> finishAffinity ());
builder.create ().show ();
private void showRestartNeeded() {
setDone(binding.install);
final MaterialAlertDialogBuilder builder = DialogUtils.newMaterialDialogBuilder(this);
builder.setTitle(R.string.title_restart);
builder.setMessage(R.string.msg_restart);
builder.setCancelable(false);
builder.setPositiveButton(android.R.string.ok, (p1, p2) -> finishAffinity());
builder.create().show();
}
private ProgressSheet getProgressSheet () {
return progressSheet == null ? progressSheet = new ProgressSheet ().setMessage (getString (R.string.please_wait)) : progressSheet;
private ProgressSheet getProgressSheet() {
return progressSheet == null
? progressSheet = new ProgressSheet().setMessage(getString(R.string.please_wait))
: progressSheet;
}
private final FileFilter ARCHIVE_FILTER = p1 -> p1.isFile () && (p1.getName ().endsWith (".tar.xz") || p1.getName ().endsWith (".zip"));
private final FileFilter ARCHIVE_FILTER =
p1 ->
p1.isFile()
&& (p1.getName().endsWith(".tar.xz") || p1.getName().endsWith(".zip"));
private static class InstallationException extends Exception {
private final int exitCode;
public InstallationException (int exitCode) {
public InstallationException(int exitCode) {
this.exitCode = exitCode;
}
}
File diff suppressed because it is too large Load Diff
@@ -27,6 +27,8 @@ import android.view.View;
import androidx.transition.TransitionManager;
import abhishekti7.unicorn.filepicker.UnicornFilePicker;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import com.google.android.material.transition.MaterialContainerTransform;
import com.itsaky.androidide.app.StudioActivity;
@@ -52,363 +54,403 @@ import java.util.ArrayList;
import java.util.Objects;
import java.util.concurrent.Callable;
import abhishekti7.unicorn.filepicker.UnicornFilePicker;
public class MainActivity extends StudioActivity
implements View.OnClickListener, ProjectWriterCallback {
public class MainActivity extends StudioActivity implements View.OnClickListener, ProjectWriterCallback {
private ActivityMainBinding binding;
private LayoutCreateProjectBinding createBinding;
private LayoutCreateProjectContentBinding createLayoutBinding;
private ProgressSheet mProgressSheet;
private ArrayList<ProjectTemplate> mTemplates = new ArrayList<> ();
private ArrayList<ProjectTemplate> mTemplates = new ArrayList<>();
private int currentTemplateIndex = 0;
@Override
protected View bindLayout () {
binding = ActivityMainBinding.inflate (getLayoutInflater ());
protected View bindLayout() {
binding = ActivityMainBinding.inflate(getLayoutInflater());
createBinding = binding.createLayout;
createLayoutBinding = createBinding.createProjectContent;
return binding.getRoot ();
return binding.getRoot();
}
@Override
protected void onCreate (Bundle savedInstanceState) {
super.onCreate (savedInstanceState);
binding.createProject.setOnClickListener (this);
binding.openProject.setOnClickListener (this);
binding.openTerminal.setOnClickListener (this);
createBinding.createprojectClose.setOnClickListener (this);
createLayoutBinding.createprojectCreate.setOnClickListener (this);
createLayoutBinding.previousCard.setOnClickListener (this);
createLayoutBinding.nextCard.setOnClickListener (this);
binding.createNewCard.setVisibility (View.GONE);
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
binding.createProject.setOnClickListener(this);
binding.openProject.setOnClickListener(this);
binding.openTerminal.setOnClickListener(this);
createBinding.createprojectClose.setOnClickListener(this);
createLayoutBinding.createprojectCreate.setOnClickListener(this);
createLayoutBinding.previousCard.setOnClickListener(this);
createLayoutBinding.nextCard.setOnClickListener(this);
binding.createNewCard.setVisibility(View.GONE);
currentTemplateIndex = 0;
createTemplates ();
createProgressSheet ();
showTemplate (currentTemplateIndex);
binding.gotoPreferences.setOnClickListener (this);
binding.getRoot ().post (() -> {
PreferenceManager manager = getApp ().getPrefManager ();
if (manager.autoOpenProject () && manager.wasProjectOpened () && ConstantsBridge.SPLASH_TO_MAIN) {
String path = manager.getOpenedProject ();
if (path == null || path.trim ().isEmpty ()) {
getApp ().toast (R.string.msg_opened_project_does_not_exist, Toaster.Type.INFO);
} else {
File root = new File (manager.getOpenedProject ());
if (!root.exists ()) {
getApp ().toast (R.string.msg_opened_project_does_not_exist, Toaster.Type.INFO);
} else {
if (manager.confirmProjectOpen ()) {
askProjectOpenPermission (root);
} else {
openProject (root);
}
}
}
}
ConstantsBridge.SPLASH_TO_MAIN = false;
});
createTemplates();
createProgressSheet();
showTemplate(currentTemplateIndex);
binding.gotoPreferences.setOnClickListener(this);
binding.getRoot()
.post(
() -> {
PreferenceManager manager = getApp().getPrefManager();
if (manager.autoOpenProject()
&& manager.wasProjectOpened()
&& ConstantsBridge.SPLASH_TO_MAIN) {
String path = manager.getOpenedProject();
if (path == null || path.trim().isEmpty()) {
getApp().toast(
R.string.msg_opened_project_does_not_exist,
Toaster.Type.INFO);
} else {
File root = new File(manager.getOpenedProject());
if (!root.exists()) {
getApp().toast(
R.string.msg_opened_project_does_not_exist,
Toaster.Type.INFO);
} else {
if (manager.confirmProjectOpen()) {
askProjectOpenPermission(root);
} else {
openProject(root);
}
}
}
}
ConstantsBridge.SPLASH_TO_MAIN = false;
});
}
private void askProjectOpenPermission (File root) {
final MaterialAlertDialogBuilder builder = DialogUtils.newMaterialDialogBuilder (this);
builder.setTitle (R.string.title_confirm_open_project);
builder.setMessage (getString (R.string.msg_confirm_open_project, root.getAbsolutePath ()));
builder.setCancelable (false);
builder.setPositiveButton (R.string.yes, (d, w) -> openProject (root));
builder.setNegativeButton (R.string.no, null);
builder.show ();
private void askProjectOpenPermission(File root) {
final MaterialAlertDialogBuilder builder = DialogUtils.newMaterialDialogBuilder(this);
builder.setTitle(R.string.title_confirm_open_project);
builder.setMessage(getString(R.string.msg_confirm_open_project, root.getAbsolutePath()));
builder.setCancelable(false);
builder.setPositiveButton(R.string.yes, (d, w) -> openProject(root));
builder.setNegativeButton(R.string.no, null);
builder.show();
}
private void gotoSettings () {
startActivity (new Intent (this, PreferencesActivity.class));
private void gotoSettings() {
startActivity(new Intent(this, PreferencesActivity.class));
}
@Override
protected void onStorageGranted () {
}
protected void onStorageGranted() {}
@Override
protected void onStorageDenied () {
getApp ().toast (R.string.msg_storage_denied, Toaster.Type.ERROR);
finishAffinity ();
protected void onStorageDenied() {
getApp().toast(R.string.msg_storage_denied, Toaster.Type.ERROR);
finishAffinity();
}
@Override
public void onClick (View p1) {
if (p1.getId () == binding.createProject.getId ()) {
showCreateProject ();
} else if (p1.getId () == createBinding.createprojectClose.getId ()) {
hideCreateProject ();
} else if (p1.getId () == createLayoutBinding.nextCard.getId ()) {
public void onClick(View p1) {
if (p1.getId() == binding.createProject.getId()) {
showCreateProject();
} else if (p1.getId() == createBinding.createprojectClose.getId()) {
hideCreateProject();
} else if (p1.getId() == createLayoutBinding.nextCard.getId()) {
currentTemplateIndex++;
if (currentTemplateIndex >= mTemplates.size ()) {
if (currentTemplateIndex >= mTemplates.size()) {
currentTemplateIndex = 0;
}
showTemplate (currentTemplateIndex);
} else if (p1.getId () == createLayoutBinding.previousCard.getId ()) {
showTemplate(currentTemplateIndex);
} else if (p1.getId() == createLayoutBinding.previousCard.getId()) {
currentTemplateIndex--;
if (currentTemplateIndex < 0) {
currentTemplateIndex = mTemplates.size () - 1;
currentTemplateIndex = mTemplates.size() - 1;
}
showTemplate (currentTemplateIndex);
} else if (p1.getId () == createLayoutBinding.createprojectCreate.getId ()) {
createNewProject ();
} else if (p1.getId () == binding.gotoPreferences.getId ()) {
gotoSettings ();
} else if (p1.getId () == binding.openProject.getId ()) {
pickProject ();
} else if (p1.getId () == binding.openTerminal.getId ()) {
startActivity (new Intent (this, TerminalActivity.class));
showTemplate(currentTemplateIndex);
} else if (p1.getId() == createLayoutBinding.createprojectCreate.getId()) {
createNewProject();
} else if (p1.getId() == binding.gotoPreferences.getId()) {
gotoSettings();
} else if (p1.getId() == binding.openProject.getId()) {
pickProject();
} else if (p1.getId() == binding.openTerminal.getId()) {
startActivity(new Intent(this, TerminalActivity.class));
}
}
@Override
public void beforeBegin () {
public void beforeBegin() {
if (mProgressSheet == null) {
createProgressSheet ();
createProgressSheet();
}
setMessage (R.string.msg_begin_project_write);
setMessage(R.string.msg_begin_project_write);
}
@Override
public void onProcessTask (String taskName) {
setMessage (taskName);
public void onProcessTask(String taskName) {
setMessage(taskName);
}
@Override
public void onSuccess (File root) {
public void onSuccess(File root) {
if (mProgressSheet == null) {
createProgressSheet ();
createProgressSheet();
}
if (mProgressSheet.isShowing ()) {
mProgressSheet.dismiss ();
if (mProgressSheet.isShowing()) {
mProgressSheet.dismiss();
}
getApp ().toast (R.string.project_created_successfully, Toaster.Type.SUCCESS);
openProject (root);
getApp().toast(R.string.project_created_successfully, Toaster.Type.SUCCESS);
openProject(root);
}
@Override
public void onFailed (String reason) {
public void onFailed(String reason) {
if (mProgressSheet == null) {
createProgressSheet ();
createProgressSheet();
}
if (mProgressSheet.isShowing ()) {
mProgressSheet.dismiss ();
if (mProgressSheet.isShowing()) {
mProgressSheet.dismiss();
}
getApp ().toast (reason, Toaster.Type.ERROR);
getApp().toast(reason, Toaster.Type.ERROR);
}
@Override
protected void onActivityResult (int requestCode, int resultCode, Intent data) {
super.onActivityResult (requestCode, resultCode, data);
if (requestCode == abhishekti7.unicorn.filepicker.utils.Constants.REQ_UNICORN_FILE && resultCode == RESULT_OK) {
ArrayList<String> files = data.getStringArrayListExtra ("filePaths");
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == abhishekti7.unicorn.filepicker.utils.Constants.REQ_UNICORN_FILE
&& resultCode == RESULT_OK) {
ArrayList<String> files = data.getStringArrayListExtra("filePaths");
if (files != null) {
if (files.size () == 1) {
File choseDir = new File (files.get (0));
if (choseDir.exists () && choseDir.isDirectory ()) {
openProject (choseDir);
if (files.size() == 1) {
File choseDir = new File(files.get(0));
if (choseDir.exists() && choseDir.isDirectory()) {
openProject(choseDir);
} else {
getApp ().toast (R.string.msg_picked_isnt_dir, Toaster.Type.ERROR);
getApp().toast(R.string.msg_picked_isnt_dir, Toaster.Type.ERROR);
}
} else {
getApp ().toast (R.string.msg_pick_single_file, Toaster.Type.ERROR);
getApp().toast(R.string.msg_pick_single_file, Toaster.Type.ERROR);
}
}
}
}
@Override
public void onBackPressed () {
if (binding.createNewCard.getVisibility () == View.VISIBLE) {
hideCreateProject ();
public void onBackPressed() {
if (binding.createNewCard.getVisibility() == View.VISIBLE) {
hideCreateProject();
} else {
super.onBackPressed ();
super.onBackPressed();
}
}
@Override
protected void onResume () {
super.onResume ();
if (mProgressSheet != null
&& mProgressSheet.isShowing ()) {
mProgressSheet.dismiss ();
protected void onResume() {
super.onResume();
if (mProgressSheet != null && mProgressSheet.isShowing()) {
mProgressSheet.dismiss();
}
}
private void showTemplate (int index) {
if (index < 0 || index >= mTemplates.size ()) {
private void showTemplate(int index) {
if (index < 0 || index >= mTemplates.size()) {
return;
}
final ProjectTemplate template = mTemplates.get (index);
createLayoutBinding.createprojectTemplateLabel.setText (template.getName ());
createLayoutBinding.createprojectTemplateDescription.setText (template.getDescription ());
createLayoutBinding.createprojectTemplateImage.setImageResource (template.getImageId ());
final ProjectTemplate template = mTemplates.get(index);
createLayoutBinding.createprojectTemplateLabel.setText(template.getName());
createLayoutBinding.createprojectTemplateDescription.setText(template.getDescription());
createLayoutBinding.createprojectTemplateImage.setImageResource(template.getImageId());
}
/**
* Must be called in/after onViewCreated
*/
private void createTemplates () {
mTemplates = new ArrayList<> ();
/** Must be called in/after onViewCreated */
private void createTemplates() {
mTemplates = new ArrayList<>();
ProjectTemplate
empty = new ProjectTemplate ()
.setId (0)
.setName (this, R.string.template_empty)
.setDescription (this, R.string.template_description_empty)
.setImageId (R.drawable.template_empty),
basic = new ProjectTemplate ()
.setId (1)
.setName (this, R.string.template_basic)
.setDescription (this, R.string.template_description_basic)
.setImageId (R.drawable.template_basic),
drawer = new ProjectTemplate ()
.setId (2)
.setName (this, R.string.template_navigation_drawer)
.setDescription (this, R.string.template_description_navigation_drawer)
.setImageId (R.drawable.template_navigation_drawer),
kotlinBasic = new ProjectTemplate ()
.setId (3)
.setName (this, R.string.template_kotlin_basic)
.setDescription (this, R.string.template_description_kotlin_basic)
.setImageId (R.drawable.template_basic),
libgdx = new ProjectTemplate ()
.setId (4)
.setName (this, R.string.template_libgdx)
.setDescription (this, R.string.template_description_libgdx)
.setImageId (R.drawable.template_libgdx);
mTemplates.add (empty);
mTemplates.add (basic);
mTemplates.add (drawer);
mTemplates.add (kotlinBasic);
mTemplates.add (libgdx);
empty =
new ProjectTemplate()
.setId(0)
.setName(this, R.string.template_empty)
.setDescription(this, R.string.template_description_empty)
.setImageId(R.drawable.template_empty),
basic =
new ProjectTemplate()
.setId(1)
.setName(this, R.string.template_basic)
.setDescription(this, R.string.template_description_basic)
.setImageId(R.drawable.template_basic),
drawer =
new ProjectTemplate()
.setId(2)
.setName(this, R.string.template_navigation_drawer)
.setDescription(
this, R.string.template_description_navigation_drawer)
.setImageId(R.drawable.template_navigation_drawer),
kotlinBasic =
new ProjectTemplate()
.setId(3)
.setName(this, R.string.template_kotlin_basic)
.setDescription(this, R.string.template_description_kotlin_basic)
.setImageId(R.drawable.template_basic),
libgdx =
new ProjectTemplate()
.setId(4)
.setName(this, R.string.template_libgdx)
.setDescription(this, R.string.template_description_libgdx)
.setImageId(R.drawable.template_libgdx);
mTemplates.add(empty);
mTemplates.add(basic);
mTemplates.add(drawer);
mTemplates.add(kotlinBasic);
mTemplates.add(libgdx);
}
private void createProgressSheet () {
mProgressSheet = new ProgressSheet ();
mProgressSheet.setShowShadow (false);
private void createProgressSheet() {
mProgressSheet = new ProgressSheet();
mProgressSheet.setShowShadow(false);
}
private void setMessage (int msg) {
setMessage (getString (msg));
private void setMessage(int msg) {
setMessage(getString(msg));
}
private void setMessage (String msg) {
mProgressSheet.setMessage (msg);
private void setMessage(String msg) {
mProgressSheet.setMessage(msg);
}
private void openProject (File root) {
setMessage (R.string.msg_opening_project);
mProgressSheet.setCancelable (false);
mProgressSheet.show (getSupportFragmentManager (), "opening_project");
new TaskExecutor ().executeAsync (new ProjectReader (root), this::openEditor);
private void openProject(File root) {
setMessage(R.string.msg_opening_project);
mProgressSheet.setCancelable(false);
mProgressSheet.show(getSupportFragmentManager(), "opening_project");
new TaskExecutor().executeAsync(new ProjectReader(root), this::openEditor);
}
private void pickProject () {
UnicornFilePicker.from (this)
.addConfigBuilder ()
.addItemDivider (false)
.selectMultipleFiles (false)
.setRootDirectory (Environment.getExternalStorageDirectory ().getAbsolutePath ())
.showHiddenFiles (true)
.showOnlyDirectory (true)
.theme (R.style.AppTheme_FilePicker)
.build ()
.forResult (abhishekti7.unicorn.filepicker.utils.Constants.REQ_UNICORN_FILE);
private void pickProject() {
UnicornFilePicker.from(this)
.addConfigBuilder()
.addItemDivider(false)
.selectMultipleFiles(false)
.setRootDirectory(Environment.getExternalStorageDirectory().getAbsolutePath())
.showHiddenFiles(true)
.showOnlyDirectory(true)
.theme(R.style.AppTheme_FilePicker)
.build()
.forResult(abhishekti7.unicorn.filepicker.utils.Constants.REQ_UNICORN_FILE);
}
private void openEditor (AndroidProject project) {
private void openEditor(AndroidProject project) {
if (project == null) {
getApp ().toast (R.string.msg_invalid_project, Toaster.Type.ERROR);
getApp().toast(R.string.msg_invalid_project, Toaster.Type.ERROR);
} else {
startActivity (new Intent (this, EditorActivity.class)
.putExtra (EditorActivity.EXTRA_PROJECT, project));
startActivity(
new Intent(this, EditorActivity.class)
.putExtra(EditorActivity.EXTRA_PROJECT, project));
}
}
private void createNewProject () {
final String appName = Objects.requireNonNull (createLayoutBinding.createprojectTextAppName.getEditText ()).getText ().toString ().trim ();
final String packageName = Objects.requireNonNull (createLayoutBinding.createprojectTextPackageName.getEditText ()).getText ().toString ().trim ();
final int minSdk = getMinSdk ();
final int targetSdk = getTargetSdk ();
if (!isValid (appName, packageName, minSdk, targetSdk)) {
getApp ().toast (R.string.invalid_values, Toaster.Type.ERROR);
private void createNewProject() {
final String appName =
Objects.requireNonNull(createLayoutBinding.createprojectTextAppName.getEditText())
.getText()
.toString()
.trim();
final String packageName =
Objects.requireNonNull(
createLayoutBinding.createprojectTextPackageName.getEditText())
.getText()
.toString()
.trim();
final int minSdk = getMinSdk();
final int targetSdk = getTargetSdk();
if (!isValid(appName, packageName, minSdk, targetSdk)) {
getApp().toast(R.string.invalid_values, Toaster.Type.ERROR);
return;
}
createProject (appName, packageName, minSdk, targetSdk);
createProject(appName, packageName, minSdk, targetSdk);
}
private void createProject (String appName, String packageName, int minSdk, int targetSdk) {
new TaskExecutor ().executeAsync (new ProjectCreatorCallable (mTemplates.get (currentTemplateIndex), new NewProjectDetails (appName, packageName, minSdk, targetSdk), this), r -> {
});
private void createProject(String appName, String packageName, int minSdk, int targetSdk) {
new TaskExecutor()
.executeAsync(
new ProjectCreatorCallable(
mTemplates.get(currentTemplateIndex),
new NewProjectDetails(appName, packageName, minSdk, targetSdk),
this),
r -> {});
}
private boolean isValid (String appName, String packageName, int minSdk, int targetSdk) {
return appName != null && appName.length () > 0
&& packageName != null && packageName.length () > 0
&& minSdk > 1 && minSdk < 99
&& targetSdk > 1 && targetSdk < 99;
private boolean isValid(String appName, String packageName, int minSdk, int targetSdk) {
return appName != null
&& appName.length() > 0
&& packageName != null
&& packageName.length() > 0
&& minSdk > 1
&& minSdk < 99
&& targetSdk > 1
&& targetSdk < 99;
}
private int getMinSdk () {
private int getMinSdk() {
try {
return Integer.parseInt (Objects.requireNonNull (createLayoutBinding.createprojectTextMinSdk.getEditText ()).getText ().toString ());
return Integer.parseInt(
Objects.requireNonNull(
createLayoutBinding.createprojectTextMinSdk.getEditText())
.getText()
.toString());
} catch (Exception e) {
getApp ().toast (e.getMessage (), Toaster.Type.ERROR);
getApp().toast(e.getMessage(), Toaster.Type.ERROR);
}
return -1;
}
private int getTargetSdk () {
private int getTargetSdk() {
try {
return Integer.parseInt (Objects.requireNonNull (createLayoutBinding.createprojectTextTargetSdk.getEditText ()).getText ().toString ());
return Integer.parseInt(
Objects.requireNonNull(
createLayoutBinding.createprojectTextTargetSdk.getEditText())
.getText()
.toString());
} catch (Exception e) {
getApp ().toast (e.getMessage (), Toaster.Type.ERROR);
getApp().toast(e.getMessage(), Toaster.Type.ERROR);
}
return -1;
}
private void showCreateProject () {
MaterialContainerTransform transform = TransformUtils.createContainerTransformFor (binding.createProject, binding.createNewCard, binding.realContainer);
TransitionManager.beginDelayedTransition (binding.getRoot (), transform);
binding.createNewCard.setVisibility (View.VISIBLE);
private void showCreateProject() {
MaterialContainerTransform transform =
TransformUtils.createContainerTransformFor(
binding.createProject, binding.createNewCard, binding.realContainer);
TransitionManager.beginDelayedTransition(binding.getRoot(), transform);
binding.createNewCard.setVisibility(View.VISIBLE);
}
private void hideCreateProject () {
MaterialContainerTransform transform = TransformUtils.createContainerTransformFor (binding.createNewCard, binding.createProject, binding.realContainer);
TransitionManager.beginDelayedTransition (binding.getRoot (), transform);
binding.createNewCard.setVisibility (View.GONE);
private void hideCreateProject() {
MaterialContainerTransform transform =
TransformUtils.createContainerTransformFor(
binding.createNewCard, binding.createProject, binding.realContainer);
TransitionManager.beginDelayedTransition(binding.getRoot(), transform);
binding.createNewCard.setVisibility(View.GONE);
}
private static final class ProjectReader implements Callable<AndroidProject> {
private final File root;
public ProjectReader (File root) {
public ProjectReader(File root) {
this.root = root;
}
@Override
public AndroidProject call () throws Exception {
return ProjectFinder.fromFolder (root);
public AndroidProject call() throws Exception {
return ProjectFinder.fromFolder(root);
}
}
}
@@ -1,69 +1,68 @@
/************************************************************************************
* This file is part of AndroidIDE.
*
*
*
*
* AndroidIDE is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
*
* AndroidIDE is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
*
* You should have received a copy of the GNU General Public License
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
*
**************************************************************************************/
**************************************************************************************/
package com.itsaky.androidide;
import android.os.Bundle;
import android.view.View;
import androidx.fragment.app.Fragment;
import com.itsaky.androidide.app.StudioActivity;
import com.itsaky.androidide.databinding.ActivityPreferencesBinding;
import com.itsaky.androidide.fragments.preferences.BuildPreferences;
import com.itsaky.androidide.fragments.preferences.EditorPreferences;
import com.itsaky.androidide.fragments.preferences.IDEPreferences;
public class PreferencesActivity extends StudioActivity {
private ActivityPreferencesBinding binding;
private IDEPreferences mPref;
@Override
protected View bindLayout() {
binding = ActivityPreferencesBinding.inflate(getLayoutInflater());
return binding.getRoot();
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setSupportActionBar(binding.toolbar);
getSupportActionBar().setTitle(R.string.ide_preferences);
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
private ActivityPreferencesBinding binding;
private IDEPreferences mPref;
@Override
protected View bindLayout() {
binding = ActivityPreferencesBinding.inflate(getLayoutInflater());
return binding.getRoot();
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setSupportActionBar(binding.toolbar);
getSupportActionBar().setTitle(R.string.ide_preferences);
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
binding.toolbar.setNavigationOnClickListener(v -> onBackPressed());
loadFragment(getPrefsFragment());
}
private IDEPreferences getPrefsFragment() {
return mPref == null ? mPref = new IDEPreferences() : mPref;
}
private void loadFragment(Fragment frag) {
super.loadFragment(frag, binding.fragmentContainer.getId());
}
@Override
public void onBackPressed() {
super.onBackPressed();
}
loadFragment(getPrefsFragment());
}
private IDEPreferences getPrefsFragment() {
return mPref == null ? mPref = new IDEPreferences() : mPref;
}
private void loadFragment(Fragment frag) {
super.loadFragment(frag, binding.fragmentContainer.getId());
}
@Override
public void onBackPressed() {
super.onBackPressed();
}
}
@@ -1,23 +1,22 @@
/************************************************************************************
* This file is part of AndroidIDE.
*
*
*
*
* AndroidIDE is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
*
* AndroidIDE is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
*
* You should have received a copy of the GNU General Public License
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
*
**************************************************************************************/
**************************************************************************************/
package com.itsaky.androidide;
@@ -25,6 +24,7 @@ import android.content.Intent;
import android.os.Bundle;
import android.os.Handler;
import android.view.View;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import com.itsaky.androidide.app.StudioActivity;
import com.itsaky.androidide.app.StudioApp;
@@ -32,69 +32,63 @@ import com.itsaky.androidide.databinding.ActivitySplashBinding;
import com.itsaky.androidide.models.ConstantsBridge;
import com.itsaky.androidide.utils.DialogUtils;
import android.text.TextUtils;
import org.eclipse.jdt.core.Signature;
public class SplashActivity extends StudioActivity {
private ActivitySplashBinding binding;
public class SplashActivity extends StudioActivity
{
private ActivitySplashBinding binding;
private static final Handler mHandler = new Handler();
private final Runnable mRunnable = new Runnable(){
private final Runnable mRunnable =
new Runnable() {
@Override
public void run() {
ConstantsBridge.SPLASH_TO_MAIN = true;
startActivity(new Intent(SplashActivity.this, MainActivity.class));
finish();
}
};
@Override
public void run() {
ConstantsBridge.SPLASH_TO_MAIN = true;
startActivity(new Intent(SplashActivity.this, MainActivity.class));
finish();
}
};
@Override
protected View bindLayout() {
binding = ActivitySplashBinding.inflate(getLayoutInflater());
return binding.getRoot();
}
@Override
protected View bindLayout() {
binding = ActivitySplashBinding.inflate(getLayoutInflater());
return binding.getRoot();
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
binding.splashText.setText(getString(R.string.msg_checking_storage_permissions));
binding.splashText.setText(getString(R.string.msg_checking_storage_permissions));
}
@Override
protected void onStorageGranted()
{
super.onStorageGranted();
proceed();
}
@Override
protected void onStorageGranted() {
super.onStorageGranted();
proceed();
}
@Override
protected void onStorageAlreadyGranted()
{
super.onStorageAlreadyGranted();
proceed();
}
@Override
protected void onStorageAlreadyGranted() {
super.onStorageAlreadyGranted();
proceed();
}
private void proceed()
{
if(!StudioApp.isAbiSupported()) {
final MaterialAlertDialogBuilder builder = DialogUtils.newMaterialDialogBuilder (this);
builder.setTitle(R.string.title_device_not_supported);
builder.setMessage(R.string.msg_device_not_supported);
builder.setCancelable(false);
builder.setPositiveButton(android.R.string.ok, (p1, p2) -> finishAffinity());
builder.create().show();
} else {
binding.splashText.setText(getString(R.string.msg_storage_granted));
if (getApp().isFrameworkInstalled()) {
goToMain();
} else {
startActivity(new Intent(this, DownloadActivity.class));
finish();
}
}
}
private void proceed() {
if (!StudioApp.isAbiSupported()) {
final MaterialAlertDialogBuilder builder = DialogUtils.newMaterialDialogBuilder(this);
builder.setTitle(R.string.title_device_not_supported);
builder.setMessage(R.string.msg_device_not_supported);
builder.setCancelable(false);
builder.setPositiveButton(android.R.string.ok, (p1, p2) -> finishAffinity());
builder.create().show();
} else {
binding.splashText.setText(getString(R.string.msg_storage_granted));
if (getApp().isFrameworkInstalled()) {
goToMain();
} else {
startActivity(new Intent(this, DownloadActivity.class));
finish();
}
}
}
private void goToMain() {
mHandler.removeCallbacks(mRunnable);
@@ -64,230 +64,250 @@ import java.io.FileOutputStream;
import java.util.Map;
public class TerminalActivity extends StudioActivity {
private ActivityTerminalBinding binding;
private TerminalView terminal;
private TerminalSession session;
private boolean isVisible = false;
private KeyListener listener;
private final Client client = new Client ();
private static final Logger LOG = Logger.instance ("TerminalActivity");
private final Client client = new Client();
private static final Logger LOG = Logger.instance("TerminalActivity");
public static final String KEY_WORKING_DIRECTORY = "terminal_workingDirectory";
private static final byte[] SOURCES_LIST_CONTENT = "deb https://androidide.com/packages/ stable main".getBytes ();
private static final byte[] SOURCES_LIST_CONTENT =
"deb https://androidide.com/packages/ stable main".getBytes();
@Override
protected View bindLayout () {
binding = ActivityTerminalBinding.inflate (getLayoutInflater ());
return binding.getRoot ();
protected View bindLayout() {
binding = ActivityTerminalBinding.inflate(getLayoutInflater());
return binding.getRoot();
}
@Override
protected void onCreate (Bundle savedInstanceState) {
super.onCreate (savedInstanceState);
final var bash = new File (BIN_DIR, "bash");
final var useSystemShell = getApp ().getPrefManager ().getBoolean (PreferenceManager.KEY_TERMINAL_USE_SYSTEM_SHELL);
if ((SYSROOT.exists () && SYSROOT.isDirectory () && bash.exists () && bash.isFile () && bash.canExecute ()) || useSystemShell) {
setupTerminalView ();
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
final var bash = new File(BIN_DIR, "bash");
final var useSystemShell =
getApp().getPrefManager()
.getBoolean(PreferenceManager.KEY_TERMINAL_USE_SYSTEM_SHELL);
if ((SYSROOT.exists()
&& SYSROOT.isDirectory()
&& bash.exists()
&& bash.isFile()
&& bash.canExecute())
|| useSystemShell) {
setupTerminalView();
} else {
LOG.info ("Bootstrap is not installed.");
LOG.info("Bootstrap is not installed.");
// Show the progress sheet
final var progress = new ProgressSheet ();
progress.setShowShadow (false);
progress.setSubMessageEnabled (true);
progress.setShowTitle (false);
progress.setMessage (getString (R.string.please_wait));
progress.setSubMessage (getString (R.string.msg_reading_bootstrap));
progress.setCancelable (false);
progress.show (getSupportFragmentManager (), "extract_bootstrap_progress");
LOG.debug ("Starting installation...");
final var progress = new ProgressSheet();
progress.setShowShadow(false);
progress.setSubMessageEnabled(true);
progress.setShowTitle(false);
progress.setMessage(getString(R.string.please_wait));
progress.setSubMessage(getString(R.string.msg_reading_bootstrap));
progress.setCancelable(false);
progress.show(getSupportFragmentManager(), "extract_bootstrap_progress");
LOG.debug("Starting installation...");
// Install bootstrap asynchronously
final var future = BootstrapInstaller.doInstall (this, message -> runOnUiThread (() -> progress.setSubMessage (message)));
future.whenComplete ((voidResult, throwable) -> {
LOG.debug ("Completable future has been complete.", throwable);
runOnUiThread (() -> {
progress.dismissAllowingStateLoss ();
if (future.isCompletedExceptionally () || throwable != null) {
LOG.error ("Future has been completed exceptionally.");
showInstallationError (throwable);
return;
}
setupTerminalView ();
});
});
final var future =
BootstrapInstaller.doInstall(
this, message -> runOnUiThread(() -> progress.setSubMessage(message)));
future.whenComplete(
(voidResult, throwable) -> {
LOG.debug("Completable future has been complete.", throwable);
runOnUiThread(
() -> {
progress.dismissAllowingStateLoss();
if (future.isCompletedExceptionally() || throwable != null) {
LOG.error("Future has been completed exceptionally.");
showInstallationError(throwable);
return;
}
setupTerminalView();
});
});
}
}
private void showInstallationError (Throwable throwable) {
binding.virtualKeyTable.setVisibility (View.GONE);
final var frag = CrashReportFragment.newInstance (
getString (R.string.title_installation_failed),
getString (R.string.msg_bootstrap_installation_error),
ThrowableUtils.getFullStackTrace (throwable))
.setCloseAppOnClick (false);
getSupportFragmentManager ().beginTransaction ()
.replace (binding.root.getId (), frag, "bootstrap_install_error_fragment")
.addToBackStack (null)
.commit ();
private void showInstallationError(Throwable throwable) {
binding.virtualKeyTable.setVisibility(View.GONE);
final var frag =
CrashReportFragment.newInstance(
getString(R.string.title_installation_failed),
getString(R.string.msg_bootstrap_installation_error),
ThrowableUtils.getFullStackTrace(throwable))
.setCloseAppOnClick(false);
getSupportFragmentManager()
.beginTransaction()
.replace(binding.root.getId(), frag, "bootstrap_install_error_fragment")
.addToBackStack(null)
.commit();
}
private void setupTerminalView () {
terminal = new TerminalView (this, null);
terminal.setTerminalViewClient (client);
terminal.attachSession (createSession (getWorkingDirectory ()));
terminal.setKeepScreenOn (true);
terminal.setTextSize (SizeUtils.dp2px (10));
terminal.setTypeface (TypefaceUtils.jetbrainsMono ());
LinearLayout.LayoutParams params = new LinearLayout.LayoutParams (-1, 0);
private void setupTerminalView() {
terminal = new TerminalView(this, null);
terminal.setTerminalViewClient(client);
terminal.attachSession(createSession(getWorkingDirectory()));
terminal.setKeepScreenOn(true);
terminal.setTextSize(SizeUtils.dp2px(10));
terminal.setTypeface(TypefaceUtils.jetbrainsMono());
LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(-1, 0);
params.weight = 1f;
binding.getRoot ().addView (terminal, 0, params);
binding.getRoot().addView(terminal, 0, params);
try {
binding.virtualKeyTable.setVirtualKeysViewClient (getKeyListener ());
binding.virtualKeyTable.reload (new VirtualKeysInfo (ConstantsBridge.VIRTUAL_KEYS, "", VirtualKeysConstants.CONTROL_CHARS_ALIASES));
binding.virtualKeyTable.setVirtualKeysViewClient(getKeyListener());
binding.virtualKeyTable.reload(
new VirtualKeysInfo(
ConstantsBridge.VIRTUAL_KEYS,
"",
VirtualKeysConstants.CONTROL_CHARS_ALIASES));
} catch (JSONException e) {
LOG.error ("Unable to parse terminal virtual keys json data", e);
LOG.error("Unable to parse terminal virtual keys json data", e);
}
}
@NonNull
private String getWorkingDirectory () {
final Bundle extras = getIntent ().getExtras ();
if (extras != null && extras.containsKey (KEY_WORKING_DIRECTORY)) {
String directory = extras.getString (KEY_WORKING_DIRECTORY, null);
if (directory == null || directory.trim ().length () <= 0) {
directory = HOME.getAbsolutePath ();
private String getWorkingDirectory() {
final Bundle extras = getIntent().getExtras();
if (extras != null && extras.containsKey(KEY_WORKING_DIRECTORY)) {
String directory = extras.getString(KEY_WORKING_DIRECTORY, null);
if (directory == null || directory.trim().length() <= 0) {
directory = HOME.getAbsolutePath();
}
return directory;
}
return HOME.getAbsolutePath ();
return HOME.getAbsolutePath();
}
private KeyListener getKeyListener () {
return listener == null ? listener = new KeyListener (terminal) : listener;
private KeyListener getKeyListener() {
return listener == null ? listener = new KeyListener(terminal) : listener;
}
@Override
protected void onResume () {
super.onResume ();
setTerminalCursorBlinkingState (true);
protected void onResume() {
super.onResume();
setTerminalCursorBlinkingState(true);
}
@Override
protected void onStart () {
super.onStart ();
protected void onStart() {
super.onStart();
isVisible = true;
}
@Override
protected void onStop () {
super.onStop ();
protected void onStop() {
super.onStop();
isVisible = false;
setTerminalCursorBlinkingState (false);
setTerminalCursorBlinkingState(false);
}
public boolean isVisible () {
public boolean isVisible() {
return isVisible;
}
private void setTerminalCursorBlinkingState (boolean start) {
private void setTerminalCursorBlinkingState(boolean start) {
if (terminal != null && terminal.mEmulator != null) {
terminal.setTerminalCursorBlinkerState (start, true);
terminal.setTerminalCursorBlinkerState(start, true);
}
}
private TerminalSession createSession (final String workingDirectory) {
final Map<String, String> envs = getEnvironment ();
final String[] env = new String[envs.size ()];
private TerminalSession createSession(final String workingDirectory) {
final Map<String, String> envs = getEnvironment();
final String[] env = new String[envs.size()];
int i = 0;
for (Map.Entry<String, String> entry : envs.entrySet ()) {
env[i] = entry.getKey () + "=" + entry.getValue ();
for (Map.Entry<String, String> entry : envs.entrySet()) {
env[i] = entry.getKey() + "=" + entry.getValue();
i++;
}
session = new TerminalSession (
getShellPath (), // Shell command
workingDirectory, // Working directory
new String[]{}, // Arguments
env, // Environment variables
TerminalEmulator.DEFAULT_TERMINAL_TRANSCRIPT_ROWS, // Transcript rows
client // TerminalSessionClient
);
session =
new TerminalSession(
getShellPath(), // Shell command
workingDirectory, // Working directory
new String[] {}, // Arguments
env, // Environment variables
TerminalEmulator.DEFAULT_TERMINAL_TRANSCRIPT_ROWS, // Transcript rows
client // TerminalSessionClient
);
try {
final var file = new File (SYSROOT, "etc/apt/sources.list");
final var out = new FileOutputStream (file);
out.write (SOURCES_LIST_CONTENT);
out.flush ();
out.close ();
final var file = new File(SYSROOT, "etc/apt/sources.list");
final var out = new FileOutputStream(file);
out.write(SOURCES_LIST_CONTENT);
out.flush();
out.close();
} catch (Throwable th) {
LOG.error ("Unable to update sources.list", th);
LOG.error("Unable to update sources.list", th);
}
return session;
}
@NonNull
private String getShellPath () {
final var useSystemShell = getApp ().getPrefManager ().getBoolean (PreferenceManager.KEY_TERMINAL_USE_SYSTEM_SHELL);
if (!useSystemShell &&
LOGIN_SHELL.exists () &&
LOGIN_SHELL.isFile ()) {
return LOGIN_SHELL.getAbsolutePath ();
private String getShellPath() {
final var useSystemShell =
getApp().getPrefManager()
.getBoolean(PreferenceManager.KEY_TERMINAL_USE_SYSTEM_SHELL);
if (!useSystemShell && LOGIN_SHELL.exists() && LOGIN_SHELL.isFile()) {
return LOGIN_SHELL.getAbsolutePath();
}
if (!useSystemShell) {
LOG.error ("Default shell does not exist. Falling back to '/system/bin/sh'.", "This should not happen in normal circumstances.");
LOG.error(
"Default shell does not exist. Falling back to '/system/bin/sh'.",
"This should not happen in normal circumstances.");
}
return "/system/bin/sh";
}
private static final class KeyListener implements VirtualKeysView.IVirtualKeysView {
private final TerminalView terminal;
public KeyListener (TerminalView terminal) {
public KeyListener(TerminalView terminal) {
this.terminal = terminal;
}
@Override
public void onVirtualKeyButtonClick (View view, VirtualKeyButton buttonInfo, Button button) {
public void onVirtualKeyButtonClick(View view, VirtualKeyButton buttonInfo, Button button) {
if (terminal == null) {
return;
}
if (buttonInfo.isMacro ()) {
String[] keys = buttonInfo.getKey ().split (" ");
if (buttonInfo.isMacro()) {
String[] keys = buttonInfo.getKey().split(" ");
boolean ctrlDown = false;
boolean altDown = false;
boolean shiftDown = false;
boolean fnDown = false;
for (String key : keys) {
if (SpecialButton.CTRL.getKey ().equals (key)) {
if (SpecialButton.CTRL.getKey().equals(key)) {
ctrlDown = true;
} else if (SpecialButton.ALT.getKey ().equals (key)) {
} else if (SpecialButton.ALT.getKey().equals(key)) {
altDown = true;
} else if (SpecialButton.SHIFT.getKey ().equals (key)) {
} else if (SpecialButton.SHIFT.getKey().equals(key)) {
shiftDown = true;
} else if (SpecialButton.FN.getKey ().equals (key)) {
} else if (SpecialButton.FN.getKey().equals(key)) {
fnDown = true;
} else {
onTerminalExtraKeyButtonClick (key, ctrlDown, altDown, shiftDown, fnDown);
onTerminalExtraKeyButtonClick(key, ctrlDown, altDown, shiftDown, fnDown);
ctrlDown = false;
altDown = false;
shiftDown = false;
@@ -295,13 +315,14 @@ public class TerminalActivity extends StudioActivity {
}
}
} else {
onTerminalExtraKeyButtonClick (buttonInfo.getKey (), false, false, false, false);
onTerminalExtraKeyButtonClick(buttonInfo.getKey(), false, false, false, false);
}
}
protected void onTerminalExtraKeyButtonClick (String key, boolean ctrlDown, boolean altDown, boolean shiftDown, boolean fnDown) {
if (VirtualKeysConstants.PRIMARY_KEY_CODES_FOR_STRINGS.containsKey (key)) {
Integer keyCode = VirtualKeysConstants.PRIMARY_KEY_CODES_FOR_STRINGS.get (key);
protected void onTerminalExtraKeyButtonClick(
String key, boolean ctrlDown, boolean altDown, boolean shiftDown, boolean fnDown) {
if (VirtualKeysConstants.PRIMARY_KEY_CODES_FOR_STRINGS.containsKey(key)) {
Integer keyCode = VirtualKeysConstants.PRIMARY_KEY_CODES_FOR_STRINGS.get(key);
if (keyCode == null) {
return;
}
@@ -318,197 +339,183 @@ public class TerminalActivity extends StudioActivity {
if (fnDown) {
metaState |= KeyEvent.META_FUNCTION_ON;
}
KeyEvent keyEvent = new KeyEvent (0, 0, KeyEvent.ACTION_UP, keyCode, 0, metaState);
terminal.onKeyDown (keyCode, keyEvent);
KeyEvent keyEvent = new KeyEvent(0, 0, KeyEvent.ACTION_UP, keyCode, 0, metaState);
terminal.onKeyDown(keyCode, keyEvent);
} else {
// not a control char
for (int off = 0; off < key.length (); ) {
int codePoint = key.codePointAt (off);
terminal.inputCodePoint (codePoint, ctrlDown, altDown);
off += Character.charCount (codePoint);
for (int off = 0; off < key.length(); ) {
int codePoint = key.codePointAt(off);
terminal.inputCodePoint(codePoint, ctrlDown, altDown);
off += Character.charCount(codePoint);
}
}
}
@Override
public boolean performVirtualKeyButtonHapticFeedback (View view, VirtualKeyButton buttonInfo, Button button) {
public boolean performVirtualKeyButtonHapticFeedback(
View view, VirtualKeyButton buttonInfo, Button button) {
// No need to handle this
// VirtualKeysView will take care of performing haptic feedback
return false;
}
}
private final class Client implements TerminalViewClient, TerminalSessionClient {
@Override
public boolean onKeyUp (int keyCode, KeyEvent e) {
public boolean onKeyUp(int keyCode, KeyEvent e) {
return false;
}
@Override
public boolean onKeyDown (int keyCode, KeyEvent e, TerminalSession session) {
if (keyCode == KeyEvent.KEYCODE_ENTER && !session.isRunning ()) {
finish ();
public boolean onKeyDown(int keyCode, KeyEvent e, TerminalSession session) {
if (keyCode == KeyEvent.KEYCODE_ENTER && !session.isRunning()) {
finish();
return true;
}
return false;
}
@Override
public boolean onCodePoint (int codePoint, boolean ctrlDown, TerminalSession session) {
public boolean onCodePoint(int codePoint, boolean ctrlDown, TerminalSession session) {
return false;
}
@Override
public void onTextChanged (TerminalSession changedSession) {
terminal.onScreenUpdated ();
public void onTextChanged(TerminalSession changedSession) {
terminal.onScreenUpdated();
}
@Override
public void onTitleChanged (TerminalSession changedSession) {
public void onTitleChanged(TerminalSession changedSession) {}
@Override
public void onSessionFinished(TerminalSession finishedSession) {
finish();
}
@Override
public void onSessionFinished (TerminalSession finishedSession) {
finish ();
public void onCopyTextToClipboard(TerminalSession session, String text) {
ClipboardUtils.copyText("AndroidIDE Terminal", text);
}
@Override
public void onCopyTextToClipboard (TerminalSession session, String text) {
ClipboardUtils.copyText ("AndroidIDE Terminal", text);
}
@Override
public void onPasteTextFromClipboard (TerminalSession session) {
String clip = ClipboardUtils.getText ().toString ();
if (clip.trim ().length () > 0
&& terminal != null
&& terminal.mEmulator != null) {
terminal.mEmulator.paste (clip);
public void onPasteTextFromClipboard(TerminalSession session) {
String clip = ClipboardUtils.getText().toString();
if (clip.trim().length() > 0 && terminal != null && terminal.mEmulator != null) {
terminal.mEmulator.paste(clip);
}
}
@Override
public void onBell (TerminalSession session) {
}
public void onBell(TerminalSession session) {}
@Override
public void onColorsChanged (TerminalSession session) {
}
public void onColorsChanged(TerminalSession session) {}
@Override
public void onTerminalCursorStateChange (boolean state) {
}
public void onTerminalCursorStateChange(boolean state) {}
@Override
public Integer getTerminalCursorStyle () {
public Integer getTerminalCursorStyle() {
return TerminalEmulator.DEFAULT_TERMINAL_CURSOR_STYLE;
}
@Override
public float onScale (float scale) {
public float onScale(float scale) {
return 1.0f;
}
@Override
public void onSingleTapUp (MotionEvent e) {
KeyboardUtils.showSoftInput (terminal);
public void onSingleTapUp(MotionEvent e) {
KeyboardUtils.showSoftInput(terminal);
}
@Override
public boolean shouldBackButtonBeMappedToEscape () {
public boolean shouldBackButtonBeMappedToEscape() {
return false;
}
@Override
public boolean shouldEnforceCharBasedInput () {
public boolean shouldEnforceCharBasedInput() {
return true;
}
@Override
public boolean shouldUseCtrlSpaceWorkaround () {
public boolean shouldUseCtrlSpaceWorkaround() {
return false;
}
@Override
public boolean isTerminalViewSelected () {
public boolean isTerminalViewSelected() {
return true;
}
@Override
public void copyModeChanged (boolean copyMode) {
}
public void copyModeChanged(boolean copyMode) {}
@Override
public boolean onLongPress (MotionEvent event) {
public boolean onLongPress(MotionEvent event) {
return false;
}
@Override
public boolean readControlKey () {
Boolean state = binding.virtualKeyTable.readSpecialButton (SpecialButton.CTRL, true);
public boolean readControlKey() {
Boolean state = binding.virtualKeyTable.readSpecialButton(SpecialButton.CTRL, true);
return state != null && state;
}
@Override
public boolean readAltKey () {
Boolean state = binding.virtualKeyTable.readSpecialButton (SpecialButton.ALT, true);
public boolean readAltKey() {
Boolean state = binding.virtualKeyTable.readSpecialButton(SpecialButton.ALT, true);
return state != null && state;
}
@Override
public boolean readFnKey () {
public boolean readFnKey() {
return false;
}
@Override
public boolean readShiftKey () {
public boolean readShiftKey() {
return false;
}
@Override
public void onEmulatorSet () {
setTerminalCursorBlinkingState (true);
public void onEmulatorSet() {
setTerminalCursorBlinkingState(true);
if (session != null) {
binding.getRoot ().setBackgroundColor (session.getEmulator ().mColors.mCurrentColors[TextStyle.COLOR_INDEX_BACKGROUND]);
binding.getRoot()
.setBackgroundColor(
session.getEmulator()
.mColors
.mCurrentColors[TextStyle.COLOR_INDEX_BACKGROUND]);
}
}
@Override
public void logError (String tag, String message) {
public void logError(String tag, String message) {
// No logs
}
@Override
public void logWarn (String tag, String message) {
}
public void logWarn(String tag, String message) {}
@Override
public void logInfo (String tag, String message) {
}
public void logInfo(String tag, String message) {}
@Override
public void logDebug (String tag, String message) {
}
public void logDebug(String tag, String message) {}
@Override
public void logVerbose (String tag, String message) {
}
public void logVerbose(String tag, String message) {}
@Override
public void logStackTraceWithMessage (String tag, String message, Exception e) {
}
public void logStackTraceWithMessage(String tag, String message, Exception e) {}
@Override
public void logStackTrace (String tag, Exception e) {
}
public void logStackTrace(String tag, Exception e) {}
}
}
@@ -27,52 +27,55 @@ import java.util.List;
import java.util.function.Consumer;
/**
* An {@link com.itsaky.androidide.adapters.IconTextAdapter} for {@link com.itsaky.attrinfo.models.Attr}.
* An {@link com.itsaky.androidide.adapters.IconTextAdapter} for {@link
* com.itsaky.attrinfo.models.Attr}.
*
* @author Akash Yadav
*/
public class AttrListAdapter extends IconTextAdapter<Attr> {
private final Consumer<Attr> clickConsumer;
private final List<Attr> attrs;
public AttrListAdapter (Consumer<Attr> clickConsumer, List<Attr> attrs) {
public AttrListAdapter(Consumer<Attr> clickConsumer, List<Attr> attrs) {
this.clickConsumer = clickConsumer;
if (attrs == null) {
attrs = new ArrayList<> ();
attrs = new ArrayList<>();
}
this.attrs = attrs;
}
@NonNull
@Override
public Attr getItemAt (int index) {
return attrs.get (index);
public Attr getItemAt(int index) {
return attrs.get(index);
}
@Override
public int getIconResource (int index) {
public int getIconResource(int index) {
return R.drawable.ic_xml_attribute;
}
@NonNull
@Override
public String getItemText (int index) {
return getItemAt (index).name;
public String getItemText(int index) {
return getItemAt(index).name;
}
@Override
public int getItemCount () {
return attrs.size ();
public int getItemCount() {
return attrs.size();
}
@Override
public void onBindViewHolder (@NonNull VH holder, int position) {
super.onBindViewHolder (holder, position);
public void onBindViewHolder(@NonNull VH holder, int position) {
super.onBindViewHolder(holder, position);
if (clickConsumer != null) {
holder.binding.getRoot ().setOnClickListener (v -> clickConsumer.accept (getItemAt (position)));
holder.binding
.getRoot()
.setOnClickListener(v -> clickConsumer.accept(getItemAt(position)));
}
}
}
@@ -5,12 +5,12 @@
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
*
* AndroidIDE is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
*
* You should have received a copy of the GNU General Public License
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
*
@@ -48,119 +48,153 @@ import com.itsaky.lsp.models.CompletionItemKind;
import io.github.rosemoe.sora.widget.component.EditorCompletionAdapter;
public class CompletionListAdapter extends EditorCompletionAdapter {
private static final Logger LOG = Logger.instance("CompletionListAdapter");
@Override
public int getItemHeight() {
return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 40, Resources.getSystem().getDisplayMetrics());
return (int)
TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_DIP, 40, Resources.getSystem().getDisplayMetrics());
}
@Override
protected View getView(int position, View convertView, ViewGroup parent, boolean isCurrentCursorPosition) {
final LayoutCompletionItemBinding binding = LayoutCompletionItemBinding.inflate(LayoutInflater.from(getContext()), parent, false);
var item = (CompletionItem) getItem(position);
String label = item.getLabel(), desc = item.getDetail(), type = item.getKind().toString();
String header = type.length() <= 0 ? "O" : String.valueOf(type.charAt(0));
protected View getView(
int position, View convertView, ViewGroup parent, boolean isCurrentCursorPosition) {
final LayoutCompletionItemBinding binding =
LayoutCompletionItemBinding.inflate(
LayoutInflater.from(getContext()), parent, false);
var item = (CompletionItem) getItem(position);
String label = item.getLabel(), desc = item.getDetail(), type = item.getKind().toString();
String header = type.length() <= 0 ? "O" : String.valueOf(type.charAt(0));
binding.completionIconText.setText(header);
binding.completionLabel.setText(label);
binding.completionType.setText(type);
binding.completionDetail.setText(desc);
binding.completionIconText.setTypeface(TypefaceUtils.jetbrainsMono(), Typeface.BOLD);
if (desc.isEmpty()) {
if (desc.isEmpty()) {
binding.completionDetail.setVisibility(View.GONE);
}
if (isCurrentCursorPosition) {
binding.getRoot().setBackgroundColor(ContextCompat.getColor(getContext(), R.color.completionList_backgroundSelected));
binding.getRoot()
.setBackgroundColor(
ContextCompat.getColor(
getContext(), R.color.completionList_backgroundSelected));
} else {
binding.getRoot().setBackgroundColor(ContextCompat.getColor(getContext(), R.color.completionList_background));
binding.getRoot()
.setBackgroundColor(
ContextCompat.getColor(
getContext(), R.color.completionList_background));
}
binding.completionApiInfo.setVisibility(View.GONE);
showApiInfoIfNeeded(item, binding.completionApiInfo);
return binding.getRoot();
}
private void showApiInfoIfNeeded(final CompletionItem item, final TextView completionApiInfo) {
new Thread(() -> {
final ApiInfo apiInfo = StudioApp.getInstance().apiInfo();
boolean hasRead = apiInfo != null && apiInfo.hasRead();
boolean isValid = isValidForApiVersion(item);
if (hasRead && isValid) {
JsonElement element = new Gson().toJsonTree(item.getData());
if (element == null || !element.isJsonObject()) return;
JsonObject data = element.getAsJsonObject();
if (!data.has("className")) return;
final String className = data.get("className").getAsString();
CompletionItemKind kind = item.getKind();
ClassInfo clazz = apiInfo.getClassByName(className);
if(clazz == null) return;
Info info = clazz;
// If this Info is not a class info, find the right member
if (kind == CompletionItemKind.METHOD
&& data.has("erasedParameterTypes")
&& data.has("memberName")) {
JsonElement erasedParameterTypesElement = data.get("erasedParameterTypes");
if(erasedParameterTypesElement.isJsonArray()) {
String simpleName = data.get("memberName").getAsString();
JsonArray erasedParameterTypes = erasedParameterTypesElement.getAsJsonArray();
String[] paramTypes = new String[erasedParameterTypes.size()];
for(int i=0;i<erasedParameterTypes.size();i++) {
paramTypes[i] = erasedParameterTypes.get(i).getAsString();
}
MethodInfo method = clazz.getMethod(simpleName, paramTypes);
if(method != null) {
info = method;
}
}
} else if(kind == CompletionItemKind.FIELD
&& data.has("memberName")) {
String simpleName = data.get("memberName").getAsString();
FieldInfo field = clazz.getFieldByName(simpleName);
if(field != null) {
info = field;
}
}
new Thread(
() -> {
final ApiInfo apiInfo = StudioApp.getInstance().apiInfo();
boolean hasRead = apiInfo != null && apiInfo.hasRead();
boolean isValid = isValidForApiVersion(item);
final StringBuilder infoBuilder = new StringBuilder();
if (info.since > 1) {
infoBuilder.append(completionApiInfo.getContext().getString(R.string.msg_api_info_since, info.since));
infoBuilder.append("\n");
}
if (hasRead && isValid) {
if (info.removed > 0) {
infoBuilder.append(completionApiInfo.getContext().getString(R.string.msg_api_info_removed, info.removed));
infoBuilder.append("\n");
}
JsonElement element = new Gson().toJsonTree(item.getData());
if (element == null || !element.isJsonObject()) return;
JsonObject data = element.getAsJsonObject();
if (!data.has("className")) return;
if (info.deprecated > 0) {
infoBuilder.append(completionApiInfo.getContext().getString(R.string.msg_api_info_deprecated, info.deprecated));
infoBuilder.append("\n");
}
ThreadUtils.runOnUiThread(() -> {
if(infoBuilder.toString().trim().length() > 0) {
completionApiInfo.setText(infoBuilder.toString().trim());
completionApiInfo.setVisibility(View.VISIBLE);
} else completionApiInfo.setVisibility(View.GONE);
});
}
}).start();
final String className = data.get("className").getAsString();
CompletionItemKind kind = item.getKind();
ClassInfo clazz = apiInfo.getClassByName(className);
if (clazz == null) return;
Info info = clazz;
// If this Info is not a class info, find the right member
if (kind == CompletionItemKind.METHOD
&& data.has("erasedParameterTypes")
&& data.has("memberName")) {
JsonElement erasedParameterTypesElement =
data.get("erasedParameterTypes");
if (erasedParameterTypesElement.isJsonArray()) {
String simpleName = data.get("memberName").getAsString();
JsonArray erasedParameterTypes =
erasedParameterTypesElement.getAsJsonArray();
String[] paramTypes =
new String[erasedParameterTypes.size()];
for (int i = 0; i < erasedParameterTypes.size(); i++) {
paramTypes[i] =
erasedParameterTypes.get(i).getAsString();
}
MethodInfo method = clazz.getMethod(simpleName, paramTypes);
if (method != null) {
info = method;
}
}
} else if (kind == CompletionItemKind.FIELD
&& data.has("memberName")) {
String simpleName = data.get("memberName").getAsString();
FieldInfo field = clazz.getFieldByName(simpleName);
if (field != null) {
info = field;
}
}
final StringBuilder infoBuilder = new StringBuilder();
if (info.since > 1) {
infoBuilder.append(
completionApiInfo
.getContext()
.getString(
R.string.msg_api_info_since,
info.since));
infoBuilder.append("\n");
}
if (info.removed > 0) {
infoBuilder.append(
completionApiInfo
.getContext()
.getString(
R.string.msg_api_info_removed,
info.removed));
infoBuilder.append("\n");
}
if (info.deprecated > 0) {
infoBuilder.append(
completionApiInfo
.getContext()
.getString(
R.string.msg_api_info_deprecated,
info.deprecated));
infoBuilder.append("\n");
}
ThreadUtils.runOnUiThread(
() -> {
if (infoBuilder.toString().trim().length() > 0) {
completionApiInfo.setText(
infoBuilder.toString().trim());
completionApiInfo.setVisibility(View.VISIBLE);
} else completionApiInfo.setVisibility(View.GONE);
});
}
})
.start();
}
private boolean isValidForApiVersion(CompletionItem item) {
@@ -168,23 +202,23 @@ public class CompletionListAdapter extends EditorCompletionAdapter {
final CompletionItemKind type = item.getKind();
JsonElement element = new Gson().toJsonTree(item.getData());
if (
// These represent a class type
// These represent a class type
(type == CompletionItemKind.CLASS
|| type == CompletionItemKind.INTERFACE
|| type == CompletionItemKind.ENUM
// These represent a method type
|| type == CompletionItemKind.METHOD
|| type == CompletionItemKind.CONSTRUCTOR
// A field type
|| type == CompletionItemKind.FIELD)
&& element != null && element.isJsonObject()) {
|| type == CompletionItemKind.INTERFACE
|| type == CompletionItemKind.ENUM
// These represent a method type
|| type == CompletionItemKind.METHOD
|| type == CompletionItemKind.CONSTRUCTOR
// A field type
|| type == CompletionItemKind.FIELD)
&& element != null
&& element.isJsonObject()) {
JsonObject data = element.getAsJsonObject();
return data.has("className");
}
return false;
}
}
}
@@ -5,12 +5,12 @@
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
*
* AndroidIDE is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
*
* You should have received a copy of the GNU General Public License
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
*
@@ -35,56 +35,67 @@ import java.io.File;
import java.util.List;
public class DiagnosticItemAdapter extends RecyclerView.Adapter<DiagnosticItemAdapter.VH> {
private final List<DiagnosticItem> diags;
private final File file;
private final DiagnosticClickListener listener;
public DiagnosticItemAdapter (List<DiagnosticItem> diags, File file, DiagnosticClickListener listener) {
public DiagnosticItemAdapter(
List<DiagnosticItem> diags, File file, DiagnosticClickListener listener) {
this.diags = diags;
this.file = file;
this.listener = listener;
}
@NonNull
@Override
public DiagnosticItemAdapter.VH onCreateViewHolder(@NonNull ViewGroup p1, int p2) {
return new VH (LayoutDiagnosticItemBinding.inflate (LayoutInflater.from (p1.getContext ()), p1, false));
return new VH(
LayoutDiagnosticItemBinding.inflate(
LayoutInflater.from(p1.getContext()), p1, false));
}
@Override
public void onBindViewHolder(DiagnosticItemAdapter.VH p1, int p2) {
final DiagnosticItem diagnostic = diags.get(p2);
final LayoutDiagnosticItemBinding binding = p1.binding;
binding.icon.setImageResource(getDiagnosticIconId(diagnostic));
binding.icon.setColorFilter(ContextCompat.getColor(binding.icon.getContext(), diagnostic.getSeverity() == DiagnosticSeverity.ERROR ? R.color.diagnostic_error : R.color.diagnostic_warning), PorterDuff.Mode.SRC_ATOP);
binding.icon.setColorFilter(
ContextCompat.getColor(
binding.icon.getContext(),
diagnostic.getSeverity() == DiagnosticSeverity.ERROR
? R.color.diagnostic_error
: R.color.diagnostic_warning),
PorterDuff.Mode.SRC_ATOP);
binding.title.setText(diagnostic.getMessage());
binding.getRoot().setOnClickListener(v -> {
if(listener != null) {
listener.onDiagnosticClick(file, diagnostic);
}
});
binding.getRoot()
.setOnClickListener(
v -> {
if (listener != null) {
listener.onDiagnosticClick(file, diagnostic);
}
});
}
@Override
public int getItemCount() {
return diags.size();
}
private int getDiagnosticIconId(DiagnosticItem diagnostic) {
if(diagnostic.getSeverity() == DiagnosticSeverity.ERROR)
if (diagnostic.getSeverity() == DiagnosticSeverity.ERROR)
return R.drawable.ic_compilation_error;
return R.drawable.ic_info;
}
public static class VH extends RecyclerView.ViewHolder {
private LayoutDiagnosticItemBinding binding;
public VH(LayoutDiagnosticItemBinding binding) {
super(binding.getRoot());
this.binding = binding;
}
}
}
}
@@ -1,40 +1,42 @@
/************************************************************************************
* This file is part of AndroidIDE.
*
*
*
*
* AndroidIDE is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
*
* AndroidIDE is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
*
* You should have received a copy of the GNU General Public License
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
*
**************************************************************************************/
**************************************************************************************/
package com.itsaky.androidide.adapters;
import android.graphics.PorterDuff;
import android.view.LayoutInflater;
import android.view.ViewGroup;
import androidx.core.content.ContextCompat;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import com.itsaky.androidide.R;
import com.itsaky.androidide.databinding.LayoutDiagnosticGroupBinding;
import com.itsaky.androidide.interfaces.DiagnosticClickListener;
import com.itsaky.androidide.models.DiagnosticGroup;
import java.util.List;
public class DiagnosticsAdapter extends RecyclerView.Adapter<DiagnosticsAdapter.VH> {
private final List<DiagnosticGroup> diags;
private final DiagnosticClickListener listener;
@@ -42,43 +44,51 @@ public class DiagnosticsAdapter extends RecyclerView.Adapter<DiagnosticsAdapter.
this.diags = diags;
this.listener = listener;
}
@Override
public DiagnosticsAdapter.VH onCreateViewHolder(ViewGroup p1, int p2) {
return new VH(LayoutDiagnosticGroupBinding.inflate(LayoutInflater.from(p1.getContext()), p1, false));
return new VH(
LayoutDiagnosticGroupBinding.inflate(
LayoutInflater.from(p1.getContext()), p1, false));
}
@Override
public void onBindViewHolder(DiagnosticsAdapter.VH p1, int p2) {
final DiagnosticGroup group = diags.get(p2);
final LayoutDiagnosticGroupBinding binding = p1.binding;
final int color = ContextCompat.getColor(binding.info.icon.getContext(), R.color.secondaryColor);
final int color =
ContextCompat.getColor(binding.info.icon.getContext(), R.color.secondaryColor);
binding.info.icon.setImageResource(group.icon);
binding.info.icon.setColorFilter(color, PorterDuff.Mode.SRC_ATOP);
binding.info.title.setText(group.text);
binding.info.title.setTextColor(color);
binding.diagnostics.setLayoutManager(new LinearLayoutManager(binding.diagnostics.getContext()));
binding.diagnostics.setAdapter(new DiagnosticItemAdapter (group.diagnostics, group.file, listener));
binding.info.getRoot().setOnClickListener(v -> {
if(listener != null) {
listener.onGroupClick(group);
}
});
binding.diagnostics.setLayoutManager(
new LinearLayoutManager(binding.diagnostics.getContext()));
binding.diagnostics.setAdapter(
new DiagnosticItemAdapter(group.diagnostics, group.file, listener));
binding.info
.getRoot()
.setOnClickListener(
v -> {
if (listener != null) {
listener.onGroupClick(group);
}
});
}
@Override
public int getItemCount() {
return diags.size();
}
public class VH extends RecyclerView.ViewHolder {
private LayoutDiagnosticGroupBinding binding;
public VH(LayoutDiagnosticGroupBinding binding) {
super(binding.getRoot());
this.binding = binding;
}
}
}
@@ -24,98 +24,109 @@ import androidx.fragment.app.FragmentActivity;
import androidx.viewpager2.adapter.FragmentStateAdapter;
import com.itsaky.androidide.R;
import com.itsaky.androidide.fragments.SimpleOutputFragment;
import com.itsaky.androidide.fragments.DiagnosticsListFragment;
import com.itsaky.androidide.fragments.IDELogFragment;
import com.itsaky.androidide.fragments.LogViewFragment;
import com.itsaky.androidide.fragments.SearchResultFragment;
import com.itsaky.androidide.fragments.SimpleOutputFragment;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
public class EditorBottomSheetTabAdapter extends FragmentStateAdapter {
private final List<Tab> fragments;
public EditorBottomSheetTabAdapter (@NonNull FragmentActivity fragmentActivity) {
super (fragmentActivity);
this.fragments = new ArrayList<> ();
this.fragments.add (new Tab (fragmentActivity.getString (R.string.build_output), new SimpleOutputFragment ()));
this.fragments.add (new Tab (fragmentActivity.getString (R.string.app_logs), new LogViewFragment ()));
this.fragments.add (new Tab (fragmentActivity.getString (R.string.ide_logs), new IDELogFragment ()));
this.fragments.add (new Tab (fragmentActivity.getString (R.string.view_diags), new DiagnosticsListFragment ()));
this.fragments.add (new Tab (fragmentActivity.getString (R.string.view_search_results), new SearchResultFragment ()));
public EditorBottomSheetTabAdapter(@NonNull FragmentActivity fragmentActivity) {
super(fragmentActivity);
this.fragments = new ArrayList<>();
this.fragments.add(
new Tab(
fragmentActivity.getString(R.string.build_output),
new SimpleOutputFragment()));
this.fragments.add(
new Tab(fragmentActivity.getString(R.string.app_logs), new LogViewFragment()));
this.fragments.add(
new Tab(fragmentActivity.getString(R.string.ide_logs), new IDELogFragment()));
this.fragments.add(
new Tab(
fragmentActivity.getString(R.string.view_diags),
new DiagnosticsListFragment()));
this.fragments.add(
new Tab(
fragmentActivity.getString(R.string.view_search_results),
new SearchResultFragment()));
}
public Fragment getFragmentAtIndex (int index) {
return this.fragments.get (index).fragment;
public Fragment getFragmentAtIndex(int index) {
return this.fragments.get(index).fragment;
}
@NonNull
@Override
public Fragment createFragment (int position) {
return this.fragments.get (position).fragment;
public Fragment createFragment(int position) {
return this.fragments.get(position).fragment;
}
@Override
public int getItemCount () {
return fragments.size ();
public int getItemCount() {
return fragments.size();
}
public String getTitle (int position) {
return fragments.get (position).title;
public String getTitle(int position) {
return fragments.get(position).title;
}
@NonNull
public SimpleOutputFragment getBuildOutputFragment () {
return Objects.requireNonNull (findFragmentByClass (SimpleOutputFragment.class));
public SimpleOutputFragment getBuildOutputFragment() {
return Objects.requireNonNull(findFragmentByClass(SimpleOutputFragment.class));
}
@NonNull
public LogViewFragment getLogFragment () {
return Objects.requireNonNull (findFragmentByClass (LogViewFragment.class));
public LogViewFragment getLogFragment() {
return Objects.requireNonNull(findFragmentByClass(LogViewFragment.class));
}
@NonNull
public DiagnosticsListFragment getDiagnosticsFragment () {
return Objects.requireNonNull (findFragmentByClass (DiagnosticsListFragment.class));
public DiagnosticsListFragment getDiagnosticsFragment() {
return Objects.requireNonNull(findFragmentByClass(DiagnosticsListFragment.class));
}
@NonNull
public SearchResultFragment getSearchResultFragment () {
return Objects.requireNonNull (findFragmentByClass (SearchResultFragment.class));
public SearchResultFragment getSearchResultFragment() {
return Objects.requireNonNull(findFragmentByClass(SearchResultFragment.class));
}
@Nullable
private <T extends Fragment> T findFragmentByClass (Class<T> clazz) {
private <T extends Fragment> T findFragmentByClass(Class<T> clazz) {
for (final var tab : this.fragments) {
if (tab.fragment.getClass () == clazz) {
if (tab.fragment.getClass() == clazz) {
return (T) tab.fragment;
}
}
return null;
}
public <T extends Fragment> int findIndexOfFragmentByClass (Class<T> tClass) {
for (int i = 0; i < this.fragments.size (); i++) {
final var tab = this.fragments.get (i);
if (tab.fragment.getClass () == tClass) {
public <T extends Fragment> int findIndexOfFragmentByClass(Class<T> tClass) {
for (int i = 0; i < this.fragments.size(); i++) {
final var tab = this.fragments.get(i);
if (tab.fragment.getClass() == tClass) {
return i;
}
}
return -1;
}
static class Tab {
public Tab (String title, Fragment fragment) {
public Tab(String title, Fragment fragment) {
this.title = title;
this.fragment = fragment;
}
String title;
Fragment fragment;
}
@@ -31,45 +31,47 @@ import com.itsaky.androidide.databinding.LayoutSimpleIconTextBinding;
*
* @author Akash Yadav
*/
public abstract class IconTextAdapter <E> extends RecyclerView.Adapter<IconTextAdapter.VH> {
public abstract class IconTextAdapter<E> extends RecyclerView.Adapter<IconTextAdapter.VH> {
private OnBindListener<E> bindListener;
public IconTextAdapter<E> setOnBindListener (OnBindListener<E> listener) {
public IconTextAdapter<E> setOnBindListener(OnBindListener<E> listener) {
this.bindListener = listener;
return this;
}
@NonNull
@Override
public VH onCreateViewHolder (@NonNull ViewGroup parent, int viewType) {
return new VH (LayoutSimpleIconTextBinding.inflate (LayoutInflater.from (parent.getContext ()), parent, false));
public VH onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
return new VH(
LayoutSimpleIconTextBinding.inflate(
LayoutInflater.from(parent.getContext()), parent, false));
}
@Override
public void onBindViewHolder (@NonNull VH holder, int position) {
public void onBindViewHolder(@NonNull VH holder, int position) {
final var binding = holder.binding;
final var item = getItemAt (position);
if (this.bindListener != null && this.bindListener.onBind (item, holder, position)) {
final var item = getItemAt(position);
if (this.bindListener != null && this.bindListener.onBind(item, holder, position)) {
return;
}
final var icon = getIconResource (position);
final var icon = getIconResource(position);
if (icon == -1) {
binding.icon.setVisibility (View.GONE);
binding.icon.setVisibility(View.GONE);
} else {
binding.icon.setImageResource (icon);
binding.icon.setImageResource(icon);
}
binding.text.setText (getItemText (position));
binding.text.setText(getItemText(position));
if (this.bindListener != null) {
this.bindListener.postBind (item, holder, position);
this.bindListener.postBind(item, holder, position);
}
}
/**
* Get the list item at the given position.
*
@@ -77,16 +79,16 @@ public abstract class IconTextAdapter <E> extends RecyclerView.Adapter<IconTextA
* @return The item at the given index. Must not be <code>null</code>.
*/
@NonNull
public abstract E getItemAt (int index);
public abstract E getItemAt(int index);
/**
* Get the icon resource ID of the item the given index.
*
* @param index The index of the item.
* @return The icon resource id or <b>-1</b> to hide the icon.
*/
public abstract int getIconResource (int index);
public abstract int getIconResource(int index);
/**
* Get the title of the item at the given index.
*
@@ -94,25 +96,23 @@ public abstract class IconTextAdapter <E> extends RecyclerView.Adapter<IconTextA
* @return The title of the item.
*/
@NonNull
public abstract String getItemText (int index);
public interface OnBindListener <T> {
default boolean onBind (T item, VH holder, int position) {
public abstract String getItemText(int index);
public interface OnBindListener<T> {
default boolean onBind(T item, VH holder, int position) {
return false;
}
default void postBind (T item, VH holder, int position) {
}
default void postBind(T item, VH holder, int position) {}
}
public static class VH extends RecyclerView.ViewHolder {
public final LayoutSimpleIconTextBinding binding;
public VH (@NonNull LayoutSimpleIconTextBinding binding) {
super (binding.getRoot ());
public VH(@NonNull LayoutSimpleIconTextBinding binding) {
super(binding.getRoot());
this.binding = binding;
}
}
}
@@ -1,74 +1,82 @@
/************************************************************************************
* This file is part of AndroidIDE.
*
*
*
*
* AndroidIDE is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
*
* AndroidIDE is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
*
* You should have received a copy of the GNU General Public License
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
*
**************************************************************************************/
**************************************************************************************/
package com.itsaky.androidide.adapters;
import android.view.LayoutInflater;
import android.view.ViewGroup;
import androidx.recyclerview.widget.RecyclerView;
import com.itsaky.androidide.databinding.LayoutOptionssheetItemBinding;
import com.itsaky.androidide.models.SheetOption;
import java.util.List;
public class OptionsSheetAdapter extends RecyclerView.Adapter<OptionsSheetAdapter.VH> {
private OnOptionsClickListener listener;
private List<SheetOption> options;
private OnOptionsClickListener listener;
private List<SheetOption> options;
public OptionsSheetAdapter(List<SheetOption> options, OnOptionsClickListener listener) {
this.options = options;
this.listener = listener;
}
public OptionsSheetAdapter(List<SheetOption> options, OnOptionsClickListener listener) {
this.options = options;
this.listener = listener;
}
@Override
public OptionsSheetAdapter.VH onCreateViewHolder(ViewGroup p1, int p2) {
return new VH(LayoutOptionssheetItemBinding.inflate(LayoutInflater.from(p1.getContext()), p1, false));
}
@Override
public OptionsSheetAdapter.VH onCreateViewHolder(ViewGroup p1, int p2) {
return new VH(
LayoutOptionssheetItemBinding.inflate(
LayoutInflater.from(p1.getContext()), p1, false));
}
@Override
public void onBindViewHolder(OptionsSheetAdapter.VH p1, int p2) {
final LayoutOptionssheetItemBinding binding = p1.binding;
final SheetOption option = options.get(p2);
@Override
public void onBindViewHolder(OptionsSheetAdapter.VH p1, int p2) {
final LayoutOptionssheetItemBinding binding = p1.binding;
final SheetOption option = options.get(p2);
binding.text.setText(option.titleRes);
binding.icon.setImageResource(option.iconRes);
binding.getRoot().setOnClickListener(v -> { if(listener != null) listener.onOptionClick(option); });
}
binding.text.setText(option.titleRes);
binding.icon.setImageResource(option.iconRes);
@Override
public int getItemCount() {
return options.size();
}
binding.getRoot()
.setOnClickListener(
v -> {
if (listener != null) listener.onOptionClick(option);
});
}
@Override
public int getItemCount() {
return options.size();
}
public class VH extends RecyclerView.ViewHolder {
private LayoutOptionssheetItemBinding binding;
public VH(LayoutOptionssheetItemBinding binding) {
super(binding.getRoot());
this.binding = binding;
}
}
public static interface OnOptionsClickListener {
public void onOptionClick(SheetOption option);
}
private LayoutOptionssheetItemBinding binding;
public VH(LayoutOptionssheetItemBinding binding) {
super(binding.getRoot());
this.binding = binding;
}
}
public static interface OnOptionsClickListener {
public void onOptionClick(SheetOption option);
}
}
@@ -1,23 +1,22 @@
/************************************************************************************
* This file is part of AndroidIDE.
*
*
*
*
* AndroidIDE is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
*
* AndroidIDE is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
*
* You should have received a copy of the GNU General Public License
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
*
**************************************************************************************/
**************************************************************************************/
package com.itsaky.androidide.adapters;
@@ -25,121 +24,148 @@ import android.text.TextUtils;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import androidx.recyclerview.widget.RecyclerView;
import androidx.transition.ChangeBounds;
import androidx.transition.TransitionManager;
import com.blankj.utilcode.util.FileUtils;
import com.bumptech.glide.Glide;
import com.itsaky.androidide.R;
import com.itsaky.androidide.app.StudioApp;
import com.itsaky.androidide.databinding.LayoutProjectsItemBinding;
import com.itsaky.androidide.project.AndroidProject;
import com.itsaky.androidide.tasks.TaskExecutor;
import com.itsaky.toaster.Toaster;
import java.io.File;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import androidx.transition.TransitionManager;
import androidx.transition.ChangeBounds;
import com.itsaky.androidide.tasks.TaskExecutor;
import com.blankj.utilcode.util.FileUtils;
import com.itsaky.androidide.app.StudioApp;
import com.itsaky.toaster.Toaster;
public class ProjectsListAdapter extends RecyclerView.Adapter<ProjectsListAdapter.VH> {
private ArrayList<AndroidProject> mProjects;
private OnProjectClickListener listener;
private final Comparator<AndroidProject> PROJECTS_SORTER = new Comparator<AndroidProject>(){
private ArrayList<AndroidProject> mProjects;
private OnProjectClickListener listener;
@Override
public int compare(AndroidProject p1, AndroidProject p2) {
return p1.getAppName().compareTo(p2.getAppName());
}
};
private final Comparator<AndroidProject> PROJECTS_SORTER =
new Comparator<AndroidProject>() {
public ProjectsListAdapter(ArrayList<AndroidProject> mProjects, OnProjectClickListener listener) {
this.mProjects = mProjects;
this.listener = listener;
Collections.sort(this.mProjects, PROJECTS_SORTER);
}
@Override
public ProjectsListAdapter.VH onCreateViewHolder(ViewGroup p1, int p2) {
return new VH(LayoutProjectsItemBinding.inflate(LayoutInflater.from(p1.getContext()), p1, false));
}
@Override
public int compare(AndroidProject p1, AndroidProject p2) {
return p1.getAppName().compareTo(p2.getAppName());
}
};
@Override
public void onBindViewHolder(ProjectsListAdapter.VH holder, int p2) {
final AndroidProject project = mProjects.get(p2);
holder.binding.projecsitemName.setText(project.getAppName());
holder.binding.projecsitemPkgName.setText(project.getPackageName());
holder.binding.deleteMsg.setText(holder.binding.deleteMsg.getContext().getString(R.string.msg_confirm_delete, project.getAppName()));
if(!TextUtils.isEmpty(project.getIconPath())) Glide.with(holder.itemView.getContext().getApplicationContext()).load(new File(project.getIconPath())).into(holder.binding.projecsitemIcon);
else holder.binding.projecsitemIcon.setImageResource(R.drawable.ic_android);
holder.binding.projectsitemClickableContainer.setOnClickListener(v -> sendClickEvent(project));
holder.binding.projectsitemClickableContainer.setOnLongClickListener(v -> holder.toggleDelete());
holder.binding.delete.setOnClickListener(v -> holder.showConfirmDelete());
holder.binding.deleteCancel.setOnClickListener(v -> holder.hideAllDelete());
holder.binding.deleteOk.setOnClickListener(v -> deleteProject(project, p2));
}
public ProjectsListAdapter(
ArrayList<AndroidProject> mProjects, OnProjectClickListener listener) {
this.mProjects = mProjects;
this.listener = listener;
Collections.sort(this.mProjects, PROJECTS_SORTER);
}
private void deleteProject(AndroidProject project, int index) {
index = index >= mProjects.size() ? mProjects.size() - 1 : index;
if(index < 0) return;
mProjects.remove(index);
notifyItemRemoved(index);
new TaskExecutor().executeAsync(() -> FileUtils.delete(project.getProjectPath()), __ -> {
StudioApp.getInstance().toast(__ ? R.string.msg_project_deleted : R.string.msg_project_delete_failed, Toaster.Type.SUCCESS);
});
}
private void sendClickEvent(AndroidProject project) {
if(listener != null) listener.onProjectClick(project);
}
@Override
public ProjectsListAdapter.VH onCreateViewHolder(ViewGroup p1, int p2) {
return new VH(
LayoutProjectsItemBinding.inflate(LayoutInflater.from(p1.getContext()), p1, false));
}
@Override
public int getItemCount() {
return mProjects.size();
}
public class VH extends RecyclerView.ViewHolder {
LayoutProjectsItemBinding binding;
public VH(LayoutProjectsItemBinding binding) {
super(binding.getRoot());
this.binding = binding;
}
@Override
public void onBindViewHolder(ProjectsListAdapter.VH holder, int p2) {
final AndroidProject project = mProjects.get(p2);
holder.binding.projecsitemName.setText(project.getAppName());
holder.binding.projecsitemPkgName.setText(project.getPackageName());
holder.binding.deleteMsg.setText(
holder.binding
.deleteMsg
.getContext()
.getString(R.string.msg_confirm_delete, project.getAppName()));
if (!TextUtils.isEmpty(project.getIconPath()))
Glide.with(holder.itemView.getContext().getApplicationContext())
.load(new File(project.getIconPath()))
.into(holder.binding.projecsitemIcon);
else holder.binding.projecsitemIcon.setImageResource(R.drawable.ic_android);
public void showConfirmDelete() {
binding.confirmDeleteContainer.setEnabled(true);
TransitionManager.beginDelayedTransition(binding.getRoot(), new ChangeBounds().setDuration(85));
binding.confirmDeleteContainer.setVisibility(View.VISIBLE);
}
public void hideAllDelete() {
hideDelete();
hideConfirmDelete();
}
public boolean toggleDelete() {
binding.delete.setEnabled(!binding.delete.isEnabled());
TransitionManager.beginDelayedTransition(binding.getRoot(), new ChangeBounds().setDuration(85));
binding.delete.setVisibility(binding.delete.isEnabled() ? View.VISIBLE : View.GONE);
return true;
}
public void hideDelete() {
binding.delete.setEnabled(false);
TransitionManager.beginDelayedTransition(binding.getRoot(), new ChangeBounds().setDuration(85));
binding.delete.setVisibility(View.GONE);
}
public void hideConfirmDelete() {
binding.confirmDeleteContainer.setEnabled(false);
TransitionManager.beginDelayedTransition(binding.getRoot(), new ChangeBounds().setDuration(85));
binding.confirmDeleteContainer.setVisibility(View.GONE);
}
}
public interface OnProjectClickListener {
public void onProjectClick(AndroidProject project);
}
holder.binding.projectsitemClickableContainer.setOnClickListener(
v -> sendClickEvent(project));
holder.binding.projectsitemClickableContainer.setOnLongClickListener(
v -> holder.toggleDelete());
holder.binding.delete.setOnClickListener(v -> holder.showConfirmDelete());
holder.binding.deleteCancel.setOnClickListener(v -> holder.hideAllDelete());
holder.binding.deleteOk.setOnClickListener(v -> deleteProject(project, p2));
}
private void deleteProject(AndroidProject project, int index) {
index = index >= mProjects.size() ? mProjects.size() - 1 : index;
if (index < 0) return;
mProjects.remove(index);
notifyItemRemoved(index);
new TaskExecutor()
.executeAsync(
() -> FileUtils.delete(project.getProjectPath()),
__ -> {
StudioApp.getInstance()
.toast(
__
? R.string.msg_project_deleted
: R.string.msg_project_delete_failed,
Toaster.Type.SUCCESS);
});
}
private void sendClickEvent(AndroidProject project) {
if (listener != null) listener.onProjectClick(project);
}
@Override
public int getItemCount() {
return mProjects.size();
}
public class VH extends RecyclerView.ViewHolder {
LayoutProjectsItemBinding binding;
public VH(LayoutProjectsItemBinding binding) {
super(binding.getRoot());
this.binding = binding;
}
public void showConfirmDelete() {
binding.confirmDeleteContainer.setEnabled(true);
TransitionManager.beginDelayedTransition(
binding.getRoot(), new ChangeBounds().setDuration(85));
binding.confirmDeleteContainer.setVisibility(View.VISIBLE);
}
public void hideAllDelete() {
hideDelete();
hideConfirmDelete();
}
public boolean toggleDelete() {
binding.delete.setEnabled(!binding.delete.isEnabled());
TransitionManager.beginDelayedTransition(
binding.getRoot(), new ChangeBounds().setDuration(85));
binding.delete.setVisibility(binding.delete.isEnabled() ? View.VISIBLE : View.GONE);
return true;
}
public void hideDelete() {
binding.delete.setEnabled(false);
TransitionManager.beginDelayedTransition(
binding.getRoot(), new ChangeBounds().setDuration(85));
binding.delete.setVisibility(View.GONE);
}
public void hideConfirmDelete() {
binding.confirmDeleteContainer.setEnabled(false);
TransitionManager.beginDelayedTransition(
binding.getRoot(), new ChangeBounds().setDuration(85));
binding.confirmDeleteContainer.setVisibility(View.GONE);
}
}
public interface OnProjectClickListener {
public void onProjectClick(AndroidProject project);
}
}
@@ -1,23 +1,22 @@
/************************************************************************************
* This file is part of AndroidIDE.
*
*
*
*
* AndroidIDE is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
*
* AndroidIDE is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
*
* You should have received a copy of the GNU General Public License
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
*
**************************************************************************************/
**************************************************************************************/
package com.itsaky.androidide.adapters;
@@ -25,16 +24,17 @@ import android.graphics.PorterDuff;
import android.text.SpannableStringBuilder;
import android.view.LayoutInflater;
import android.view.ViewGroup;
import androidx.core.content.ContextCompat;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import com.blankj.utilcode.util.ThreadUtils;
import com.blankj.utilcode.util.ThrowableUtils;
import com.itsaky.androidide.databinding.LayoutSearchResultGroupBinding;
import com.itsaky.androidide.databinding.LayoutSearchResultItemBinding;
import com.itsaky.androidide.models.SearchResult;
import com.itsaky.androidide.syntax.highlighters.JavaHighlighter;
import com.itsaky.androidide.utils.Logger;
import java.io.File;
import java.util.ArrayList;
import java.util.HashMap;
@@ -49,9 +49,11 @@ public class SearchListAdapter extends RecyclerView.Adapter<SearchListAdapter.VH
private final OnFileClickListener fileListener;
private final OnMatchClickListener matchListener;
public SearchListAdapter(Map<File, List<SearchResult>> results, OnFileClickListener fileListener, OnMatchClickListener matchListener) {
if (results == null)
results = new HashMap<>();
public SearchListAdapter(
Map<File, List<SearchResult>> results,
OnFileClickListener fileListener,
OnMatchClickListener matchListener) {
if (results == null) results = new HashMap<>();
this.results = results;
this.keys = new ArrayList<File>(results.keySet());
this.fileListener = fileListener;
@@ -68,17 +70,20 @@ public class SearchListAdapter extends RecyclerView.Adapter<SearchListAdapter.VH
final LayoutSearchResultGroupBinding binding = p1.binding;
final File file = keys.get(p2);
final List<SearchResult> matches = results.get(file);
final int color = ContextCompat.getColor(binding.icon.getContext(), com.itsaky.androidide.R.color.secondaryColor);
final int color =
ContextCompat.getColor(
binding.icon.getContext(), com.itsaky.androidide.R.color.secondaryColor);
binding.title.setText(file.getName());
binding.icon.setImageResource(com.itsaky.androidide.R.drawable.ic_language_java);
binding.icon.setColorFilter(color, PorterDuff.Mode.SRC_ATOP);
binding.items.setLayoutManager(new LinearLayoutManager(binding.items.getContext()));
binding.items.setAdapter(new ChildAdapter(matches));
binding.getRoot().setOnClickListener(v -> {
if(fileListener != null)
fileListener.onClick(file);
});
binding.getRoot()
.setOnClickListener(
v -> {
if (fileListener != null) fileListener.onClick(file);
});
}
@Override
@@ -91,33 +96,40 @@ public class SearchListAdapter extends RecyclerView.Adapter<SearchListAdapter.VH
private final List<SearchResult> matches;
public ChildAdapter(List<SearchResult> matches) {
if (matches == null)
matches = new ArrayList<>();
if (matches == null) matches = new ArrayList<>();
this.matches = matches;
}
@Override
public SearchListAdapter.ChildVH onCreateViewHolder(ViewGroup p1, int p2) {
return new ChildVH(LayoutSearchResultItemBinding.inflate(LayoutInflater.from(p1.getContext())));
return new ChildVH(
LayoutSearchResultItemBinding.inflate(LayoutInflater.from(p1.getContext())));
}
@Override
public void onBindViewHolder(SearchListAdapter.ChildVH p1, int p2) {
final SearchResult match = matches.get(p2);
final LayoutSearchResultItemBinding binding = p1.binding;
new Thread(() -> {
try {
final SpannableStringBuilder sb = new JavaHighlighter().highlight(match.line, match.match);
ThreadUtils.runOnUiThread(() -> binding.text.setText(sb));
} catch (Exception e) {
ThreadUtils.runOnUiThread(() -> binding.text.setText(match.match));
}
}).start();
binding.getRoot().setOnClickListener(v -> {
if(matchListener != null) {
matchListener.onClick(match);
}
});
new Thread(
() -> {
try {
final SpannableStringBuilder sb =
new JavaHighlighter()
.highlight(match.line, match.match);
ThreadUtils.runOnUiThread(() -> binding.text.setText(sb));
} catch (Exception e) {
ThreadUtils.runOnUiThread(
() -> binding.text.setText(match.match));
}
})
.start();
binding.getRoot()
.setOnClickListener(
v -> {
if (matchListener != null) {
matchListener.onClick(match);
}
});
}
@Override
@@ -128,19 +140,20 @@ public class SearchListAdapter extends RecyclerView.Adapter<SearchListAdapter.VH
public class VH extends RecyclerView.ViewHolder {
private LayoutSearchResultGroupBinding binding;
public VH(LayoutSearchResultGroupBinding binding) {
super(binding.getRoot());
this.binding = binding;
}
}
public class ChildVH extends RecyclerView.ViewHolder {
private LayoutSearchResultItemBinding binding;
public ChildVH(LayoutSearchResultItemBinding binding) {
super(binding.getRoot());
this.binding = binding;
}
}
public static interface OnFileClickListener {
@@ -29,32 +29,32 @@ import java.util.List;
* @author Akash Yadav
*/
public class SimpleIconTextAdapter extends IconTextAdapter<IconTextListItem> {
private final List<? extends IconTextListItem> items;
public SimpleIconTextAdapter (List<? extends IconTextListItem> items) {
public SimpleIconTextAdapter(List<? extends IconTextListItem> items) {
this.items = items;
}
@NonNull
@Override
public IconTextListItem getItemAt (int index) {
return items.get (index);
public IconTextListItem getItemAt(int index) {
return items.get(index);
}
@Override
public int getIconResource (int index) {
return getItemAt (index).getIconResource ();
public int getIconResource(int index) {
return getItemAt(index).getIconResource();
}
@NonNull
@Override
public String getItemText (int index) {
return getItemAt (index).getText ();
public String getItemText(int index) {
return getItemAt(index).getText();
}
@Override
public int getItemCount () {
return items.size ();
public int getItemCount() {
return items.size();
}
}
@@ -5,12 +5,12 @@
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
*
* AndroidIDE is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
*
* You should have received a copy of the GNU General Public License
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
*
@@ -25,98 +25,103 @@ import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import com.itsaky.androidide.databinding.LayoutSymbolItemBinding;
import com.itsaky.androidide.views.editor.IDEEditor;
import com.itsaky.androidide.views.SymbolInputView.Symbol;
import com.itsaky.androidide.views.editor.IDEEditor;
import java.util.ArrayList;
import java.util.List;
public class SymbolInputAdapter extends RecyclerView.Adapter<SymbolInputAdapter.VH> {
private final IDEEditor editor;
private Symbol[] symbols;
public SymbolInputAdapter(IDEEditor editor) {
this(editor, null);
}
public SymbolInputAdapter(IDEEditor editor, Symbol[] symbols) {
this.editor = editor;
this.symbols = symbols == null ? new Symbol[0] : symbols;
}
@SuppressLint("NotifyDataSetChanged")
public void setSymbols(boolean notify, Symbol... symbols) {
this.symbols = symbols;
if(notify) {
notifyDataSetChanged();
}
}
@NonNull
@Override
public VH onCreateViewHolder(@NonNull ViewGroup parent, int itemType) {
return new VH (LayoutSymbolItemBinding.inflate (LayoutInflater.from (parent.getContext ()), parent, false));
}
@Override
public void onBindViewHolder(@NonNull VH holder, int position) {
if(symbols == null || symbols[position] == null) return;
final Symbol symbol = symbols[position];
holder.binding.symbol.setText(symbol.label);
private final IDEEditor editor;
private Symbol[] symbols;
public SymbolInputAdapter(IDEEditor editor) {
this(editor, null);
}
public SymbolInputAdapter(IDEEditor editor, Symbol[] symbols) {
this.editor = editor;
this.symbols = symbols == null ? new Symbol[0] : symbols;
}
@SuppressLint("NotifyDataSetChanged")
public void setSymbols(boolean notify, Symbol... symbols) {
this.symbols = symbols;
if (notify) {
notifyDataSetChanged();
}
}
@NonNull
@Override
public VH onCreateViewHolder(@NonNull ViewGroup parent, int itemType) {
return new VH(
LayoutSymbolItemBinding.inflate(
LayoutInflater.from(parent.getContext()), parent, false));
}
@Override
public void onBindViewHolder(@NonNull VH holder, int position) {
if (symbols == null || symbols[position] == null) return;
final Symbol symbol = symbols[position];
holder.binding.symbol.setText(symbol.label);
holder.binding.symbol.setOnClickListener(__ -> insertSymbol(symbol.commit, symbol.offset));
}
@Override
public int getItemCount() {
return symbols == null ? 0 : symbols.length;
}
public static class VH extends RecyclerView.ViewHolder {
LayoutSymbolItemBinding binding;
public VH(LayoutSymbolItemBinding binding) {
super(binding.getRoot());
this.binding = binding;
}
}
void insertSymbol(String text, int selectionOffset) {
if (selectionOffset < 0 || selectionOffset > text.length()) {
return;
}
var cur = editor.getText().getCursor();
if (cur.isSelected()) {
editor.getText ().delete (cur.getLeftLine (),
cur.getLeftColumn (),
cur.getRightLine (),
cur.getRightColumn ()
);
editor.notifyIMEExternalCursorChange ();
}
if(cur.getLeftColumn() < editor.getText().getColumnCount(cur.getLeftLine())
&& text.length() == 1
&& text.charAt(0) == editor.getText().charAt(cur.getLeftLine(), cur.getLeftColumn())
&& pairs.contains(text.charAt(0))) {
editor.moveSelectionRight();
} else {
editor.commitText(text);
if (selectionOffset != text.length()) {
editor.setSelection(cur.getRightLine(), cur.getRightColumn() - (text.length() - selectionOffset));
}
}
}
private static final List<Character> pairs;
static {
pairs = new ArrayList<>();
pairs.add('}');
pairs.add(')');
pairs.add(']');
pairs.add('"');
pairs.add('\'');
pairs.add('>');
}
}
@Override
public int getItemCount() {
return symbols == null ? 0 : symbols.length;
}
public static class VH extends RecyclerView.ViewHolder {
LayoutSymbolItemBinding binding;
public VH(LayoutSymbolItemBinding binding) {
super(binding.getRoot());
this.binding = binding;
}
}
void insertSymbol(String text, int selectionOffset) {
if (selectionOffset < 0 || selectionOffset > text.length()) {
return;
}
var cur = editor.getText().getCursor();
if (cur.isSelected()) {
editor.getText()
.delete(
cur.getLeftLine(),
cur.getLeftColumn(),
cur.getRightLine(),
cur.getRightColumn());
editor.notifyIMEExternalCursorChange();
}
if (cur.getLeftColumn() < editor.getText().getColumnCount(cur.getLeftLine())
&& text.length() == 1
&& text.charAt(0) == editor.getText().charAt(cur.getLeftLine(), cur.getLeftColumn())
&& pairs.contains(text.charAt(0))) {
editor.moveSelectionRight();
} else {
editor.commitText(text);
if (selectionOffset != text.length()) {
editor.setSelection(
cur.getRightLine(),
cur.getRightColumn() - (text.length() - selectionOffset));
}
}
}
private static final List<Character> pairs;
static {
pairs = new ArrayList<>();
pairs.add('}');
pairs.add(')');
pairs.add(']');
pairs.add('"');
pairs.add('\'');
pairs.add('>');
}
}
@@ -39,61 +39,68 @@ import java.util.function.Consumer;
* @author Akash Yadav
*/
public class TextActionItemAdapter extends RecyclerView.Adapter<TextActionItemAdapter.VH> {
private final List<IDEEditor.TextAction> actions;
private final Consumer<IDEEditor.TextAction> onClick;
private final boolean isHorizontal;
public TextActionItemAdapter (List<IDEEditor.TextAction> actions, Consumer<IDEEditor.TextAction> onClick) {
Objects.requireNonNull (actions);
public TextActionItemAdapter(
List<IDEEditor.TextAction> actions, Consumer<IDEEditor.TextAction> onClick) {
Objects.requireNonNull(actions);
this.actions = actions;
this.onClick = onClick;
this.isHorizontal = StudioApp.getInstance ().getPrefManager ().getBoolean (PreferenceManager.KEY_EDITOR_HORIZONTAL_POPUP, false);
this.isHorizontal =
StudioApp.getInstance()
.getPrefManager()
.getBoolean(PreferenceManager.KEY_EDITOR_HORIZONTAL_POPUP, false);
}
@NonNull
@Override
public VH onCreateViewHolder (@NonNull ViewGroup parent, int viewType) {
return new VH (LayoutTextActionItemBinding.inflate (LayoutInflater.from (parent.getContext ()), parent, false));
public VH onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
return new VH(
LayoutTextActionItemBinding.inflate(
LayoutInflater.from(parent.getContext()), parent, false));
}
@Override
public void onBindViewHolder (@NonNull VH holder, int position) {
public void onBindViewHolder(@NonNull VH holder, int position) {
final var binding = holder.binding;
final var action = actions.get (position);
final var button = binding.getRoot ();
final var action = actions.get(position);
final var button = binding.getRoot();
if (isHorizontal) {
// If not set to wrap_content, one item will take whole screen width.
final var params = button.getLayoutParams ();
final var params = button.getLayoutParams();
params.width = ViewGroup.LayoutParams.WRAP_CONTENT;
button.setLayoutParams (params);
button.setLayoutParams(params);
// looks better
button.setGravity (Gravity.CENTER);
button.setGravity(Gravity.CENTER);
}
button.setText (action.titleId);
button.setCompoundDrawablesRelative (action.icon, null, null, null);
button.setOnClickListener (v -> {
if (onClick != null) {
onClick.accept (action);
}
});
button.setText(action.titleId);
button.setCompoundDrawablesRelative(action.icon, null, null, null);
button.setOnClickListener(
v -> {
if (onClick != null) {
onClick.accept(action);
}
});
}
@Override
public int getItemCount () {
return actions.size ();
public int getItemCount() {
return actions.size();
}
static class VH extends RecyclerView.ViewHolder {
private final LayoutTextActionItemBinding binding;
public VH (@NonNull LayoutTextActionItemBinding binding) {
super (binding.getRoot ());
public VH(@NonNull LayoutTextActionItemBinding binding) {
super(binding.getRoot());
this.binding = binding;
}
}
@@ -29,40 +29,46 @@ import com.itsaky.androidide.models.UIWidgetGroup;
import java.util.List;
public class WidgetGroupItemAdapter extends SimpleIconTextAdapter implements SimpleIconTextAdapter.OnBindListener<IconTextListItem> {
public class WidgetGroupItemAdapter extends SimpleIconTextAdapter
implements SimpleIconTextAdapter.OnBindListener<IconTextListItem> {
private final OnGroupClickListener clickListener;
public WidgetGroupItemAdapter (@NonNull List<UIWidgetGroup> groups, @Nullable OnGroupClickListener clickListener) {
super (groups);
public WidgetGroupItemAdapter(
@NonNull List<UIWidgetGroup> groups, @Nullable OnGroupClickListener clickListener) {
super(groups);
this.clickListener = clickListener;
super.setOnBindListener (this);
super.setOnBindListener(this);
}
@Override
public boolean onBind (IconTextListItem item, VH holder, int position) {
public boolean onBind(IconTextListItem item, VH holder, int position) {
// Ignored
return false;
}
@Override
public void postBind (IconTextListItem item, @NonNull VH holder, int position) {
public void postBind(IconTextListItem item, @NonNull VH holder, int position) {
final var binding = holder.binding;
final var group = (UIWidgetGroup) item;
if (group.isSelected ()) {
binding.getRoot ().setBackground (ContextCompat.getDrawable (binding.getRoot ().getContext (), R.drawable.bg_widget_group_selected));
if (group.isSelected()) {
binding.getRoot()
.setBackground(
ContextCompat.getDrawable(
binding.getRoot().getContext(),
R.drawable.bg_widget_group_selected));
} else {
binding.getRoot ().setBackgroundColor (Color.TRANSPARENT);
binding.getRoot().setBackgroundColor(Color.TRANSPARENT);
}
if (this.clickListener != null) {
binding.getRoot ().setOnClickListener (v -> this.clickListener.onGroupClick (group));
binding.getRoot().setOnClickListener(v -> this.clickListener.onGroupClick(group));
}
}
public interface OnGroupClickListener {
void onGroupClick (UIWidgetGroup group);
void onGroupClick(UIWidgetGroup group);
}
}
@@ -30,46 +30,50 @@ import com.itsaky.androidide.ui.WidgetDragShadowBuilder;
import java.util.List;
public class WidgetItemAdapter extends SimpleIconTextAdapter implements SimpleIconTextAdapter.OnBindListener<IconTextListItem> {
public class WidgetItemAdapter extends SimpleIconTextAdapter
implements SimpleIconTextAdapter.OnBindListener<IconTextListItem> {
private final OnDragStartListener dragStartListener;
public WidgetItemAdapter (List<UIWidget> widgets, OnDragStartListener dragStartListener) {
super (widgets);
public WidgetItemAdapter(List<UIWidget> widgets, OnDragStartListener dragStartListener) {
super(widgets);
this.dragStartListener = dragStartListener;
setOnBindListener (this);
setOnBindListener(this);
}
@Override
public boolean onBind (IconTextListItem item, @NonNull VH holder, int position) {
public boolean onBind(IconTextListItem item, @NonNull VH holder, int position) {
return false; // Returning true will not set the text and icon
}
@Override
public void postBind (IconTextListItem item, VH holder, int position) {
public void postBind(IconTextListItem item, VH holder, int position) {
final var binding = holder.binding;
final var root = binding.getRoot ();
final var widget = (UIWidget) getItemAt (position);
root.setOnLongClickListener (v -> {
final var dragData = new WidgetDragData (false, null, widget);
final var shadow = new WidgetDragShadowBuilder (binding.icon);
final var dataItem = new ClipData.Item (DesignerActivity.DRAGGING_WIDGET_TAG);
final var data = new ClipData (DesignerActivity.DRAGGING_WIDGET_TAG,
new String[]{DesignerActivity.DRAGGING_WIDGET_MIME},
dataItem);
binding.icon.startDragAndDrop (data, shadow, dragData, 0);
if (this.dragStartListener != null) {
this.dragStartListener.onDragStarted (v);
}
return true;
});
final var root = binding.getRoot();
final var widget = (UIWidget) getItemAt(position);
root.setOnLongClickListener(
v -> {
final var dragData = new WidgetDragData(false, null, widget);
final var shadow = new WidgetDragShadowBuilder(binding.icon);
final var dataItem = new ClipData.Item(DesignerActivity.DRAGGING_WIDGET_TAG);
final var data =
new ClipData(
DesignerActivity.DRAGGING_WIDGET_TAG,
new String[] {DesignerActivity.DRAGGING_WIDGET_MIME},
dataItem);
binding.icon.startDragAndDrop(data, shadow, dragData, 0);
if (this.dragStartListener != null) {
this.dragStartListener.onDragStarted(v);
}
return true;
});
}
public interface OnDragStartListener {
void onDragStarted (View view);
void onDragStarted(View view);
}
}
@@ -36,60 +36,70 @@ import com.itsaky.androidide.models.XMLAttribute;
import java.util.List;
public class XMLAttributeListAdapter extends RecyclerView.Adapter<XMLAttributeListAdapter.VH> {
private final List<XMLAttribute> attributes;
private final OnClickListener clickListener;
public XMLAttributeListAdapter (List<XMLAttribute> attributes, OnClickListener clickListener) {
public XMLAttributeListAdapter(List<XMLAttribute> attributes, OnClickListener clickListener) {
this.attributes = attributes;
this.clickListener = clickListener;
}
@NonNull
@Override
public VH onCreateViewHolder (@NonNull ViewGroup parent, int viewType) {
return new VH (LayoutAttrEditorSheetItemBinding.inflate (LayoutInflater.from (parent.getContext ()), parent, false));
public VH onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
return new VH(
LayoutAttrEditorSheetItemBinding.inflate(
LayoutInflater.from(parent.getContext()), parent, false));
}
@Override
public void onBindViewHolder (@NonNull VH holder, int position) {
public void onBindViewHolder(@NonNull VH holder, int position) {
final var binding = holder.binding;
final var attr = attributes.get (position);
final var sb = new SpannableStringBuilder ();
sb.append (attr.getAttributeName ());
if (attr.isApplied ()) {
sb.append (" = ");
sb.append (attr.getValue (), new StyleSpan (Typeface.BOLD), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
final var length = attr.getValue ().length ();
sb.setSpan (new ForegroundColorSpan (ContextCompat.getColor (binding.text.getContext (), R.color.primaryTextColor)),
sb.length () - length,
sb.length () - 1,
final var attr = attributes.get(position);
final var sb = new SpannableStringBuilder();
sb.append(attr.getAttributeName());
if (attr.isApplied()) {
sb.append(" = ");
sb.append(
attr.getValue(),
new StyleSpan(Typeface.BOLD),
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
final var length = attr.getValue().length();
sb.setSpan(
new ForegroundColorSpan(
ContextCompat.getColor(
binding.text.getContext(), R.color.primaryTextColor)),
sb.length() - length,
sb.length() - 1,
SpannableStringBuilder.SPAN_EXCLUSIVE_EXCLUSIVE);
}
binding.text.setText (sb);
binding.getRoot ().setOnClickListener (v -> {
if (this.clickListener != null) {
this.clickListener.onClick (binding, attr);
}
});
binding.text.setText(sb);
binding.getRoot()
.setOnClickListener(
v -> {
if (this.clickListener != null) {
this.clickListener.onClick(binding, attr);
}
});
}
@Override
public int getItemCount () {
return attributes.size ();
public int getItemCount() {
return attributes.size();
}
public interface OnClickListener {
void onClick (LayoutAttrEditorSheetItemBinding binding, XMLAttribute attribute);
void onClick(LayoutAttrEditorSheetItemBinding binding, XMLAttribute attribute);
}
static class VH extends RecyclerView.ViewHolder {
LayoutAttrEditorSheetItemBinding binding;
VH (@NonNull LayoutAttrEditorSheetItemBinding binding) {
super (binding.getRoot ());
VH(@NonNull LayoutAttrEditorSheetItemBinding binding) {
super(binding.getRoot());
this.binding = binding;
}
}
@@ -1,23 +1,22 @@
/************************************************************************************
* This file is part of AndroidIDE.
*
*
*
*
* AndroidIDE is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
*
* AndroidIDE is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
*
* You should have received a copy of the GNU General Public License
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
*
**************************************************************************************/
**************************************************************************************/
package com.itsaky.androidide.adapters.viewholders;
@@ -39,56 +38,76 @@ import com.unnamed.b.atv.model.TreeNode;
import java.io.File;
public class FileTreeViewHolder extends TreeNode.BaseNodeViewHolder<File> {
private Context context;
private ImageView chevron;
private LayoutFiletreeItemBinding binding;
public FileTreeViewHolder(Context context) {
super(context);
this.context = context;
}
private Context context;
private ImageView chevron;
private LayoutFiletreeItemBinding binding;
@Override
public View createNodeView(TreeNode node, File file) {
binding = LayoutFiletreeItemBinding.inflate(LayoutInflater.from(context));
int dp15 = context instanceof StudioActivity ? ((StudioActivity) context).dpToPx(15) : (int) (TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 15, context.getResources().getDisplayMetrics()));
int icon = 0;
if (file.isDirectory())
icon = R.drawable.ic_folder;
else if (file.getName().endsWith(".java"))
icon = R.drawable.ic_language_java;
else if (file.getName().endsWith(".kt"))
icon = R.drawable.ic_language_kotlin;
else if (file.getName().endsWith(".xml"))
icon = R.drawable.ic_language_xml;
else if (file.getName().endsWith(".gradle"))
icon = R.drawable.ic_language_gradle;
else if (file.getName().endsWith(".json"))
icon = R.drawable.ic_language_json;
else
icon = R.drawable.ic_file_unknown;
public FileTreeViewHolder(Context context) {
super(context);
this.context = context;
}
final boolean isGradle = file.getAbsolutePath().equals(Environment.GRADLE_USER_HOME.getAbsolutePath());
chevron = binding.getRoot().findViewById(R.id.filetree_chevron);
binding.filetreeName.setText(isGradle ? "GRADLE_HOME" : file.getName());
binding.filetreeIcon.setImageResource(icon);
binding.getRoot().setPadding(binding.getRoot().getPaddingLeft() + (dp15 * (node.getLevel() - 1)), binding.getRoot().getPaddingTop(), binding.getRoot().getPaddingRight(), binding.getRoot().getPaddingBottom());
chevron.setVisibility(file.isFile() ? View.INVISIBLE : View.VISIBLE);
chevron.setImageResource(node.isExpanded() ? R.drawable.ic_chevron_down : R.drawable.ic_chevron_right);
chevron.getDrawable().setColorFilter(ContextCompat.getColor(context, R.color.secondaryLightColor), PorterDuff.Mode.SRC_ATOP);
binding.filetreeIcon.getDrawable().setColorFilter(ContextCompat.getColor(context, R.color.secondaryLightColor), PorterDuff.Mode.SRC_ATOP);
return binding.getRoot();
}
public void setLoading() {
chevron.getDrawable().setColorFilter(ContextCompat.getColor(context, R.color.secondaryLightColor), PorterDuff.Mode.SRC_ATOP);
}
@Override
public View createNodeView(TreeNode node, File file) {
binding = LayoutFiletreeItemBinding.inflate(LayoutInflater.from(context));
int dp15 =
context instanceof StudioActivity
? ((StudioActivity) context).dpToPx(15)
: (int)
(TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_DIP,
15,
context.getResources().getDisplayMetrics()));
int icon = 0;
if (file.isDirectory()) icon = R.drawable.ic_folder;
else if (file.getName().endsWith(".java")) icon = R.drawable.ic_language_java;
else if (file.getName().endsWith(".kt")) icon = R.drawable.ic_language_kotlin;
else if (file.getName().endsWith(".xml")) icon = R.drawable.ic_language_xml;
else if (file.getName().endsWith(".gradle")) icon = R.drawable.ic_language_gradle;
else if (file.getName().endsWith(".json")) icon = R.drawable.ic_language_json;
else icon = R.drawable.ic_file_unknown;
public void updateChevron(boolean isExpanded) {
chevron.setImageResource(isExpanded ? R.drawable.ic_chevron_down : R.drawable.ic_chevron_right);
chevron.getDrawable().setColorFilter(ContextCompat.getColor(context, R.color.secondaryLightColor), PorterDuff.Mode.SRC_ATOP);
}
final boolean isGradle =
file.getAbsolutePath().equals(Environment.GRADLE_USER_HOME.getAbsolutePath());
chevron = binding.getRoot().findViewById(R.id.filetree_chevron);
binding.filetreeName.setText(isGradle ? "GRADLE_HOME" : file.getName());
binding.filetreeIcon.setImageResource(icon);
binding.getRoot()
.setPadding(
binding.getRoot().getPaddingLeft() + (dp15 * (node.getLevel() - 1)),
binding.getRoot().getPaddingTop(),
binding.getRoot().getPaddingRight(),
binding.getRoot().getPaddingBottom());
chevron.setVisibility(file.isFile() ? View.INVISIBLE : View.VISIBLE);
chevron.setImageResource(
node.isExpanded() ? R.drawable.ic_chevron_down : R.drawable.ic_chevron_right);
chevron.getDrawable()
.setColorFilter(
ContextCompat.getColor(context, R.color.secondaryLightColor),
PorterDuff.Mode.SRC_ATOP);
binding.filetreeIcon
.getDrawable()
.setColorFilter(
ContextCompat.getColor(context, R.color.secondaryLightColor),
PorterDuff.Mode.SRC_ATOP);
return binding.getRoot();
}
public void setLoading() {
chevron.getDrawable()
.setColorFilter(
ContextCompat.getColor(context, R.color.secondaryLightColor),
PorterDuff.Mode.SRC_ATOP);
}
public void updateChevron(boolean isExpanded) {
chevron.setImageResource(
isExpanded ? R.drawable.ic_chevron_down : R.drawable.ic_chevron_right);
chevron.getDrawable()
.setColorFilter(
ContextCompat.getColor(context, R.color.secondaryLightColor),
PorterDuff.Mode.SRC_ATOP);
}
}
@@ -1,97 +1,104 @@
/************************************************************************************
* This file is part of AndroidIDE.
*
*
* AndroidIDE is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
*
* AndroidIDE is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
*
* You should have received a copy of the GNU General Public License
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
*
**************************************************************************************/
**************************************************************************************/
package com.itsaky.androidide.app;
import android.Manifest;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.os.Environment;
import android.provider.Settings;
import android.util.TypedValue;
import android.view.View;
import androidx.annotation.AttrRes;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentTransaction;
import com.itsaky.androidide.R;
import com.itsaky.androidide.services.builder.IDEService;
import com.itsaky.androidide.utils.Logger;
import com.itsaky.toaster.Toaster;
public abstract class StudioActivity extends AppCompatActivity {
private boolean toRequestStorage = true;
private boolean toRequestStorage = true;
public static final String TAG = "StudioActivity";
public static final int REQCODE_STORAGE = 1009;
public static final String TAG = "StudioActivity";
public static final int REQCODE_STORAGE = 1009;
protected static Logger LOG = Logger.instance("StudioActivity");
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(bindLayout());
onSetContentView();
if (toRequestStorage && !isStoragePermissionGranted()) {
requestStorage();
}
if (isStoragePermissionGranted()) {
onStorageAlreadyGranted();
} else {
onStorageDenied ();
}
super.onCreate(savedInstanceState);
setContentView(bindLayout());
onSetContentView();
if (toRequestStorage && !isStoragePermissionGranted()) {
requestStorage();
}
if (isStoragePermissionGranted()) {
onStorageAlreadyGranted();
} else {
onStorageDenied();
}
}
public boolean isStoragePermissionGranted() {
return ContextCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED
&& ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED;
}
public boolean isStoragePermissionGranted() {
return ContextCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE)
== PackageManager.PERMISSION_GRANTED
&& ContextCompat.checkSelfPermission(
this, Manifest.permission.WRITE_EXTERNAL_STORAGE)
== PackageManager.PERMISSION_GRANTED;
}
protected void doNotRequestStorage() {
this.toRequestStorage = false;
}
protected void doNotRequestStorage() {
this.toRequestStorage = false;
}
protected void requestStorage() {
if (isStoragePermissionGranted()) { onStorageGranted(); return; }
ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE, Manifest.permission.READ_EXTERNAL_STORAGE}, REQCODE_STORAGE);
}
public void loadFrament(Fragment frag, View view) {
loadFragment(frag, view.getId());
}
public void loadFragment(Fragment fragment, int id) {
protected void requestStorage() {
if (isStoragePermissionGranted()) {
onStorageGranted();
return;
}
ActivityCompat.requestPermissions(
this,
new String[] {
Manifest.permission.WRITE_EXTERNAL_STORAGE,
Manifest.permission.READ_EXTERNAL_STORAGE
},
REQCODE_STORAGE);
}
public void loadFrament(Fragment frag, View view) {
loadFragment(frag, view.getId());
}
public void loadFragment(Fragment fragment, int id) {
FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
transaction.replace(id, fragment);
transaction.commit();
}
public StudioApp getApp() {
return (StudioApp) getApplication();
public StudioApp getApp() {
return (StudioApp) getApplication();
}
public TypedValue getTypedValueForAttr(@AttrRes int attrRes) {
public TypedValue getTypedValueForAttr(@AttrRes int attrRes) {
TypedValue typedValue = new TypedValue();
getTheme().resolveAttribute(attrRes, typedValue, true);
return typedValue;
@@ -109,24 +116,31 @@ public abstract class StudioActivity extends AppCompatActivity {
return getTypedValueForAttr(R.attr.colorAccent).data;
}
public int dpToPx(int dp) {
return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, getResources().getDisplayMetrics());
}
@Override
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
if (requestCode == REQCODE_STORAGE) {
if (grantResults != null && grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED)
onStorageGranted();
else onStorageDenied();
}
}
public int dpToPx(int dp) {
return (int)
TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_DIP, dp, getResources().getDisplayMetrics());
}
protected void onStorageAlreadyGranted() {}
protected void onStorageGranted() {}
protected void onStorageDenied() {}
protected void onSetContentView() {}
protected abstract View bindLayout();
@Override
public void onRequestPermissionsResult(
int requestCode, String[] permissions, int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
if (requestCode == REQCODE_STORAGE) {
if (grantResults != null
&& grantResults.length > 0
&& grantResults[0] == PackageManager.PERMISSION_GRANTED) onStorageGranted();
else onStorageDenied();
}
}
protected void onStorageAlreadyGranted() {}
protected void onStorageGranted() {}
protected void onStorageDenied() {}
protected void onSetContentView() {}
protected abstract View bindLayout();
}
@@ -43,111 +43,114 @@ import java.io.File;
import java.util.Set;
public class StudioApp extends BaseApplication {
private static final Logger LOG = Logger.instance ("StudioApp");
private static final Logger LOG = Logger.instance("StudioApp");
private static StudioApp instance;
private static SDKInfo sdkInfo;
private final ILanguageServer mJavaLanguageServer = new JavaLanguageServer ();
private final ILanguageServer mXMLLanguageServer = new XMLLanguageServer ();
private final ILanguageServer mJavaLanguageServer = new JavaLanguageServer();
private final ILanguageServer mXMLLanguageServer = new XMLLanguageServer();
private IResourceTable mResTable;
private ILayoutInflater mLayoutInflater;
private Thread.UncaughtExceptionHandler uncaughtExceptionHandler;
public static StudioApp getInstance () {
public static StudioApp getInstance() {
return instance;
}
@Override
public void onCreate () {
public void onCreate() {
instance = this;
this.uncaughtExceptionHandler = Thread.getDefaultUncaughtExceptionHandler ();
Thread.setDefaultUncaughtExceptionHandler (this::handleCrash);
super.onCreate ();
FirebaseMessaging.getInstance ().subscribeToTopic (MessagingService.TOPIC_UPDATE);
FirebaseMessaging.getInstance ().subscribeToTopic (MessagingService.TOPIC_DEV_MSGS);
initializeApiInformation ();
this.uncaughtExceptionHandler = Thread.getDefaultUncaughtExceptionHandler();
Thread.setDefaultUncaughtExceptionHandler(this::handleCrash);
super.onCreate();
FirebaseMessaging.getInstance().subscribeToTopic(MessagingService.TOPIC_UPDATE);
FirebaseMessaging.getInstance().subscribeToTopic(MessagingService.TOPIC_DEV_MSGS);
initializeApiInformation();
}
@NonNull
public ILanguageServer getJavaLanguageServer () {
public ILanguageServer getJavaLanguageServer() {
return mJavaLanguageServer;
}
@NonNull
public ILanguageServer getXMLLanguageServer () {
public ILanguageServer getXMLLanguageServer() {
return mXMLLanguageServer;
}
private void handleCrash (Thread thread, Throwable th) {
writeException (th);
private void handleCrash(Thread thread, Throwable th) {
writeException(th);
try {
final var intent = new Intent ();
intent.setAction (CrashHandlerActivity.REPORT_ACTION);
intent.putExtra (CrashHandlerActivity.TRACE_KEY, ThrowableUtils.getFullStackTrace (th));
intent.addFlags (Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity (intent);
final var intent = new Intent();
intent.setAction(CrashHandlerActivity.REPORT_ACTION);
intent.putExtra(CrashHandlerActivity.TRACE_KEY, ThrowableUtils.getFullStackTrace(th));
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent);
if (this.uncaughtExceptionHandler != null) {
this.uncaughtExceptionHandler.uncaughtException (thread, th);
this.uncaughtExceptionHandler.uncaughtException(thread, th);
}
System.exit (1);
System.exit(1);
} catch (Throwable error) {
LOG.error ("Unable to show crash handler activity", error);
LOG.error("Unable to show crash handler activity", error);
}
}
public void createInflater (LayoutInflaterConfiguration config) {
this.mLayoutInflater = ILayoutInflater.newInstance (config);
public void createInflater(LayoutInflaterConfiguration config) {
this.mLayoutInflater = ILayoutInflater.newInstance(config);
}
public LayoutInflaterConfiguration createInflaterConfig (ILayoutInflater.ContextProvider contextProvider, Set<File> resDirs) {
return new LayoutInflaterConfiguration.Builder ()
.setAttrInfo (this.attrInfo ())
.setWidgetInfo (this.widgetInfo ())
.setResourceFinder (mResTable == null ? mResTable = new ProjectResourceTable () : mResTable)
.setResourceDirectories (resDirs)
.setContextProvider (contextProvider)
.create ();
public LayoutInflaterConfiguration createInflaterConfig(
ILayoutInflater.ContextProvider contextProvider, Set<File> resDirs) {
return new LayoutInflaterConfiguration.Builder()
.setAttrInfo(this.attrInfo())
.setWidgetInfo(this.widgetInfo())
.setResourceFinder(
mResTable == null ? mResTable = new ProjectResourceTable() : mResTable)
.setResourceDirectories(resDirs)
.setContextProvider(contextProvider)
.create();
}
public ILayoutInflater getLayoutInflater () {
public ILayoutInflater getLayoutInflater() {
return mLayoutInflater;
}
public IResourceTable getResourceTable () {
public IResourceTable getResourceTable() {
return this.mResTable;
}
/**
* Reads API version information from api-versions.xml
*/
public void initializeApiInformation () {
new Thread (() -> {
try {
sdkInfo = new SDKInfo (StudioApp.this);
((XMLLanguageServer) mXMLLanguageServer).setupSDK (sdkInfo);
} catch (Throwable th) {
LOG.error (getString (R.string.err_init_sdkinfo), th);
}
}, "SDKInfo Loader").start ();
/** Reads API version information from api-versions.xml */
public void initializeApiInformation() {
new Thread(
() -> {
try {
sdkInfo = new SDKInfo(StudioApp.this);
((XMLLanguageServer) mXMLLanguageServer).setupSDK(sdkInfo);
} catch (Throwable th) {
LOG.error(getString(R.string.err_init_sdkinfo), th);
}
},
"SDKInfo Loader")
.start();
}
public void stopAllDaemons () {
newShell (null).bgAppend ("gradle --stop");
public void stopAllDaemons() {
newShell(null).bgAppend("gradle --stop");
}
public ApiInfo apiInfo () {
return sdkInfo.getApiInfo ();
public ApiInfo apiInfo() {
return sdkInfo.getApiInfo();
}
public AttrInfo attrInfo () {
return sdkInfo.getAttrInfo ();
public AttrInfo attrInfo() {
return sdkInfo.getAttrInfo();
}
public WidgetInfo widgetInfo () {
return sdkInfo.getWidgetInfo ();
public WidgetInfo widgetInfo() {
return sdkInfo.getWidgetInfo();
}
}
@@ -1,32 +1,31 @@
/************************************************************************************
* This file is part of AndroidIDE.
*
*
*
*
* AndroidIDE is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
*
* AndroidIDE is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
*
* You should have received a copy of the GNU General Public License
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
*
**************************************************************************************/
**************************************************************************************/
package com.itsaky.androidide.fragments;
import androidx.fragment.app.Fragment;
import com.itsaky.androidide.app.StudioActivity;
public class BaseFragment extends Fragment
{
protected StudioActivity getStudioActivity() {
return (StudioActivity) getActivity();
}
public class BaseFragment extends Fragment {
protected StudioActivity getStudioActivity() {
return (StudioActivity) getActivity();
}
}
@@ -38,112 +38,119 @@ import com.itsaky.androidide.app.BaseApplication;
import com.itsaky.androidide.databinding.LayoutCrashReportBinding;
public class CrashReportFragment extends Fragment {
private LayoutCrashReportBinding binding;
private boolean closeAppOnClick = true;
public static final String KEY_TITLE = "crash_title";
public static final String KEY_MESSAGE = "crash_message";
public static final String KEY_TRACE = "crash_trace";
@NonNull
public static CrashReportFragment newInstance (@NonNull final String trace) {
return CrashReportFragment.newInstance (null, null, trace);
public static CrashReportFragment newInstance(@NonNull final String trace) {
return CrashReportFragment.newInstance(null, null, trace);
}
@NonNull
public static CrashReportFragment newInstance (@Nullable final String title, @Nullable final String message, @NonNull final String trace) {
final var frag = new CrashReportFragment ();
final var args = new Bundle ();
args.putString (KEY_TRACE, trace);
public static CrashReportFragment newInstance(
@Nullable final String title,
@Nullable final String message,
@NonNull final String trace) {
final var frag = new CrashReportFragment();
final var args = new Bundle();
args.putString(KEY_TRACE, trace);
if (title != null) {
args.putString (KEY_TITLE, title);
args.putString(KEY_TITLE, title);
}
if (message != null) {
args.putString (KEY_MESSAGE, message);
args.putString(KEY_MESSAGE, message);
}
frag.setArguments (args);
frag.setArguments(args);
return frag;
}
public CrashReportFragment setCloseAppOnClick (boolean closeAppOnClick) {
public CrashReportFragment setCloseAppOnClick(boolean closeAppOnClick) {
this.closeAppOnClick = closeAppOnClick;
return this;
}
@Nullable
@Override
public View onCreateView (@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
return (binding = LayoutCrashReportBinding.inflate (inflater, container, false)).getRoot ();
public View onCreateView(
@NonNull LayoutInflater inflater,
@Nullable ViewGroup container,
@Nullable Bundle savedInstanceState) {
return (binding = LayoutCrashReportBinding.inflate(inflater, container, false)).getRoot();
}
@Override
public void onViewCreated (@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated (view, savedInstanceState);
final var args = requireArguments ();
var title = getString (R.string.msg_ide_crashed);
var message = getString (R.string.msg_report_crash);
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
final var args = requireArguments();
var title = getString(R.string.msg_ide_crashed);
var message = getString(R.string.msg_report_crash);
var trace = "";
if (args.containsKey (KEY_TITLE)) {
title = args.getString (KEY_TITLE);
if (args.containsKey(KEY_TITLE)) {
title = args.getString(KEY_TITLE);
}
if (args.containsKey (KEY_MESSAGE)) {
message = args.getString (KEY_MESSAGE);
if (args.containsKey(KEY_MESSAGE)) {
message = args.getString(KEY_MESSAGE);
}
if (args.containsKey (KEY_TRACE)) {
trace = args.getString (KEY_TRACE);
trace = buildReportText (trace);
if (args.containsKey(KEY_TRACE)) {
trace = args.getString(KEY_TRACE);
trace = buildReportText(trace);
} else {
trace = "No stack strace was provided for the report";
}
binding.crashTitle.setText (title);
binding.crashSubtitle.setText (message);
binding.logText.setText (trace);
binding.crashTitle.setText(title);
binding.crashSubtitle.setText(message);
binding.logText.setText(trace);
final var report = trace;
binding.closeButton.setOnClickListener (v -> {
if (closeAppOnClick) {
requireActivity ().finishAffinity ();
} else {
requireActivity ().finish ();
}
});
binding.reportButton.setOnClickListener (v -> reportTrace (report));
binding.closeButton.setOnClickListener(
v -> {
if (closeAppOnClick) {
requireActivity().finishAffinity();
} else {
requireActivity().finish();
}
});
binding.reportButton.setOnClickListener(v -> reportTrace(report));
}
private void reportTrace (String report) {
ClipboardUtils.copyText ("AndroidIDE CrashLog", report);
final var url = BaseApplication.GITHUB_URL.concat ("/issues");
final var intent = new Intent ();
private void reportTrace(String report) {
ClipboardUtils.copyText("AndroidIDE CrashLog", report);
final var url = BaseApplication.GITHUB_URL.concat("/issues");
final var intent = new Intent();
intent.setAction(Intent.ACTION_VIEW);
intent.setData (Uri.parse (url));
intent.addFlags (Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity (intent);
intent.setData(Uri.parse(url));
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent);
}
@NonNull
private String buildReportText (String trace) {
return "AndroidIDE crash report\n" +
"Manufacturer: " +
getManufacturer () +
"\n" +
"Device: " +
getModel () +
"\n" +
"App version: " +
getAppVersionName () +
" (" +
getAppVersionCode () +
")\n\n Stacktrace: \n" +
trace;
private String buildReportText(String trace) {
return "AndroidIDE crash report\n"
+ "Manufacturer: "
+ getManufacturer()
+ "\n"
+ "Device: "
+ getModel()
+ "\n"
+ "App version: "
+ getAppVersionName()
+ " ("
+ getAppVersionCode()
+ ")\n\n Stacktrace: \n"
+ trace;
}
}
@@ -35,49 +35,55 @@ import com.itsaky.androidide.views.EmptyView;
import java.util.ArrayList;
public class DiagnosticsListFragment extends Fragment {
private RecyclerView list;
private EmptyView emptyView;
private DiagnosticsAdapter unsavedAdapter;
@Nullable
@Override
public View onCreateView (@NonNull LayoutInflater inflater, @Nullable ViewGroup parent, @Nullable Bundle savedInstanceState) {
final var container = new LinearLayout (inflater.getContext ());
container.setOrientation (LinearLayout.VERTICAL);
container.setLayoutParams (new ViewGroup.LayoutParams (-1, -1));
this.list = new RecyclerView (inflater.getContext ());
this.emptyView = new EmptyView (inflater.getContext ());
container.addView (list, new LinearLayout.LayoutParams (-1, -1));
container.addView (emptyView, new LinearLayout.LayoutParams (-1, -1));
public View onCreateView(
@NonNull LayoutInflater inflater,
@Nullable ViewGroup parent,
@Nullable Bundle savedInstanceState) {
final var container = new LinearLayout(inflater.getContext());
container.setOrientation(LinearLayout.VERTICAL);
container.setLayoutParams(new ViewGroup.LayoutParams(-1, -1));
this.list = new RecyclerView(inflater.getContext());
this.emptyView = new EmptyView(inflater.getContext());
container.addView(list, new LinearLayout.LayoutParams(-1, -1));
container.addView(emptyView, new LinearLayout.LayoutParams(-1, -1));
return container;
}
@Override
public void onViewCreated (@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated (view, savedInstanceState);
list.setLayoutManager (new LinearLayoutManager (list.getContext ()));
list.setAdapter (unsavedAdapter != null ? unsavedAdapter : new DiagnosticsAdapter (new ArrayList<> (), null));
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
list.setLayoutManager(new LinearLayoutManager(list.getContext()));
list.setAdapter(
unsavedAdapter != null
? unsavedAdapter
: new DiagnosticsAdapter(new ArrayList<>(), null));
unsavedAdapter = null;
}
public void setAdapter (@NonNull DiagnosticsAdapter adapter) {
public void setAdapter(@NonNull DiagnosticsAdapter adapter) {
if (list != null) {
list.setAdapter (adapter);
list.setAdapter(adapter);
} else {
unsavedAdapter = adapter;
}
}
public void handleResultVisibility (boolean error) {
public void handleResultVisibility(boolean error) {
if (emptyView != null && list != null) {
emptyView.setVisibility (error ? View.VISIBLE : View.GONE);
list.setVisibility (error ? View.GONE : View.VISIBLE);
emptyView.setVisibility(error ? View.VISIBLE : View.GONE);
list.setVisibility(error ? View.GONE : View.VISIBLE);
}
}
}
@@ -18,7 +18,6 @@
*
**************************************************************************************/
package com.itsaky.androidide.fragments;
import android.content.Context;
@@ -53,209 +52,228 @@ import com.unnamed.b.atv.view.AndroidTreeView;
import java.io.File;
import java.util.Arrays;
public class FileTreeFragment extends BottomSheetDialogFragment implements TreeNode.TreeNodeClickListener, TreeNode.TreeNodeLongClickListener {
public class FileTreeFragment extends BottomSheetDialogFragment
implements TreeNode.TreeNodeClickListener, TreeNode.TreeNodeLongClickListener {
private LayoutEditorFileTreeBinding binding;
private AndroidTreeView mFileTreeView;
private FileActionListener mFileActionListener;
private AndroidProject mProject;
private TreeNode mRoot;
public FileTreeFragment () {
}
public FileTreeFragment setFileActionListener (FileActionListener listener) {
public FileTreeFragment() {}
public FileTreeFragment setFileActionListener(FileActionListener listener) {
this.mFileActionListener = listener;
return this;
}
@NonNull
public static FileTreeFragment newInstance (AndroidProject project) {
Bundle bundle = new Bundle ();
bundle.putParcelable ("project", project);
FileTreeFragment frag = new FileTreeFragment ();
frag.setArguments (bundle);
public static FileTreeFragment newInstance(AndroidProject project) {
Bundle bundle = new Bundle();
bundle.putParcelable("project", project);
FileTreeFragment frag = new FileTreeFragment();
frag.setArguments(bundle);
return frag;
}
@Override
public View onCreateView (LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
binding = LayoutEditorFileTreeBinding.inflate (inflater, container, false);
return binding.getRoot ();
public View onCreateView(
LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
binding = LayoutEditorFileTreeBinding.inflate(inflater, container, false);
return binding.getRoot();
}
@Override
public void onViewCreated (View view, Bundle savedInstanceState) {
super.onViewCreated (view, savedInstanceState);
mProject = requireArguments ().getParcelable ("project");
listProjectFiles ();
public void onViewCreated(View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
mProject = requireArguments().getParcelable("project");
listProjectFiles();
}
@Override
public void onClick (TreeNode node, Object p2) {
public void onClick(TreeNode node, Object p2) {
final File f = (File) p2;
if (!f.exists ()) {
if (!f.exists()) {
return;
}
if (f.isFile ()) {
if (f.getName ().endsWith (".apk")) {
final var intent = IntentUtils.getInstallAppIntent (f);
if (f.isFile()) {
if (f.getName().endsWith(".apk")) {
final var intent = IntentUtils.getInstallAppIntent(f);
if (intent != null) {
requireContext ().startActivity (intent);
requireContext().startActivity(intent);
} else {
StudioApp.getInstance ().toast (getString(R.string.msg_apk_install_intent_failed), Toaster.Type.ERROR);
StudioApp.getInstance()
.toast(
getString(R.string.msg_apk_install_intent_failed),
Toaster.Type.ERROR);
}
} else if (mFileActionListener != null && (FileUtils.isUtf8 (f) || f.length () == 0)) {
mFileActionListener.openFile (f);
} else if (mFileActionListener != null && (FileUtils.isUtf8(f) || f.length() == 0)) {
mFileActionListener.openFile(f);
}
} else if (f.isDirectory () && f.exists ()) {
if (node.isExpanded ()) {
collapseNode (node);
} else if (f.getAbsolutePath ().equals (Environment.GRADLE_USER_HOME.getAbsolutePath ()) && !node.isExpanded ()) {
expandNode (node);
} else if (f.isDirectory() && f.exists()) {
if (node.isExpanded()) {
collapseNode(node);
} else if (f.getAbsolutePath().equals(Environment.GRADLE_USER_HOME.getAbsolutePath())
&& !node.isExpanded()) {
expandNode(node);
} else {
setLoading (node);
listNode (node);
setLoading(node);
listNode(node);
}
}
}
private void listNode (@NonNull TreeNode node) {
node.getChildren ().clear ();
node.setExpanded (false);
new TaskExecutor ().executeAsync (() -> {
getNodeFromFiles (node.getValue ().listFiles (/* new FileTreeCallable.HiddenFilesFilter() */), node);
TreeNode temp = node;
while (temp.size () == 1) {
temp = temp.childAt (0);
if (!temp.getValue ().isDirectory ()) {
break;
}
getNodeFromFiles (temp.getValue ().listFiles (/* new FileTreeCallable.HiddenFilesFilter() */), temp);
temp.setExpanded (true);
}
return null;
}, __ -> {
updateChevron (node);
expandNode (node);
});
private void listNode(@NonNull TreeNode node) {
node.getChildren().clear();
node.setExpanded(false);
new TaskExecutor()
.executeAsync(
() -> {
getNodeFromFiles(
node.getValue()
.listFiles(
/* new FileTreeCallable.HiddenFilesFilter() */ ),
node);
TreeNode temp = node;
while (temp.size() == 1) {
temp = temp.childAt(0);
if (!temp.getValue().isDirectory()) {
break;
}
getNodeFromFiles(
temp.getValue()
.listFiles(
/* new FileTreeCallable.HiddenFilesFilter() */ ),
temp);
temp.setExpanded(true);
}
return null;
},
__ -> {
updateChevron(node);
expandNode(node);
});
}
private void getNodeFromFiles (File[] files, TreeNode parent) {
Arrays.sort (files, new FileTreeCallable.SortFileName ());
Arrays.sort (files, new FileTreeCallable.SortFolder ());
private void getNodeFromFiles(File[] files, TreeNode parent) {
Arrays.sort(files, new FileTreeCallable.SortFileName());
Arrays.sort(files, new FileTreeCallable.SortFolder());
for (File file : files) {
TreeNode node = new TreeNode (file);
node.setViewHolder (new FileTreeViewHolder (getContext ()));
parent.addChild (node);
TreeNode node = new TreeNode(file);
node.setViewHolder(new FileTreeViewHolder(getContext()));
parent.addChild(node);
}
}
private void setLoading (@NonNull TreeNode node) {
if (node.getViewHolder () instanceof FileTreeViewHolder) {
((FileTreeViewHolder) node.getViewHolder ()).setLoading ();
private void setLoading(@NonNull TreeNode node) {
if (node.getViewHolder() instanceof FileTreeViewHolder) {
((FileTreeViewHolder) node.getViewHolder()).setLoading();
}
}
private void updateChevron (@NonNull TreeNode node) {
if (node.getViewHolder () instanceof FileTreeViewHolder) {
((FileTreeViewHolder) node.getViewHolder ()).updateChevron (!node.isExpanded ());
private void updateChevron(@NonNull TreeNode node) {
if (node.getViewHolder() instanceof FileTreeViewHolder) {
((FileTreeViewHolder) node.getViewHolder()).updateChevron(!node.isExpanded());
}
}
@Override
public boolean onLongClick (TreeNode node, Object value) {
public boolean onLongClick(TreeNode node, Object value) {
if (mFileActionListener != null) {
mFileActionListener.showFileOptions ((File) value, node);
mFileActionListener.showFileOptions((File) value, node);
}
return true;
}
public void expandNode (TreeNode node) {
public void expandNode(TreeNode node) {
if (mFileTreeView == null) {
return;
}
TransitionManager.beginDelayedTransition (binding.getRoot (), new ChangeBounds ());
mFileTreeView.expandNode (node);
TransitionManager.beginDelayedTransition(binding.getRoot(), new ChangeBounds());
mFileTreeView.expandNode(node);
}
public void collapseNode (TreeNode node) {
public void collapseNode(TreeNode node) {
if (mFileTreeView == null) {
return;
}
TransitionManager.beginDelayedTransition (binding.getRoot (), new ChangeBounds ());
mFileTreeView.collapseNode (node);
TransitionManager.beginDelayedTransition(binding.getRoot(), new ChangeBounds());
mFileTreeView.collapseNode(node);
}
public void listProjectFiles () {
public void listProjectFiles() {
if (mProject == null) {
return;
}
final File gradleProps = Environment.GRADLE_PROPS;
final File gradleHome = Environment.GRADLE_USER_HOME;
File projectDir = new File (mProject.getProjectPath ());
mRoot = TreeNode.root (projectDir);
if (gradleHome.exists () && gradleHome.isDirectory ()) {
if (!gradleProps.exists ()) {
FileIOUtils.writeFileFromString (gradleProps, Environment.SAMPLE_GRADLE_PROP_CONTENTS);
File projectDir = new File(mProject.getProjectPath());
mRoot = TreeNode.root(projectDir);
if (gradleHome.exists() && gradleHome.isDirectory()) {
if (!gradleProps.exists()) {
FileIOUtils.writeFileFromString(
gradleProps, Environment.SAMPLE_GRADLE_PROP_CONTENTS);
}
TreeNode home = new TreeNode (gradleHome);
home.setViewHolder (new FileTreeViewHolder (getContext ()));
TreeNode prop = new TreeNode (gradleProps);
prop.setViewHolder (new FileTreeViewHolder (getContext ()));
home.addChild (prop);
mRoot.addChild (home);
TreeNode home = new TreeNode(gradleHome);
home.setViewHolder(new FileTreeViewHolder(getContext()));
TreeNode prop = new TreeNode(gradleProps);
prop.setViewHolder(new FileTreeViewHolder(getContext()));
home.addChild(prop);
mRoot.addChild(home);
}
mRoot.setViewHolder (new FileTreeViewHolder (getContext ()));
getScrollView ().setVisibility (View.GONE);
getLoadingProgress ().setVisibility (View.VISIBLE);
new TaskExecutor ().executeAsync (new FileTreeCallable (getContext (), mRoot, projectDir), (result) -> {
getScrollView ().setVisibility (View.VISIBLE);
getLoadingProgress ().setVisibility (View.GONE);
AndroidTreeView tree = createTreeView (mRoot);
if (tree != null) {
tree.setUseAutoToggle (false);
tree.setDefaultNodeClickListener (FileTreeFragment.this);
tree.setDefaultNodeLongClickListener (FileTreeFragment.this);
getScrollView ().removeAllViews ();
getScrollView ().addView (tree.getView ());
}
});
mRoot.setViewHolder(new FileTreeViewHolder(getContext()));
getScrollView().setVisibility(View.GONE);
getLoadingProgress().setVisibility(View.VISIBLE);
new TaskExecutor()
.executeAsync(
new FileTreeCallable(getContext(), mRoot, projectDir),
(result) -> {
getScrollView().setVisibility(View.VISIBLE);
getLoadingProgress().setVisibility(View.GONE);
AndroidTreeView tree = createTreeView(mRoot);
if (tree != null) {
tree.setUseAutoToggle(false);
tree.setDefaultNodeClickListener(FileTreeFragment.this);
tree.setDefaultNodeLongClickListener(FileTreeFragment.this);
getScrollView().removeAllViews();
getScrollView().addView(tree.getView());
}
});
}
public AndroidTreeView createTreeView (TreeNode node) {
public AndroidTreeView createTreeView(TreeNode node) {
Context ctx = null;
if (getActivity () != null) {
ctx = getActivity ();
} else if (getContext () != null) {
ctx = getContext ();
if (getActivity() != null) {
ctx = getActivity();
} else if (getContext() != null) {
ctx = getContext();
}
if (ctx == null) {
return null;
}
return mFileTreeView = new AndroidTreeView (ctx, node, R.drawable.bg_ripple);
return mFileTreeView = new AndroidTreeView(ctx, node, R.drawable.bg_ripple);
}
public HorizontalScrollView getScrollView () {
public HorizontalScrollView getScrollView() {
return binding.filetreeHorizontalScrollView;
}
public ProgressBar getLoadingProgress () {
public ProgressBar getLoadingProgress() {
return binding.fileTreeLoadingProgress;
}
public interface FileActionListener {
CodeEditorView openFile (File file);
void showFileOptions (File file, TreeNode node);
CodeEditorView openFile(File file);
void showFileOptions(File file, TreeNode node);
}
}
@@ -20,14 +20,14 @@ package com.itsaky.androidide.fragments;
import com.itsaky.androidide.utils.Logger;
public class IDELogFragment extends SimpleOutputFragment {
private final Logger.LogListener listener = this::log;
public IDELogFragment () {
Logger.addLogListener (listener);
public IDELogFragment() {
Logger.addLogListener(listener);
}
private void log (int priority, String tag, String message) {
appendOutput (String.format ("%s: %s", tag, message));
private void log(int priority, String tag, String message) {
appendOutput(String.format("%s: %s", tag, message));
}
}
@@ -27,51 +27,51 @@ import com.blankj.utilcode.util.ThreadUtils;
import com.itsaky.androidide.language.logs.LogLanguageImpl;
import com.itsaky.androidide.models.LogLine;
import io.github.rosemoe.sora.lang.Language;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import io.github.rosemoe.sora.lang.Language;
public class LogViewFragment extends NonEditableEditorFragment {
private final Language language = getLanguage ();
private final List<LogLine> unsavedLines = new ArrayList<> ();
private final Language language = getLanguage();
private final List<LogLine> unsavedLines = new ArrayList<>();
@Override
public void onViewCreated (@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated (view, savedInstanceState);
Objects.requireNonNull (getEditor ()).setEditorLanguage (language);
if (!unsavedLines.isEmpty ()) {
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
Objects.requireNonNull(getEditor()).setEditorLanguage(language);
if (!unsavedLines.isEmpty()) {
for (var line : unsavedLines) {
applyToLanguage (line);
getEditor ().append (line.toString ().trim () + "\n");
applyToLanguage(line);
getEditor().append(line.toString().trim() + "\n");
}
unsavedLines.clear ();
unsavedLines.clear();
}
}
public void appendLog (LogLine line) {
if (getEditor () == null) {
unsavedLines.add (line);
public void appendLog(LogLine line) {
if (getEditor() == null) {
unsavedLines.add(line);
return;
}
applyToLanguage (line);
final var lineString = line.toString ();
final var msg = lineString.endsWith ("\n") ? lineString : lineString + "\n";
ThreadUtils.runOnUiThread (() -> getEditor ().append (msg));
applyToLanguage(line);
final var lineString = line.toString();
final var msg = lineString.endsWith("\n") ? lineString : lineString + "\n";
ThreadUtils.runOnUiThread(() -> getEditor().append(msg));
}
private void applyToLanguage (LogLine line) {
private void applyToLanguage(LogLine line) {
if (language instanceof LogLanguageImpl) {
((LogLanguageImpl)this.language).addLine (line);
((LogLanguageImpl) this.language).addLine(line);
}
}
protected Language getLanguage () {
return new LogLanguageImpl ();
protected Language getLanguage() {
return new LogLanguageImpl();
}
}
@@ -34,34 +34,37 @@ import com.itsaky.androidide.views.editor.IDEEditor;
import io.github.rosemoe.sora.lang.EmptyLanguage;
public abstract class NonEditableEditorFragment extends Fragment {
private FragmentNonEditableEditorBinding binding;
@Nullable
@Override
public View onCreateView (@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
this.binding = FragmentNonEditableEditorBinding.inflate (inflater, container, false);
return binding.getRoot ();
public View onCreateView(
@NonNull LayoutInflater inflater,
@Nullable ViewGroup container,
@Nullable Bundle savedInstanceState) {
this.binding = FragmentNonEditableEditorBinding.inflate(inflater, container, false);
return binding.getRoot();
}
@Override
public void onViewCreated (@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated (view, savedInstanceState);
final var editor = binding.getRoot ();
editor.setEditable (false);
editor.setDividerWidth (0);
editor.setEditorLanguage (new EmptyLanguage ());
editor.setWordwrap (false);
editor.setUndoEnabled (false);
editor.setTypefaceLineNumber (TypefaceUtils.jetbrainsMono ());
editor.setTypefaceText (TypefaceUtils.jetbrainsMono ());
editor.setTextSize (12);
editor.setColorScheme (new SchemeAndroidIDE ());
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
final var editor = binding.getRoot();
editor.setEditable(false);
editor.setDividerWidth(0);
editor.setEditorLanguage(new EmptyLanguage());
editor.setWordwrap(false);
editor.setUndoEnabled(false);
editor.setTypefaceLineNumber(TypefaceUtils.jetbrainsMono());
editor.setTypefaceText(TypefaceUtils.jetbrainsMono());
editor.setTextSize(12);
editor.setColorScheme(new SchemeAndroidIDE());
}
@Nullable
public IDEEditor getEditor () {
public IDEEditor getEditor() {
if (binding == null) {
return null;
}
@@ -1,146 +1,148 @@
/************************************************************************************
* This file is part of AndroidIDE.
*
*
*
*
* AndroidIDE is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
*
* AndroidIDE is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
*
* You should have received a copy of the GNU General Public License
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
*
**************************************************************************************/
**************************************************************************************/
package com.itsaky.androidide.fragments;
import android.content.Intent;
import android.os.Bundle;
import android.text.TextUtils;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ProgressBar;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import com.blankj.utilcode.util.FileIOUtils;
import com.itsaky.androidide.EditorActivity;
import com.itsaky.androidide.adapters.ProjectsListAdapter;
import com.itsaky.androidide.app.StudioApp;
import com.itsaky.androidide.databinding.LayoutProjectsBinding;
import com.itsaky.androidide.project.AndroidProject;
import com.itsaky.androidide.tasks.TaskExecutor;
import com.itsaky.androidide.utils.FileUtil;
import fr.arnaudguyon.xmltojsonlib.XmlToJson;
import com.itsaky.androidide.utils.ProjectFinder;
import java.io.File;
import java.io.FileFilter;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.regex.Pattern;
import org.json.JSONException;
import org.json.JSONObject;
import com.itsaky.androidide.utils.ProjectFinder;
public class ProjectsFragment extends BaseFragment {
private LayoutProjectsBinding binding;
private ArrayList<AndroidProject> mProjects;
private boolean isLoading = false;
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
binding = LayoutProjectsBinding.inflate(inflater, container, false);
return binding.getRoot();
}
private LayoutProjectsBinding binding;
private ArrayList<AndroidProject> mProjects;
private boolean isLoading = false;
@Override
public void onViewCreated(View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
if (getActivity() == null) return;
@Override
public View onCreateView(
LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
binding = LayoutProjectsBinding.inflate(inflater, container, false);
return binding.getRoot();
}
loadProjects();
}
@Override
public void onViewCreated(View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
if (getActivity() == null) return;
public void loadProjects() {
if (!isLoading && getStudioActivity().isStoragePermissionGranted()) {
getProgressBar().setVisibility(View.VISIBLE);
getRecyclerView().setVisibility(View.GONE);
setLoading(true);
final ProjectsFragment frag = this;
new TaskExecutor().executeAsync(new GetProjectsTask(), (result) -> {
frag.setLoading(false);
loadProjects();
}
if (frag.getProjectsList() == null || frag.getProjectsList().size() == 0) {
frag.getProgressBar().setVisibility(View.GONE);
frag.getRecyclerView().setVisibility(View.GONE);
frag.getNothingLayout().setVisibility(View.VISIBLE);
} else {
frag.getNothingLayout().setVisibility(View.GONE);
frag.getProgressBar().setVisibility(View.GONE);
frag.getRecyclerView().setVisibility(View.VISIBLE);
frag.getRecyclerView().setLayoutManager(new LinearLayoutManager(getActivity()));
frag.getRecyclerView().setAdapter(new ProjectsListAdapter(mProjects, (project) -> openProject(project)));
}
});
}
}
public void loadProjects() {
if (!isLoading && getStudioActivity().isStoragePermissionGranted()) {
getProgressBar().setVisibility(View.VISIBLE);
getRecyclerView().setVisibility(View.GONE);
setLoading(true);
final ProjectsFragment frag = this;
new TaskExecutor()
.executeAsync(
new GetProjectsTask(),
(result) -> {
frag.setLoading(false);
private void openProject(AndroidProject project) {
startActivity(new Intent(getActivity(), EditorActivity.class)
.putExtra("project", project));
}
if (frag.getProjectsList() == null
|| frag.getProjectsList().size() == 0) {
frag.getProgressBar().setVisibility(View.GONE);
frag.getRecyclerView().setVisibility(View.GONE);
frag.getNothingLayout().setVisibility(View.VISIBLE);
} else {
frag.getNothingLayout().setVisibility(View.GONE);
frag.getProgressBar().setVisibility(View.GONE);
frag.getRecyclerView().setVisibility(View.VISIBLE);
frag.getRecyclerView()
.setLayoutManager(
new LinearLayoutManager(getActivity()));
frag.getRecyclerView()
.setAdapter(
new ProjectsListAdapter(
mProjects,
(project) -> openProject(project)));
}
});
}
}
private void openProject(AndroidProject project) {
startActivity(new Intent(getActivity(), EditorActivity.class).putExtra("project", project));
}
public void getProjects() throws Exception {
mProjects = new ArrayList<>();
File[] files = StudioApp.getInstance().listProjects();
for (File file : files) {
AndroidProject project = ProjectFinder.fromFolder(file);
if (project == null) continue;
public void getProjects() throws Exception {
mProjects = new ArrayList<>();
File[] files = StudioApp.getInstance().listProjects();
for (File file : files) {
AndroidProject project = ProjectFinder.fromFolder(file);
if(project == null)
continue;
mProjects.add(project);
}
}
public ProgressBar getProgressBar() {
return binding.projectsProgressBar;
}
}
}
public RecyclerView getRecyclerView() {
return binding.mainProjectsRecyclerView;
}
public ProgressBar getProgressBar() {
return binding.projectsProgressBar;
}
public boolean isLoading() {
return isLoading;
}
public RecyclerView getRecyclerView() {
return binding.mainProjectsRecyclerView;
}
public void setLoading(boolean isLoading) {
this.isLoading = isLoading;
}
public boolean isLoading() {
return isLoading;
}
public ArrayList<AndroidProject> getProjectsList() {
return mProjects;
}
public void setLoading(boolean isLoading) {
this.isLoading = isLoading;
}
public View getNothingLayout() {
return binding.projectsNothingLayout;
}
public ArrayList<AndroidProject> getProjectsList() {
return mProjects;
}
private class GetProjectsTask implements Callable<List<AndroidProject>> {
@Override
public List<AndroidProject> call() throws Exception {
getProjects();
return null;
}
}
public View getNothingLayout() {
return binding.projectsNothingLayout;
}
private class GetProjectsTask implements Callable<List<AndroidProject>> {
@Override
public List<AndroidProject> call() throws Exception {
getProjects();
return null;
}
}
}
@@ -33,49 +33,53 @@ import com.itsaky.androidide.adapters.SearchListAdapter;
import com.itsaky.androidide.views.EmptyView;
public class SearchResultFragment extends Fragment {
private RecyclerView list;
private EmptyView emptyView;
private SearchListAdapter unsavedAdapter;
@Nullable
@Override
public View onCreateView (@NonNull LayoutInflater inflater, @Nullable ViewGroup parent, @Nullable Bundle savedInstanceState) {
final var container = new LinearLayout (inflater.getContext ());
container.setOrientation (LinearLayout.VERTICAL);
container.setLayoutParams (new ViewGroup.LayoutParams (-1, -1));
this.list = new RecyclerView (inflater.getContext ());
this.emptyView = new EmptyView (inflater.getContext ());
container.addView (list, new LinearLayout.LayoutParams (-1, -1));
container.addView (emptyView, new LinearLayout.LayoutParams (-1, -1));
public View onCreateView(
@NonNull LayoutInflater inflater,
@Nullable ViewGroup parent,
@Nullable Bundle savedInstanceState) {
final var container = new LinearLayout(inflater.getContext());
container.setOrientation(LinearLayout.VERTICAL);
container.setLayoutParams(new ViewGroup.LayoutParams(-1, -1));
this.list = new RecyclerView(inflater.getContext());
this.emptyView = new EmptyView(inflater.getContext());
container.addView(list, new LinearLayout.LayoutParams(-1, -1));
container.addView(emptyView, new LinearLayout.LayoutParams(-1, -1));
return container;
}
@Override
public void onViewCreated (@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated (view, savedInstanceState);
list.setLayoutManager (new LinearLayoutManager (list.getContext ()));
list.setAdapter (unsavedAdapter != null ? unsavedAdapter : new SearchListAdapter (null, null, null));
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
list.setLayoutManager(new LinearLayoutManager(list.getContext()));
list.setAdapter(
unsavedAdapter != null ? unsavedAdapter : new SearchListAdapter(null, null, null));
unsavedAdapter = null;
}
public void setAdapter (@NonNull final SearchListAdapter adapter) {
if (list == null) {
public void setAdapter(@NonNull final SearchListAdapter adapter) {
if (list == null) {
unsavedAdapter = adapter;
return;
}
this.list.setAdapter (adapter);
this.list.setAdapter(adapter);
}
public void handleResultVisibility (boolean error) {
public void handleResultVisibility(boolean error) {
if (emptyView != null && list != null) {
emptyView.setVisibility (error ? View.VISIBLE : View.GONE);
list.setVisibility (error ? View.GONE : View.VISIBLE);
emptyView.setVisibility(error ? View.VISIBLE : View.GONE);
list.setVisibility(error ? View.GONE : View.VISIBLE);
}
}
}
@@ -30,29 +30,31 @@ import java.util.List;
import java.util.Objects;
public class SimpleOutputFragment extends NonEditableEditorFragment {
private final List<String> unsavedLines = new ArrayList<> ();
private final List<String> unsavedLines = new ArrayList<>();
@Override
public void onViewCreated (@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated (view, savedInstanceState);
if (!unsavedLines.isEmpty ()) {
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
if (!unsavedLines.isEmpty()) {
for (String line : unsavedLines) {
Objects.requireNonNull (getEditor ()).append (line.trim () + "\n");
Objects.requireNonNull(getEditor()).append(line.trim() + "\n");
}
unsavedLines.clear ();
unsavedLines.clear();
}
}
public void appendOutput (String output) {
if (getEditor () == null) {
unsavedLines.add (output);
public void appendOutput(String output) {
if (getEditor() == null) {
unsavedLines.add(output);
return;
}
ThreadUtils.runOnUiThread (() -> {
final var message = output == null || output.endsWith ("\n") ? output : output + "\n";
getEditor ().append (message);
});
ThreadUtils.runOnUiThread(
() -> {
final var message =
output == null || output.endsWith("\n") ? output : output + "\n";
getEditor().append(message);
});
}
}
@@ -1,23 +1,22 @@
/************************************************************************************
* This file is part of AndroidIDE.
*
*
*
*
* AndroidIDE is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
*
* AndroidIDE is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
*
* You should have received a copy of the GNU General Public License
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
*
**************************************************************************************/
**************************************************************************************/
package com.itsaky.androidide.fragments.preferences;
@@ -25,28 +24,35 @@ import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import androidx.core.content.ContextCompat;
import androidx.preference.PreferenceFragmentCompat;
import com.itsaky.androidide.app.StudioApp;
import com.itsaky.androidide.managers.PreferenceManager;
public abstract class BasePreferenceFragment extends PreferenceFragmentCompat {
protected PreferenceManager mPrefManager;
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View v = super.onCreateView(inflater, container, savedInstanceState);
v.setBackgroundColor(ContextCompat.getColor(StudioApp.getInstance(), com.itsaky.androidide.R.color.content_background));
return v;
}
@Override
public void onCreatePreferences(Bundle p1, String p2) {
setPreferencesFromResource(com.itsaky.androidide.R.xml.ide_prefs, p2);
}
protected PreferenceManager getPrefManager() {
return mPrefManager == null ? mPrefManager = new PreferenceManager(getContext()) : mPrefManager;
}
protected PreferenceManager mPrefManager;
@Override
public View onCreateView(
LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View v = super.onCreateView(inflater, container, savedInstanceState);
v.setBackgroundColor(
ContextCompat.getColor(
StudioApp.getInstance(), com.itsaky.androidide.R.color.content_background));
return v;
}
@Override
public void onCreatePreferences(Bundle p1, String p2) {
setPreferencesFromResource(com.itsaky.androidide.R.xml.ide_prefs, p2);
}
protected PreferenceManager getPrefManager() {
return mPrefManager == null
? mPrefManager = new PreferenceManager(getContext())
: mPrefManager;
}
}
@@ -1,23 +1,22 @@
/************************************************************************************
* This file is part of AndroidIDE.
*
*
*
*
* AndroidIDE is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
*
* AndroidIDE is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
*
* You should have received a copy of the GNU General Public License
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
*
**************************************************************************************/
**************************************************************************************/
package com.itsaky.androidide.fragments.preferences;
@@ -37,117 +36,126 @@ import com.itsaky.androidide.utils.Environment;
import java.io.File;
public class BuildPreferences extends BasePreferenceFragment implements Preference.OnPreferenceClickListener {
public static final String KEY_GRADLE_COMMAMDS = "idepref_build_gradleCommands";
public static final String KEY_GRADLE_CLEAR_CACHE = "idepref_build_gradleClearCache";
private ProgressSheet progressSheet;
@Override
public void onCreatePreferences(Bundle p1, String p2) {
super.onCreatePreferences(p1, p2);
if(getContext() == null) return;
final PreferenceScreen screen = getPreferenceScreen();
final PreferenceCategory categoryGradle = new PreferenceCategory(getContext());
final Preference customCommands = new Preference(getContext());
final Preference clearCache = new Preference(getContext());
screen.addPreference(categoryGradle);
customCommands.setKey(KEY_GRADLE_COMMAMDS);
customCommands.setIcon(R.drawable.ic_bash_commands);
customCommands.setTitle(R.string.idepref_build_customgradlecommands_title);
customCommands.setSummary(R.string.idepref_build_customgradlecommands_summary);
clearCache.setKey(KEY_GRADLE_CLEAR_CACHE);
clearCache.setIcon(R.drawable.ic_delete);
clearCache.setTitle(R.string.idepref_build_clearCache_title);
clearCache.setSummary(R.string.idepref_build_clearCache_summary);
categoryGradle.setTitle(R.string.gradle);
categoryGradle.addPreference(customCommands);
categoryGradle.addPreference(clearCache);
setPreferenceScreen(screen);
customCommands.setOnPreferenceClickListener(this);
clearCache.setOnPreferenceClickListener(this);
}
public class BuildPreferences extends BasePreferenceFragment
implements Preference.OnPreferenceClickListener {
@Override
public boolean onPreferenceClick(Preference p1) {
final String key = p1.getKey();
if(key.equals(KEY_GRADLE_COMMAMDS)) {
showGradleCommandsDialog();
} else if(key.equals(KEY_GRADLE_CLEAR_CACHE)) {
showClearCacheDialog();
}
return true;
}
private void showGradleCommandsDialog() {
final String[] labels = {
"--stacktrace",
"--info",
"--debug",
"--scan",
"--warning-mode all"
};
final boolean[] checked = {
getPrefManager().isStackTraceEnabled (),
getPrefManager().isGradleInfoEnabled(),
getPrefManager().isGradleDebugEnabled(),
getPrefManager().isGradleScanEnabled(),
getPrefManager().isGradleWarningEnabled()
};
final MaterialAlertDialogBuilder builder = DialogUtils.newMaterialDialogBuilder (getContext ());
builder.setTitle(R.string.idepref_build_customgradlecommands_title);
builder.setMultiChoiceItems(labels, checked, (p1, p2, p3) -> {
boolean isChecked = p3;
int pos = p2;
if(pos == 0) {
getPrefManager().setGradleStacktraceEnabled(isChecked);
} else if(pos == 1) {
getPrefManager().setGradleInfoEnabled(isChecked);
} else if(pos == 2) {
getPrefManager().setGradleDebugEnabled(isChecked);
} else if(pos == 3) {
getPrefManager().setGradleScanEnabled(isChecked);
} else if(pos == 4) {
getPrefManager().setGradleWarningEnabled(isChecked);
}
});
builder.setPositiveButton(android.R.string.ok, null);
builder.setCancelable(false);
builder.create().show();
}
public static final String KEY_GRADLE_COMMAMDS = "idepref_build_gradleCommands";
public static final String KEY_GRADLE_CLEAR_CACHE = "idepref_build_gradleClearCache";
private void showClearCacheDialog() {
final MaterialAlertDialogBuilder builder = DialogUtils.newMaterialDialogBuilder (getContext ());
builder.setTitle(R.string.idepref_build_clearCache_title);
builder.setMessage(R.string.msg_clear_cache);
builder.setCancelable(false);
builder.setPositiveButton(R.string.yes, (p1, p2) -> {
p1.dismiss();
getProgressSheet().show(getChildFragmentManager(), "progress_sheet");
new TaskExecutor().executeAsync(this::deleteCaches, __ -> {
getProgressSheet().dismiss();
});
});
builder.setNegativeButton(R.string.no, null);
builder.create().show();
}
private Object deleteCaches() {
File file = new File(Environment.GRADLE_USER_HOME, "caches");
if(file.exists()) {
FileUtils.delete(file);
}
return null;
}
private ProgressSheet getProgressSheet() {
return progressSheet == null ? progressSheet = new ProgressSheet().setMessage(getString(R.string.please_wait)) : progressSheet;
}
private ProgressSheet progressSheet;
@Override
public void onCreatePreferences(Bundle p1, String p2) {
super.onCreatePreferences(p1, p2);
if (getContext() == null) return;
final PreferenceScreen screen = getPreferenceScreen();
final PreferenceCategory categoryGradle = new PreferenceCategory(getContext());
final Preference customCommands = new Preference(getContext());
final Preference clearCache = new Preference(getContext());
screen.addPreference(categoryGradle);
customCommands.setKey(KEY_GRADLE_COMMAMDS);
customCommands.setIcon(R.drawable.ic_bash_commands);
customCommands.setTitle(R.string.idepref_build_customgradlecommands_title);
customCommands.setSummary(R.string.idepref_build_customgradlecommands_summary);
clearCache.setKey(KEY_GRADLE_CLEAR_CACHE);
clearCache.setIcon(R.drawable.ic_delete);
clearCache.setTitle(R.string.idepref_build_clearCache_title);
clearCache.setSummary(R.string.idepref_build_clearCache_summary);
categoryGradle.setTitle(R.string.gradle);
categoryGradle.addPreference(customCommands);
categoryGradle.addPreference(clearCache);
setPreferenceScreen(screen);
customCommands.setOnPreferenceClickListener(this);
clearCache.setOnPreferenceClickListener(this);
}
@Override
public boolean onPreferenceClick(Preference p1) {
final String key = p1.getKey();
if (key.equals(KEY_GRADLE_COMMAMDS)) {
showGradleCommandsDialog();
} else if (key.equals(KEY_GRADLE_CLEAR_CACHE)) {
showClearCacheDialog();
}
return true;
}
private void showGradleCommandsDialog() {
final String[] labels = {
"--stacktrace", "--info", "--debug", "--scan", "--warning-mode all"
};
final boolean[] checked = {
getPrefManager().isStackTraceEnabled(),
getPrefManager().isGradleInfoEnabled(),
getPrefManager().isGradleDebugEnabled(),
getPrefManager().isGradleScanEnabled(),
getPrefManager().isGradleWarningEnabled()
};
final MaterialAlertDialogBuilder builder =
DialogUtils.newMaterialDialogBuilder(getContext());
builder.setTitle(R.string.idepref_build_customgradlecommands_title);
builder.setMultiChoiceItems(
labels,
checked,
(p1, p2, p3) -> {
boolean isChecked = p3;
int pos = p2;
if (pos == 0) {
getPrefManager().setGradleStacktraceEnabled(isChecked);
} else if (pos == 1) {
getPrefManager().setGradleInfoEnabled(isChecked);
} else if (pos == 2) {
getPrefManager().setGradleDebugEnabled(isChecked);
} else if (pos == 3) {
getPrefManager().setGradleScanEnabled(isChecked);
} else if (pos == 4) {
getPrefManager().setGradleWarningEnabled(isChecked);
}
});
builder.setPositiveButton(android.R.string.ok, null);
builder.setCancelable(false);
builder.create().show();
}
private void showClearCacheDialog() {
final MaterialAlertDialogBuilder builder =
DialogUtils.newMaterialDialogBuilder(getContext());
builder.setTitle(R.string.idepref_build_clearCache_title);
builder.setMessage(R.string.msg_clear_cache);
builder.setCancelable(false);
builder.setPositiveButton(
R.string.yes,
(p1, p2) -> {
p1.dismiss();
getProgressSheet().show(getChildFragmentManager(), "progress_sheet");
new TaskExecutor()
.executeAsync(
this::deleteCaches,
__ -> {
getProgressSheet().dismiss();
});
});
builder.setNegativeButton(R.string.no, null);
builder.create().show();
}
private Object deleteCaches() {
File file = new File(Environment.GRADLE_USER_HOME, "caches");
if (file.exists()) {
FileUtils.delete(file);
}
return null;
}
private ProgressSheet getProgressSheet() {
return progressSheet == null
? progressSheet = new ProgressSheet().setMessage(getString(R.string.please_wait))
: progressSheet;
}
}
@@ -49,115 +49,115 @@ import com.itsaky.androidide.models.PrefBasedJavaServerSettings;
import com.itsaky.androidide.utils.DialogUtils;
public class EditorPreferences extends BasePreferenceFragment {
@Override
public void onCreatePreferences (Bundle p1, String p2) {
super.onCreatePreferences (p1, p2);
if (getContext () == null) {
public void onCreatePreferences(Bundle p1, String p2) {
super.onCreatePreferences(p1, p2);
if (getContext() == null) {
return;
}
final var screen = getPreferenceScreen ();
final var commonCategory = new PreferenceCategory (getContext ());
final var javaCategory = new PreferenceCategory (getContext ());
final var fontSize = new Preference (getContext ());
final var nonPrintable = new Preference (getContext ());
final var tabSize = new Preference (getContext ());
final var drawHex = new SwitchPreference (getContext ());
final var fontLigatures = new SwitchPreference (getContext ());
final var autoSave = new SwitchPreference (getContext ());
final var usePopupActions = new SwitchPreference (getContext ());
final var horizontalPopup = new SwitchPreference (getContext ());
final var javaMatchLower = new SwitchPreference (getContext ());
screen.addPreference (commonCategory);
screen.addPreference (javaCategory);
fontSize.setIcon (R.drawable.ic_text_size);
fontSize.setKey (KEY_EDITOR_FONT_SIZE);
fontSize.setTitle (R.string.idepref_editor_fontsize_title);
fontSize.setSummary (R.string.idepref_editor_fontsize_summary);
nonPrintable.setIcon (R.drawable.ic_drawing);
nonPrintable.setKey (KEY_EDITOR_PRINTABLE_CHARS);
nonPrintable.setTitle (R.string.idepref_editor_paintingflags_title);
nonPrintable.setSummary (R.string.idepref_editor_paintingflags_summary);
tabSize.setIcon (R.drawable.ic_tab);
tabSize.setKey (KEY_EDITOR_TAB_SIZE);
tabSize.setTitle (R.string.title_tab_size);
tabSize.setSummary (R.string.msg_tab_size);
drawHex.setIcon (R.drawable.ic_hexadecimal);
drawHex.setKey (KEY_EDITOR_DRAW_HEX);
drawHex.setTitle (R.string.idepref_editor_drawhexcolors_title);
drawHex.setSummary (R.string.idepref_editor_drawhexcolors_summary);
fontLigatures.setIcon (R.drawable.ic_font_ligatures);
fontLigatures.setKey (KEY_EDITOR_FONT_LIGATURES);
fontLigatures.setTitle (getString (R.string.idepref_editor_ligatures_title));
fontLigatures.setSummary (getString (R.string.idepref_editor_ligatures_summary));
usePopupActions.setIcon (R.drawable.ic_text_actions);
usePopupActions.setKey (KEY_EDITOR_USE_POPUP);
usePopupActions.setTitle (getString (R.string.title_use_popup_actions));
usePopupActions.setSummary (getString (R.string.msg_use_popup_actions));
horizontalPopup.setIcon (R.drawable.ic_text_actions_horizontal);
horizontalPopup.setKey (KEY_EDITOR_HORIZONTAL_POPUP);
horizontalPopup.setTitle (getString (R.string.title_use_horizontal_popup));
horizontalPopup.setSummary (getString (R.string.msg_use_horizontal_popup));
autoSave.setIcon (R.drawable.ic_save);
autoSave.setKey (KEY_EDITOR_AUTO_SAVE);
autoSave.setTitle (getString (R.string.idepref_editor_autoSave_title));
autoSave.setSummary (getString (R.string.idepref_editor_autoSave_summary));
javaMatchLower.setKey (KEY_JAVA_PREF_MATCH_LOWER);
javaMatchLower.setIcon (R.drawable.ic_text_lower);
javaMatchLower.setTitle (getString (R.string.idepref_java_matchLower_title));
javaMatchLower.setSummary (getString (R.string.idepref_java_matchLower_summary));
commonCategory.setTitle (getString (R.string.idepref_editor_category_common));
commonCategory.addPreference (fontSize);
commonCategory.addPreference (fontLigatures);
commonCategory.addPreference (usePopupActions);
commonCategory.addPreference (horizontalPopup);
commonCategory.addPreference (nonPrintable);
commonCategory.addPreference (tabSize);
commonCategory.addPreference (drawHex);
commonCategory.addPreference (autoSave);
javaCategory.setTitle (getString (R.string.idepref_editor_category_java));
javaCategory.addPreference (javaMatchLower);
setPreferenceScreen (screen);
fontSize.setOnPreferenceClickListener (this::onPreferenceClick);
fontLigatures.setOnPreferenceChangeListener (this::onPreferenceChange);
usePopupActions.setOnPreferenceChangeListener (this::onPreferenceChange);
horizontalPopup.setOnPreferenceChangeListener (this::onPreferenceChange);
nonPrintable.setOnPreferenceClickListener (this::onPreferenceClick);
tabSize.setOnPreferenceClickListener (this::onPreferenceClick);
drawHex.setOnPreferenceChangeListener (this::onPreferenceChange);
autoSave.setOnPreferenceChangeListener (this::onPreferenceChange);
javaMatchLower.setOnPreferenceChangeListener (this::onPreferenceChange);
fontLigatures.setChecked (getPrefManager ().getBoolean (KEY_EDITOR_FONT_LIGATURES, true));
usePopupActions.setChecked (getPrefManager ().getBoolean (KEY_EDITOR_USE_POPUP, false));
horizontalPopup.setChecked (getPrefManager ().getBoolean (KEY_EDITOR_HORIZONTAL_POPUP, false));
drawHex.setChecked (getPrefManager ().getBoolean (KEY_EDITOR_DRAW_HEX, true));
autoSave.setChecked (getPrefManager ().getBoolean (KEY_EDITOR_AUTO_SAVE, false));
javaMatchLower.setChecked (getPrefManager ().getBoolean (KEY_JAVA_PREF_MATCH_LOWER, false));
final var screen = getPreferenceScreen();
final var commonCategory = new PreferenceCategory(getContext());
final var javaCategory = new PreferenceCategory(getContext());
final var fontSize = new Preference(getContext());
final var nonPrintable = new Preference(getContext());
final var tabSize = new Preference(getContext());
final var drawHex = new SwitchPreference(getContext());
final var fontLigatures = new SwitchPreference(getContext());
final var autoSave = new SwitchPreference(getContext());
final var usePopupActions = new SwitchPreference(getContext());
final var horizontalPopup = new SwitchPreference(getContext());
final var javaMatchLower = new SwitchPreference(getContext());
screen.addPreference(commonCategory);
screen.addPreference(javaCategory);
fontSize.setIcon(R.drawable.ic_text_size);
fontSize.setKey(KEY_EDITOR_FONT_SIZE);
fontSize.setTitle(R.string.idepref_editor_fontsize_title);
fontSize.setSummary(R.string.idepref_editor_fontsize_summary);
nonPrintable.setIcon(R.drawable.ic_drawing);
nonPrintable.setKey(KEY_EDITOR_PRINTABLE_CHARS);
nonPrintable.setTitle(R.string.idepref_editor_paintingflags_title);
nonPrintable.setSummary(R.string.idepref_editor_paintingflags_summary);
tabSize.setIcon(R.drawable.ic_tab);
tabSize.setKey(KEY_EDITOR_TAB_SIZE);
tabSize.setTitle(R.string.title_tab_size);
tabSize.setSummary(R.string.msg_tab_size);
drawHex.setIcon(R.drawable.ic_hexadecimal);
drawHex.setKey(KEY_EDITOR_DRAW_HEX);
drawHex.setTitle(R.string.idepref_editor_drawhexcolors_title);
drawHex.setSummary(R.string.idepref_editor_drawhexcolors_summary);
fontLigatures.setIcon(R.drawable.ic_font_ligatures);
fontLigatures.setKey(KEY_EDITOR_FONT_LIGATURES);
fontLigatures.setTitle(getString(R.string.idepref_editor_ligatures_title));
fontLigatures.setSummary(getString(R.string.idepref_editor_ligatures_summary));
usePopupActions.setIcon(R.drawable.ic_text_actions);
usePopupActions.setKey(KEY_EDITOR_USE_POPUP);
usePopupActions.setTitle(getString(R.string.title_use_popup_actions));
usePopupActions.setSummary(getString(R.string.msg_use_popup_actions));
horizontalPopup.setIcon(R.drawable.ic_text_actions_horizontal);
horizontalPopup.setKey(KEY_EDITOR_HORIZONTAL_POPUP);
horizontalPopup.setTitle(getString(R.string.title_use_horizontal_popup));
horizontalPopup.setSummary(getString(R.string.msg_use_horizontal_popup));
autoSave.setIcon(R.drawable.ic_save);
autoSave.setKey(KEY_EDITOR_AUTO_SAVE);
autoSave.setTitle(getString(R.string.idepref_editor_autoSave_title));
autoSave.setSummary(getString(R.string.idepref_editor_autoSave_summary));
javaMatchLower.setKey(KEY_JAVA_PREF_MATCH_LOWER);
javaMatchLower.setIcon(R.drawable.ic_text_lower);
javaMatchLower.setTitle(getString(R.string.idepref_java_matchLower_title));
javaMatchLower.setSummary(getString(R.string.idepref_java_matchLower_summary));
commonCategory.setTitle(getString(R.string.idepref_editor_category_common));
commonCategory.addPreference(fontSize);
commonCategory.addPreference(fontLigatures);
commonCategory.addPreference(usePopupActions);
commonCategory.addPreference(horizontalPopup);
commonCategory.addPreference(nonPrintable);
commonCategory.addPreference(tabSize);
commonCategory.addPreference(drawHex);
commonCategory.addPreference(autoSave);
javaCategory.setTitle(getString(R.string.idepref_editor_category_java));
javaCategory.addPreference(javaMatchLower);
setPreferenceScreen(screen);
fontSize.setOnPreferenceClickListener(this::onPreferenceClick);
fontLigatures.setOnPreferenceChangeListener(this::onPreferenceChange);
usePopupActions.setOnPreferenceChangeListener(this::onPreferenceChange);
horizontalPopup.setOnPreferenceChangeListener(this::onPreferenceChange);
nonPrintable.setOnPreferenceClickListener(this::onPreferenceClick);
tabSize.setOnPreferenceClickListener(this::onPreferenceClick);
drawHex.setOnPreferenceChangeListener(this::onPreferenceChange);
autoSave.setOnPreferenceChangeListener(this::onPreferenceChange);
javaMatchLower.setOnPreferenceChangeListener(this::onPreferenceChange);
fontLigatures.setChecked(getPrefManager().getBoolean(KEY_EDITOR_FONT_LIGATURES, true));
usePopupActions.setChecked(getPrefManager().getBoolean(KEY_EDITOR_USE_POPUP, false));
horizontalPopup.setChecked(getPrefManager().getBoolean(KEY_EDITOR_HORIZONTAL_POPUP, false));
drawHex.setChecked(getPrefManager().getBoolean(KEY_EDITOR_DRAW_HEX, true));
autoSave.setChecked(getPrefManager().getBoolean(KEY_EDITOR_AUTO_SAVE, false));
javaMatchLower.setChecked(getPrefManager().getBoolean(KEY_JAVA_PREF_MATCH_LOWER, false));
}
public boolean onPreferenceChange (@NonNull Preference preference, Object newValue) {
public boolean onPreferenceChange(@NonNull Preference preference, Object newValue) {
final boolean value = (boolean) newValue;
switch (preference.getKey ()) {
switch (preference.getKey()) {
case KEY_EDITOR_DRAW_HEX:
ConstantsBridge.EDITOR_PREF_DRAW_HEX_CHANGED = true;
break;
@@ -165,130 +165,143 @@ public class EditorPreferences extends BasePreferenceFragment {
ConstantsBridge.EDITOR_PREF_LIGATURES_CHANGED = true;
break;
case KEY_JAVA_PREF_MATCH_LOWER:
final var javaServer = StudioApp.getInstance ().getJavaLanguageServer ();
javaServer.applySettings (PrefBasedJavaServerSettings.getInstance ());
final var javaServer = StudioApp.getInstance().getJavaLanguageServer();
javaServer.applySettings(PrefBasedJavaServerSettings.getInstance());
break;
}
getPrefManager ().putBoolean (preference.getKey (), value);
final var horizontalPopup = findPreference (KEY_EDITOR_HORIZONTAL_POPUP);
getPrefManager().putBoolean(preference.getKey(), value);
final var horizontalPopup = findPreference(KEY_EDITOR_HORIZONTAL_POPUP);
if (horizontalPopup != null) {
horizontalPopup.setEnabled (getPrefManager ().getBoolean (KEY_EDITOR_USE_POPUP));
horizontalPopup.setEnabled(getPrefManager().getBoolean(KEY_EDITOR_USE_POPUP));
}
return true;
}
public boolean onPreferenceClick (@NonNull Preference preference) {
final String key = preference.getKey ();
public boolean onPreferenceClick(@NonNull Preference preference) {
final String key = preference.getKey();
switch (key) {
case KEY_EDITOR_FONT_SIZE:
showTextSizeDialog ();
showTextSizeDialog();
break;
case KEY_EDITOR_PRINTABLE_CHARS:
showPrintableCharsDialog ();
showPrintableCharsDialog();
break;
case KEY_EDITOR_TAB_SIZE:
showTabSizeDialog ();
showTabSizeDialog();
break;
}
return true;
}
private void showTabSizeDialog () {
final MaterialAlertDialogBuilder builder = DialogUtils.newMaterialDialogBuilder (getContext ());
final String[] sizes = new String[]{"2", "4", "6", "8"};
private void showTabSizeDialog() {
final MaterialAlertDialogBuilder builder =
DialogUtils.newMaterialDialogBuilder(getContext());
final String[] sizes = new String[] {"2", "4", "6", "8"};
// We apply simple maths,
// assuming that tab size options have a difference of 2
// And the array is sorted
// Like, 2, 4, 6, 8, 10, etc
//
// If current tab size if 4, 4/2 = 2 and 2 - 1 = 1, so option at index 1 will be selected which is current tab size (4)!
int current = (getPrefManager ().getEditorTabSize () / 2) - 1;
//
// If current tab size if 4, 4/2 = 2 and 2 - 1 = 1, so option at index 1 will be selected
// which
// is current tab size (4)!
int current = (getPrefManager().getEditorTabSize() / 2) - 1;
if (current < 0 || current >= sizes.length) {
current = 1;
}
builder.setTitle (R.string.title_tab_size);
builder.setSingleChoiceItems (sizes, current, (d, i) -> {
d.dismiss ();
// Reversing the logic applied above...
int tabSize = (i + 1) * 2;
if (tabSize < 2 || tabSize > 8) {
tabSize = 4;
}
getPrefManager ().putInt (KEY_EDITOR_TAB_SIZE, tabSize);
});
builder.setCancelable (true);
builder.show ();
builder.setTitle(R.string.title_tab_size);
builder.setSingleChoiceItems(
sizes,
current,
(d, i) -> {
d.dismiss();
// Reversing the logic applied above...
int tabSize = (i + 1) * 2;
if (tabSize < 2 || tabSize > 8) {
tabSize = 4;
}
getPrefManager().putInt(KEY_EDITOR_TAB_SIZE, tabSize);
});
builder.setCancelable(true);
builder.show();
}
private void showTextSizeDialog () {
final LayoutTextSizeSliderBinding binding = LayoutTextSizeSliderBinding.inflate (LayoutInflater.from (getContext ()));
final MaterialAlertDialogBuilder builder = DialogUtils.newMaterialDialogBuilder (getContext ());
float size = getPrefManager ().getFloat (KEY_EDITOR_FONT_SIZE, 14);
private void showTextSizeDialog() {
final LayoutTextSizeSliderBinding binding =
LayoutTextSizeSliderBinding.inflate(LayoutInflater.from(getContext()));
final MaterialAlertDialogBuilder builder =
DialogUtils.newMaterialDialogBuilder(getContext());
float size = getPrefManager().getFloat(KEY_EDITOR_FONT_SIZE, 14);
if (size < 6 || size > 32) {
size = 14;
}
changeTextSize (binding, size);
binding.slider.setLabelFormatter (p1 -> String.valueOf ((int) p1));
builder.setTitle (R.string.title_change_text_size);
builder.setMessage (R.string.msg_editor_font_size);
builder.setView (binding.getRoot ());
builder.setPositiveButton (android.R.string.ok, (p1, p2) -> {
p1.dismiss ();
float s = binding.slider.getValue ();
changeTextSize (binding, s);
});
builder.setNeutralButton (R.string.reset, (p1, p2) -> changeTextSize (binding, 14));
builder.setCancelable (false);
builder.show ();
changeTextSize(binding, size);
binding.slider.setLabelFormatter(p1 -> String.valueOf((int) p1));
builder.setTitle(R.string.title_change_text_size);
builder.setMessage(R.string.msg_editor_font_size);
builder.setView(binding.getRoot());
builder.setPositiveButton(
android.R.string.ok,
(p1, p2) -> {
p1.dismiss();
float s = binding.slider.getValue();
changeTextSize(binding, s);
});
builder.setNeutralButton(R.string.reset, (p1, p2) -> changeTextSize(binding, 14));
builder.setCancelable(false);
builder.show();
}
private void changeTextSize (LayoutTextSizeSliderBinding binding, float size) {
getPrefManager ().putFloat (KEY_EDITOR_FONT_SIZE, size);
binding.slider.setValue (size);
private void changeTextSize(LayoutTextSizeSliderBinding binding, float size) {
getPrefManager().putFloat(KEY_EDITOR_FONT_SIZE, size);
binding.slider.setValue(size);
ConstantsBridge.EDITOR_PREF_SIZE_CHANGED = true;
}
private void showPrintableCharsDialog () {
private void showPrintableCharsDialog() {
String[] labels = {
"Leading whitespaces",
"Trailing whitespaces",
"Inner whitespaces",
"Whitespaces in empty lines",
"Line breaks"
"Leading whitespaces",
"Trailing whitespaces",
"Inner whitespaces",
"Whitespaces in empty lines",
"Line breaks"
};
final boolean[] checked = {
getPrefManager ().getBoolean (KEY_EDITOR_FLAG_WS_LEADING, true),
getPrefManager ().getBoolean (KEY_EDITOR_FLAG_WS_TRAILING, false),
getPrefManager ().getBoolean (KEY_EDITOR_FLAG_WS_INNER, true),
getPrefManager ().getBoolean (KEY_EDITOR_FLAG_WS_EMPTY_LINE, true),
getPrefManager ().getBoolean (KEY_EDITOR_FLAG_LINE_BREAK, true)
getPrefManager().getBoolean(KEY_EDITOR_FLAG_WS_LEADING, true),
getPrefManager().getBoolean(KEY_EDITOR_FLAG_WS_TRAILING, false),
getPrefManager().getBoolean(KEY_EDITOR_FLAG_WS_INNER, true),
getPrefManager().getBoolean(KEY_EDITOR_FLAG_WS_EMPTY_LINE, true),
getPrefManager().getBoolean(KEY_EDITOR_FLAG_LINE_BREAK, true)
};
final MaterialAlertDialogBuilder builder = DialogUtils.newMaterialDialogBuilder (getContext ());
builder.setTitle (R.string.idepref_editor_paintingflags_title);
builder.setMultiChoiceItems (labels, checked, (dialog, which, isChecked) -> {
if (which == 0) {
getPrefManager ().putBoolean (KEY_EDITOR_FLAG_WS_LEADING, isChecked);
} else if (which == 1) {
getPrefManager ().putBoolean (KEY_EDITOR_FLAG_WS_TRAILING, isChecked);
} else if (which == 2) {
getPrefManager ().putBoolean (KEY_EDITOR_FLAG_WS_INNER, isChecked);
} else if (which == 3) {
getPrefManager ().putBoolean (KEY_EDITOR_FLAG_WS_EMPTY_LINE, isChecked);
} else if (which == 4) {
getPrefManager ().putBoolean (KEY_EDITOR_FLAG_LINE_BREAK, isChecked);
}
ConstantsBridge.EDITOR_PREF_FLAGS_CHANGED = true;
});
builder.setPositiveButton (android.R.string.ok, null);
builder.setCancelable (false);
builder.show ();
final MaterialAlertDialogBuilder builder =
DialogUtils.newMaterialDialogBuilder(getContext());
builder.setTitle(R.string.idepref_editor_paintingflags_title);
builder.setMultiChoiceItems(
labels,
checked,
(dialog, which, isChecked) -> {
if (which == 0) {
getPrefManager().putBoolean(KEY_EDITOR_FLAG_WS_LEADING, isChecked);
} else if (which == 1) {
getPrefManager().putBoolean(KEY_EDITOR_FLAG_WS_TRAILING, isChecked);
} else if (which == 2) {
getPrefManager().putBoolean(KEY_EDITOR_FLAG_WS_INNER, isChecked);
} else if (which == 3) {
getPrefManager().putBoolean(KEY_EDITOR_FLAG_WS_EMPTY_LINE, isChecked);
} else if (which == 4) {
getPrefManager().putBoolean(KEY_EDITOR_FLAG_LINE_BREAK, isChecked);
}
ConstantsBridge.EDITOR_PREF_FLAGS_CHANGED = true;
});
builder.setPositiveButton(android.R.string.ok, null);
builder.setCancelable(false);
builder.show();
}
}
}
@@ -5,83 +5,85 @@
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
*
* AndroidIDE is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
*
* You should have received a copy of the GNU General Public License
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
*
**************************************************************************************/
**************************************************************************************/
package com.itsaky.androidide.fragments.preferences;
import static com.itsaky.androidide.managers.PreferenceManager.*;
import android.os.Bundle;
import androidx.annotation.NonNull;
import androidx.preference.Preference;
import androidx.preference.PreferenceScreen;
import androidx.preference.SwitchPreference;
import com.itsaky.androidide.R;
import static com.itsaky.androidide.managers.PreferenceManager.*;
public class GeneralPreferences extends BasePreferenceFragment
implements Preference.OnPreferenceChangeListener {
public class GeneralPreferences extends BasePreferenceFragment implements Preference.OnPreferenceChangeListener {
@Override
public void onCreatePreferences(Bundle p1, String p2) {
super.onCreatePreferences(p1, p2);
if(getContext() == null) return;
if (getContext() == null) return;
final PreferenceScreen screen = getPreferenceScreen();
final SwitchPreference openProjects = new SwitchPreference(getContext());
final SwitchPreference openProjects = new SwitchPreference(getContext());
final SwitchPreference confirmProjectOpen = new SwitchPreference(getContext());
final SwitchPreference shell = new SwitchPreference (getContext ());
final SwitchPreference shell = new SwitchPreference(getContext());
openProjects.setKey(KEY_OPEN_PROJECTS);
openProjects.setTitle(R.string.title_open_projects);
openProjects.setIcon(R.drawable.ic_open_project);
openProjects.setSummary(R.string.msg_open_projects);
confirmProjectOpen.setKey(KEY_CONFIRM_PROJECT_OPEN);
confirmProjectOpen.setTitle(R.string.title_confirm_project_open);
confirmProjectOpen.setSummary(R.string.msg_confirm_project_open);
confirmProjectOpen.setIcon(R.drawable.ic_open_project);
shell.setKey (KEY_TERMINAL_USE_SYSTEM_SHELL);
shell.setTitle (getString (R.string.title_default_shell));
shell.setSummary (getString (R.string.msg_default_shell));
shell.setIcon (R.drawable.ic_bash_commands);
shell.setKey(KEY_TERMINAL_USE_SYSTEM_SHELL);
shell.setTitle(getString(R.string.title_default_shell));
shell.setSummary(getString(R.string.msg_default_shell));
shell.setIcon(R.drawable.ic_bash_commands);
openProjects.setChecked(getPrefManager().autoOpenProject());
confirmProjectOpen.setChecked(getPrefManager().confirmProjectOpen());
shell.setChecked (getPrefManager ().getBoolean (KEY_TERMINAL_USE_SYSTEM_SHELL));
shell.setChecked(getPrefManager().getBoolean(KEY_TERMINAL_USE_SYSTEM_SHELL));
screen.addPreference(openProjects);
screen.addPreference(confirmProjectOpen);
screen.addPreference (shell);
screen.addPreference(shell);
setPreferenceScreen(screen);
openProjects.setOnPreferenceChangeListener(this);
confirmProjectOpen.setOnPreferenceChangeListener(this);
shell.setOnPreferenceChangeListener (this);
shell.setOnPreferenceChangeListener(this);
}
@Override
public boolean onPreferenceChange(@NonNull Preference p1, Object p2) {
boolean checked = (Boolean) p2;
final var key = p1.getKey ();
final var key = p1.getKey();
switch (key) {
case KEY_OPEN_PROJECTS:
getPrefManager ().putBoolean (KEY_OPEN_PROJECTS, checked);
getPrefManager().putBoolean(KEY_OPEN_PROJECTS, checked);
break;
case KEY_CONFIRM_PROJECT_OPEN:
getPrefManager ().putBoolean (KEY_CONFIRM_PROJECT_OPEN, checked);
getPrefManager().putBoolean(KEY_CONFIRM_PROJECT_OPEN, checked);
break;
case KEY_TERMINAL_USE_SYSTEM_SHELL:
getPrefManager ().putBoolean (KEY_TERMINAL_USE_SYSTEM_SHELL, checked);
getPrefManager().putBoolean(KEY_TERMINAL_USE_SYSTEM_SHELL, checked);
break;
}
return true;
@@ -1,23 +1,22 @@
/************************************************************************************
* This file is part of AndroidIDE.
*
*
*
*
* AndroidIDE is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
*
* AndroidIDE is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
*
* You should have received a copy of the GNU General Public License
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
*
**************************************************************************************/
**************************************************************************************/
package com.itsaky.androidide.fragments.preferences;
@@ -32,14 +31,16 @@ import android.view.ViewGroup;
import android.widget.HorizontalScrollView;
import android.widget.ScrollView;
import android.widget.TextView;
import androidx.core.content.ContextCompat;
import androidx.preference.Preference;
import androidx.preference.PreferenceScreen;
import com.blankj.utilcode.util.ResourceUtils;
import com.blankj.utilcode.util.SizeUtils;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import com.itsaky.androidide.R;
import com.itsaky.androidide.AboutActivity;
import com.itsaky.androidide.R;
import com.itsaky.androidide.app.StudioApp;
import com.itsaky.androidide.utils.DialogUtils;
@@ -47,160 +48,168 @@ import org.json.JSONArray;
import org.json.JSONObject;
public class IDEPreferences extends BasePreferenceFragment {
private GeneralPreferences mGeneralPrefs;
private BuildPreferences mBuildPreferences;
private EditorPreferences mEditorPreferences;
private String CHANGELOG = null;
private GeneralPreferences mGeneralPrefs;
private BuildPreferences mBuildPreferences;
private EditorPreferences mEditorPreferences;
private String CHANGELOG = null;
public static final String KEY_GENERAL = "idepref_general";
public static final String KEY_APPEARANCE = "idepref_appearance";
public static final String KEY_EDITOR = "idepref_editor";
public static final String KEY_BUILD = "idepref_build";
public static final String KEY_TELEGRAM = "idepref_telegram";
public static final String KEY_ISSUES = "idepref_issues";
public static final String KEY_CHANGELOG = "idepref_changelog";
public static final String KEY_APPEARANCE = "idepref_appearance";
public static final String KEY_EDITOR = "idepref_editor";
public static final String KEY_BUILD = "idepref_build";
public static final String KEY_TELEGRAM = "idepref_telegram";
public static final String KEY_ISSUES = "idepref_issues";
public static final String KEY_CHANGELOG = "idepref_changelog";
public static final String KEY_ABOUT = "idepref_about";
@Override
public void onCreatePreferences(Bundle savedState, String rootKey) {
super.onCreatePreferences(savedState, rootKey);
if(getContext() == null) return;
final PreferenceScreen screen = getPreferenceScreen();
@Override
public void onCreatePreferences(Bundle savedState, String rootKey) {
super.onCreatePreferences(savedState, rootKey);
if (getContext() == null) return;
final PreferenceScreen screen = getPreferenceScreen();
final Preference general = new Preference(getContext());
final Preference editor = new Preference(getContext());
final Preference build = new Preference(getContext());
final Preference telegram = new Preference(getContext());
final Preference github = new Preference(getContext());
final Preference changelog = new Preference(getContext());
final Preference about = new Preference(getContext());
general.setKey(KEY_GENERAL);
general.setIconSpaceReserved(false);
general.setFragment(getGeneralFrag().getClass().getName());
general.setTitle(R.string.title_general);
editor.setKey(KEY_EDITOR);
editor.setIconSpaceReserved(false);
editor.setFragment(getEditorFrag().getClass().getName());
editor.setTitle(R.string.idepref_editor_title);
build.setKey(KEY_BUILD);
build.setIconSpaceReserved(false);
build.setFragment(getBuildFrag().getClass().getName());
build.setTitle(R.string.idepref_build_title);
github.setKey(KEY_ISSUES);
github.setIconSpaceReserved(false);
github.setTitle(R.string.title_github);
telegram.setKey(KEY_TELEGRAM);
telegram.setIconSpaceReserved(false);
telegram.setTitle(R.string.discussions_on_telegram);
changelog.setKey(KEY_CHANGELOG);
changelog.setIconSpaceReserved(false);
changelog.setTitle(R.string.pref_changelog);
final Preference editor = new Preference(getContext());
final Preference build = new Preference(getContext());
final Preference telegram = new Preference(getContext());
final Preference github = new Preference(getContext());
final Preference changelog = new Preference(getContext());
final Preference about = new Preference(getContext());
general.setKey(KEY_GENERAL);
general.setIconSpaceReserved(false);
general.setFragment(getGeneralFrag().getClass().getName());
general.setTitle(R.string.title_general);
editor.setKey(KEY_EDITOR);
editor.setIconSpaceReserved(false);
editor.setFragment(getEditorFrag().getClass().getName());
editor.setTitle(R.string.idepref_editor_title);
build.setKey(KEY_BUILD);
build.setIconSpaceReserved(false);
build.setFragment(getBuildFrag().getClass().getName());
build.setTitle(R.string.idepref_build_title);
github.setKey(KEY_ISSUES);
github.setIconSpaceReserved(false);
github.setTitle(R.string.title_github);
telegram.setKey(KEY_TELEGRAM);
telegram.setIconSpaceReserved(false);
telegram.setTitle(R.string.discussions_on_telegram);
changelog.setKey(KEY_CHANGELOG);
changelog.setIconSpaceReserved(false);
changelog.setTitle(R.string.pref_changelog);
about.setKey(KEY_ABOUT);
about.setIconSpaceReserved(false);
about.setTitle(R.string.idepref_about_title);
screen.addPreference(general);
screen.addPreference(editor);
screen.addPreference(build);
screen.addPreference(github);
screen.addPreference(telegram);
screen.addPreference(changelog);
screen.addPreference(editor);
screen.addPreference(build);
screen.addPreference(github);
screen.addPreference(telegram);
screen.addPreference(changelog);
screen.addPreference(about);
final Preference.OnPreferenceClickListener listener = getListener();
github.setOnPreferenceClickListener(listener);
telegram.setOnPreferenceClickListener(listener);
changelog.setOnPreferenceClickListener(listener);
final Preference.OnPreferenceClickListener listener = getListener();
github.setOnPreferenceClickListener(listener);
telegram.setOnPreferenceClickListener(listener);
changelog.setOnPreferenceClickListener(listener);
about.setOnPreferenceClickListener(listener);
}
private GeneralPreferences getGeneralFrag() {
return mGeneralPrefs == null ? mGeneralPrefs = new GeneralPreferences() : mGeneralPrefs;
}
private EditorPreferences getEditorFrag() {
return mEditorPreferences == null ? mEditorPreferences = new EditorPreferences() : mEditorPreferences;
}
private BuildPreferences getBuildFrag() {
return mBuildPreferences == null ? mBuildPreferences = new BuildPreferences() : mBuildPreferences;
}
private void showChangelog() {
final int dp8 = SizeUtils.dp2px(8);
final int dp16 = SizeUtils.dp2px(16);
}
private GeneralPreferences getGeneralFrag() {
return mGeneralPrefs == null ? mGeneralPrefs = new GeneralPreferences() : mGeneralPrefs;
}
private EditorPreferences getEditorFrag() {
return mEditorPreferences == null
? mEditorPreferences = new EditorPreferences()
: mEditorPreferences;
}
private BuildPreferences getBuildFrag() {
return mBuildPreferences == null
? mBuildPreferences = new BuildPreferences()
: mBuildPreferences;
}
private void showChangelog() {
final int dp8 = SizeUtils.dp2px(8);
final int dp16 = SizeUtils.dp2px(16);
final ScrollView vScroll = new ScrollView(getContext());
vScroll.setFillViewport(true);
vScroll.setLayoutParams(new ViewGroup.LayoutParams(-1, -1));
final HorizontalScrollView scroll = new HorizontalScrollView(getContext());
scroll.setLayoutParams(new ViewGroup.LayoutParams(-1, -2));
scroll.setFillViewport(true);
scroll.setPadding(dp16, dp8, dp16, dp8);
scroll.setPaddingRelative(dp16, dp8, dp16, dp8);
final TextView text = new TextView(getContext());
text.setText(CHANGELOG == null ? CHANGELOG = changelogAsString() : CHANGELOG);
text.setTextSize(TypedValue.COMPLEX_UNIT_SP, 13);
text.setTextColor(ContextCompat.getColor(getContext(), R.color.primaryTextColor));
scroll.removeAllViews();
vScroll.removeAllViews();
scroll.addView(text, new ViewGroup.LayoutParams(-1, -1));
vScroll.addView(scroll);
final MaterialAlertDialogBuilder builder = DialogUtils.newMaterialDialogBuilder (getContext ());
builder.setTitle(R.string.pref_changelog);
builder.setView(vScroll);
builder.setPositiveButton(android.R.string.ok, null);
builder.create().show();
}
private String changelogAsString() {
try {
final SpannableStringBuilder sb = new SpannableStringBuilder();
JSONArray arr = new JSONArray(ResourceUtils.readAssets2String("changelog.json"));
for(int i =0;i<arr.length();i++) {
JSONObject version = arr.getJSONObject(i);
sb.append("v".concat(version.getString("version")), new StyleSpan(Typeface.BOLD), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
JSONArray changes = version.getJSONArray("changes");
for(int j=0;j<changes.length();j++) {
sb.append("\n ");
sb.append("\u2022 ".concat(changes.getString(j)));
}
sb.append("\n\n");
}
return sb.toString();
} catch (Throwable th) {
return getString(R.string.msg_failed_get_changelog);
}
}
private Preference.OnPreferenceClickListener getListener() {
return new Preference.OnPreferenceClickListener(){
@Override
public boolean onPreferenceClick(Preference p1) {
final String key = p1.getKey();
if(key.equals(KEY_CHANGELOG)) {
showChangelog();
} else if(key.equals(KEY_TELEGRAM)) {
StudioApp.getInstance().openTelegramGroup();
} else if(key.equals(KEY_ISSUES)) {
StudioApp.getInstance().openGitHub ();
} else if(key.equals(KEY_ABOUT)) {
final HorizontalScrollView scroll = new HorizontalScrollView(getContext());
scroll.setLayoutParams(new ViewGroup.LayoutParams(-1, -2));
scroll.setFillViewport(true);
scroll.setPadding(dp16, dp8, dp16, dp8);
scroll.setPaddingRelative(dp16, dp8, dp16, dp8);
final TextView text = new TextView(getContext());
text.setText(CHANGELOG == null ? CHANGELOG = changelogAsString() : CHANGELOG);
text.setTextSize(TypedValue.COMPLEX_UNIT_SP, 13);
text.setTextColor(ContextCompat.getColor(getContext(), R.color.primaryTextColor));
scroll.removeAllViews();
vScroll.removeAllViews();
scroll.addView(text, new ViewGroup.LayoutParams(-1, -1));
vScroll.addView(scroll);
final MaterialAlertDialogBuilder builder =
DialogUtils.newMaterialDialogBuilder(getContext());
builder.setTitle(R.string.pref_changelog);
builder.setView(vScroll);
builder.setPositiveButton(android.R.string.ok, null);
builder.create().show();
}
private String changelogAsString() {
try {
final SpannableStringBuilder sb = new SpannableStringBuilder();
JSONArray arr = new JSONArray(ResourceUtils.readAssets2String("changelog.json"));
for (int i = 0; i < arr.length(); i++) {
JSONObject version = arr.getJSONObject(i);
sb.append(
"v".concat(version.getString("version")),
new StyleSpan(Typeface.BOLD),
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
JSONArray changes = version.getJSONArray("changes");
for (int j = 0; j < changes.length(); j++) {
sb.append("\n ");
sb.append("\u2022 ".concat(changes.getString(j)));
}
sb.append("\n\n");
}
return sb.toString();
} catch (Throwable th) {
return getString(R.string.msg_failed_get_changelog);
}
}
private Preference.OnPreferenceClickListener getListener() {
return new Preference.OnPreferenceClickListener() {
@Override
public boolean onPreferenceClick(Preference p1) {
final String key = p1.getKey();
if (key.equals(KEY_CHANGELOG)) {
showChangelog();
} else if (key.equals(KEY_TELEGRAM)) {
StudioApp.getInstance().openTelegramGroup();
} else if (key.equals(KEY_ISSUES)) {
StudioApp.getInstance().openGitHub();
} else if (key.equals(KEY_ABOUT)) {
startActivity(new Intent(getContext(), AboutActivity.class));
}
return true;
}
};
}
return true;
}
};
}
}
@@ -69,7 +69,8 @@ import java.util.Objects;
import java.util.TreeSet;
import java.util.stream.Collectors;
public class AttrEditorSheet extends BottomSheetDialogFragment implements SimpleIconTextAdapter.OnBindListener<IconTextListItem> {
public class AttrEditorSheet extends BottomSheetDialogFragment
implements SimpleIconTextAdapter.OnBindListener<IconTextListItem> {
private static final List<IconTextListItem> VIEW_ACTIONS = new ArrayList<> ();
private static final Logger LOG = Logger.instance ("AttrBottomSheet");
@@ -81,8 +82,13 @@ public class AttrEditorSheet extends BottomSheetDialogFragment implements Simple
@Nullable
@Override
public View onCreateView (@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
this.binding = LayoutAttrEditorSheetBinding.inflate (LayoutInflater.from (getContext ()), container, false);
public View onCreateView (
@NonNull LayoutInflater inflater,
@Nullable ViewGroup container,
@Nullable Bundle savedInstanceState) {
this.binding =
LayoutAttrEditorSheetBinding.inflate (
LayoutInflater.from (getContext ()), container, false);
return binding.getRoot ();
}
@@ -91,11 +97,15 @@ public class AttrEditorSheet extends BottomSheetDialogFragment implements Simple
super.onViewCreated (view, savedInstanceState);
if (VIEW_ACTIONS.isEmpty ()) {
Collections.addAll (VIEW_ACTIONS,
IconTextListItem.create (getString (R.string.msg_viewaction_add_attr), R.drawable.ic_add),
IconTextListItem.create (getString (R.string.title_viewaction_delete), R.drawable.ic_delete),
IconTextListItem.create (getString (R.string.msg_viewaction_select_parent), R.drawable.ic_view_select_parent)
);
Collections.addAll (
VIEW_ACTIONS,
IconTextListItem.create (
getString (R.string.msg_viewaction_add_attr), R.drawable.ic_add),
IconTextListItem.create (
getString (R.string.title_viewaction_delete), R.drawable.ic_delete),
IconTextListItem.create (
getString (R.string.msg_viewaction_select_parent),
R.drawable.ic_view_select_parent));
}
binding.valueEditorLayout.closeButton.setOnClickListener (v -> hideValueEditorLayout ());
@@ -124,7 +134,8 @@ public class AttrEditorSheet extends BottomSheetDialogFragment implements Simple
}
private void setupViewData () {
binding.actionsList.setAdapter (new SimpleIconTextAdapter (VIEW_ACTIONS).setOnBindListener (this));
binding.actionsList.setAdapter (
new SimpleIconTextAdapter (VIEW_ACTIONS).setOnBindListener (this));
if (this.selectedView == null) {
LOG.error ("Cannot edit attributes of a null view.");
@@ -132,19 +143,20 @@ public class AttrEditorSheet extends BottomSheetDialogFragment implements Simple
}
binding.widgetName.setText (this.selectedView.getXmlTag ());
binding.attrList.setAdapter (new XMLAttributeListAdapter (
this.selectedView.getAttributes ()
.stream ()
.map (XMLAttribute::new)
.collect (Collectors.toList ()), this::onAttrClick)
);
binding.attrList.setAdapter (
new XMLAttributeListAdapter (
this.selectedView.getAttributes ().stream ()
.map (XMLAttribute::new)
.collect (Collectors.toList ()),
this::onAttrClick));
}
public void setSelectedView (IView view) {
this.selectedView = view;
}
private void onAttrClick (LayoutAttrEditorSheetItemBinding binding, @NonNull XMLAttribute attribute) {
private void onAttrClick (
LayoutAttrEditorSheetItemBinding binding, @NonNull XMLAttribute attribute) {
if (this.selectedView == null) {
LOG.error ("Cannot edit attributes of a null view.");
@@ -153,7 +165,8 @@ public class AttrEditorSheet extends BottomSheetDialogFragment implements Simple
final var format = attribute.findFormat ();
if (format == -1) {
StudioApp.getInstance ().toast (getString (R.string.msg_no_attr_format), Toaster.Type.ERROR);
StudioApp.getInstance ()
.toast (getString (R.string.msg_no_attr_format), Toaster.Type.ERROR);
LOG.error (getString (R.string.msg_no_attr_format), attribute);
return;
}
@@ -169,9 +182,11 @@ public class AttrEditorSheet extends BottomSheetDialogFragment implements Simple
private void showEditorDialog (@NonNull XMLAttribute attribute) {
final var attr = attribute.getAttr ();
final var values = attr.possibleValues.toArray (new String[0]);
final AttributeDialogs.OnClickListener onDone = (dialog, which, newValue) -> applyNewValue (attribute, newValue);
final AttributeDialogs.OnClickListener onDone =
(dialog, which, newValue) -> applyNewValue (attribute, newValue);
final ColorPickerView.OnPickListener mPickListener = (color, hexCode) -> applyNewValue (attribute, hexCode);
final ColorPickerView.OnPickListener mPickListener =
(color, hexCode) -> applyNewValue (attribute, hexCode);
AlertDialog dialog = null;
if (attribute.hasFormat (DIMENSION)) {
@@ -197,16 +212,21 @@ public class AttrEditorSheet extends BottomSheetDialogFragment implements Simple
}
private void applyNewValue (@NonNull IAttribute attribute, String newValue) {
if (this.selectedView.hasAttribute (attribute.getNamespace (), attribute.getAttributeName ())) {
if (!this.selectedView.updateAttribute (attribute.getNamespace (), attribute.getAttributeName (), newValue)) {
StudioApp.getInstance ().toast (getString (R.string.msg_attr_not_updated), Toaster.Type.ERROR);
if (this.selectedView.hasAttribute (
attribute.getNamespace (), attribute.getAttributeName ())) {
if (!this.selectedView.updateAttribute (
attribute.getNamespace (), attribute.getAttributeName (), newValue)) {
StudioApp.getInstance ()
.toast (getString (R.string.msg_attr_not_updated), Toaster.Type.ERROR);
} else {
// Update the view data
// This will make sure that the attributes list has been updated
setupViewData ();
}
} else {
addAttribute (new UiAttribute (attribute.getNamespace (), attribute.getAttributeName (), newValue));
addAttribute (
new UiAttribute (
attribute.getNamespace (), attribute.getAttributeName (), newValue));
}
}
@@ -217,7 +237,8 @@ public class AttrEditorSheet extends BottomSheetDialogFragment implements Simple
}
@Override
public void postBind (IconTextListItem item, @NonNull SimpleIconTextAdapter.VH holder, int position) {
public void postBind (
IconTextListItem item, @NonNull SimpleIconTextAdapter.VH holder, int position) {
final var binding = holder.binding;
binding.getRoot ().setOnClickListener (v -> onViewActionClick (position));
}
@@ -269,7 +290,8 @@ public class AttrEditorSheet extends BottomSheetDialogFragment implements Simple
attributes.addAll (parentLayoutParams.attributes);
}
final var paramSuperclasses = widgetInfo.getLayoutParamSuperClasses (parentWidget.name);
final var paramSuperclasses =
widgetInfo.getLayoutParamSuperClasses (parentWidget.name);
if (paramSuperclasses != null) {
for (var superclass : paramSuperclasses) {
if ("java.lang.Object".equals (superclass)) {
@@ -279,8 +301,10 @@ public class AttrEditorSheet extends BottomSheetDialogFragment implements Simple
final var split = superclass.split ("\\.");
final var simpleClassName = split[split.length - 2];
var paramName = split[split.length - 1];
paramName = paramName.substring (0, paramName.length () - "Params".length ());
final var superParamEntry = attrs.getStyle (simpleClassName + "_" + paramName);
paramName =
paramName.substring (0, paramName.length () - "Params".length ());
final var superParamEntry =
attrs.getStyle (simpleClassName + "_" + paramName);
if (superParamEntry != null) {
attributes.addAll (superParamEntry.attributes);
}
@@ -294,20 +318,31 @@ public class AttrEditorSheet extends BottomSheetDialogFragment implements Simple
sheet.show (getChildFragmentManager (), "attr_list_sheet");
} else if (position == 1) { // Delete
DialogUtils.newYesNoDialog (getContext (), (dialog, which) -> {
var handled = selectedView.removeFromParent ();
if (!handled) {
handled = mDeletionFailedListener != null && mDeletionFailedListener.onDeletionFailed (this.selectedView);
}
if (!handled) {
StudioApp.getInstance ().toast (getString (R.string.msg_view_deletion_failed), Toaster.Type.ERROR);
} else {
dismiss ();
}
}, (dialog, which) -> dialog.dismiss ()).show ();
DialogUtils.newYesNoDialog (
getContext (),
(dialog, which) -> {
var handled = selectedView.removeFromParent ();
if (!handled) {
handled =
mDeletionFailedListener != null
&& mDeletionFailedListener.onDeletionFailed (
this.selectedView);
}
if (!handled) {
StudioApp.getInstance ()
.toast (
getString (R.string.msg_view_deletion_failed),
Toaster.Type.ERROR);
} else {
dismiss ();
}
},
(dialog, which) -> dialog.dismiss ())
.show ();
} else if (position == 2) { // Select parent
if (this.selectedView.getParent () == null) {
StudioApp.getInstance ().toast (getString (R.string.msg_no_view_parent), Toaster.Type.ERROR);
StudioApp.getInstance ()
.toast (getString (R.string.msg_no_view_parent), Toaster.Type.ERROR);
return;
}
@@ -343,13 +378,14 @@ public class AttrEditorSheet extends BottomSheetDialogFragment implements Simple
}
/**
* A listener can be used to get notified when we fail to remove
* a view from its parent. This is used in {@link com.itsaky.androidide.DesignerActivity DesignerActivity}.
* <p>
* When the user tries to remove the outermost view of the inflated layout,
* AttrEditorSheet fails to remove the view from its parent ({@link IView#getParent()} is null for root XML layout).
* In this case, DesignerActivity check if the view that we were trying to delete is the root layout or not.
* If it is the root layout, then it deletes the layout from the layout container.
* A listener can be used to get notified when we fail to remove a view from its parent. This is
* used in {@link com.itsaky.androidide.DesignerActivity DesignerActivity}.
*
* <p>When the user tries to remove the outermost view of the inflated layout, AttrEditorSheet
* fails to remove the view from its parent ({@link IView#getParent()} is null for root XML
* layout). In this case, DesignerActivity check if the view that we were trying to delete is
* the root layout or not. If it is the root layout, then it deletes the layout from the layout
* container.
*
* @author Akash Yadav
*/
@@ -361,4 +397,4 @@ public class AttrEditorSheet extends BottomSheetDialogFragment implements Simple
*/
boolean onDeletionFailed (IView view);
}
}
}
@@ -41,84 +41,91 @@ import java.util.function.Consumer;
import java.util.stream.Collectors;
/**
* A bottom sheet dialog for showing a simple list of
* text icon pair.
* A bottom sheet dialog for showing a simple list of text icon pair.
*
* @author Akash Yadav
* @see com.itsaky.androidide.models.IconTextListItem
*/
public class AttributeListSheet extends BottomSheetDialogFragment {
private LayoutAddAttrSheetBinding binding;
private Consumer<Attr> clickConsumer;
private List<Attr> mItems;
@Override
public void onCreate (@Nullable Bundle savedInstanceState) {
super.onCreate (savedInstanceState);
setStyle (DialogFragment.STYLE_NORMAL, R.style.AppTheme_SheetAboveKeyboard);
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setStyle(DialogFragment.STYLE_NORMAL, R.style.AppTheme_SheetAboveKeyboard);
}
@Nullable
@Override
public View onCreateView (@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
this.binding = LayoutAddAttrSheetBinding.inflate (inflater, container, false);
return binding.getRoot ();
public View onCreateView(
@NonNull LayoutInflater inflater,
@Nullable ViewGroup container,
@Nullable Bundle savedInstanceState) {
this.binding = LayoutAddAttrSheetBinding.inflate(inflater, container, false);
return binding.getRoot();
}
@Override
public void onViewCreated (@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated (view, savedInstanceState);
binding.searchBar.setOnQueryTextListener (new SearchView.OnQueryTextListener () {
@Override
public boolean onQueryTextSubmit (String query) {
return filterAttributes (query.trim ());
}
@Override
public boolean onQueryTextChange (String newText) {
return filterAttributes (newText.trim ());
}
});
binding.attrList.setAdapter (new AttrListAdapter (clickConsumer, mItems));
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
binding.searchBar.setOnQueryTextListener(
new SearchView.OnQueryTextListener() {
@Override
public boolean onQueryTextSubmit(String query) {
return filterAttributes(query.trim());
}
@Override
public boolean onQueryTextChange(String newText) {
return filterAttributes(newText.trim());
}
});
binding.attrList.setAdapter(new AttrListAdapter(clickConsumer, mItems));
}
private boolean filterAttributes (@NonNull String query) {
if (mItems == null || TextUtils.isEmpty (query)) {
update ();
private boolean filterAttributes(@NonNull String query) {
if (mItems == null || TextUtils.isEmpty(query)) {
update();
return true;
}
final var filtered = this.mItems
.stream ()
.filter (attr -> attr.name.toLowerCase (Locale.ROOT).contains (query.toLowerCase (Locale.ROOT)))
.collect (Collectors.toList ());
update (filtered);
final var filtered =
this.mItems.stream()
.filter(
attr ->
attr.name
.toLowerCase(Locale.ROOT)
.contains(query.toLowerCase(Locale.ROOT)))
.collect(Collectors.toList());
update(filtered);
return true;
}
public void setItems (@Nullable List<Attr> items) {
public void setItems(@Nullable List<Attr> items) {
if (items == null) {
items = new ArrayList<> ();
items = new ArrayList<>();
}
mItems = items;
update ();
update();
}
public void onItemClick (Consumer<Attr> onClick) {
public void onItemClick(Consumer<Attr> onClick) {
this.clickConsumer = onClick;
}
public void update () {
update (mItems);
public void update() {
update(mItems);
}
public void update (List<Attr> items) {
public void update(List<Attr> items) {
if (binding != null) {
binding.attrList.setAdapter (new AttrListAdapter (clickConsumer, items));
binding.attrList.setAdapter(new AttrListAdapter(clickConsumer, items));
}
}
}
@@ -30,84 +30,84 @@ import com.google.android.material.bottomsheet.BottomSheetDialogFragment;
import com.itsaky.androidide.databinding.LayoutSheetBinding;
public abstract class BaseBottomSheetFragment extends BottomSheetDialogFragment {
protected Dialog mDialog;
protected boolean shadowEnabled = true;
protected boolean titleEnabled = true;
private LayoutSheetBinding binding;
@NonNull
@Override
public Dialog onCreateDialog (Bundle savedInstanceState) {
mDialog = super.onCreateDialog (savedInstanceState);
mDialog.setOnShowListener (p1 -> onShow ());
public Dialog onCreateDialog(Bundle savedInstanceState) {
mDialog = super.onCreateDialog(savedInstanceState);
mDialog.setOnShowListener(p1 -> onShow());
return mDialog;
}
@Override
public View onCreateView (@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
binding = LayoutSheetBinding.inflate (inflater, container, false);
return binding.getRoot ();
public View onCreateView(
@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
binding = LayoutSheetBinding.inflate(inflater, container, false);
return binding.getRoot();
}
@Override
public void onViewCreated (@NonNull View view, Bundle savedInstanceState) {
super.onViewCreated (view, savedInstanceState);
bind (binding.container);
binding.title.setText (getTitle ());
binding.title.setOnClickListener (v -> handleTitleClick ());
if (shouldHideTitle ()) {
binding.getRoot ().removeView (binding.title);
public void onViewCreated(@NonNull View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
bind(binding.container);
binding.title.setText(getTitle());
binding.title.setOnClickListener(v -> handleTitleClick());
if (shouldHideTitle()) {
binding.getRoot().removeView(binding.title);
}
if (!shadowEnabled) {
binding.shadow.setVisibility (View.GONE);
binding.shadow.setVisibility(View.GONE);
}
}
private void handleTitleClick () {
if (isCancelable ()) {
dismiss ();
private void handleTitleClick() {
if (isCancelable()) {
dismiss();
}
}
public boolean isShowing () {
return mDialog != null && mDialog.isShowing ();
public boolean isShowing() {
return mDialog != null && mDialog.isShowing();
}
protected boolean shouldHideTitle () {
protected boolean shouldHideTitle() {
return !titleEnabled;
}
protected String getTitle () {
protected String getTitle() {
return "";
}
public BaseBottomSheetFragment setTitle (int title) {
public BaseBottomSheetFragment setTitle(int title) {
if (binding != null) {
binding.title.setText (title);
binding.title.setText(title);
}
return this;
}
public BaseBottomSheetFragment setTitle (String title) {
public BaseBottomSheetFragment setTitle(String title) {
if (binding != null) {
binding.title.setText (title);
binding.title.setText(title);
}
return this;
}
public void setShowShadow (boolean enabled) {
public void setShowShadow(boolean enabled) {
this.shadowEnabled = enabled;
}
public void setShowTitle (boolean enabled) {
public void setShowTitle(boolean enabled) {
this.titleEnabled = enabled;
}
protected void onShow () {
}
protected abstract void bind (LinearLayout container);
protected void onShow() {}
protected abstract void bind(LinearLayout container);
}
@@ -1,23 +1,22 @@
/************************************************************************************
* This file is part of AndroidIDE.
*
*
*
*
* AndroidIDE is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
*
* AndroidIDE is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
*
* You should have received a copy of the GNU General Public License
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
*
**************************************************************************************/
**************************************************************************************/
package com.itsaky.androidide.fragments.sheets;
@@ -26,74 +25,76 @@ import android.view.View;
import android.widget.LinearLayout;
import androidx.annotation.NonNull;
import androidx.fragment.app.FragmentManager;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import com.itsaky.androidide.adapters.OptionsSheetAdapter;
import com.itsaky.androidide.models.SheetOption;
import java.util.ArrayList;
import java.util.List;
public class OptionsListFragment extends BaseBottomSheetFragment {
private RecyclerView mList;
private OnOptionsClickListener listener;
private final List<SheetOption> mOptions = new ArrayList<>();
protected boolean dismissOnItemClick = true;
@Override
protected void bind(LinearLayout container) {
mList = new RecyclerView(getContext());
container.removeAllViews();
container.addView(mList, new LinearLayout.LayoutParams(-1, -1));
}
@Override
public void onViewCreated(@NonNull View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
mList.setLayoutManager(new LinearLayoutManager(getContext()));
mList.setAdapter(new OptionsSheetAdapter(mOptions, __ -> {
if(dismissOnItemClick)
dismiss();
if(listener != null)
listener.onOptionsClick(__);
}));
}
public OptionsListFragment setOnOptionsClickListener(OnOptionsClickListener listener) {
this.listener = listener;
return this;
}
public OptionsListFragment addOption(SheetOption option) {
if(!mOptions.contains(option)) {
mOptions.add(option);
}
return this;
}
public OptionsListFragment removeOption(int optionIndex) {
return removeOption(mOptions.get(optionIndex));
}
public OptionsListFragment removeOption(SheetOption option) {
mOptions.remove(option);
return this;
}
private RecyclerView mList;
private OnOptionsClickListener listener;
private final List<SheetOption> mOptions = new ArrayList<>();
protected boolean dismissOnItemClick = true;
@Override
protected void bind(LinearLayout container) {
mList = new RecyclerView(getContext());
container.removeAllViews();
container.addView(mList, new LinearLayout.LayoutParams(-1, -1));
}
@Override
public void onViewCreated(@NonNull View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
mList.setLayoutManager(new LinearLayoutManager(getContext()));
mList.setAdapter(
new OptionsSheetAdapter(
mOptions,
__ -> {
if (dismissOnItemClick) dismiss();
if (listener != null) listener.onOptionsClick(__);
}));
}
public OptionsListFragment setOnOptionsClickListener(OnOptionsClickListener listener) {
this.listener = listener;
return this;
}
public OptionsListFragment addOption(SheetOption option) {
if (!mOptions.contains(option)) {
mOptions.add(option);
}
return this;
}
public OptionsListFragment removeOption(int optionIndex) {
return removeOption(mOptions.get(optionIndex));
}
public OptionsListFragment removeOption(SheetOption option) {
mOptions.remove(option);
return this;
}
public OptionsListFragment setDismissOnItemClick(boolean dissmiss) {
this.dismissOnItemClick = dissmiss;
return this;
}
@Override
protected String getTitle() {
return getString(com.itsaky.androidide.R.string.file_options);
}
public static interface OnOptionsClickListener {
void onOptionsClick(SheetOption option);
}
@Override
protected String getTitle() {
return getString(com.itsaky.androidide.R.string.file_options);
}
public static interface OnOptionsClickListener {
void onOptionsClick(SheetOption option);
}
}
@@ -1,23 +1,22 @@
/************************************************************************************
* This file is part of AndroidIDE.
*
*
*
*
* AndroidIDE is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
*
* AndroidIDE is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
*
* You should have received a copy of the GNU General Public License
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
*
**************************************************************************************/
**************************************************************************************/
package com.itsaky.androidide.fragments.sheets;
@@ -26,106 +25,106 @@ import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.LinearLayout;
import com.itsaky.androidide.databinding.LayoutProgressSheetBinding;
import com.blankj.utilcode.util.SizeUtils;
import com.itsaky.androidide.utils.Logger;
import android.widget.RelativeLayout;
import androidx.annotation.NonNull;
import com.itsaky.androidide.databinding.LayoutProgressSheetBinding;
import com.itsaky.androidide.utils.Logger;
public class ProgressSheet extends BaseBottomSheetFragment {
private LayoutProgressSheetBinding binding;
private Drawable drawable = null;
private String message = "";
private String subMessage = "";
private boolean subMessageEnabled = false;
private boolean welcomTextEnabled = false;
private final Logger LOG = Logger.instance ("ProgressSheet");
@Override
protected void bind(LinearLayout container) {
binding = LayoutProgressSheetBinding.inflate(LayoutInflater.from(getContext()));
container.addView(binding.getRoot());
}
@Override
public void onViewCreated(@NonNull View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
binding.message.setText(message);
if(subMessageEnabled) {
binding.subMessage.setText(subMessage);
binding.subMessage.setVisibility(View.VISIBLE);
RelativeLayout.LayoutParams p = (RelativeLayout.LayoutParams) binding.message.getLayoutParams();
try {
p.removeRule(RelativeLayout.CENTER_VERTICAL);
} catch (Throwable th) {
LOG.error ("Unable to remove center_vertical rule.", th);
}
binding.message.setLayoutParams(p);
} else {
binding.subMessage.setVisibility(View.GONE);
RelativeLayout.LayoutParams p = (RelativeLayout.LayoutParams) binding.message.getLayoutParams();
try {
p.addRule(RelativeLayout.CENTER_VERTICAL);
} catch (Throwable th) {
LOG.error ("Unable to remove center_vertical rule.", th);
}
binding.message.setLayoutParams(p);
}
if(!welcomTextEnabled) {
binding.welcomeText.setVisibility(View.GONE);
}
}
private LayoutProgressSheetBinding binding;
private Drawable drawable = null;
private String message = "";
private String subMessage = "";
private boolean subMessageEnabled = false;
private boolean welcomTextEnabled = false;
@Override
protected boolean shouldHideTitle() {
return true;
}
public ProgressSheet setWelcomeTextEnabled(boolean enabled) {
this.welcomTextEnabled = enabled;
return this;
}
public ProgressSheet setSubMessageEnabled(boolean enabled) {
this.subMessageEnabled = enabled;
return this;
}
public ProgressSheet setSubMessage(String msg) {
this.subMessage = msg;
if(isShowing()) {
binding.subMessage.setText(msg);
}
return this;
}
public ProgressSheet setMessage(String message) {
this.message = message;
if(isShowing()) {
binding.message.setText(message);
}
return this;
}
public ProgressSheet setProgressDrawable(Drawable drawable) {
this.drawable = drawable;
if(isShowing()) {
binding.progress.setIndeterminateDrawable(drawable);
}
return this;
}
private final Logger LOG = Logger.instance("ProgressSheet");
@Override
public void dismiss() {
if(isShowing())
super.dismiss();
}
@Override
protected void bind(LinearLayout container) {
binding = LayoutProgressSheetBinding.inflate(LayoutInflater.from(getContext()));
container.addView(binding.getRoot());
}
@Override
public void onViewCreated(@NonNull View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
binding.message.setText(message);
if (subMessageEnabled) {
binding.subMessage.setText(subMessage);
binding.subMessage.setVisibility(View.VISIBLE);
RelativeLayout.LayoutParams p =
(RelativeLayout.LayoutParams) binding.message.getLayoutParams();
try {
p.removeRule(RelativeLayout.CENTER_VERTICAL);
} catch (Throwable th) {
LOG.error("Unable to remove center_vertical rule.", th);
}
binding.message.setLayoutParams(p);
} else {
binding.subMessage.setVisibility(View.GONE);
RelativeLayout.LayoutParams p =
(RelativeLayout.LayoutParams) binding.message.getLayoutParams();
try {
p.addRule(RelativeLayout.CENTER_VERTICAL);
} catch (Throwable th) {
LOG.error("Unable to remove center_vertical rule.", th);
}
binding.message.setLayoutParams(p);
}
if (!welcomTextEnabled) {
binding.welcomeText.setVisibility(View.GONE);
}
}
@Override
protected boolean shouldHideTitle() {
return true;
}
public ProgressSheet setWelcomeTextEnabled(boolean enabled) {
this.welcomTextEnabled = enabled;
return this;
}
public ProgressSheet setSubMessageEnabled(boolean enabled) {
this.subMessageEnabled = enabled;
return this;
}
public ProgressSheet setSubMessage(String msg) {
this.subMessage = msg;
if (isShowing()) {
binding.subMessage.setText(msg);
}
return this;
}
public ProgressSheet setMessage(String message) {
this.message = message;
if (isShowing()) {
binding.message.setText(message);
}
return this;
}
public ProgressSheet setProgressDrawable(Drawable drawable) {
this.drawable = drawable;
if (isShowing()) {
binding.progress.setIndeterminateDrawable(drawable);
}
return this;
}
@Override
public void dismiss() {
if (isShowing()) super.dismiss();
}
}
@@ -5,12 +5,12 @@
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
*
* AndroidIDE is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
*
* You should have received a copy of the GNU General Public License
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
*/
@@ -32,7 +32,7 @@ import com.itsaky.androidide.views.editor.IDEEditor;
import io.github.rosemoe.sora.lang.EmptyLanguage;
public class TextSheetFragment extends BaseBottomSheetFragment {
private SpannableStringBuilder outputBuilder;
private IDEEditor editor;
private boolean textSelectable;
@@ -49,7 +49,7 @@ public class TextSheetFragment extends BaseBottomSheetFragment {
container.setPaddingRelative(0, 0, 0, 0);
LinearLayout.LayoutParams p = new LinearLayout.LayoutParams(-1, 0);
p.weight = 1.0f;
if(e.getParent() != null && e.getParent() instanceof ViewGroup) {
if (e.getParent() != null && e.getParent() instanceof ViewGroup) {
((ViewGroup) e.getParent()).removeView(e);
}
container.addView(getEditor(), p);
@@ -65,10 +65,9 @@ public class TextSheetFragment extends BaseBottomSheetFragment {
super.onShow();
editor.setText(getOutputBuilder());
}
private void scrollToBottom() {
if(getActivity() != null)
getActivity().runOnUiThread(() -> editor.goToEnd());
if (getActivity() != null) getActivity().runOnUiThread(() -> editor.goToEnd());
}
public TextSheetFragment setTitleText(int res) {
@@ -86,26 +85,33 @@ public class TextSheetFragment extends BaseBottomSheetFragment {
}
public void append(String text, int spanColor, boolean appendLine) {
if(text != null && text.trim().length() > 0) {
appendInternal(appendLine && !text.endsWith("\n") ? text.concat("\n") : text, spanColor);
if (text != null && text.trim().length() > 0) {
appendInternal(
appendLine && !text.endsWith("\n") ? text.concat("\n") : text, spanColor);
}
}
private void appendInternal(String text, int spanColor) {
SpannableString str = new SpannableString(text);
str.setSpan(new ForegroundColorSpan(spanColor == -1 ? Color.WHITE : spanColor), 0, text.length(), SpannableString.SPAN_EXCLUSIVE_EXCLUSIVE);
str.setSpan(
new ForegroundColorSpan(spanColor == -1 ? Color.WHITE : spanColor),
0,
text.length(),
SpannableString.SPAN_EXCLUSIVE_EXCLUSIVE);
getOutputBuilder().append(str);
if(mDialog != null && mDialog.isShowing()) {
if (mDialog != null && mDialog.isShowing()) {
setToEditor(str);
}
}
private void setToEditor(SpannableString line) {
if(getActivity() != null)
getActivity().runOnUiThread(() -> {
editor.append(line);
scrollToBottom();
});
if (getActivity() != null)
getActivity()
.runOnUiThread(
() -> {
editor.append(line);
scrollToBottom();
});
}
private IDEEditor getEditor() {
@@ -113,10 +119,10 @@ public class TextSheetFragment extends BaseBottomSheetFragment {
}
private IDEEditor createEditor() {
editor = new IDEEditor (getContext());
editor = new IDEEditor(getContext());
editor.setEditable(false);
editor.setDividerWidth(0);
editor.setEditorLanguage(new EmptyLanguage ());
editor.setEditorLanguage(new EmptyLanguage());
editor.setWordwrap(false);
editor.setUndoEnabled(false);
editor.setTypefaceLineNumber(TypefaceUtils.jetbrainsMono());
@@ -1,23 +1,24 @@
/************************************************************************************
* This file is part of AndroidIDE.
*
*
* AndroidIDE is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
*
* AndroidIDE is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
*
* You should have received a copy of the GNU General Public License
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
*
**************************************************************************************/
**************************************************************************************/
package com.itsaky.androidide.handlers;
import android.view.View;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import com.itsaky.androidide.R;
import com.itsaky.androidide.managers.PreferenceManager;
@@ -30,6 +31,7 @@ import com.itsaky.androidide.tasks.GradleTask;
import com.itsaky.androidide.tasks.gradle.build.ApkGeneratingTask;
import com.itsaky.androidide.utils.DialogUtils;
import com.itsaky.androidide.utils.Environment;
import java.io.File;
import java.util.ArrayList;
import java.util.Collection;
@@ -39,33 +41,33 @@ import java.util.Set;
import java.util.stream.Collectors;
public class BuildServiceHandler extends IDEHandler implements BuildListener {
private IDEService service;
private boolean shouldInstallApk = false;
public BuildServiceHandler(Provider provider) {
super(provider);
}
public IDEService getService() {
return service;
}
@Override
public void start() {
if(this.activity() == null) {
if (this.activity() == null) {
throwNPE();
}
AndroidProject project = provider.provideAndroidProject();
if(project == null) {
if (project == null) {
throwNPE();
}
if(project.getProjectPath() == null) {
if (project.getProjectPath() == null) {
throwNPE();
}
this.service = new IDEService(new File(project.getProjectPath()));
this.service.setListener(this);
}
@@ -75,159 +77,182 @@ public class BuildServiceHandler extends IDEHandler implements BuildListener {
this.service.stopAllDaemons();
this.service.exit();
}
@Override
public void onBuildModified() {
// Does nothing currently
}
@Override
public void onProjectLoaded(IDEProject project, Optional<IDEModule> appModule) {
project.iconPath = provider.provideAndroidProject().getIconPath();
activity().setIDEProject(project);
if(appModule.isPresent()) {
if (appModule.isPresent()) {
IDEModule app = appModule.get();
activity().setStatus(activity().getString(R.string.msg_starting_completion));
final List<String> paths = app.dependencies.stream().filter(this::isClasspathValid).collect(Collectors.toList());
final List<String> paths =
app.dependencies.stream()
.filter(this::isClasspathValid)
.collect(Collectors.toList());
provider.provideAndroidProject().setClassPaths(paths);
File androidJar = new File(Environment.ANDROID_HOME, String.format("platforms/%s/android.jar", app.compileSdkVersion));
if(androidJar.exists()) {
File androidJar =
new File(
Environment.ANDROID_HOME,
String.format("platforms/%s/android.jar", app.compileSdkVersion));
if (androidJar.exists()) {
Environment.setBootClasspath(androidJar);
provider.provideAndroidProject().addClasspath(androidJar.getAbsolutePath());
activity().updateServices ();
activity().updateServices();
} else activity().setStatus("android.jar not found!");
} else activity().setStatus("Cannot get :app module...");
}
@Override
public void onStartingGradleDaemon(GradleTask task) {
activity().setStatus(activity().getString(R.string.msg_starting_daemon));
}
@Override
public void onRunTask(GradleTask task, String name) {
if(task != null && task.canOutput()) {
if (task != null && task.canOutput()) {
activity().setStatus(service.typeString(task.getTaskID()) + " " + name);
}
}
@Override
public void onBuildSuccessful(GradleTask task, String msg) {
appendOutput(task, msg);
if(task == null || activity() == null) return;
if(task.canOutput()) {
if (task == null || activity() == null) return;
if (task.canOutput()) {
activity().setStatus(msg);
}
// If this task generates APK, ask user if he/she wants to install them
if (shouldInstallApk && task instanceof ApkGeneratingTask) {
final IDEProject project = activity().provideIDEProject();
final Optional<IDEModule> app = project.getModuleByPath(":app"); // TODO Handle multiple application modules
if(app.isPresent()) {
final Optional<IDEModule> app =
project.getModuleByPath(":app"); // TODO Handle multiple application modules
if (app.isPresent()) {
final String path = app.get().projectDir;
if(path != null) {
final File buildDir = new File (path, "build");
installApks (((ApkGeneratingTask) task).getApks(buildDir));
if (path != null) {
final File buildDir = new File(path, "build");
installApks(((ApkGeneratingTask) task).getApks(buildDir));
}
}
}
activity().getApp().getPrefManager().putBoolean(PreferenceManager.KEY_IS_FIRST_PROJECT_BUILD, false);
activity()
.getApp()
.getPrefManager()
.putBoolean(PreferenceManager.KEY_IS_FIRST_PROJECT_BUILD, false);
activity().invalidateOptionsMenu();
activity().getBinding().buildProgressIndicator.setVisibility(View.GONE);
}
@Override
public void onBuildFailed(GradleTask task, String msg) {
appendOutput(task, msg);
if(task == null) return;
if(task.canOutput()) {
if (task == null) return;
if (task.canOutput()) {
activity().setStatus(msg);
}
activity().getApp().getPrefManager().putBoolean(PreferenceManager.KEY_IS_FIRST_PROJECT_BUILD, false);
activity()
.getApp()
.getPrefManager()
.putBoolean(PreferenceManager.KEY_IS_FIRST_PROJECT_BUILD, false);
activity().invalidateOptionsMenu();
activity().getBinding().buildProgressIndicator.setVisibility(View.GONE);
}
@Override
public void saveFiles() {
activity().saveAll(false, false);
}
@Override
public void appendOutput(GradleTask task, CharSequence text) {
if(text == null)
return;
if(task != null && !task.canOutput()) {
if (text == null) return;
if (task != null && !task.canOutput()) {
return;
}
activity().appendBuildOut(text.toString());
}
@Override
public void prepareBuild() {
boolean isFirstBuild = activity().getApp().getPrefManager().getBoolean(PreferenceManager.KEY_IS_FIRST_PROJECT_BUILD, true);
activity().setStatus(activity().getString(isFirstBuild ? R.string.preparing_first : R.string.preparing));
if(isFirstBuild) {
boolean isFirstBuild =
activity()
.getApp()
.getPrefManager()
.getBoolean(PreferenceManager.KEY_IS_FIRST_PROJECT_BUILD, true);
activity()
.setStatus(
activity()
.getString(
isFirstBuild
? R.string.preparing_first
: R.string.preparing));
if (isFirstBuild) {
activity().showFirstBuildNotice();
}
activity().getBinding().buildProgressIndicator.setVisibility(View.VISIBLE);
}
public void setShouldInstallApk (boolean install) {
public void setShouldInstallApk(boolean install) {
this.shouldInstallApk = install;
}
public void assembleDebug (boolean shouldInstallApk) {
public void assembleDebug(boolean shouldInstallApk) {
setShouldInstallApk(shouldInstallApk);
getService().assembleDebug(shouldInstallApk);
}
private boolean isClasspathValid(String path) {
if(path == null || path.trim().length() <= 0 || path.trim().equals("/")) return false;
if (path == null || path.trim().length() <= 0 || path.trim().equals("/")) return false;
File file = new File(path);
return file.isFile()
&& file.getName().endsWith(".jar")
/**
* Release R.jar shouldn't be included in classpath
*/
&& !file.getAbsolutePath().endsWith("/release/R.jar");
&& file.getName().endsWith(".jar")
/** Release R.jar shouldn't be included in classpath */
&& !file.getAbsolutePath().endsWith("/release/R.jar");
}
private void installApks (final Set<File> apks) {
if (activity () == null || apks == null || apks.isEmpty()) {
private void installApks(final Set<File> apks) {
if (activity() == null || apks == null || apks.isEmpty()) {
return;
}
if(apks.size() == 1) {
if (apks.size() == 1) {
activity().install(apks.iterator().next());
} else {
final List<File> files = new ArrayList<File> (apks);
final MaterialAlertDialogBuilder builder = DialogUtils.newMaterialDialogBuilder (activity ());
final List<File> files = new ArrayList<File>(apks);
final MaterialAlertDialogBuilder builder =
DialogUtils.newMaterialDialogBuilder(activity());
builder.setTitle(activity().getString(R.string.title_install_apks));
builder.setItems(getNames(files), (d, w) -> {
d.dismiss();
activity().install(files.get(w));
});
builder.setItems(
getNames(files),
(d, w) -> {
d.dismiss();
activity().install(files.get(w));
});
builder.show();
}
}
private CharSequence[] getNames (final Collection<File> apks) {
private CharSequence[] getNames(final Collection<File> apks) {
final String[] names = new String[apks.size()];
int i = 0;
for (File apk : apks) {
names[i] = apk.getName();
++i;
}
return names;
}
}
@@ -1,20 +1,20 @@
/************************************************************************************
* This file is part of AndroidIDE.
*
*
* AndroidIDE is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
*
* AndroidIDE is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
*
* You should have received a copy of the GNU General Public License
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
*
**************************************************************************************/
**************************************************************************************/
package com.itsaky.androidide.handlers;
import android.view.LayoutInflater;
@@ -39,76 +39,101 @@ import java.io.File;
import java.util.Objects;
import java.util.regex.Pattern;
public class FileOptionsHandler extends IDEHandler implements OptionsListFragment.OnOptionsClickListener {
public class FileOptionsHandler extends IDEHandler
implements OptionsListFragment.OnOptionsClickListener {
public static final String RES_PATH_REGEX = "/.*/src/.*/res";
public static final String LAYOUT_RES_PATH_REGEX = "/.*/src/.*/res/layout";
public static final String MENU_RES_PATH_REGEX = "/.*/src/.*/res/menu";
public static final String DRAWABLE_RES_PATH_REGEX = "/.*/src/.*/res/drawable";
public static final String JAVA_PATH_REGEX = "/.*/src/.*/java";
public FileOptionsHandler(Provider provider) {
super(provider);
}
@Override
public void start() {
if(activity() == null) {
if (activity() == null) {
throwNPE();
}
}
@Override
public void stop() {
}
public void stop() {}
@Override
public void onOptionsClick(SheetOption option) {
if(option.extra instanceof File) {
if (option.extra instanceof File) {
final File f = (File) option.extra;
switch(option.id) {
case 0 :
switch (option.id) {
case 0:
ClipboardUtils.copyText("[AndroidIDE] Copied File Path", f.getAbsolutePath());
activity().getApp().toast(R.string.copied, Toaster.Type.SUCCESS);
break;
case 1 :
case 1:
renameFile(f);
break;
case 2 :
case 2:
delete(f);
break;
case 3 :
case 3:
createNewFile(f);
break;
case 4 :
case 4:
createNewFolder(f);
break;
}
}
}
}
private void createNewFile(File f) {
createNewFile(f, false);
}
private void createNewFile(final File f, boolean forceUnknownType) {
if(forceUnknownType) {
if (forceUnknownType) {
createNewEmptyFile(f);
} else {
final boolean isJava = Pattern.compile(Pattern.quote(androidProject().getProjectPath()) + JAVA_PATH_REGEX).matcher(f.getAbsolutePath()).find();
final boolean isRes = Pattern.compile(Pattern.quote(androidProject().getProjectPath()) + RES_PATH_REGEX).matcher(f.getAbsolutePath()).find();
final boolean isLayoutRes = Pattern.compile(Pattern.quote(androidProject().getProjectPath()) + LAYOUT_RES_PATH_REGEX).matcher(f.getAbsolutePath()).find();
final boolean isMenuRes = Pattern.compile(Pattern.quote(androidProject().getProjectPath()) + MENU_RES_PATH_REGEX).matcher(f.getAbsolutePath()).find();
final boolean isDrawableRes = Pattern.compile(Pattern.quote(androidProject().getProjectPath()) + DRAWABLE_RES_PATH_REGEX).matcher(f.getAbsolutePath()).find();
if(isJava) {
final boolean isJava =
Pattern.compile(
Pattern.quote(androidProject().getProjectPath())
+ JAVA_PATH_REGEX)
.matcher(f.getAbsolutePath())
.find();
final boolean isRes =
Pattern.compile(
Pattern.quote(androidProject().getProjectPath())
+ RES_PATH_REGEX)
.matcher(f.getAbsolutePath())
.find();
final boolean isLayoutRes =
Pattern.compile(
Pattern.quote(androidProject().getProjectPath())
+ LAYOUT_RES_PATH_REGEX)
.matcher(f.getAbsolutePath())
.find();
final boolean isMenuRes =
Pattern.compile(
Pattern.quote(androidProject().getProjectPath())
+ MENU_RES_PATH_REGEX)
.matcher(f.getAbsolutePath())
.find();
final boolean isDrawableRes =
Pattern.compile(
Pattern.quote(androidProject().getProjectPath())
+ DRAWABLE_RES_PATH_REGEX)
.matcher(f.getAbsolutePath())
.find();
if (isJava) {
createJavaClass(f);
} else if(isLayoutRes && f.getName().equals("layout")) {
} else if (isLayoutRes && f.getName().equals("layout")) {
createLayoutRes(f);
} else if(isMenuRes && f.getName().equals("menu")) {
} else if (isMenuRes && f.getName().equals("menu")) {
createMenuRes(f);
} else if(isDrawableRes && f.getName().equals("drawable")) {
} else if (isDrawableRes && f.getName().equals("drawable")) {
createDrawableRes(f);
} else if(isRes && f.getName().equals("res")) {
} else if (isRes && f.getName().equals("res")) {
createNewResource(f);
} else {
createNewEmptyFile(f);
@@ -117,71 +142,93 @@ public class FileOptionsHandler extends IDEHandler implements OptionsListFragmen
}
private void createJavaClass(final File f) {
final MaterialAlertDialogBuilder builder = DialogUtils.newMaterialDialogBuilder (activity ());
final LayoutCreateFileJavaBinding binding = LayoutCreateFileJavaBinding.inflate(activity().getLayoutInflater());
final MaterialAlertDialogBuilder builder = DialogUtils.newMaterialDialogBuilder(activity());
final LayoutCreateFileJavaBinding binding =
LayoutCreateFileJavaBinding.inflate(activity().getLayoutInflater());
builder.setView(binding.getRoot());
builder.setTitle(R.string.new_java_class);
builder.setPositiveButton(R.string.text_create, (p1, p2) -> {
p1.dismiss();
final String name = Objects.requireNonNull (binding.name.getEditText ()).getText().toString().trim();
final String pkgName = ProjectWriter.getPackageName(f);
if(pkgName == null || pkgName.trim().length() <= 0) {
activity().getApp().toast(R.string.msg_get_package_failed, Toaster.Type.ERROR);
} else {
final int id = binding.typeGroup.getCheckedButtonId();
final var javaName = name.endsWith (".java") ? name : name.concat (".java");
final var className = !name.contains (".") ? name : name.substring (0, name.lastIndexOf ("."));
if(id == binding.typeClass.getId()) {
createFile(f, javaName, ProjectWriter.createJavaClass(pkgName, className));
} else if(id == binding.typeInterface.getId()) {
createFile(f, javaName, ProjectWriter.createJavaInterface(pkgName, className));
} else if(id == binding.typeEnum.getId()) {
createFile(f, javaName, ProjectWriter.createJavaEnum(pkgName, className));
} else if(id == binding.typeActivity.getId()) {
createFile(f, javaName, ProjectWriter.createActivity(pkgName, className));
} else {
createFile(f, name, "");
}
}
});
builder.setPositiveButton(
R.string.text_create,
(p1, p2) -> {
p1.dismiss();
final String name =
Objects.requireNonNull(binding.name.getEditText())
.getText()
.toString()
.trim();
final String pkgName = ProjectWriter.getPackageName(f);
if (pkgName == null || pkgName.trim().length() <= 0) {
activity()
.getApp()
.toast(R.string.msg_get_package_failed, Toaster.Type.ERROR);
} else {
final int id = binding.typeGroup.getCheckedButtonId();
final var javaName = name.endsWith(".java") ? name : name.concat(".java");
final var className =
!name.contains(".")
? name
: name.substring(0, name.lastIndexOf("."));
if (id == binding.typeClass.getId()) {
createFile(
f, javaName, ProjectWriter.createJavaClass(pkgName, className));
} else if (id == binding.typeInterface.getId()) {
createFile(
f,
javaName,
ProjectWriter.createJavaInterface(pkgName, className));
} else if (id == binding.typeEnum.getId()) {
createFile(
f, javaName, ProjectWriter.createJavaEnum(pkgName, className));
} else if (id == binding.typeActivity.getId()) {
createFile(
f, javaName, ProjectWriter.createActivity(pkgName, className));
} else {
createFile(f, name, "");
}
}
});
builder.setNegativeButton(android.R.string.cancel, null);
builder.setCancelable(false);
builder.create().show();
}
private void createLayoutRes(File f) {
createNewFileWithContent(Environment.mkdirIfNotExits(f), ProjectWriter.createLayout(), ".xml");
createNewFileWithContent(
Environment.mkdirIfNotExits(f), ProjectWriter.createLayout(), ".xml");
}
private void createMenuRes(File f) {
createNewFileWithContent(Environment.mkdirIfNotExits(f), ProjectWriter.createMenu(), ".xml");
createNewFileWithContent(
Environment.mkdirIfNotExits(f), ProjectWriter.createMenu(), ".xml");
}
private void createDrawableRes(File f) {
createNewFileWithContent(Environment.mkdirIfNotExits(f), ProjectWriter.createDrawable(), ".xml");
createNewFileWithContent(
Environment.mkdirIfNotExits(f), ProjectWriter.createDrawable(), ".xml");
}
private void createNewResource(File f) {
final String[] labels = {
activity(). getString(R.string.restype_drawable),
activity(). getString(R.string.restype_layout),
activity(). getString(R.string.restype_menu),
activity(). getString(R.string.restype_other)
activity().getString(R.string.restype_drawable),
activity().getString(R.string.restype_layout),
activity().getString(R.string.restype_menu),
activity().getString(R.string.restype_other)
};
final MaterialAlertDialogBuilder builder = DialogUtils.newMaterialDialogBuilder (activity ());
final MaterialAlertDialogBuilder builder = DialogUtils.newMaterialDialogBuilder(activity());
builder.setTitle(R.string.new_xml_resource);
builder.setItems(labels, (dialogInterface, position) -> {
if(position == 0) {
createDrawableRes(new File(f, "drawable"));
} else if(position == 1) {
createLayoutRes(new File(f, "layout"));
} else if(position == 2) {
createMenuRes(new File(f, "menu"));
} else if(position == 3) {
createNewFile(f, true);
}
});
builder.setItems(
labels,
(dialogInterface, position) -> {
if (position == 0) {
createDrawableRes(new File(f, "drawable"));
} else if (position == 1) {
createLayoutRes(new File(f, "layout"));
} else if (position == 2) {
createMenuRes(new File(f, "menu"));
} else if (position == 3) {
createNewFile(f, true);
}
});
builder.create().show();
}
@@ -194,153 +241,204 @@ public class FileOptionsHandler extends IDEHandler implements OptionsListFragmen
}
private void createNewFileWithContent(File folder, String content, String extension) {
final LayoutDialogTextInputBinding binding = LayoutDialogTextInputBinding.inflate(activity(). getLayoutInflater());
final MaterialAlertDialogBuilder builder = DialogUtils.newMaterialDialogBuilder (activity ());
final LayoutDialogTextInputBinding binding =
LayoutDialogTextInputBinding.inflate(activity().getLayoutInflater());
final MaterialAlertDialogBuilder builder = DialogUtils.newMaterialDialogBuilder(activity());
Objects.requireNonNull (binding.name.getEditText ()).setHint(R.string.file_name);
Objects.requireNonNull(binding.name.getEditText()).setHint(R.string.file_name);
builder.setTitle(R.string.new_file);
builder.setMessage(activity(). getString(R.string.msg_can_contain_slashes).concat("\n\n").concat(activity(). getString(R.string.msg_newfile_dest, folder.getAbsolutePath())));
builder.setMessage(
activity()
.getString(R.string.msg_can_contain_slashes)
.concat("\n\n")
.concat(
activity()
.getString(
R.string.msg_newfile_dest,
folder.getAbsolutePath())));
builder.setView(binding.getRoot());
builder.setCancelable(false);
builder.setPositiveButton(R.string.text_create, (p1, p2) -> {
p1.dismiss();
String name = binding.name.getEditText().getText().toString().trim();
if(extension != null && extension.trim().length() > 0) {
name = name.endsWith(extension) ? name : name.concat(extension);
}
createFile(folder, name, content);
});
builder.setPositiveButton(
R.string.text_create,
(p1, p2) -> {
p1.dismiss();
String name = binding.name.getEditText().getText().toString().trim();
if (extension != null && extension.trim().length() > 0) {
name = name.endsWith(extension) ? name : name.concat(extension);
}
createFile(folder, name, content);
});
builder.setNegativeButton(android.R.string.cancel, null);
builder.create().show();
}
private void createFile(File f, String name, String content) {
if(name.length() > 0 && name.length() <= 40 && !name.startsWith("/")) {
if (name.length() > 0 && name.length() <= 40 && !name.startsWith("/")) {
final File file = new File(f, name);
if(file.exists()) {
if (file.exists()) {
activity().getApp().toast(R.string.msg_file_exists, Toaster.Type.ERROR);
} else {
if(FileIOUtils.writeFileFromString(file, content)) {
// TODO Notify language servers about file created event
activity(). getApp().toast(R.string.msg_file_created, Toaster.Type.SUCCESS);
if(activity().getLastHoldTreeNode() != null) {
if (FileIOUtils.writeFileFromString(file, content)) {
// TODO Notify language servers about file created event
activity().getApp().toast(R.string.msg_file_created, Toaster.Type.SUCCESS);
if (activity().getLastHoldTreeNode() != null) {
TreeNode node = new TreeNode(file);
node.setViewHolder(new FileTreeViewHolder(activity()));
activity().getLastHoldTreeNode().addChild(node);
activity().getFileTreeFragment().expandNode(activity().getLastHoldTreeNode());
activity()
.getFileTreeFragment()
.expandNode(activity().getLastHoldTreeNode());
} else {
activity().getFileTreeFragment().listProjectFiles();
}
} else {
activity(). getApp().toast(R.string.msg_file_creation_failed, Toaster.Type.ERROR);
activity()
.getApp()
.toast(R.string.msg_file_creation_failed, Toaster.Type.ERROR);
}
}
} else {
activity(). getApp().toast(R.string.msg_invalid_name, Toaster.Type.ERROR);
activity().getApp().toast(R.string.msg_invalid_name, Toaster.Type.ERROR);
}
}
private void createNewFolder(File f) {
final LayoutDialogTextInputBinding binding = LayoutDialogTextInputBinding.inflate(activity(). getLayoutInflater());
final MaterialAlertDialogBuilder builder = DialogUtils.newMaterialDialogBuilder (activity ());
final LayoutDialogTextInputBinding binding =
LayoutDialogTextInputBinding.inflate(activity().getLayoutInflater());
final MaterialAlertDialogBuilder builder = DialogUtils.newMaterialDialogBuilder(activity());
Objects.requireNonNull (binding.name.getEditText ()).setHint(R.string.folder_name);
Objects.requireNonNull(binding.name.getEditText()).setHint(R.string.folder_name);
builder.setTitle(R.string.new_folder);
builder.setMessage(R.string.msg_can_contain_slashes);
builder.setView(binding.getRoot());
builder.setCancelable(false);
builder.setPositiveButton(R.string.text_create, (p1, p2) -> {
p1.dismiss();
final String name = binding.name.getEditText().getText().toString().trim();
if(name.length() > 0 && name.length() <= 40 && !name.startsWith("/")) {
final File file = new File(f, name);
if(file.exists()) {
activity(). getApp().toast(R.string.msg_folder_exists, Toaster.Type.ERROR);
} else {
if(file.mkdirs()) {
activity(). getApp().toast(R.string.msg_folder_created, Toaster.Type.SUCCESS);
if(activity().getLastHoldTreeNode() != null) {
TreeNode node = new TreeNode(file);
node.setViewHolder(new FileTreeViewHolder(activity()));
activity().getLastHoldTreeNode().addChild(node);
activity().getFileTreeFragment().expandNode(activity().getLastHoldTreeNode());
builder.setPositiveButton(
R.string.text_create,
(p1, p2) -> {
p1.dismiss();
final String name = binding.name.getEditText().getText().toString().trim();
if (name.length() > 0 && name.length() <= 40 && !name.startsWith("/")) {
final File file = new File(f, name);
if (file.exists()) {
activity()
.getApp()
.toast(R.string.msg_folder_exists, Toaster.Type.ERROR);
} else {
activity().getFileTreeFragment().listProjectFiles();
if (file.mkdirs()) {
activity()
.getApp()
.toast(R.string.msg_folder_created, Toaster.Type.SUCCESS);
if (activity().getLastHoldTreeNode() != null) {
TreeNode node = new TreeNode(file);
node.setViewHolder(new FileTreeViewHolder(activity()));
activity().getLastHoldTreeNode().addChild(node);
activity()
.getFileTreeFragment()
.expandNode(activity().getLastHoldTreeNode());
} else {
activity().getFileTreeFragment().listProjectFiles();
}
} else {
activity()
.getApp()
.toast(
R.string.msg_folder_creation_failed,
Toaster.Type.ERROR);
}
}
} else {
activity().getApp().toast(R.string.msg_folder_creation_failed, Toaster.Type.ERROR);
activity().getApp().toast(R.string.msg_invalid_name, Toaster.Type.ERROR);
}
}
} else {
activity(). getApp().toast(R.string.msg_invalid_name, Toaster.Type.ERROR);
}
});
});
builder.setNegativeButton(android.R.string.cancel, null);
builder.create().show();
}
private void delete(final File f) {
MaterialAlertDialogBuilder builder = DialogUtils.newMaterialDialogBuilder (activity ());
builder
.setNegativeButton(R.string.no, null)
.setPositiveButton(R.string.yes, (p1, p2) -> {
p1.dismiss();
final boolean deleted = FileUtils.delete(f);
activity(). getApp().toast(deleted ? R.string.deleted : R.string.delete_failed, deleted ? Toaster.Type.SUCCESS : Toaster.Type.ERROR);
if(deleted) {
// TODO Notify language servers about file delete event
if(activity().getLastHoldTreeNode() != null) {
TreeNode parent = activity().getLastHoldTreeNode().getParent();
parent.deleteChild(activity().getLastHoldTreeNode());
activity().getFileTreeFragment().expandNode(parent);
} else {
activity().getFileTreeFragment().listProjectFiles();
}
final var frag = activity().getEditorForFile (f);
if(frag != null) {
activity().closeFile(activity().findIndexOfEditorByFile (frag.getFile ()));
}
}
})
.setTitle(R.string.title_confirm_delete)
.setMessage(activity().getString(R.string.msg_confirm_delete, String.format("%s [%s]", f.getName(), f.getAbsolutePath())))
.setCancelable(false)
.create().show();
MaterialAlertDialogBuilder builder = DialogUtils.newMaterialDialogBuilder(activity());
builder.setNegativeButton(R.string.no, null)
.setPositiveButton(
R.string.yes,
(p1, p2) -> {
p1.dismiss();
final boolean deleted = FileUtils.delete(f);
activity()
.getApp()
.toast(
deleted ? R.string.deleted : R.string.delete_failed,
deleted ? Toaster.Type.SUCCESS : Toaster.Type.ERROR);
if (deleted) {
// TODO Notify language servers about file delete event
if (activity().getLastHoldTreeNode() != null) {
TreeNode parent = activity().getLastHoldTreeNode().getParent();
parent.deleteChild(activity().getLastHoldTreeNode());
activity().getFileTreeFragment().expandNode(parent);
} else {
activity().getFileTreeFragment().listProjectFiles();
}
final var frag = activity().getEditorForFile(f);
if (frag != null) {
activity()
.closeFile(
activity()
.findIndexOfEditorByFile(
frag.getFile()));
}
}
})
.setTitle(R.string.title_confirm_delete)
.setMessage(
activity()
.getString(
R.string.msg_confirm_delete,
String.format("%s [%s]", f.getName(), f.getAbsolutePath())))
.setCancelable(false)
.create()
.show();
}
private void renameFile(File f) {
final LayoutDialogTextInputBinding binding = LayoutDialogTextInputBinding.inflate(LayoutInflater.from(activity()));
MaterialAlertDialogBuilder builder = DialogUtils.newMaterialDialogBuilder (activity ());
final LayoutDialogTextInputBinding binding =
LayoutDialogTextInputBinding.inflate(LayoutInflater.from(activity()));
MaterialAlertDialogBuilder builder = DialogUtils.newMaterialDialogBuilder(activity());
Objects.requireNonNull (binding.name.getEditText ()).setHint(activity(). getString(R.string.new_name));
Objects.requireNonNull(binding.name.getEditText())
.setHint(activity().getString(R.string.new_name));
binding.name.getEditText().setText(f.getName());
builder.setTitle(R.string.rename_file);
builder.setMessage(R.string.msg_rename_file);
builder.setView(binding.getRoot());
builder.setNegativeButton(android.R.string.cancel, null);
builder.setPositiveButton(R.string.rename_file, (p1, p2) -> {
p1.dismiss();
String name = binding.name.getEditText().getText().toString().trim();
boolean renamed = name.length () > 0 && name.length () <= 40 && FileUtils.rename (f, name);
activity(). getApp().toast(renamed ? R.string.renamed : R.string.rename_failed, renamed ? Toaster.Type.SUCCESS : Toaster.Type.ERROR);
if(renamed) {
// TODO Notify language servers about file rename event
if(activity().getLastHoldTreeNode() != null) {
TreeNode parent = activity().getLastHoldTreeNode().getParent();
parent.deleteChild(activity().getLastHoldTreeNode());
TreeNode node = new TreeNode(new File(f.getParentFile(), name));
node.setViewHolder(new FileTreeViewHolder(activity()));
parent.addChild(node);
activity().getFileTreeFragment().expandNode(parent);
} else {
activity().getFileTreeFragment().listProjectFiles();
}
}
});
builder.setPositiveButton(
R.string.rename_file,
(p1, p2) -> {
p1.dismiss();
String name = binding.name.getEditText().getText().toString().trim();
boolean renamed =
name.length() > 0 && name.length() <= 40 && FileUtils.rename(f, name);
activity()
.getApp()
.toast(
renamed ? R.string.renamed : R.string.rename_failed,
renamed ? Toaster.Type.SUCCESS : Toaster.Type.ERROR);
if (renamed) {
// TODO Notify language servers about file rename event
if (activity().getLastHoldTreeNode() != null) {
TreeNode parent = activity().getLastHoldTreeNode().getParent();
parent.deleteChild(activity().getLastHoldTreeNode());
TreeNode node = new TreeNode(new File(f.getParentFile(), name));
node.setViewHolder(new FileTreeViewHolder(activity()));
parent.addChild(node);
activity().getFileTreeFragment().expandNode(parent);
} else {
activity().getFileTreeFragment().listProjectFiles();
}
}
});
builder.create().show();
}
}
}
@@ -5,16 +5,16 @@
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
*
* AndroidIDE is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
*
* You should have received a copy of the GNU General Public License
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
*
**************************************************************************************/
**************************************************************************************/
package com.itsaky.androidide.handlers;
import com.itsaky.androidide.EditorActivity;
@@ -23,57 +23,56 @@ import com.itsaky.androidide.project.IDEProject;
import com.itsaky.androidide.utils.Logger;
/**
* A handler is an implementation that handles a specific feature, data or anything else in AndroidIDE
* A handler is an implementation that handles a specific feature, data or anything else in
* AndroidIDE
*/
public abstract class IDEHandler {
protected static final Logger LOG = Logger.instance();
protected Provider provider;
public IDEHandler(Provider provider) {
this.provider = provider;
}
protected void throwNPE () {
protected void throwNPE() {
throw new NullPointerException();
}
protected EditorActivity activity() {
return provider.provideEditorActivity();
}
protected AndroidProject androidProject() {
return provider.provideAndroidProject();
}
protected IDEProject ideProject() {
return provider.provideIDEProject();
}
public abstract void start();
public abstract void stop ();
/**
* An interface to communicate between a handler and its client
*/
public abstract void stop();
/** An interface to communicate between a handler and its client */
public interface Provider {
/**
* Called by handler to get a reference to {@link EditorActivity}
*
* @throws NullPointerException is this is required
*/
EditorActivity provideEditorActivity();
/**
* Called by handler to get a reference of the current {@link AndroidProject}
*
* @throws NullPointerException is this is required
*/
AndroidProject provideAndroidProject();
/**
* Called by handler to get a reference of the current {@link IDEProject}
*
@@ -1,20 +1,20 @@
/************************************************************************************
* This file is part of AndroidIDE.
*
*
* AndroidIDE is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
*
* AndroidIDE is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
*
* You should have received a copy of the GNU General Public License
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
*
**************************************************************************************/
**************************************************************************************/
package com.itsaky.androidide.interfaces;
import com.itsaky.androidide.models.DiagnosticGroup;
@@ -24,5 +24,6 @@ import java.io.File;
public interface DiagnosticClickListener {
void onGroupClick(DiagnosticGroup group);
void onDiagnosticClick(File file, DiagnosticItem diagnostic);
}
@@ -1,29 +1,30 @@
/************************************************************************************
* This file is part of AndroidIDE.
*
*
* AndroidIDE is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
*
* AndroidIDE is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
*
* You should have received a copy of the GNU General Public License
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
*
**************************************************************************************/
**************************************************************************************/
package com.itsaky.androidide.interfaces;
import com.itsaky.androidide.EditorActivity;
public interface EditorActivityProvider {
/**
* Provides a reference to {@link com.itsaky.androidide.EditorActivity EditorActivity}.
* This is used by LanguageClients to get reference to EditorActivity without keeping a strong reference.
* Provides a reference to {@link com.itsaky.androidide.EditorActivity EditorActivity}. This is
* used by LanguageClients to get reference to EditorActivity without keeping a strong
* reference.
*/
EditorActivity provide();
}
@@ -27,6 +27,11 @@ import com.itsaky.lsp.models.CompletionParams;
import com.itsaky.lsp.models.CompletionResult;
import com.itsaky.lsp.models.Position;
import io.github.rosemoe.sora.lang.completion.CompletionHelper;
import io.github.rosemoe.sora.text.CharPosition;
import io.github.rosemoe.sora.text.ContentReference;
import io.github.rosemoe.sora.util.MyCharacter;
import org.jetbrains.annotations.Contract;
import java.nio.file.Path;
@@ -38,94 +43,103 @@ import java.util.concurrent.CancellationException;
import java.util.concurrent.CompletableFuture;
import java.util.function.Predicate;
import io.github.rosemoe.sora.lang.completion.CompletionHelper;
import io.github.rosemoe.sora.text.CharPosition;
import io.github.rosemoe.sora.text.ContentReference;
import io.github.rosemoe.sora.util.MyCharacter;
/**
* Common implementation of completion provider which requests completions
* to provided language server.
* Common implementation of completion provider which requests completions to provided language
* server.
*
* @author Akash Yadav
*/
public class CommonCompletionProvider {
private CompletableFuture<CompletionResult> future;
private final ILanguageServer server;
public CommonCompletionProvider (ILanguageServer server) {
public CommonCompletionProvider(ILanguageServer server) {
this.server = server;
}
/**
* Computes completion items using the provided language server instance.
*
* @param content The reference to the content of the editor.
* @param file The file to compute completions for.
* @param content The reference to the content of the editor.
* @param file The file to compute completions for.
* @param position The position of the cursor in the content.
* @return The computed completion items. May return an empty list if the there was an error computing the completion items.
* @return The computed completion items. May return an empty list if the there was an error
* computing the completion items.
*/
public List<CompletionItem> complete (ContentReference content, Path file, CharPosition position, Predicate<Character> prefixMatcher) {
if (this.future != null && !this.future.isDone ()) {
public List<CompletionItem> complete(
ContentReference content,
Path file,
CharPosition position,
Predicate<Character> prefixMatcher) {
if (this.future != null && !this.future.isDone()) {
try {
this.future.cancel (true);
this.future.cancel(true);
} catch (CancellationException e) {
return new ArrayList<> ();
return new ArrayList<>();
}
}
this.future = CompletableFuture.supplyAsync (() -> {
final var prefix = CompletionHelper.computePrefix (content, position, prefixMatcher::test);
final var completer = server.getCompletionProvider ();
if (!completer.canComplete (file)) {
return ICompletionProvider.EMPTY;
}
final var params = new CompletionParams (new Position (position.line, position.column, position.index), file);
params.setContent (content);
params.setPrefix (prefix);
return completer.complete (params);
});
this.future =
CompletableFuture.supplyAsync(
() -> {
final var prefix =
CompletionHelper.computePrefix(
content, position, prefixMatcher::test);
final var completer = server.getCompletionProvider();
if (!completer.canComplete(file)) {
return ICompletionProvider.EMPTY;
}
final var params =
new CompletionParams(
new Position(
position.line, position.column, position.index),
file);
params.setContent(content);
params.setPrefix(prefix);
return completer.complete(params);
});
try {
return finalizeResults (future.get ().getItems ());
return finalizeResults(future.get().getItems());
} catch (Throwable e) {
LOG.error ("Unable to compute completions", e);
return Collections.emptyList ();
LOG.error("Unable to compute completions", e);
return Collections.emptyList();
}
}
public static boolean checkJavaCompletionChar (char c) {
return MyCharacter.isJavaIdentifierPart (c) || c == '.';
public static boolean checkJavaCompletionChar(char c) {
return MyCharacter.isJavaIdentifierPart(c) || c == '.';
}
public static boolean checkXMLCompletionChar (char c) {
return MyCharacter.isJavaIdentifierPart (c) || c == '<' || c == '/';
public static boolean checkXMLCompletionChar(char c) {
return MyCharacter.isJavaIdentifierPart(c) || c == '<' || c == '/';
}
@NonNull
@Contract("_ -> param1")
private List<CompletionItem> finalizeResults (@NonNull List<CompletionItem> items) {
items.sort (RESULT_SORTER);
private List<CompletionItem> finalizeResults(@NonNull List<CompletionItem> items) {
items.sort(RESULT_SORTER);
return items;
}
private static final Comparator<CompletionItem> RESULT_SORTER = (p1, p2) -> {
if (p1 == null && p2 == null) {
return 0;
} else if (p1 == null) {
return -1;
} else if (p2 == null) {
return 1;
}
String s1 = p1.getSortText () == null ? p1.getLabel () : p1.getSortText ();
String s2 = p2.getSortText () == null ? p2.getLabel () : p2.getSortText ();
return s1.compareTo (s2);
};
private static final Logger LOG = Logger.instance ("CommonCompletionProvider");
private static final Comparator<CompletionItem> RESULT_SORTER =
(p1, p2) -> {
if (p1 == null && p2 == null) {
return 0;
} else if (p1 == null) {
return -1;
} else if (p2 == null) {
return 1;
}
String s1 = p1.getSortText() == null ? p1.getLabel() : p1.getSortText();
String s2 = p2.getSortText() == null ? p2.getLabel() : p2.getSortText();
return s1.compareTo(s2);
};
private static final Logger LOG = Logger.instance("CommonCompletionProvider");
}
@@ -5,12 +5,12 @@
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
*
* AndroidIDE is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
*
* You should have received a copy of the GNU General Public License
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
*/
@@ -21,33 +21,34 @@ import androidx.annotation.NonNull;
import com.itsaky.androidide.app.StudioApp;
import com.itsaky.lsp.models.DiagnosticItem;
import io.github.rosemoe.sora.lang.Language;
import java.util.Collections;
import java.util.List;
import io.github.rosemoe.sora.lang.Language;
/**
* Base class for language implementations in the IDE.
*
* @author Akash Yadav
*/
public abstract class IDELanguage implements Language {
@Override
public boolean useTab() {
return false;
}
public int getTabSize() {
return StudioApp.getInstance().getPrefManager().getEditorTabSize();
}
/**
* Get the diagnostics from the last analyze.
*
* @return The diagnostics. Must not be null.
*/
@NonNull
public List<DiagnosticItem> getDiagnostics () {
return Collections.emptyList ();
public List<DiagnosticItem> getDiagnostics() {
return Collections.emptyList();
}
}
@@ -5,12 +5,12 @@
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
*
* AndroidIDE is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
*
* You should have received a copy of the GNU General Public License
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
*
@@ -32,6 +32,11 @@ import com.itsaky.androidide.lexers.groovy.GroovyLexer;
import com.itsaky.androidide.utils.CharSequenceReader;
import com.itsaky.androidide.utils.Logger;
import io.github.rosemoe.sora.lang.analysis.SimpleAnalyzeManager;
import io.github.rosemoe.sora.lang.styling.CodeBlock;
import io.github.rosemoe.sora.lang.styling.MappedSpans;
import io.github.rosemoe.sora.lang.styling.Styles;
import org.antlr.v4.runtime.CharStreams;
import org.antlr.v4.runtime.CodePointCharStream;
import org.antlr.v4.runtime.Token;
@@ -39,53 +44,48 @@ import org.antlr.v4.runtime.Token;
import java.io.IOException;
import java.util.Stack;
import io.github.rosemoe.sora.lang.analysis.SimpleAnalyzeManager;
import io.github.rosemoe.sora.lang.styling.CodeBlock;
import io.github.rosemoe.sora.lang.styling.MappedSpans;
import io.github.rosemoe.sora.lang.styling.Styles;
public class GroovyAnalyzer extends SimpleAnalyzeManager<Void> {
private static final Logger LOG = Logger.instance ("GroovyAnalyzer");
private static final Logger LOG = Logger.instance("GroovyAnalyzer");
@Override
protected Styles analyze (StringBuilder text, Delegate<Void> delegate) {
final var styles = new Styles ();
final var colors = new MappedSpans.Builder ();
protected Styles analyze(StringBuilder text, Delegate<Void> delegate) {
final var styles = new Styles();
final var colors = new MappedSpans.Builder();
final CodePointCharStream stream;
try {
stream = CharStreams.fromReader (new CharSequenceReader (text));
stream = CharStreams.fromReader(new CharSequenceReader(text));
} catch (IOException e) {
LOG.error ("Unable to read text to analyze", e);
LOG.error("Unable to read text to analyze", e);
return styles;
}
final var lexer = new GroovyLexer (stream);
final var stack = new Stack<CodeBlock> ();
final var lexer = new GroovyLexer(stream);
final var stack = new Stack<CodeBlock>();
Token token;
int line, column, lastLine = 1, currSwitch = 0, maxSwitch = 0, type, previous = 0;
boolean isFirst = true;
while (!delegate.isCancelled ()) {
token = lexer.nextToken ();
while (!delegate.isCancelled()) {
token = lexer.nextToken();
if (token == null) {
break;
}
line = token.getLine () - 1;
column = token.getCharPositionInLine ();
type = token.getType ();
line = token.getLine() - 1;
column = token.getCharPositionInLine();
type = token.getType();
if (type == GroovyLexer.EOF) {
lastLine = line;
break;
}
switch(type) {
case GroovyLexer.WS :
if(isFirst) {
switch (type) {
case GroovyLexer.WS:
if (isFirst) {
colors.addNormalIfNull();
}
break;
@@ -130,7 +130,7 @@ public class GroovyAnalyzer extends SimpleAnalyzeManager<Void> {
case GroovyLexer.VOID:
case GroovyLexer.VOLATILE:
case GroovyLexer.WHILE:
colors.addIfNeeded(line, column, forKeyword ());
colors.addIfNeeded(line, column, forKeyword());
break;
case GroovyLexer.DECIMAL_LITERAL:
case GroovyLexer.HEX_LITERAL:
@@ -141,126 +141,124 @@ public class GroovyAnalyzer extends SimpleAnalyzeManager<Void> {
case GroovyLexer.BOOL_LITERAL:
case GroovyLexer.CHAR_LITERAL:
case GroovyLexer.NULL_LITERAL:
colors.addIfNeeded(line, column, get (LITERAL));
colors.addIfNeeded(line, column, get(LITERAL));
break;
case GroovyLexer.STRING_LITERAL :
case GroovyLexer.SINGLE_QUOTE_STRING :
colors.addIfNeeded (line, column, forString ());
case GroovyLexer.STRING_LITERAL:
case GroovyLexer.SINGLE_QUOTE_STRING:
colors.addIfNeeded(line, column, forString());
break;
case GroovyLexer.LPAREN :
case GroovyLexer.RPAREN :
case GroovyLexer.LBRACK :
case GroovyLexer.RBRACK :
case GroovyLexer.SEMI :
case GroovyLexer.COMMA :
case GroovyLexer.ASSIGN :
case GroovyLexer.GT :
case GroovyLexer.LT :
case GroovyLexer.BANG :
case GroovyLexer.TILDE :
case GroovyLexer.QUESTION :
case GroovyLexer.COLON :
case GroovyLexer.EQUAL :
case GroovyLexer.GE :
case GroovyLexer.LE :
case GroovyLexer.NOTEQUAL :
case GroovyLexer.AND :
case GroovyLexer.OR :
case GroovyLexer.INC :
case GroovyLexer.DEC :
case GroovyLexer.ADD :
case GroovyLexer.SUB :
case GroovyLexer.MUL :
case GroovyLexer.DIV :
case GroovyLexer.BITAND :
case GroovyLexer.BITOR :
case GroovyLexer.CARET :
case GroovyLexer.MOD :
case GroovyLexer.ADD_ASSIGN :
case GroovyLexer.SUB_ASSIGN :
case GroovyLexer.MUL_ASSIGN :
case GroovyLexer.DIV_ASSIGN :
case GroovyLexer.AND_ASSIGN :
case GroovyLexer.OR_ASSIGN :
case GroovyLexer.XOR_ASSIGN :
case GroovyLexer.MOD_ASSIGN :
case GroovyLexer.LSHIFT_ASSIGN :
case GroovyLexer.RSHIFT_ASSIGN :
case GroovyLexer.URSHIFT_ASSIGN :
case GroovyLexer.ARROW :
case GroovyLexer.COLONCOLON :
case GroovyLexer.ELLIPSIS :
case GroovyLexer.DOT :
colors.addIfNeeded(line, column, get (OPERATOR));
case GroovyLexer.LPAREN:
case GroovyLexer.RPAREN:
case GroovyLexer.LBRACK:
case GroovyLexer.RBRACK:
case GroovyLexer.SEMI:
case GroovyLexer.COMMA:
case GroovyLexer.ASSIGN:
case GroovyLexer.GT:
case GroovyLexer.LT:
case GroovyLexer.BANG:
case GroovyLexer.TILDE:
case GroovyLexer.QUESTION:
case GroovyLexer.COLON:
case GroovyLexer.EQUAL:
case GroovyLexer.GE:
case GroovyLexer.LE:
case GroovyLexer.NOTEQUAL:
case GroovyLexer.AND:
case GroovyLexer.OR:
case GroovyLexer.INC:
case GroovyLexer.DEC:
case GroovyLexer.ADD:
case GroovyLexer.SUB:
case GroovyLexer.MUL:
case GroovyLexer.DIV:
case GroovyLexer.BITAND:
case GroovyLexer.BITOR:
case GroovyLexer.CARET:
case GroovyLexer.MOD:
case GroovyLexer.ADD_ASSIGN:
case GroovyLexer.SUB_ASSIGN:
case GroovyLexer.MUL_ASSIGN:
case GroovyLexer.DIV_ASSIGN:
case GroovyLexer.AND_ASSIGN:
case GroovyLexer.OR_ASSIGN:
case GroovyLexer.XOR_ASSIGN:
case GroovyLexer.MOD_ASSIGN:
case GroovyLexer.LSHIFT_ASSIGN:
case GroovyLexer.RSHIFT_ASSIGN:
case GroovyLexer.URSHIFT_ASSIGN:
case GroovyLexer.ARROW:
case GroovyLexer.COLONCOLON:
case GroovyLexer.ELLIPSIS:
case GroovyLexer.DOT:
colors.addIfNeeded(line, column, get(OPERATOR));
break;
case GroovyLexer.BOOLEAN:
case GroovyLexer.BYTE :
case GroovyLexer.BYTE:
case GroovyLexer.CHAR:
case GroovyLexer.DOUBLE:
case GroovyLexer.ENUM :
case GroovyLexer.ENUM:
case GroovyLexer.FLOAT:
case GroovyLexer.INT :
case GroovyLexer.INT:
case GroovyLexer.LONG:
case GroovyLexer.SHORT:
colors.addIfNeeded(line, column, get (TYPE_NAME));
colors.addIfNeeded(line, column, get(TYPE_NAME));
break;
case GroovyLexer.COMMENT :
case GroovyLexer.LINE_COMMENT :
colors.addIfNeeded(line, column, forComment ());
case GroovyLexer.COMMENT:
case GroovyLexer.LINE_COMMENT:
colors.addIfNeeded(line, column, forComment());
break;
case GroovyLexer.AT :
colors.addIfNeeded(line, column, get (ANNOTATION));
case GroovyLexer.AT:
colors.addIfNeeded(line, column, get(ANNOTATION));
break;
case GroovyLexer.IDENTIFIER :
case GroovyLexer.IDENTIFIER:
if (previous == GroovyLexer.AT) {
colors.addIfNeeded(line, column, get (ANNOTATION));
colors.addIfNeeded(line, column, get(ANNOTATION));
break;
}
colors.addIfNeeded(line, column, get (TEXT_NORMAL));
colors.addIfNeeded(line, column, get(TEXT_NORMAL));
break;
case GroovyLexer.LBRACE :
case GroovyLexer.LBRACE:
colors.addIfNeeded(line, column, get(OPERATOR));
if (stack.isEmpty()) {
if (currSwitch > maxSwitch)
maxSwitch = currSwitch;
if (currSwitch > maxSwitch) maxSwitch = currSwitch;
currSwitch = 0;
}
currSwitch++;
var block = styles.obtainNewBlock ();
var block = styles.obtainNewBlock();
block.startLine = line;
block.startColumn = column;
stack.push(block);
break;
case GroovyLexer.RBRACE :
colors.addIfNeeded(line, column, get (OPERATOR));
case GroovyLexer.RBRACE:
colors.addIfNeeded(line, column, get(OPERATOR));
if (!stack.isEmpty()) {
var block2 = stack.pop();
block2.endLine = line;
block2.endColumn = column;
if (block2.startLine != block2.endLine)
styles.addCodeBlock (block2);
if (block2.startLine != block2.endLine) styles.addCodeBlock(block2);
}
break;
default:
colors.addIfNeeded(line, column, get (TEXT_NORMAL));
colors.addIfNeeded(line, column, get(TEXT_NORMAL));
break;
}
isFirst = false;
if (type != GroovyLexer.WS) {
previous = type;
}
}
if (stack.isEmpty() && currSwitch > maxSwitch) {
maxSwitch = currSwitch;
}
colors.determine(lastLine);
styles.setSuppressSwitch(maxSwitch + 10);
styles.spans = colors.build ();
styles.spans = colors.build();
return styles;
}
}
@@ -1,22 +1,22 @@
/************************************************************************************
* This file is part of AndroidIDE.
*
*
*
*
* AndroidIDE is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
*
* AndroidIDE is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
*
* You should have received a copy of the GNU General Public License
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
*
**************************************************************************************/
**************************************************************************************/
package com.itsaky.androidide.language.groovy;
import android.os.Bundle;
@@ -36,31 +36,40 @@ import java.util.List;
import java.util.Locale;
public class GroovyAutoComplete {
private static final List<String> ANDROIDX_ARTIFACTS = createArtifacts();
private static final List<String> CONFIGURATIONS = createConfigs();
private static final List<String> OTHERS = createOtherCompletions();
public void complete (ContentReference content, CharPosition position, CompletionPublisher publisher, Bundle extraArguments) {
publisher.setUpdateThreshold (0);
final var prefix = CompletionHelper.computePrefix (content, position, MyCharacter::isJavaIdentifierPart);
for(String artifact : ANDROIDX_ARTIFACTS) {
if(!artifact.toLowerCase(Locale.ROOT).startsWith(prefix.toLowerCase(Locale.ROOT))) continue;
publisher.addItem (createCompletionItem(artifact));
}
for(String config : CONFIGURATIONS) {
if(!config.toLowerCase(Locale.ROOT).startsWith(prefix.toLowerCase(Locale.ROOT))) continue;
publisher.addItem (createCompletionItem(config));
}
for(String other : OTHERS) {
if(!other.toLowerCase(Locale.ROOT).startsWith(prefix.toLowerCase(Locale.ROOT))) continue;
publisher.addItem (createCompletionItem(other));
}
}
private CompletionItem createCompletionItem (String itemLabel) {
private static final List<String> ANDROIDX_ARTIFACTS = createArtifacts();
private static final List<String> CONFIGURATIONS = createConfigs();
private static final List<String> OTHERS = createOtherCompletions();
public void complete(
ContentReference content,
CharPosition position,
CompletionPublisher publisher,
Bundle extraArguments) {
publisher.setUpdateThreshold(0);
final var prefix =
CompletionHelper.computePrefix(
content, position, MyCharacter::isJavaIdentifierPart);
for (String artifact : ANDROIDX_ARTIFACTS) {
if (!artifact.toLowerCase(Locale.ROOT).startsWith(prefix.toLowerCase(Locale.ROOT)))
continue;
publisher.addItem(createCompletionItem(artifact));
}
for (String config : CONFIGURATIONS) {
if (!config.toLowerCase(Locale.ROOT).startsWith(prefix.toLowerCase(Locale.ROOT)))
continue;
publisher.addItem(createCompletionItem(config));
}
for (String other : OTHERS) {
if (!other.toLowerCase(Locale.ROOT).startsWith(prefix.toLowerCase(Locale.ROOT)))
continue;
publisher.addItem(createCompletionItem(other));
}
}
private CompletionItem createCompletionItem(String itemLabel) {
CompletionItem item = new CompletionItem();
item.setLabel(itemLabel);
item.setDetail("");
@@ -68,311 +77,311 @@ public class GroovyAutoComplete {
item.setSortText("0" + itemLabel);
item.setInsertTextFormat(InsertTextFormat.PLAIN_TEXT);
item.setKind(CompletionItemKind.SNIPPET);
return item;
}
private static List<String> createArtifacts() {
var artifacts = new ArrayList<String>();
artifacts.add("androidx.arch.core:core-common");
artifacts.add("androidx.arch.core:core");
artifacts.add("androidx.arch.core:core-testing");
artifacts.add("androidx.arch.core:core-runtime");
artifacts.add("androidx.lifecycle:lifecycle-common");
artifacts.add("androidx.lifecycle:lifecycle-common-java8");
artifacts.add("androidx.lifecycle:lifecycle-compiler");
artifacts.add("androidx.lifecycle:lifecycle-extensions");
artifacts.add("androidx.lifecycle:lifecycle-livedata");
artifacts.add("androidx.lifecycle:lifecycle-livedata-core");
artifacts.add("androidx.lifecycle:lifecycle-reactivestreams");
artifacts.add("androidx.lifecycle:lifecycle-runtime");
artifacts.add("androidx.lifecycle:lifecycle-viewmodel");
artifacts.add("androidx.paging:paging-common");
artifacts.add("androidx.paging:paging-runtime");
artifacts.add("androidx.paging:paging-rxjava2");
artifacts.add("androidx.room:room-common");
artifacts.add("androidx.room:room-compiler");
artifacts.add("androidx.room:room-guava");
artifacts.add("androidx.room:room-migration");
artifacts.add("androidx.room:room-runtime");
artifacts.add("androidx.room:room-rxjava2");
artifacts.add("androidx.room:room-testing");
artifacts.add("androidx.sqlite:sqlite");
artifacts.add("androidx.sqlite:sqlite-framework");
artifacts.add("androidx.constraintlayout:constraintlayout");
artifacts.add("androidx.constraintlayout:constraintlayout-solver");
artifacts.add("androidx.test.espresso.idling:idling-concurrent");
artifacts.add("androidx.test.espresso.idling:idling-net");
artifacts.add("androidx.test.espresso:espresso-accessibility");
artifacts.add("androidx.test.espresso:espresso-contrib");
artifacts.add("androidx.test.espresso:espresso-core");
artifacts.add("androidx.test.espresso:espresso-idling-resource");
artifacts.add("androidx.test.espresso:espresso-intents");
artifacts.add("androidx.test.espresso:espresso-remote");
artifacts.add("androidx.test.espresso:espresso-web");
artifacts.add("androidx.test.jank:janktesthelper");
artifacts.add("androidx.test:test-services");
artifacts.add("androidx.test.uiautomator:uiautomator");
artifacts.add("androidx.test:monitor");
artifacts.add("androidx.test:orchestrator");
artifacts.add("androidx.test:rules");
artifacts.add("androidx.test:runner");
artifacts.add("androidx.vectordrawable:vectordrawable-animated");
artifacts.add("androidx.appcompat:appcompat");
artifacts.add("androidx.asynclayoutinflater:asynclayoutinflater");
artifacts.add("androidx.car:car");
artifacts.add("androidx.cardview:cardview");
artifacts.add("androidx.collection:collection");
artifacts.add("androidx.coordinatorlayout:coordinatorlayout");
artifacts.add("androidx.cursoradapter:cursoradapter");
artifacts.add("androidx.browser:browser");
artifacts.add("androidx.customview:customview");
artifacts.add("com.google.android.material:material");
artifacts.add("androidx.documentfile:documentfile");
artifacts.add("androidx.drawerlayout:drawerlayout");
artifacts.add("androidx.exifinterface:exifinterface");
artifacts.add("androidx.gridlayout:gridlayout");
artifacts.add("androidx.heifwriter:heifwriter");
artifacts.add("androidx.interpolator:interpolator");
artifacts.add("androidx.leanback:leanback");
artifacts.add("androidx.loader:loader");
artifacts.add("androidx.localbroadcastmanager:localbroadcastmanager");
artifacts.add("androidx.media2:media2");
artifacts.add("androidx.media2:media2-exoplayer");
artifacts.add("androidx.mediarouter:mediarouter");
artifacts.add("androidx.multidex:multidex");
artifacts.add("androidx.multidex:multidex-instrumentation");
artifacts.add("androidx.palette:palette");
artifacts.add("androidx.percentlayout:percentlayout");
artifacts.add("androidx.leanback:leanback-preference");
artifacts.add("androidx.legacy:legacy-preference-v14");
artifacts.add("androidx.preference:preference");
artifacts.add("androidx.print:print");
artifacts.add("androidx.recommendation:recommendation");
artifacts.add("androidx.recyclerview:recyclerview-selection");
artifacts.add("androidx.recyclerview:recyclerview");
artifacts.add("androidx.slice:slice-builders");
artifacts.add("androidx.slice:slice-core");
artifacts.add("androidx.slice:slice-view");
artifacts.add("androidx.slidingpanelayout:slidingpanelayout");
artifacts.add("androidx.annotation:annotation");
artifacts.add("androidx.core:core");
artifacts.add("androidx.contentpager:contentpager");
artifacts.add("androidx.legacy:legacy-support-core-ui");
artifacts.add("androidx.legacy:legacy-support-core-utils");
artifacts.add("androidx.dynamicanimation:dynamicanimation");
artifacts.add("androidx.emoji:emoji");
artifacts.add("androidx.emoji:emoji-appcompat");
artifacts.add("androidx.emoji:emoji-bundled");
artifacts.add("androidx.fragment:fragment");
artifacts.add("androidx.media:media");
artifacts.add("androidx.tvprovider:tvprovider");
artifacts.add("androidx.legacy:legacy-support-v13");
artifacts.add("androidx.legacy:legacy-support-v4");
artifacts.add("androidx.vectordrawable:vectordrawable");
artifacts.add("androidx.swiperefreshlayout:swiperefreshlayout");
artifacts.add("androidx.textclassifier:textclassifier");
artifacts.add("androidx.transition:transition");
artifacts.add("androidx.versionedparcelable:versionedparcelable");
artifacts.add("androidx.viewpager:viewpager");
artifacts.add("androidx.wear:wear");
artifacts.add("androidx.webkit:webkit");
artifacts.add("fileTree(dir: 'libs', include: ['*.jar'])");
return artifacts;
}
private static List<String> createConfigs() {
var configs = new ArrayList<String>();
configs.add("androidApis");
configs.add("androidTestAnnotationProcessor");
configs.add("androidTestApi");
configs.add("androidTestApk");
configs.add("androidTestCompile");
configs.add("androidTestCompileOnly");
configs.add("androidTestDebugAnnotationProcessor");
configs.add("androidTestDebugApi");
configs.add("androidTestDebugApk");
configs.add("androidTestDebugCompile");
configs.add("androidTestDebugCompileOnly");
configs.add("androidTestDebugImplementation");
configs.add("androidTestDebugProvided");
configs.add("androidTestDebugRuntimeOnly");
configs.add("androidTestDebugWearApp");
configs.add("androidTestImplementation");
configs.add("androidTestProvided");
configs.add("androidTestRuntimeOnly");
configs.add("androidTestUtil");
configs.add("androidTestWearApp");
configs.add("annotationProcessor");
configs.add("api");
configs.add("apk");
configs.add("archives");
configs.add("compile");
configs.add("compileOnly");
configs.add("coreLibraryDesugaring");
configs.add("debugAnnotationProcessor");
configs.add("debugApi");
configs.add("debugApk");
configs.add("debugCompile");
configs.add("debugCompileOnly");
configs.add("debugImplementation");
configs.add("debugProvided");
configs.add("debugRuntimeOnly");
configs.add("debugWearApp");
configs.add("default");
configs.add("implementation");
configs.add("implementation platform()");
configs.add("lintChecks");
configs.add("lintClassPath");
configs.add("lintPublish");
configs.add("provided");
configs.add("releaseAnnotationProcessor");
configs.add("releaseApi");
configs.add("releaseApk");
configs.add("releaseCompile");
configs.add("releaseCompileOnly");
configs.add("releaseImplementation");
configs.add("releaseProvided");
configs.add("releaseRuntimeOnly");
configs.add("releaseWearApp");
configs.add("runtimeOnly");
configs.add("testAnnotationProcessor");
configs.add("testApi");
configs.add("testApk");
configs.add("testCompile");
configs.add("testCompileOnly");
configs.add("testDebugAnnotationProcessor");
configs.add("testDebugApi");
configs.add("testDebugApk");
configs.add("testDebugCompile");
configs.add("testDebugCompileOnly");
configs.add("testDebugImplementation");
configs.add("testDebugProvided");
configs.add("testDebugRuntimeOnly");
configs.add("testDebugWearApp");
configs.add("testImplementation");
configs.add("testProvided");
configs.add("testReleaseAnnotationProcessor");
configs.add("testReleaseApi");
configs.add("testReleaseApk");
configs.add("testReleaseCompile");
configs.add("testReleaseCompileOnly");
configs.add("testReleaseImplementation");
configs.add("testReleaseProvided");
configs.add("testReleaseRuntimeOnly");
configs.add("testReleaseWearApp");
configs.add("testRuntimeOnly");
configs.add("testWearApp");
configs.add("wearApp");
return configs;
}
private static List<String> createOtherCompletions() {
var others = new ArrayList<String> ();
// Common words that user may use
others.add ("android");
others.add ("applicationVariants");
others.add ("compileSdkVersion");
others.add ("buildToolsVersion");
others.add ("defaultConfig");
others.add ("applicationId");
others.add ("minSdkVersion");
others.add ("targetSdkVersion");
others.add ("versionCode");
others.add ("versionName");
others.add ("multiDexEnabled");
others.add ("vectorDrawables.useSupportLibrary");
others.add ("compileOptions");
others.add ("coreLibraryDesugaringEnabled");
others.add ("sourceCompatibility");
others.add ("targetCompatibility");
others.add ("JavaVersion.VERSION_1_8");
others.add ("JavaVersion.VERSION_1_7");
// Lint options
others.add ("lintOptions");
others.add ("quiet");
others.add ("abortOnError");
others.add ("checkReleaseBuilds");
others.add ("ignoreWarnings");
others.add ("checkAllWarnings");
others.add ("warningsAsErrors");
others.add ("disable");
others.add ("enable");
others.add ("check");
others.add ("noLines");
others.add ("showAll");
others.add ("explainIssues");
others.add ("lintConfig");
others.add ("textReport");
others.add ("textOutput");
others.add ("xmlReport");
others.add ("xmlOutput");
others.add ("htmlReport");
others.add ("htmlOutput");
others.add ("fatal");
others.add ("error");
others.add ("warning");
others.add ("ignore");
others.add ("informational");
others.add ("baseline");
others.add ("checkTestSources");
others.add ("ignoreTestSources");
others.add ("checkGeneratedSources");
others.add ("checkDependencies");
// buildFeatures options
others.add ("buildFeatures");
others.add ("aidl");
others.add ("buildConfig");
others.add ("compose");
others.add ("prefab");
others.add ("renderScript");
others.add ("resValues");
others.add ("shaders");
others.add ("viewBinding");
// Signing configs and all
others.add ("signingConfigs");
others.add ("debug");
others.add ("storeFile");
others.add ("storePassword");
others.add ("keyAlias");
others.add ("keyPassword");
others.add ("release");
others.add ("storeFile");
others.add ("storePassword");
others.add ("keyAlias");
others.add ("keyPassword");
others.add ("buildTypes");
others.add ("debug");
others.add ("minifyEnabled");
others.add ("proguardFiles");
others.add ("signingConfig");
others.add ("stringfog.enable");
others.add ("release");
others.add ("minifyEnabled");
others.add ("proguardFiles");
others.add ("signingConfig");
others.add ("stringfog.enable");
others.add ("packagingOptions");
others.add ("exclude");
// Maybe used in root project's build.gradle
others.add ("buildscript");
others.add ("ext");
others.add ("project.ext");
others.add ("repositories");
others.add ("maven");
others.add ("url");
others.add ("google()");
others.add ("mavenLocal()");
others.add ("mavenCentral()");
others.add ("jcenter()");
others.add ("classpath");
others.add ("allprojects");
others.add ("subprojects");
return others;
}
return item;
}
private static List<String> createArtifacts() {
var artifacts = new ArrayList<String>();
artifacts.add("androidx.arch.core:core-common");
artifacts.add("androidx.arch.core:core");
artifacts.add("androidx.arch.core:core-testing");
artifacts.add("androidx.arch.core:core-runtime");
artifacts.add("androidx.lifecycle:lifecycle-common");
artifacts.add("androidx.lifecycle:lifecycle-common-java8");
artifacts.add("androidx.lifecycle:lifecycle-compiler");
artifacts.add("androidx.lifecycle:lifecycle-extensions");
artifacts.add("androidx.lifecycle:lifecycle-livedata");
artifacts.add("androidx.lifecycle:lifecycle-livedata-core");
artifacts.add("androidx.lifecycle:lifecycle-reactivestreams");
artifacts.add("androidx.lifecycle:lifecycle-runtime");
artifacts.add("androidx.lifecycle:lifecycle-viewmodel");
artifacts.add("androidx.paging:paging-common");
artifacts.add("androidx.paging:paging-runtime");
artifacts.add("androidx.paging:paging-rxjava2");
artifacts.add("androidx.room:room-common");
artifacts.add("androidx.room:room-compiler");
artifacts.add("androidx.room:room-guava");
artifacts.add("androidx.room:room-migration");
artifacts.add("androidx.room:room-runtime");
artifacts.add("androidx.room:room-rxjava2");
artifacts.add("androidx.room:room-testing");
artifacts.add("androidx.sqlite:sqlite");
artifacts.add("androidx.sqlite:sqlite-framework");
artifacts.add("androidx.constraintlayout:constraintlayout");
artifacts.add("androidx.constraintlayout:constraintlayout-solver");
artifacts.add("androidx.test.espresso.idling:idling-concurrent");
artifacts.add("androidx.test.espresso.idling:idling-net");
artifacts.add("androidx.test.espresso:espresso-accessibility");
artifacts.add("androidx.test.espresso:espresso-contrib");
artifacts.add("androidx.test.espresso:espresso-core");
artifacts.add("androidx.test.espresso:espresso-idling-resource");
artifacts.add("androidx.test.espresso:espresso-intents");
artifacts.add("androidx.test.espresso:espresso-remote");
artifacts.add("androidx.test.espresso:espresso-web");
artifacts.add("androidx.test.jank:janktesthelper");
artifacts.add("androidx.test:test-services");
artifacts.add("androidx.test.uiautomator:uiautomator");
artifacts.add("androidx.test:monitor");
artifacts.add("androidx.test:orchestrator");
artifacts.add("androidx.test:rules");
artifacts.add("androidx.test:runner");
artifacts.add("androidx.vectordrawable:vectordrawable-animated");
artifacts.add("androidx.appcompat:appcompat");
artifacts.add("androidx.asynclayoutinflater:asynclayoutinflater");
artifacts.add("androidx.car:car");
artifacts.add("androidx.cardview:cardview");
artifacts.add("androidx.collection:collection");
artifacts.add("androidx.coordinatorlayout:coordinatorlayout");
artifacts.add("androidx.cursoradapter:cursoradapter");
artifacts.add("androidx.browser:browser");
artifacts.add("androidx.customview:customview");
artifacts.add("com.google.android.material:material");
artifacts.add("androidx.documentfile:documentfile");
artifacts.add("androidx.drawerlayout:drawerlayout");
artifacts.add("androidx.exifinterface:exifinterface");
artifacts.add("androidx.gridlayout:gridlayout");
artifacts.add("androidx.heifwriter:heifwriter");
artifacts.add("androidx.interpolator:interpolator");
artifacts.add("androidx.leanback:leanback");
artifacts.add("androidx.loader:loader");
artifacts.add("androidx.localbroadcastmanager:localbroadcastmanager");
artifacts.add("androidx.media2:media2");
artifacts.add("androidx.media2:media2-exoplayer");
artifacts.add("androidx.mediarouter:mediarouter");
artifacts.add("androidx.multidex:multidex");
artifacts.add("androidx.multidex:multidex-instrumentation");
artifacts.add("androidx.palette:palette");
artifacts.add("androidx.percentlayout:percentlayout");
artifacts.add("androidx.leanback:leanback-preference");
artifacts.add("androidx.legacy:legacy-preference-v14");
artifacts.add("androidx.preference:preference");
artifacts.add("androidx.print:print");
artifacts.add("androidx.recommendation:recommendation");
artifacts.add("androidx.recyclerview:recyclerview-selection");
artifacts.add("androidx.recyclerview:recyclerview");
artifacts.add("androidx.slice:slice-builders");
artifacts.add("androidx.slice:slice-core");
artifacts.add("androidx.slice:slice-view");
artifacts.add("androidx.slidingpanelayout:slidingpanelayout");
artifacts.add("androidx.annotation:annotation");
artifacts.add("androidx.core:core");
artifacts.add("androidx.contentpager:contentpager");
artifacts.add("androidx.legacy:legacy-support-core-ui");
artifacts.add("androidx.legacy:legacy-support-core-utils");
artifacts.add("androidx.dynamicanimation:dynamicanimation");
artifacts.add("androidx.emoji:emoji");
artifacts.add("androidx.emoji:emoji-appcompat");
artifacts.add("androidx.emoji:emoji-bundled");
artifacts.add("androidx.fragment:fragment");
artifacts.add("androidx.media:media");
artifacts.add("androidx.tvprovider:tvprovider");
artifacts.add("androidx.legacy:legacy-support-v13");
artifacts.add("androidx.legacy:legacy-support-v4");
artifacts.add("androidx.vectordrawable:vectordrawable");
artifacts.add("androidx.swiperefreshlayout:swiperefreshlayout");
artifacts.add("androidx.textclassifier:textclassifier");
artifacts.add("androidx.transition:transition");
artifacts.add("androidx.versionedparcelable:versionedparcelable");
artifacts.add("androidx.viewpager:viewpager");
artifacts.add("androidx.wear:wear");
artifacts.add("androidx.webkit:webkit");
artifacts.add("fileTree(dir: 'libs', include: ['*.jar'])");
return artifacts;
}
private static List<String> createConfigs() {
var configs = new ArrayList<String>();
configs.add("androidApis");
configs.add("androidTestAnnotationProcessor");
configs.add("androidTestApi");
configs.add("androidTestApk");
configs.add("androidTestCompile");
configs.add("androidTestCompileOnly");
configs.add("androidTestDebugAnnotationProcessor");
configs.add("androidTestDebugApi");
configs.add("androidTestDebugApk");
configs.add("androidTestDebugCompile");
configs.add("androidTestDebugCompileOnly");
configs.add("androidTestDebugImplementation");
configs.add("androidTestDebugProvided");
configs.add("androidTestDebugRuntimeOnly");
configs.add("androidTestDebugWearApp");
configs.add("androidTestImplementation");
configs.add("androidTestProvided");
configs.add("androidTestRuntimeOnly");
configs.add("androidTestUtil");
configs.add("androidTestWearApp");
configs.add("annotationProcessor");
configs.add("api");
configs.add("apk");
configs.add("archives");
configs.add("compile");
configs.add("compileOnly");
configs.add("coreLibraryDesugaring");
configs.add("debugAnnotationProcessor");
configs.add("debugApi");
configs.add("debugApk");
configs.add("debugCompile");
configs.add("debugCompileOnly");
configs.add("debugImplementation");
configs.add("debugProvided");
configs.add("debugRuntimeOnly");
configs.add("debugWearApp");
configs.add("default");
configs.add("implementation");
configs.add("implementation platform()");
configs.add("lintChecks");
configs.add("lintClassPath");
configs.add("lintPublish");
configs.add("provided");
configs.add("releaseAnnotationProcessor");
configs.add("releaseApi");
configs.add("releaseApk");
configs.add("releaseCompile");
configs.add("releaseCompileOnly");
configs.add("releaseImplementation");
configs.add("releaseProvided");
configs.add("releaseRuntimeOnly");
configs.add("releaseWearApp");
configs.add("runtimeOnly");
configs.add("testAnnotationProcessor");
configs.add("testApi");
configs.add("testApk");
configs.add("testCompile");
configs.add("testCompileOnly");
configs.add("testDebugAnnotationProcessor");
configs.add("testDebugApi");
configs.add("testDebugApk");
configs.add("testDebugCompile");
configs.add("testDebugCompileOnly");
configs.add("testDebugImplementation");
configs.add("testDebugProvided");
configs.add("testDebugRuntimeOnly");
configs.add("testDebugWearApp");
configs.add("testImplementation");
configs.add("testProvided");
configs.add("testReleaseAnnotationProcessor");
configs.add("testReleaseApi");
configs.add("testReleaseApk");
configs.add("testReleaseCompile");
configs.add("testReleaseCompileOnly");
configs.add("testReleaseImplementation");
configs.add("testReleaseProvided");
configs.add("testReleaseRuntimeOnly");
configs.add("testReleaseWearApp");
configs.add("testRuntimeOnly");
configs.add("testWearApp");
configs.add("wearApp");
return configs;
}
private static List<String> createOtherCompletions() {
var others = new ArrayList<String>();
// Common words that user may use
others.add("android");
others.add("applicationVariants");
others.add("compileSdkVersion");
others.add("buildToolsVersion");
others.add("defaultConfig");
others.add("applicationId");
others.add("minSdkVersion");
others.add("targetSdkVersion");
others.add("versionCode");
others.add("versionName");
others.add("multiDexEnabled");
others.add("vectorDrawables.useSupportLibrary");
others.add("compileOptions");
others.add("coreLibraryDesugaringEnabled");
others.add("sourceCompatibility");
others.add("targetCompatibility");
others.add("JavaVersion.VERSION_1_8");
others.add("JavaVersion.VERSION_1_7");
// Lint options
others.add("lintOptions");
others.add("quiet");
others.add("abortOnError");
others.add("checkReleaseBuilds");
others.add("ignoreWarnings");
others.add("checkAllWarnings");
others.add("warningsAsErrors");
others.add("disable");
others.add("enable");
others.add("check");
others.add("noLines");
others.add("showAll");
others.add("explainIssues");
others.add("lintConfig");
others.add("textReport");
others.add("textOutput");
others.add("xmlReport");
others.add("xmlOutput");
others.add("htmlReport");
others.add("htmlOutput");
others.add("fatal");
others.add("error");
others.add("warning");
others.add("ignore");
others.add("informational");
others.add("baseline");
others.add("checkTestSources");
others.add("ignoreTestSources");
others.add("checkGeneratedSources");
others.add("checkDependencies");
// buildFeatures options
others.add("buildFeatures");
others.add("aidl");
others.add("buildConfig");
others.add("compose");
others.add("prefab");
others.add("renderScript");
others.add("resValues");
others.add("shaders");
others.add("viewBinding");
// Signing configs and all
others.add("signingConfigs");
others.add("debug");
others.add("storeFile");
others.add("storePassword");
others.add("keyAlias");
others.add("keyPassword");
others.add("release");
others.add("storeFile");
others.add("storePassword");
others.add("keyAlias");
others.add("keyPassword");
others.add("buildTypes");
others.add("debug");
others.add("minifyEnabled");
others.add("proguardFiles");
others.add("signingConfig");
others.add("stringfog.enable");
others.add("release");
others.add("minifyEnabled");
others.add("proguardFiles");
others.add("signingConfig");
others.add("stringfog.enable");
others.add("packagingOptions");
others.add("exclude");
// Maybe used in root project's build.gradle
others.add("buildscript");
others.add("ext");
others.add("project.ext");
others.add("repositories");
others.add("maven");
others.add("url");
others.add("google()");
others.add("mavenLocal()");
others.add("mavenCentral()");
others.add("jcenter()");
others.add("classpath");
others.add("allprojects");
others.add("subprojects");
return others;
}
}
@@ -26,11 +26,6 @@ import com.itsaky.androidide.lexers.groovy.GroovyLexer;
import com.itsaky.androidide.utils.CharSequenceReader;
import com.itsaky.androidide.utils.Logger;
import org.antlr.v4.runtime.CharStreams;
import org.antlr.v4.runtime.Token;
import java.io.StringReader;
import io.github.rosemoe.sora.lang.analysis.AnalyzeManager;
import io.github.rosemoe.sora.lang.completion.CompletionCancelledException;
import io.github.rosemoe.sora.lang.completion.CompletionPublisher;
@@ -41,25 +36,30 @@ import io.github.rosemoe.sora.text.ContentReference;
import io.github.rosemoe.sora.text.TextUtils;
import io.github.rosemoe.sora.widget.SymbolPairMatch;
import org.antlr.v4.runtime.CharStreams;
import org.antlr.v4.runtime.Token;
import java.io.StringReader;
public class GroovyLanguage extends IDELanguage {
private final GroovyAnalyzer analyzer;
private final GroovyAutoComplete completer;
private static final Logger LOG = Logger.instance ("GroovyLanguage");
public GroovyLanguage () {
analyzer = new GroovyAnalyzer ();
completer = new GroovyAutoComplete ();
private static final Logger LOG = Logger.instance("GroovyLanguage");
public GroovyLanguage() {
analyzer = new GroovyAnalyzer();
completer = new GroovyAutoComplete();
}
public int getIndentAdvance (String p1) {
public int getIndentAdvance(String p1) {
try {
GroovyLexer lexer = new GroovyLexer (CharStreams.fromReader (new StringReader (p1)));
GroovyLexer lexer = new GroovyLexer(CharStreams.fromReader(new StringReader(p1)));
Token token = null;
int advance = 0;
while (((token = lexer.nextToken ()) != null && token.getType () != token.EOF)) {
switch (token.getType ()) {
while (((token = lexer.nextToken()) != null && token.getType() != token.EOF)) {
switch (token.getType()) {
case GroovyLexer.LBRACE:
advance++;
break;
@@ -68,48 +68,50 @@ public class GroovyLanguage extends IDELanguage {
break;
}
}
advance = Math.max (0, advance);
return advance * getTabSize ();
advance = Math.max(0, advance);
return advance * getTabSize();
} catch (Throwable e) {
LOG.error ("Failed to calculate indent advance", e);
LOG.error("Failed to calculate indent advance", e);
}
return 0;
}
@Override
public SymbolPairMatch getSymbolPairs () {
return new SymbolPairMatch.DefaultSymbolPairs ();
public SymbolPairMatch getSymbolPairs() {
return new SymbolPairMatch.DefaultSymbolPairs();
}
@NonNull
@Override
public AnalyzeManager getAnalyzeManager () {
public AnalyzeManager getAnalyzeManager() {
return analyzer;
}
@Override
public int getInterruptionLevel () {
public int getInterruptionLevel() {
return INTERRUPTION_LEVEL_STRONG;
}
@Override
public void requireAutoComplete (
public void requireAutoComplete(
@NonNull ContentReference content,
@NonNull CharPosition position,
@NonNull CompletionPublisher publisher,
@NonNull Bundle extraArguments) throws CompletionCancelledException {
completer.complete (content, position, publisher, extraArguments);
@NonNull Bundle extraArguments)
throws CompletionCancelledException {
completer.complete(content, position, publisher, extraArguments);
}
@Override
public int getIndentAdvance (@NonNull ContentReference content, int line, int column) {
public int getIndentAdvance(@NonNull ContentReference content, int line, int column) {
try {
GroovyLexer lexer = new GroovyLexer (CharStreams.fromReader (new CharSequenceReader (content)));
GroovyLexer lexer =
new GroovyLexer(CharStreams.fromReader(new CharSequenceReader(content)));
Token token;
int advance = 0;
while (((token = lexer.nextToken ()) != null && token.getType () != token.EOF)) {
switch (token.getType ()) {
while (((token = lexer.nextToken()) != null && token.getType() != token.EOF)) {
switch (token.getType()) {
case GroovyLexer.LBRACE:
advance++;
break;
@@ -118,49 +120,56 @@ public class GroovyLanguage extends IDELanguage {
break;
}
}
advance = Math.max (0, advance);
return advance * getTabSize ();
advance = Math.max(0, advance);
return advance * getTabSize();
} catch (Throwable e) {
LOG.error ("Failed to calculate indent advance", e);
LOG.error("Failed to calculate indent advance", e);
}
return 0;
}
@Override
public CharSequence format (CharSequence content) {
public CharSequence format(CharSequence content) {
return content;
}
private final NewlineHandler[] newlineHandlers = new NewlineHandler[]{new BraceHandler ()};
private final NewlineHandler[] newlineHandlers = new NewlineHandler[] {new BraceHandler()};
@Override
public NewlineHandler[] getNewlineHandlers () {
public NewlineHandler[] getNewlineHandlers() {
return newlineHandlers;
}
@Override
public void destroy () {
}
public void destroy() {}
class BraceHandler implements NewlineHandler {
@Override
public boolean matchesRequirement (String beforeText, String afterText) {
beforeText = beforeText.trim ();
afterText = afterText.trim ();
return beforeText.endsWith ("{") && afterText.startsWith ("}");
public boolean matchesRequirement(String beforeText, String afterText) {
beforeText = beforeText.trim();
afterText = afterText.trim();
return beforeText.endsWith("{") && afterText.startsWith("}");
}
@Override
public NewlineHandleResult handleNewline (String beforeText, String afterText, int tabSize) {
int count = TextUtils.countLeadingSpaceCount (beforeText, tabSize);
int advanceBefore = getIndentAdvance (beforeText);
int advanceAfter = getIndentAdvance (afterText);
public NewlineHandleResult handleNewline(String beforeText, String afterText, int tabSize) {
int count = TextUtils.countLeadingSpaceCount(beforeText, tabSize);
int advanceBefore = getIndentAdvance(beforeText);
int advanceAfter = getIndentAdvance(afterText);
String text;
StringBuilder sb = new StringBuilder ("\n").append (TextUtils.createIndent (count + advanceBefore, tabSize, useTab ())).append ('\n').append (text = TextUtils.createIndent (count + advanceAfter, tabSize, useTab ()));
int shiftLeft = text.length () + 1;
return new NewlineHandleResult (sb, shiftLeft);
StringBuilder sb =
new StringBuilder("\n")
.append(
TextUtils.createIndent(
count + advanceBefore, tabSize, useTab()))
.append('\n')
.append(
text =
TextUtils.createIndent(
count + advanceAfter, tabSize, useTab()));
int shiftLeft = text.length() + 1;
return new NewlineHandleResult(sb, shiftLeft);
}
}
}
@@ -41,6 +41,12 @@ import com.itsaky.lsp.models.DiagnosticSeverity;
import com.itsaky.lsp.models.Position;
import com.itsaky.lsp.models.Range;
import io.github.rosemoe.sora.lang.analysis.SimpleAnalyzeManager;
import io.github.rosemoe.sora.lang.styling.CodeBlock;
import io.github.rosemoe.sora.lang.styling.MappedSpans;
import io.github.rosemoe.sora.lang.styling.Span;
import io.github.rosemoe.sora.lang.styling.Styles;
import org.antlr.v4.runtime.CharStreams;
import org.antlr.v4.runtime.CodePointCharStream;
import org.antlr.v4.runtime.Token;
@@ -53,95 +59,92 @@ import java.util.List;
import java.util.Stack;
import java.util.concurrent.CompletableFuture;
import io.github.rosemoe.sora.lang.analysis.SimpleAnalyzeManager;
import io.github.rosemoe.sora.lang.styling.CodeBlock;
import io.github.rosemoe.sora.lang.styling.MappedSpans;
import io.github.rosemoe.sora.lang.styling.Span;
import io.github.rosemoe.sora.lang.styling.Styles;
/**
* Code analyzer for the Java Language
*
* @author Akash Yadav
*/
public class JavaAnalyzer extends SimpleAnalyzeManager<Void> {
private List<DiagnosticItem> diagnostics = new ArrayList<> ();
private final List<DiagnosticItem> ideDiagnostics = new ArrayList<> ();
private List<DiagnosticItem> diagnostics = new ArrayList<>();
private final List<DiagnosticItem> ideDiagnostics = new ArrayList<>();
private final ILanguageServer languageServer;
public JavaAnalyzer (ILanguageServer languageServer) {
public JavaAnalyzer(ILanguageServer languageServer) {
this.languageServer = languageServer;
}
@NonNull
public List<DiagnosticItem> getDiagnostics () {
final var result = new ArrayList<> (ideDiagnostics);
if (diagnostics != null && !diagnostics.isEmpty ()) {
result.addAll (diagnostics);
public List<DiagnosticItem> getDiagnostics() {
final var result = new ArrayList<>(ideDiagnostics);
if (diagnostics != null && !diagnostics.isEmpty()) {
result.addAll(diagnostics);
}
result.sort (DiagnosticItem.START_COMPARATOR);
result.sort(DiagnosticItem.START_COMPARATOR);
return result;
}
@Override
protected Styles analyze (StringBuilder text, Delegate<Void> delegate) {
final var args = getExtraArguments ();
if (args.containsKey (IDEEditor.KEY_FILE)) {
final var file = new File (args.getString (IDEEditor.KEY_FILE));
protected Styles analyze(StringBuilder text, Delegate<Void> delegate) {
final var args = getExtraArguments();
if (args.containsKey(IDEEditor.KEY_FILE)) {
final var file = new File(args.getString(IDEEditor.KEY_FILE));
if (languageServer != null && file.exists()) {
CompletableFuture.runAsync (() -> {
final var analyzer = languageServer.getCodeAnalyzer ();
diagnostics = analyzer.analyze (file.toPath ());
if (languageServer.getClient () != null) {
languageServer.getClient ().publishDiagnostics (new DiagnosticResult (file.toPath (), diagnostics));
}
});
CompletableFuture.runAsync(
() -> {
final var analyzer = languageServer.getCodeAnalyzer();
diagnostics = analyzer.analyze(file.toPath());
if (languageServer.getClient() != null) {
languageServer
.getClient()
.publishDiagnostics(
new DiagnosticResult(file.toPath(), diagnostics));
}
});
}
}
final CodePointCharStream stream;
final var styles = new Styles ();
final var colors = new MappedSpans.Builder ();
final var styles = new Styles();
final var colors = new MappedSpans.Builder();
try {
stream = CharStreams.fromReader (new CharSequenceReader (text));
stream = CharStreams.fromReader(new CharSequenceReader(text));
} catch (IOException e) {
LOG.error ("Unable to create char stream for text", e);
LOG.error("Unable to create char stream for text", e);
return styles;
}
final var lexer = new JavaLexer (stream);
final var stack = new Stack<CodeBlock> ();
final var lexer = new JavaLexer(stream);
final var stack = new Stack<CodeBlock>();
Token token;
int line, column, lastLine = 1, type, currSwitch = 0, maxSwitch = 0, previous = -1;
boolean isFirst = true;
ideDiagnostics.clear ();
while (!delegate.isCancelled ()) {
token = lexer.nextToken ();
ideDiagnostics.clear();
while (!delegate.isCancelled()) {
token = lexer.nextToken();
if (token == null) {
break;
}
type = token.getType ();
line = token.getLine () - 1;
column = token.getCharPositionInLine ();
type = token.getType();
line = token.getLine() - 1;
column = token.getCharPositionInLine();
if (type == JavaLexer.EOF) {
lastLine = line;
break;
}
final var tokenLength = token.getText ().length ();
final var tokenLength = token.getText().length();
switch (type) {
case JavaLexer.WS:
if (isFirst) {
colors.addNormalIfNull ();
colors.addNormalIfNull();
}
break;
case JavaLexer.ABSTRACT:
@@ -186,7 +189,7 @@ public class JavaAnalyzer extends SimpleAnalyzeManager<Void> {
case JavaLexer.VOLATILE:
case JavaLexer.WHILE:
case JavaLexer.VAR:
colors.addIfNeeded (line, column, forKeyword ());
colors.addIfNeeded(line, column, forKeyword());
break;
case JavaLexer.DECIMAL_LITERAL:
case JavaLexer.HEX_LITERAL:
@@ -197,10 +200,10 @@ public class JavaAnalyzer extends SimpleAnalyzeManager<Void> {
case JavaLexer.BOOL_LITERAL:
case JavaLexer.CHAR_LITERAL:
case JavaLexer.NULL_LITERAL:
colors.addIfNeeded (line, column, get (LITERAL));
colors.addIfNeeded(line, column, get(LITERAL));
break;
case JavaLexer.STRING_LITERAL:
colors.addIfNeeded (line, column, forString ());
colors.addIfNeeded(line, column, forString());
break;
case JavaLexer.LPAREN:
case JavaLexer.RPAREN:
@@ -246,7 +249,7 @@ public class JavaAnalyzer extends SimpleAnalyzeManager<Void> {
case JavaLexer.COLONCOLON:
case JavaLexer.ELLIPSIS:
case JavaLexer.DOT:
colors.addIfNeeded (line, column, get (OPERATOR));
colors.addIfNeeded(line, column, get(OPERATOR));
break;
case JavaLexer.BOOLEAN:
case JavaLexer.BYTE:
@@ -257,129 +260,133 @@ public class JavaAnalyzer extends SimpleAnalyzeManager<Void> {
case JavaLexer.INT:
case JavaLexer.LONG:
case JavaLexer.SHORT:
colors.addIfNeeded (line, column, get (TYPE_NAME));
colors.addIfNeeded(line, column, get(TYPE_NAME));
break;
case JavaLexer.BLOCK_COMMENT:
colors.addIfNeeded (line, column, forComment ());
colors.addIfNeeded(line, column, forComment());
break;
case JavaLexer.LINE_COMMENT:
var commentType = SchemeAndroidIDE.COMMENT;
// highlight special line comments
var commentText = token.getText ();
if (commentText.length () > 2) {
commentText = commentText.substring (2);
commentText = commentText.trim ();
var commentText = token.getText();
if (commentText.length() > 2) {
commentText = commentText.substring(2);
commentText = commentText.trim();
var mark = true;
if ("todo".equalsIgnoreCase (commentText.substring (0, 4))) {
if ("todo".equalsIgnoreCase(commentText.substring(0, 4))) {
commentType = SchemeAndroidIDE.TODO_COMMENT;
} else if ("fixme".equalsIgnoreCase (commentText.substring (0, 5))) {
} else if ("fixme".equalsIgnoreCase(commentText.substring(0, 5))) {
commentType = SchemeAndroidIDE.FIXME_COMMENT;
} else {
mark = false;
}
if (mark) {
if (diagnostics == null) {
diagnostics = new ArrayList<> ();
diagnostics = new ArrayList<>();
}
final var diagnostic = new DiagnosticItem ();
diagnostic.setSeverity (DiagnosticSeverity.WARNING);
diagnostic.setMessage (commentText);
diagnostic.setCode ("special.comment");
diagnostic.setRange (new Range (new Position (line, column), new Position (line, column + tokenLength)));
diagnostic.setSource (commentText);
ideDiagnostics.add (diagnostic);
final var diagnostic = new DiagnosticItem();
diagnostic.setSeverity(DiagnosticSeverity.WARNING);
diagnostic.setMessage(commentText);
diagnostic.setCode("special.comment");
diagnostic.setRange(
new Range(
new Position(line, column),
new Position(line, column + tokenLength)));
diagnostic.setSource(commentText);
ideDiagnostics.add(diagnostic);
}
}
colors.addIfNeeded (line, column, withoutCompletion (commentType));
colors.addIfNeeded(line, column, withoutCompletion(commentType));
break;
case JavaLexer.AT:
colors.addIfNeeded (line, column, get (ANNOTATION));
colors.addIfNeeded(line, column, get(ANNOTATION));
break;
case JavaLexer.IDENTIFIER:
int colorId = SchemeAndroidIDE.TEXT_NORMAL;
if (previous == JavaLexer.AT) {
colorId = SchemeAndroidIDE.ANNOTATION;
}
colors.addIfNeeded (line, column, get (colorId));
colors.addIfNeeded(line, column, get(colorId));
break;
case JavaLexer.LBRACE:
colors.addIfNeeded (line, column, get (OPERATOR));
if (stack.isEmpty ()) {
colors.addIfNeeded(line, column, get(OPERATOR));
if (stack.isEmpty()) {
if (currSwitch > maxSwitch) {
maxSwitch = currSwitch;
}
currSwitch = 0;
}
currSwitch++;
var block = styles.obtainNewBlock ();
var block = styles.obtainNewBlock();
block.startLine = line;
block.startColumn = column;
stack.push (block);
stack.push(block);
break;
case JavaLexer.RBRACE:
colors.addIfNeeded (line, column, get (OPERATOR));
if (!stack.isEmpty ()) {
var block2 = stack.pop ();
colors.addIfNeeded(line, column, get(OPERATOR));
if (!stack.isEmpty()) {
var block2 = stack.pop();
block2.endLine = line;
block2.endColumn = column;
if (block2.startLine != block2.endLine) {
styles.addCodeBlock (block2);
styles.addCodeBlock(block2);
}
}
break;
default:
colors.addIfNeeded (line, column, get (TEXT_NORMAL));
colors.addIfNeeded(line, column, get(TEXT_NORMAL));
break;
}
isFirst = false;
if (type != JavaLexer.WS) {
previous = type;
}
}
if (stack.isEmpty () && currSwitch > maxSwitch) {
if (stack.isEmpty() && currSwitch > maxSwitch) {
maxSwitch = currSwitch;
}
colors.determine (lastLine);
styles.setSuppressSwitch (maxSwitch + 10);
if (diagnostics != null && !diagnostics.isEmpty ()) {
markDiagnostics (colors, diagnostics);
colors.determine(lastLine);
styles.setSuppressSwitch(maxSwitch + 10);
if (diagnostics != null && !diagnostics.isEmpty()) {
markDiagnostics(colors, diagnostics);
}
if (!ideDiagnostics.isEmpty ()) {
markDiagnostics (colors, ideDiagnostics);
if (!ideDiagnostics.isEmpty()) {
markDiagnostics(colors, ideDiagnostics);
}
styles.spans = colors.build ();
styles.spans = colors.build();
return styles;
}
private void markDiagnostics (MappedSpans.Builder colors, List<DiagnosticItem> diagnostics) {
diagnostics.sort (DiagnosticItem.START_COMPARATOR);
private void markDiagnostics(MappedSpans.Builder colors, List<DiagnosticItem> diagnostics) {
diagnostics.sort(DiagnosticItem.START_COMPARATOR);
for (var d : diagnostics) {
var start = d.getRange ().getStart ();
var end = d.getRange ().getEnd ();
colors.markProblemRegion (convertToSpanFlag(d.getSeverity ()),
start.getLine (),
start.getColumn (),
end.getLine (),
end.getColumn ());
var start = d.getRange().getStart();
var end = d.getRange().getEnd();
colors.markProblemRegion(
convertToSpanFlag(d.getSeverity()),
start.getLine(),
start.getColumn(),
end.getLine(),
end.getColumn());
}
}
@Contract(pure = true)
private int convertToSpanFlag (@NonNull DiagnosticSeverity severity) {
private int convertToSpanFlag(@NonNull DiagnosticSeverity severity) {
switch (severity) {
case WARNING:
return Span.FLAG_WARNING;
@@ -391,6 +398,6 @@ public class JavaAnalyzer extends SimpleAnalyzeManager<Void> {
return Span.FLAG_TYPO;
}
}
private static final Logger LOG = Logger.instance ("JavaAnalyzer");
private static final Logger LOG = Logger.instance("JavaAnalyzer");
}
@@ -5,12 +5,12 @@
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
*
* AndroidIDE is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
*
* You should have received a copy of the GNU General Public License
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
*
@@ -30,16 +30,6 @@ import com.itsaky.androidide.utils.Logger;
import com.itsaky.androidide.views.editor.IDEEditor;
import com.itsaky.lsp.models.DiagnosticItem;
import org.antlr.v4.runtime.CharStreams;
import org.antlr.v4.runtime.Token;
import java.io.StringReader;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import io.github.rosemoe.sora.lang.analysis.AnalyzeManager;
import io.github.rosemoe.sora.lang.completion.CompletionCancelledException;
import io.github.rosemoe.sora.lang.completion.CompletionPublisher;
@@ -50,128 +40,154 @@ import io.github.rosemoe.sora.text.ContentReference;
import io.github.rosemoe.sora.text.TextUtils;
import io.github.rosemoe.sora.widget.SymbolPairMatch;
import org.antlr.v4.runtime.CharStreams;
import org.antlr.v4.runtime.Token;
import java.io.StringReader;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class JavaLanguage extends IDELanguage {
private JavaAnalyzer analyzer;
private CommonCompletionProvider completer;
private CommonCompletionProvider completer;
private final NewlineHandler[] newlineHandlers;
private static final Logger LOG = Logger.instance("JavaLanguage");
public JavaLanguage() {
final var server = StudioApp.getInstance ().getJavaLanguageServer ();
this.analyzer = new JavaAnalyzer (server);
this.completer = new CommonCompletionProvider (server);
public JavaLanguage() {
final var server = StudioApp.getInstance().getJavaLanguageServer();
this.analyzer = new JavaAnalyzer(server);
this.completer = new CommonCompletionProvider(server);
this.newlineHandlers = new NewlineHandler[1];
this.newlineHandlers[0] = new BraceHandler();
}
@NonNull
@Override
public List<DiagnosticItem> getDiagnostics () {
return analyzer == null ? Collections.emptyList () : analyzer.getDiagnostics ();
}
public int getIndentAdvance(String p1) {
try {
JavaLexer lexer = new JavaLexer(CharStreams.fromReader(new StringReader(p1)));
Token token;
int advance = 0;
while (((token = lexer.nextToken()) != null && token.getType() != token.EOF)) {
switch (token.getType()) {
case JavaLexer.LBRACE:
advance++;
break;
case JavaParser.RBRACE:
advance --;
break;
}
}
advance = Math.max(0, advance);
return advance * getTabSize();
} catch (Throwable e) {
LOG.error ("Error calculating indent advance", e);
}
return 0;
}
@Override
public SymbolPairMatch getSymbolPairs() {
return new JavaSymbolPairs ();
}
@NonNull
@Override
public AnalyzeManager getAnalyzeManager () {
return analyzer;
}
@Override
public int getInterruptionLevel () {
return 0;
}
@Override
public void requireAutoComplete (@NonNull ContentReference content, @NonNull CharPosition position, @NonNull CompletionPublisher publisher, @NonNull Bundle extraArguments) throws CompletionCancelledException {
if (!extraArguments.containsKey (IDEEditor.KEY_FILE)) {
return;
}
final var file = Paths.get (extraArguments.getString (IDEEditor.KEY_FILE));
publisher.setUpdateThreshold (0);
publisher.addItems (new ArrayList<> (completer.complete (content, file, position, CommonCompletionProvider::checkJavaCompletionChar)));
}
@Override
public int getIndentAdvance (@NonNull ContentReference content, int line, int column) {
return getIndentAdvance (content.getLine (line).substring (0, column));
}
@Override
public boolean useTab() {
return false;
}
@Override
public CharSequence format(CharSequence content) {
return content;
}
@NonNull
@Override
public List<DiagnosticItem> getDiagnostics() {
return analyzer == null ? Collections.emptyList() : analyzer.getDiagnostics();
}
public int getIndentAdvance(String p1) {
try {
JavaLexer lexer = new JavaLexer(CharStreams.fromReader(new StringReader(p1)));
Token token;
int advance = 0;
while (((token = lexer.nextToken()) != null && token.getType() != token.EOF)) {
switch (token.getType()) {
case JavaLexer.LBRACE:
advance++;
break;
case JavaParser.RBRACE:
advance--;
break;
}
}
advance = Math.max(0, advance);
return advance * getTabSize();
} catch (Throwable e) {
LOG.error("Error calculating indent advance", e);
}
return 0;
}
@Override
public SymbolPairMatch getSymbolPairs() {
return new JavaSymbolPairs();
}
@NonNull
@Override
public AnalyzeManager getAnalyzeManager() {
return analyzer;
}
@Override
public int getInterruptionLevel() {
return 0;
}
@Override
public void requireAutoComplete(
@NonNull ContentReference content,
@NonNull CharPosition position,
@NonNull CompletionPublisher publisher,
@NonNull Bundle extraArguments)
throws CompletionCancelledException {
if (!extraArguments.containsKey(IDEEditor.KEY_FILE)) {
return;
}
final var file = Paths.get(extraArguments.getString(IDEEditor.KEY_FILE));
publisher.setUpdateThreshold(0);
publisher.addItems(
new ArrayList<>(
completer.complete(
content,
file,
position,
CommonCompletionProvider::checkJavaCompletionChar)));
}
@Override
public int getIndentAdvance(@NonNull ContentReference content, int line, int column) {
return getIndentAdvance(content.getLine(line).substring(0, column));
}
@Override
public boolean useTab() {
return false;
}
@Override
public CharSequence format(CharSequence content) {
return content;
}
@Override
public NewlineHandler[] getNewlineHandlers() {
return newlineHandlers;
}
@Override
public void destroy () {
analyzer = null;
completer = null;
}
class BraceHandler implements NewlineHandler {
@Override
public boolean matchesRequirement (String beforeText, String afterText) {
return beforeText.endsWith("{") && afterText.startsWith("}");
}
@Override
@Override
public void destroy() {
analyzer = null;
completer = null;
}
class BraceHandler implements NewlineHandler {
@Override
public boolean matchesRequirement(String beforeText, String afterText) {
return beforeText.endsWith("{") && afterText.startsWith("}");
}
@Override
public NewlineHandleResult handleNewline(String beforeText, String afterText, int tabSize) {
int count = TextUtils.countLeadingSpaceCount(beforeText, tabSize);
int advanceBefore = getIndentAdvance(beforeText);
int advanceAfter = getIndentAdvance(afterText);
String text;
StringBuilder sb = new StringBuilder("\n")
.append(TextUtils.createIndent(count + advanceBefore, tabSize, useTab()))
.append('\n')
.append(text = TextUtils.createIndent(count + advanceAfter, tabSize, useTab()));
StringBuilder sb =
new StringBuilder("\n")
.append(
TextUtils.createIndent(
count + advanceBefore, tabSize, useTab()))
.append('\n')
.append(
text =
TextUtils.createIndent(
count + advanceAfter, tabSize, useTab()));
int shiftLeft = text.length() + 1;
return new NewlineHandleResult (sb, shiftLeft);
return new NewlineHandleResult(sb, shiftLeft);
}
}
private static class JavaSymbolPairs extends SymbolPairMatch {
public JavaSymbolPairs() {
super.putPair('{', new Replacement("{}", 1));
@@ -5,12 +5,12 @@
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
*
* AndroidIDE is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
*
* You should have received a copy of the GNU General Public License
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
*
@@ -25,9 +25,6 @@ import com.itsaky.androidide.language.IDELanguage;
import com.itsaky.androidide.models.LogLine;
import com.itsaky.androidide.syntax.colorschemes.SchemeAndroidIDE;
import java.util.ArrayList;
import java.util.List;
import io.github.rosemoe.sora.lang.analysis.AnalyzeManager;
import io.github.rosemoe.sora.lang.analysis.SimpleAnalyzeManager;
import io.github.rosemoe.sora.lang.completion.CompletionCancelledException;
@@ -39,86 +36,90 @@ import io.github.rosemoe.sora.text.CharPosition;
import io.github.rosemoe.sora.text.ContentReference;
import io.github.rosemoe.sora.widget.SymbolPairMatch;
import java.util.ArrayList;
import java.util.List;
public class LogLanguageImpl extends IDELanguage {
private static final LogAnalyzer analyzer = new LogAnalyzer();
public void addLine(LogLine line) {
analyzer.addLine (line);
}
@NonNull
@Override
public AnalyzeManager getAnalyzeManager () {
return analyzer;
}
@Override
public int getInterruptionLevel () {
return 0;
}
@Override
public void requireAutoComplete (@NonNull ContentReference content, @NonNull CharPosition position, @NonNull CompletionPublisher publisher, @NonNull Bundle extraArguments) throws CompletionCancelledException {
}
@Override
public int getIndentAdvance (@NonNull ContentReference content, int line, int column) {
return 0;
}
@Override
public boolean useTab() {
return false;
}
@Override
public CharSequence format(CharSequence text) {
return text;
}
@Override
public SymbolPairMatch getSymbolPairs() {
return new SymbolPairMatch.DefaultSymbolPairs();
}
@Override
public NewlineHandler[] getNewlineHandlers() {
return new NewlineHandler[0];
}
@Override
public void destroy () {
}
private static class LogAnalyzer extends SimpleAnalyzeManager<Void> {
private static final List<LogLine> lines = new ArrayList<>();
public LogAnalyzer addLine(LogLine line) {
if(line != null)
lines.add(line);
return this;
}
@Override
protected Styles analyze (StringBuilder text, Delegate<Void> delegate) {
final var styles = new Styles ();
final var colors = new MappedSpans.Builder ();
int lastLine = 0;
for(int i=0;i<lines.size() && !delegate.isCancelled ();i++) {
if(i==0) colors.addNormalIfNull();
LogLine line = lines.get(i);
colors.addIfNeeded(i, 0, SchemeAndroidIDE.get (line.priority));
lastLine = i;
}
colors.determine(lastLine);
styles.spans = colors.build ();
return styles;
}
}
private static final LogAnalyzer analyzer = new LogAnalyzer();
public void addLine(LogLine line) {
analyzer.addLine(line);
}
@NonNull
@Override
public AnalyzeManager getAnalyzeManager() {
return analyzer;
}
@Override
public int getInterruptionLevel() {
return 0;
}
@Override
public void requireAutoComplete(
@NonNull ContentReference content,
@NonNull CharPosition position,
@NonNull CompletionPublisher publisher,
@NonNull Bundle extraArguments)
throws CompletionCancelledException {}
@Override
public int getIndentAdvance(@NonNull ContentReference content, int line, int column) {
return 0;
}
@Override
public boolean useTab() {
return false;
}
@Override
public CharSequence format(CharSequence text) {
return text;
}
@Override
public SymbolPairMatch getSymbolPairs() {
return new SymbolPairMatch.DefaultSymbolPairs();
}
@Override
public NewlineHandler[] getNewlineHandlers() {
return new NewlineHandler[0];
}
@Override
public void destroy() {}
private static class LogAnalyzer extends SimpleAnalyzeManager<Void> {
private static final List<LogLine> lines = new ArrayList<>();
public LogAnalyzer addLine(LogLine line) {
if (line != null) lines.add(line);
return this;
}
@Override
protected Styles analyze(StringBuilder text, Delegate<Void> delegate) {
final var styles = new Styles();
final var colors = new MappedSpans.Builder();
int lastLine = 0;
for (int i = 0; i < lines.size() && !delegate.isCancelled(); i++) {
if (i == 0) colors.addNormalIfNull();
LogLine line = lines.get(i);
colors.addIfNeeded(i, 0, SchemeAndroidIDE.get(line.priority));
lastLine = i;
}
colors.determine(lastLine);
styles.spans = colors.build();
return styles;
}
}
}
@@ -21,6 +21,7 @@ import static com.itsaky.androidide.syntax.colorschemes.SchemeAndroidIDE.OPERATO
import static com.itsaky.androidide.syntax.colorschemes.SchemeAndroidIDE.TEXT_SELECTED;
import static com.itsaky.androidide.syntax.colorschemes.SchemeAndroidIDE.forComment;
import static com.itsaky.androidide.syntax.colorschemes.SchemeAndroidIDE.get;
import static io.github.rosemoe.sora.widget.schemes.EditorColorScheme.LITERAL;
import com.itsaky.androidide.lexers.xml.XMLLexer;
@@ -28,6 +29,11 @@ import com.itsaky.androidide.syntax.colorschemes.SchemeAndroidIDE;
import com.itsaky.androidide.utils.CharSequenceReader;
import com.itsaky.androidide.utils.Logger;
import io.github.rosemoe.sora.lang.analysis.SimpleAnalyzeManager;
import io.github.rosemoe.sora.lang.styling.CodeBlock;
import io.github.rosemoe.sora.lang.styling.MappedSpans;
import io.github.rosemoe.sora.lang.styling.Styles;
import org.antlr.v4.runtime.CharStreams;
import org.antlr.v4.runtime.CodePointCharStream;
import org.antlr.v4.runtime.Token;
@@ -35,65 +41,61 @@ import org.antlr.v4.runtime.Token;
import java.io.IOException;
import java.util.Stack;
import io.github.rosemoe.sora.lang.analysis.SimpleAnalyzeManager;
import io.github.rosemoe.sora.lang.styling.CodeBlock;
import io.github.rosemoe.sora.lang.styling.MappedSpans;
import io.github.rosemoe.sora.lang.styling.Styles;
/**
* Simple analyzer implementation for the XML files.
*
* @author Akash Yadav
*/
public class XMLAnalyzer extends SimpleAnalyzeManager<Void> {
private static final Logger LOG = Logger.instance ("XMLAnalyzer");
private static final Logger LOG = Logger.instance("XMLAnalyzer");
@Override
protected Styles analyze (StringBuilder text, Delegate<Void> delegate) {
final var styles = new Styles ();
final var colors = new MappedSpans.Builder ();
protected Styles analyze(StringBuilder text, Delegate<Void> delegate) {
final var styles = new Styles();
final var colors = new MappedSpans.Builder();
CodePointCharStream stream;
try {
stream = CharStreams.fromReader (new CharSequenceReader (text));
stream = CharStreams.fromReader(new CharSequenceReader(text));
} catch (IOException e) {
LOG.error ("Unable to create stream for analyze", e);
LOG.error("Unable to create stream for analyze", e);
return styles;
}
XMLLexer lexer = new XMLLexer (stream);
XMLLexer lexer = new XMLLexer(stream);
Token token;
int previous = 0;
int line, column, lastLine = 1;
var first = true;
final var stack = new Stack<CodeBlock> ();
while (!delegate.isCancelled ()) {
token = lexer.nextToken ();
final var stack = new Stack<CodeBlock>();
while (!delegate.isCancelled()) {
token = lexer.nextToken();
if (token == null) {
break;
}
if (token.getType () == XMLLexer.EOF) {
lastLine = token.getLine () - 1;
if (token.getType() == XMLLexer.EOF) {
lastLine = token.getLine() - 1;
break;
}
line = token.getLine () - 1;
column = token.getCharPositionInLine ();
line = token.getLine() - 1;
column = token.getCharPositionInLine();
lastLine = line;
switch (token.getType ()) {
switch (token.getType()) {
case XMLLexer.S:
case XMLLexer.SEA_WS:
if (first) {
colors.addNormalIfNull ();
colors.addNormalIfNull();
}
break;
case XMLLexer.COMMENT:
colors.addIfNeeded (line, column, forComment ());
colors.addIfNeeded(line, column, forComment());
break;
case XMLLexer.OPEN:
case XMLLexer.OPEN_SLASH:
@@ -103,52 +105,52 @@ public class XMLAnalyzer extends SimpleAnalyzeManager<Void> {
case XMLLexer.EQUALS:
case XMLLexer.COLON:
case XMLLexer.XMLDeclOpen:
colors.addIfNeeded (line, column, get (OPERATOR));
colors.addIfNeeded(line, column, get(OPERATOR));
break;
case XMLLexer.SLASH_CLOSE:
colors.addIfNeeded (line, column, get (OPERATOR));
final var closeBlock = stack.pop ();
colors.addIfNeeded(line, column, get(OPERATOR));
final var closeBlock = stack.pop();
closeBlock.endLine = line;
closeBlock.endColumn = column;
styles.addCodeBlock (closeBlock);
styles.addCodeBlock(closeBlock);
break;
case XMLLexer.STRING:
colors.addIfNeeded (line, column, LITERAL);
colors.addIfNeeded(line, column, LITERAL);
break;
case XMLLexer.Name:
var type = SchemeAndroidIDE.TEXT_NORMAL;
if (previous == XMLLexer.OPEN) {
type = SchemeAndroidIDE.XML_TAG;
final var block = styles.obtainNewBlock ();
final var block = styles.obtainNewBlock();
block.startLine = line;
block.startColumn = column;
stack.push (block);
stack.push(block);
}
if (previous == XMLLexer.OPEN_SLASH) {
type = SchemeAndroidIDE.XML_TAG;
final var block = stack.pop ();
final var block = stack.pop();
block.endLine = line;
block.endColumn = column;
styles.addCodeBlock (block);
styles.addCodeBlock(block);
}
colors.addIfNeeded (line, column, get (type));
colors.addIfNeeded(line, column, get(type));
break;
case XMLLexer.TEXT:
default:
colors.addIfNeeded (line, column, get (TEXT_SELECTED));
colors.addIfNeeded(line, column, get(TEXT_SELECTED));
break;
}
first = false;
if (token.getType () != XMLLexer.SEA_WS) {
previous = token.getType ();
if (token.getType() != XMLLexer.SEA_WS) {
previous = token.getType();
}
}
colors.determine (lastLine);
styles.spans = colors.build ();
colors.determine(lastLine);
styles.spans = colors.build();
return styles;
}
}
@@ -1,16 +1,16 @@
/*
* This file is part of AndroidIDE.
*
*
* AndroidIDE is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
*
* AndroidIDE is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
*
* You should have received a copy of the GNU General Public License
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
*
@@ -29,13 +29,6 @@ import com.itsaky.androidide.utils.JavaCharacter;
import com.itsaky.androidide.utils.Logger;
import com.itsaky.androidide.views.editor.IDEEditor;
import org.antlr.v4.runtime.CharStreams;
import org.antlr.v4.runtime.Token;
import java.io.StringReader;
import java.nio.file.Paths;
import java.util.ArrayList;
import io.github.rosemoe.sora.lang.analysis.AnalyzeManager;
import io.github.rosemoe.sora.lang.completion.CompletionCancelledException;
import io.github.rosemoe.sora.lang.completion.CompletionPublisher;
@@ -44,105 +37,122 @@ import io.github.rosemoe.sora.text.CharPosition;
import io.github.rosemoe.sora.text.ContentReference;
import io.github.rosemoe.sora.widget.SymbolPairMatch;
import org.antlr.v4.runtime.CharStreams;
import org.antlr.v4.runtime.Token;
import java.io.StringReader;
import java.nio.file.Paths;
import java.util.ArrayList;
public class XMLLanguage extends IDELanguage {
private XMLAnalyzer analyzer;
private final CommonCompletionProvider completer;
private final NewlineHandler[] newlineHandlers;
private static final Logger LOG = Logger.instance ("XMLLanguage");
public XMLLanguage() {
this.completer = new CommonCompletionProvider (StudioApp.getInstance ().getXMLLanguageServer ());
this.analyzer = new XMLAnalyzer();
this.newlineHandlers = new NewlineHandler[0];
}
public boolean isAutoCompleteChar(char ch) {
return JavaCharacter.isJavaIdentifierPart(ch)
|| ch == '<'
|| ch == '/';
}
public int getIndentAdvance(String content) {
try {
XMLLexer lexer = new XMLLexer(CharStreams.fromReader(new StringReader(content)));
Token token;
int advance = 0;
while (((token = lexer.nextToken()) != null && token.getType() != token.EOF)) {
switch (token.getType()) {
case XMLLexer.OPEN:
case XMLLexer.OPEN_SLASH :
case XMLLexer.XMLDeclOpen :
advance++;
break;
case XMLLexer.CLOSE:
case XMLLexer.SLASH_CLOSE :
case XMLLexer.SPECIAL_CLOSE :
advance --;
break;
default :
break;
}
}
advance = Math.max(0, advance);
return advance * getTabSize();
} catch (Throwable e) {
LOG.error ("Failed to compute indent advance", e);
}
return 0;
}
private XMLAnalyzer analyzer;
private final CommonCompletionProvider completer;
private final NewlineHandler[] newlineHandlers;
@Override
public SymbolPairMatch getSymbolPairs() {
return new SymbolPairMatch.DefaultSymbolPairs();
}
@NonNull
@Override
public AnalyzeManager getAnalyzeManager () {
return analyzer;
}
@Override
public int getInterruptionLevel () {
return INTERRUPTION_LEVEL_STRONG;
}
@Override
public void requireAutoComplete (@NonNull ContentReference content, @NonNull CharPosition position, @NonNull CompletionPublisher publisher, @NonNull Bundle extraArguments) throws CompletionCancelledException {
if (!extraArguments.containsKey (IDEEditor.KEY_FILE)) {
return;
}
final var file = Paths.get (extraArguments.getString (IDEEditor.KEY_FILE));
publisher.setUpdateThreshold (0);
publisher.addItems (new ArrayList<> (completer.complete (content, file, position, CommonCompletionProvider::checkXMLCompletionChar)));
}
@Override
public int getIndentAdvance (@NonNull ContentReference content, int line, int column) {
final var text = content.getLine (line).substring (0, column);
return getIndentAdvance (text);
}
@Override
public boolean useTab() {
return false;
}
private static final Logger LOG = Logger.instance("XMLLanguage");
@Override
public CharSequence format(CharSequence content) {
return content;
}
public XMLLanguage() {
this.completer =
new CommonCompletionProvider(StudioApp.getInstance().getXMLLanguageServer());
this.analyzer = new XMLAnalyzer();
this.newlineHandlers = new NewlineHandler[0];
}
public boolean isAutoCompleteChar(char ch) {
return JavaCharacter.isJavaIdentifierPart(ch) || ch == '<' || ch == '/';
}
public int getIndentAdvance(String content) {
try {
XMLLexer lexer = new XMLLexer(CharStreams.fromReader(new StringReader(content)));
Token token;
int advance = 0;
while (((token = lexer.nextToken()) != null && token.getType() != token.EOF)) {
switch (token.getType()) {
case XMLLexer.OPEN:
case XMLLexer.OPEN_SLASH:
case XMLLexer.XMLDeclOpen:
advance++;
break;
case XMLLexer.CLOSE:
case XMLLexer.SLASH_CLOSE:
case XMLLexer.SPECIAL_CLOSE:
advance--;
break;
default:
break;
}
}
advance = Math.max(0, advance);
return advance * getTabSize();
} catch (Throwable e) {
LOG.error("Failed to compute indent advance", e);
}
return 0;
}
@Override
public SymbolPairMatch getSymbolPairs() {
return new SymbolPairMatch.DefaultSymbolPairs();
}
@NonNull
@Override
public AnalyzeManager getAnalyzeManager() {
return analyzer;
}
@Override
public int getInterruptionLevel() {
return INTERRUPTION_LEVEL_STRONG;
}
@Override
public void requireAutoComplete(
@NonNull ContentReference content,
@NonNull CharPosition position,
@NonNull CompletionPublisher publisher,
@NonNull Bundle extraArguments)
throws CompletionCancelledException {
if (!extraArguments.containsKey(IDEEditor.KEY_FILE)) {
return;
}
final var file = Paths.get(extraArguments.getString(IDEEditor.KEY_FILE));
publisher.setUpdateThreshold(0);
publisher.addItems(
new ArrayList<>(
completer.complete(
content,
file,
position,
CommonCompletionProvider::checkXMLCompletionChar)));
}
@Override
public int getIndentAdvance(@NonNull ContentReference content, int line, int column) {
final var text = content.getLine(line).substring(0, column);
return getIndentAdvance(text);
}
@Override
public boolean useTab() {
return false;
}
@Override
public CharSequence format(CharSequence content) {
return content;
}
@Override
public NewlineHandler[] getNewlineHandlers() {
return newlineHandlers;
}
@Override
public void destroy () {
analyzer = null;
}
@Override
public void destroy() {
analyzer = null;
}
}
@@ -1,18 +1,18 @@
/*
* This file is part of AndroidIDE.
*
*
*
*
* AndroidIDE is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
*
* AndroidIDE is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
*
* You should have received a copy of the GNU General Public License
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
*
@@ -49,6 +49,8 @@ import com.itsaky.lsp.models.Range;
import com.itsaky.lsp.models.TextEdit;
import com.itsaky.toaster.Toaster;
import io.github.rosemoe.sora.text.Content;
import java.io.File;
import java.util.ArrayList;
import java.util.HashMap;
@@ -56,255 +58,253 @@ import java.util.List;
import java.util.Map;
import java.util.Objects;
import io.github.rosemoe.sora.text.Content;
/**
* AndroidIDE specific implementation of the LanguageClient
*/
/** AndroidIDE specific implementation of the LanguageClient */
public class IDELanguageClientImpl implements ILanguageClient {
protected static final Gson gson = new Gson();
protected static final Logger LOG = Logger.instance("AbstractLanguageClient");
private final Map<File, List<DiagnosticItem>> diagnostics = new HashMap<>();
private static IDELanguageClientImpl mInstance;
protected EditorActivityProvider activityProvider;
private IDELanguageClientImpl (EditorActivityProvider provider) {
private IDELanguageClientImpl(EditorActivityProvider provider) {
setActivityProvider(provider);
}
public static IDELanguageClientImpl getInstance () {
public static IDELanguageClientImpl getInstance() {
if (mInstance == null) {
throw new IllegalStateException ("Client not initialized");
throw new IllegalStateException("Client not initialized");
}
return mInstance;
}
public static IDELanguageClientImpl initialize (EditorActivityProvider provider) {
public static IDELanguageClientImpl initialize(EditorActivityProvider provider) {
if (mInstance != null) {
throw new IllegalStateException ("Client is already initialized");
throw new IllegalStateException("Client is already initialized");
}
mInstance = new IDELanguageClientImpl (provider);
mInstance = new IDELanguageClientImpl(provider);
return getInstance();
}
public static void shutdown () {
public static void shutdown() {
mInstance = null;
}
public static boolean isInitialized () {
public static boolean isInitialized() {
return mInstance != null;
}
public void setActivityProvider(EditorActivityProvider provider) {
this.activityProvider = provider;
}
protected EditorActivity activity() {
if (activityProvider == null) return null;
return activityProvider.provide();
}
private CodeEditorView findEditorByFile (File file) {
return activity ().getEditorForFile (file);
private CodeEditorView findEditorByFile(File file) {
return activity().getEditorForFile(file);
}
public void hideDiagnostics() {
if(activity() == null || activity().getDiagnosticBinding() == null) {
if (activity() == null || activity().getDiagnosticBinding() == null) {
return;
}
activity().getDiagnosticBinding().getRoot().setVisibility(View.GONE);
}
private void hideBottomDiagnosticView(final File file) {
if(activity() == null || file == null) {
if (activity() == null || file == null) {
return;
}
final var frag = findEditorByFile(file);
if(frag == null || frag.getBinding() == null) {
if (frag == null || frag.getBinding() == null) {
return;
}
frag.getBinding().diagnosticTextContainer.setVisibility(View.GONE);
frag.getBinding().diagnosticText.setVisibility(View.GONE);
frag.getBinding().diagnosticText.setClickable(false);
}
@Override
public void publishDiagnostics(DiagnosticResult result) {
boolean error = result == null;
activity().handleDiagnosticsResultVisibility(error || result.getDiagnostics().isEmpty());
if(error) {
if (error) {
return;
}
File file = result.getFile ().toFile ();
if(!file.exists() || !file.isFile()) return;
File file = result.getFile().toFile();
if (!file.exists() || !file.isFile()) return;
diagnostics.put(file, result.getDiagnostics());
activity().setDiagnosticsAdapter(newDiagnosticsAdapter());
}
/**
* Called by {@link IDEEditor IDEEditor} to show locations in EditorActivity
*/
/** Called by {@link IDEEditor IDEEditor} to show locations in EditorActivity */
public void showLocations(List<Location> locations) {
// Cannot show anything if the activity() is null
if (activity () == null) {
if (activity() == null) {
return;
}
boolean error = locations == null || locations.isEmpty ();
activity ().handleSearchResultVisibility (error);
boolean error = locations == null || locations.isEmpty();
activity().handleSearchResultVisibility(error);
if (error) {
activity ().setSearchResultAdapter (new SearchListAdapter (null, null, null));
activity().setSearchResultAdapter(new SearchListAdapter(null, null, null));
return;
}
final Map<File, List<SearchResult>> results = new HashMap<> ();
for (int i = 0; i < locations.size (); i++) {
final Map<File, List<SearchResult>> results = new HashMap<>();
for (int i = 0; i < locations.size(); i++) {
try {
final Location loc = locations.get (i);
final Location loc = locations.get(i);
if (loc == null) {
continue;
}
final File file = loc.getFile ().toFile ();
if (!file.exists () || !file.isFile ()) continue;
var frag = findEditorByFile (file);
final File file = loc.getFile().toFile();
if (!file.exists() || !file.isFile()) continue;
var frag = findEditorByFile(file);
Content content;
if (frag != null && frag.getEditor () != null)
content = frag.getEditor ().getText ();
else content = new Content (FileIOUtils.readFile2String (file));
final List<SearchResult> matches = results.containsKey (file) ? results.get (file) : new ArrayList<> ();
Objects.requireNonNull (matches).add (
new SearchResult (
loc.getRange (),
file,
content.getLineString (loc.getRange ().getStart ().getLine ()),
content.subContent (
loc.getRange ().getStart ().getLine (),
loc.getRange ().getStart ().getColumn (),
loc.getRange ().getEnd ().getLine (),
loc.getRange ().getEnd ().getColumn ()
).toString ()
)
);
results.put (file, matches);
if (frag != null && frag.getEditor() != null) content = frag.getEditor().getText();
else content = new Content(FileIOUtils.readFile2String(file));
final List<SearchResult> matches =
results.containsKey(file) ? results.get(file) : new ArrayList<>();
Objects.requireNonNull(matches)
.add(
new SearchResult(
loc.getRange(),
file,
content.getLineString(loc.getRange().getStart().getLine()),
content.subContent(
loc.getRange().getStart().getLine(),
loc.getRange().getStart().getColumn(),
loc.getRange().getEnd().getLine(),
loc.getRange().getEnd().getColumn())
.toString()));
results.put(file, matches);
} catch (Throwable th) {
LOG.error ("Failed to show file location", th);
LOG.error("Failed to show file location", th);
}
}
activity ().handleSearchResults (results);
activity().handleSearchResults(results);
}
/**
* Perform the given {@link CodeActionItem}
*
* @param editor The {@link IDEEditor} that invoked the code action request.
* This is required to reduce the time finding the code action from the edits.
* @param editor The {@link IDEEditor} that invoked the code action request. This is required to
* reduce the time finding the code action from the edits.
* @param action The action to perform
*/
public void performCodeAction(IDEEditor editor, CodeActionItem action) {
if(activity() == null || editor == null || action == null) {
if (activity() == null || editor == null || action == null) {
StudioApp.getInstance().toast(R.string.msg_cannot_perform_fix, Toaster.Type.ERROR);
return;
}
final ProgressSheet progress = new ProgressSheet();
progress.setSubMessageEnabled(false);
progress.setWelcomeTextEnabled(false);
progress.setCancelable(false);
progress.setMessage(activity().getString(R.string.msg_performing_fixes));
progress.show(activity().getSupportFragmentManager(), "quick_fix_progress");
new TaskExecutor().executeAsyncProvideError(() -> performCodeActionAsync(editor, action), (a, b) -> {
progress.dismiss();
if(a == null || b != null || !a) {
StudioApp.getInstance().toast(R.string.msg_cannot_perform_fix, Toaster.Type.ERROR);
}
});
new TaskExecutor()
.executeAsyncProvideError(
() -> performCodeActionAsync(editor, action),
(a, b) -> {
progress.dismiss();
if (a == null || b != null || !a) {
StudioApp.getInstance()
.toast(R.string.msg_cannot_perform_fix, Toaster.Type.ERROR);
}
});
}
private List<DiagnosticGroup> mapAsGroup(Map<File, List<DiagnosticItem>> diags) {
List<DiagnosticGroup> groups = new ArrayList<>();
if(diags == null || diags.size() <= 0)
return groups;
for(File file : diags.keySet()) {
if (diags == null || diags.size() <= 0) return groups;
for (File file : diags.keySet()) {
var fileDiags = diags.get(file);
if(fileDiags == null || fileDiags.size() <= 0)
continue;
DiagnosticGroup group = new DiagnosticGroup(R.drawable.ic_language_java, file, fileDiags);
if (fileDiags == null || fileDiags.size() <= 0) continue;
DiagnosticGroup group =
new DiagnosticGroup(R.drawable.ic_language_java, file, fileDiags);
groups.add(group);
}
return groups;
}
private void showAvailableQuickfixes (IDEEditor editor, List<CodeActionItem> actions) {
final MaterialAlertDialogBuilder builder = DialogUtils.newMaterialDialogBuilder (activity ());
private void showAvailableQuickfixes(IDEEditor editor, List<CodeActionItem> actions) {
final MaterialAlertDialogBuilder builder = DialogUtils.newMaterialDialogBuilder(activity());
builder.setTitle(R.string.msg_code_actions);
builder.setItems(asArray(actions), (d, w) -> {
d.dismiss();
hideDiagnostics();
hideBottomDiagnosticView(editor.getFile());
performCodeAction(editor, actions.get(w));
});
builder.setItems(
asArray(actions),
(d, w) -> {
d.dismiss();
hideDiagnostics();
hideBottomDiagnosticView(editor.getFile());
performCodeAction(editor, actions.get(w));
});
builder.show();
}
private CharSequence[] asArray(List<CodeActionItem> actions) {
final String[] arr = new String[actions.size()];
for(int i=0;i<actions.size();i++) {
for (int i = 0; i < actions.size(); i++) {
arr[i] = actions.get(i).getTitle();
}
return arr;
}
private Boolean performCodeActionAsync(final IDEEditor editor, final CodeActionItem action) {
final var changes = action.getChanges();
if(changes.isEmpty()) {
if (changes.isEmpty()) {
return Boolean.FALSE;
}
for(var change : changes) {
final var path = change.getFile ();
for (var change : changes) {
final var path = change.getFile();
if (path == null) {
continue;
}
final File file = path.toFile ();
if(!file.exists()) {
final File file = path.toFile();
if (!file.exists()) {
continue;
}
for(TextEdit edit : change.getEdits ()) {
final String editorFilepath = editor.getFile() == null ? "" : editor.getFile().getAbsolutePath();
if(file.getAbsolutePath().equals(editorFilepath)) {
for (TextEdit edit : change.getEdits()) {
final String editorFilepath =
editor.getFile() == null ? "" : editor.getFile().getAbsolutePath();
if (file.getAbsolutePath().equals(editorFilepath)) {
// Edit is in the same editor which requested the code action
editInEditor(editor, edit);
} else {
var openedFrag = findEditorByFile(file);
if(openedFrag != null && openedFrag.getEditor() != null) {
if (openedFrag != null && openedFrag.getEditor() != null) {
// Edit is in another 'opened' file
editInEditor(openedFrag.getEditor(), edit);
} else {
// Edit is in some other file which is not opened
// We should open that file and perform the edit
openedFrag = activity().openFile(file);
if(openedFrag != null && openedFrag.getEditor() != null) {
if (openedFrag != null && openedFrag.getEditor() != null) {
editInEditor(openedFrag.getEditor(), edit);
}
}
@@ -314,60 +314,75 @@ public class IDELanguageClientImpl implements ILanguageClient {
return Boolean.TRUE;
}
private void editInEditor (final IDEEditor editor, final TextEdit edit) {
private void editInEditor(final IDEEditor editor, final TextEdit edit) {
final Range range = edit.getRange();
final int startLine = range.getStart().getLine();
final int startCol = range.getStart().getColumn ();
final int startCol = range.getStart().getColumn();
final int endLine = range.getEnd().getLine();
final int endCol = range.getEnd().getColumn ();
activity().runOnUiThread(() -> {
if(startLine == endLine && startCol == endCol) {
editor.getText().insert(startLine, startCol, edit.getNewText());
} else {
editor.getText().replace(startLine, startCol, endLine, endCol, edit.getNewText());
}
});
final int endCol = range.getEnd().getColumn();
activity()
.runOnUiThread(
() -> {
if (startLine == endLine && startCol == endCol) {
editor.getText().insert(startLine, startCol, edit.getNewText());
} else {
editor.getText()
.replace(
startLine,
startCol,
endLine,
endCol,
edit.getNewText());
}
});
}
public DiagnosticsAdapter newDiagnosticsAdapter() {
return new DiagnosticsAdapter(mapAsGroup(this.diagnostics), activity());
}
@Override
public com.itsaky.lsp.models.ShowDocumentResult showDocument (com.itsaky.lsp.models.ShowDocumentParams params) {
public com.itsaky.lsp.models.ShowDocumentResult showDocument(
com.itsaky.lsp.models.ShowDocumentParams params) {
boolean success = false;
final var result = new com.itsaky.lsp.models.ShowDocumentResult (false);
if(activity() == null) {
final var result = new com.itsaky.lsp.models.ShowDocumentResult(false);
if (activity() == null) {
return result;
}
if(params != null) {
File file = params.getFile ().toFile ();
if (file.exists () && file.isFile () && FileUtils.isUtf8 (file)) {
final var range = params.getSelection ();
var frag = activity ().getEditorAtIndex (
activity ().getBinding ().tabs.getSelectedTabPosition ());
if (params != null) {
File file = params.getFile().toFile();
if (file.exists() && file.isFile() && FileUtils.isUtf8(file)) {
final var range = params.getSelection();
var frag =
activity()
.getEditorAtIndex(
activity().getBinding().tabs.getSelectedTabPosition());
if (frag != null
&& frag.getFile () != null
&& frag.getEditor () != null
&& frag.getFile ().getAbsolutePath ().equals (file.getAbsolutePath ())) {
if (LSPUtils.isEqual (range.getStart (), range.getEnd ())) {
frag.getEditor ().setSelection (range.getStart ().getLine (), range.getStart ().getColumn ());
&& frag.getFile() != null
&& frag.getEditor() != null
&& frag.getFile().getAbsolutePath().equals(file.getAbsolutePath())) {
if (LSPUtils.isEqual(range.getStart(), range.getEnd())) {
frag.getEditor()
.setSelection(
range.getStart().getLine(), range.getStart().getColumn());
} else {
frag.getEditor ().setSelectionRegion (range.getStart ().getLine (),
range.getStart ().getColumn (),
range.getEnd ().getLine (),
range.getEnd ().getColumn ());
frag.getEditor()
.setSelectionRegion(
range.getStart().getLine(),
range.getStart().getColumn(),
range.getEnd().getLine(),
range.getEnd().getColumn());
}
} else {
activity ().openFileAndSelect (file, range);
activity().openFileAndSelect(file, range);
}
success = true;
}
}
result.setSuccess(success);
return result;
}
@@ -1,42 +1,43 @@
/************************************************************************************
* This file is part of AndroidIDE.
*
*
* AndroidIDE is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
*
* AndroidIDE is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
*
* You should have received a copy of the GNU General Public License
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
*
**************************************************************************************/
**************************************************************************************/
package com.itsaky.androidide.models;
import java.util.List;
import com.google.gson.annotations.SerializedName;
import java.util.List;
public class ApkMetadata {
@SerializedName("version")
private int version;
@SerializedName("artifactType")
private ArtifactType artifactType;
@SerializedName("applicationId")
private String applicationId;
@SerializedName("variantName")
private String variantName;
@SerializedName("elements")
private List<Element> elements;
@SerializedName("elementType")
private String elementType;
@@ -89,128 +90,141 @@ public class ApkMetadata {
}
public static class ArtifactType {
public static String TYPE_APK = "APK";
@SerializedName("type")
private String type;
@SerializedName("kind")
private String kind;
public void setType(String type) {
this.type = type;
}
public void setKind(String kind) {
this.kind = kind;
}
public String getType() {
return type;
}
public String getKind() {
return kind;
}
@Override
public String toString() {
return "["+getClass().getCanonicalName()+"]"
+ "\ntype=" + type
+ ",\nkind=" + kind;
return "[" + getClass().getCanonicalName() + "]" + "\ntype=" + type + ",\nkind=" + kind;
}
}
public static class Element {
@SerializedName("type")
private String type;
@SerializedName("filters")
private List<Object> filters;
@SerializedName("attributes")
private List<Object> attributes;
@SerializedName("versionCode")
private int versionCode;
@SerializedName("versionName")
private String versionName;
@SerializedName("outputFile")
private String outputFile;
public void setType(String type) {
this.type = type;
}
public void setFilters(List<Object> filters) {
this.filters = filters;
}
public void setAttributes(List<Object> attributes) {
this.attributes = attributes;
}
public void setVersionCode(int versionCode) {
this.versionCode = versionCode;
}
public void setVersionName(String versionName) {
this.versionName = versionName;
}
public void setOutputFile(String outputFile) {
this.outputFile = outputFile;
}
public String getType() {
return type;
}
public List<Object> getFilters() {
return filters;
}
public List<Object> getAttributes() {
return attributes;
}
public int getVersionCode() {
return versionCode;
}
public String getVersionName() {
return versionName;
}
public String getOutputFile() {
return outputFile;
}
@Override
public String toString() {
return "["+getClass().getCanonicalName()+"]"
+ "\ntype=" + type
+ ",\nfilters=" + filters
+ ",\nattributes=" + attributes
+ ",\nversionCode=" + versionCode
+ ",\nversionName=" + versionName
+ ",\noutputFile=" + outputFile;
return "["
+ getClass().getCanonicalName()
+ "]"
+ "\ntype="
+ type
+ ",\nfilters="
+ filters
+ ",\nattributes="
+ attributes
+ ",\nversionCode="
+ versionCode
+ ",\nversionName="
+ versionName
+ ",\noutputFile="
+ outputFile;
}
}
@Override
public String toString() {
return "["+getClass().getCanonicalName()+"]"
+ "\nversion=" + version
+ ",\nartifactType=" + artifactType
+ ",\napplicationId=" + applicationId
+ ",\nvariantName=" + variantName
+ ",\nelements=" + elements
+ ",\nelementType=" + elementType;
return "["
+ getClass().getCanonicalName()
+ "]"
+ "\nversion="
+ version
+ ",\nartifactType="
+ artifactType
+ ",\napplicationId="
+ applicationId
+ ",\nvariantName="
+ variantName
+ ",\nelements="
+ elements
+ ",\nelementType="
+ elementType;
}
}
@@ -1,20 +1,20 @@
/************************************************************************************
* This file is part of AndroidIDE.
*
*
* AndroidIDE is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
*
* AndroidIDE is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
*
* You should have received a copy of the GNU General Public License
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
*
**************************************************************************************/
**************************************************************************************/
package com.itsaky.androidide.models;
import com.itsaky.lsp.models.DiagnosticItem;
@@ -21,22 +21,21 @@ import androidx.annotation.DrawableRes;
public interface IconTextListItem {
String getText ();
String getText();
int getIconResource ();
public static IconTextListItem create (String name, @DrawableRes int icon) {
return new IconTextListItem () {
int getIconResource();
public static IconTextListItem create(String name, @DrawableRes int icon) {
return new IconTextListItem() {
@Override
public String getText () {
public String getText() {
return name;
}
@Override
public int getIconResource () {
public int getIconResource() {
return icon;
}
};
}
}
@@ -1,20 +1,20 @@
/************************************************************************************
* This file is part of AndroidIDE.
*
*
* AndroidIDE is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
*
* AndroidIDE is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
*
* You should have received a copy of the GNU General Public License
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
*
**************************************************************************************/
**************************************************************************************/
package com.itsaky.androidide.models;
import com.google.gson.annotations.SerializedName;
@@ -22,10 +22,10 @@ import com.google.gson.annotations.SerializedName;
public class License {
@SerializedName("name")
public String name;
@SerializedName("license")
public String license;
@SerializedName("url")
public String url;
@@ -1,16 +1,16 @@
/*
* This file is part of AndroidIDE.
*
*
* AndroidIDE is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
*
* AndroidIDE is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
*
* You should have received a copy of the GNU General Public License
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
*
@@ -28,101 +28,120 @@ import com.itsaky.androidide.syntax.colorschemes.SchemeAndroidIDE;
import java.util.Locale;
public class LogLine {
public String unformatted;
public String date;
public String time;
public String pid;
public String tid;
public String priorityChar;
public String tag;
public String message;
public int priority;
public boolean formatted;
// It makes things easier in LogLanguageImpl
public static final int INFO = SchemeAndroidIDE.LOG_INFO;
public static final int DEBUG = SchemeAndroidIDE.LOG_DEBUG;
public static final int ERROR = SchemeAndroidIDE.LOG_ERROR;
public static final int WARNING = SchemeAndroidIDE.LOG_WARNING;
public LogLine (String date, String time, String pid, String tid, String priorityChar, String tag, String message) {
this (date, time, pid, tid, priorityChar, tag, message, true);
}
public LogLine (String unformatted) {
this.unformatted = unformatted;
this.formatted = false;
}
public LogLine(String date, String time, String pid, String tid, String priorityChar, @NonNull String tag, String message, boolean formatted) {
this.date = date;
this.time = time;
this.pid = pid;
this.tid = tid;
this.priorityChar = priorityChar;
this.tag = tag;
this.message = message;
this.priority = parsePriority(priorityChar);
this.formatted = formatted;
if(tag.length() > 25) {
public String unformatted;
public String date;
public String time;
public String pid;
public String tid;
public String priorityChar;
public String tag;
public String message;
public int priority;
public boolean formatted;
// It makes things easier in LogLanguageImpl
public static final int INFO = SchemeAndroidIDE.LOG_INFO;
public static final int DEBUG = SchemeAndroidIDE.LOG_DEBUG;
public static final int ERROR = SchemeAndroidIDE.LOG_ERROR;
public static final int WARNING = SchemeAndroidIDE.LOG_WARNING;
public LogLine(
String date,
String time,
String pid,
String tid,
String priorityChar,
String tag,
String message) {
this(date, time, pid, tid, priorityChar, tag, message, true);
}
public LogLine(String unformatted) {
this.unformatted = unformatted;
this.formatted = false;
}
public LogLine(
String date,
String time,
String pid,
String tid,
String priorityChar,
@NonNull String tag,
String message,
boolean formatted) {
this.date = date;
this.time = time;
this.pid = pid;
this.tid = tid;
this.priorityChar = priorityChar;
this.tag = tag;
this.message = message;
this.priority = parsePriority(priorityChar);
this.formatted = formatted;
if (tag.length() > 25) {
this.tag = "...".concat(tag.substring(tag.length() - 25));
}
}
@NonNull
public static LogLine forLogString (@NonNull final String log) {
try {
final var split = log.split ("\\s", 7);
return new LogLine(
split[0], // date
split[1], // time
split[2], // process id
split[3], // thread id
split[4], // priority
split[5], // tag
split[6] // message
);
} catch (Throwable th) {
return new LogLine (log);
}
}
private int parsePriority(String s) {
if(s == null || s.trim().length() > 1) {
return DEBUG;
}
char c = s.toLowerCase(Locale.US).charAt(0);
if(c == 'w') {
return WARNING;
} else if(c == 'e') {
return ERROR;
} else if(c == 'i') {
return INFO;
} else {
return DEBUG;
}
}
public int getColor(Context ctx) {
int id = R.color.log_normal;
if(priority == WARNING) {
id = R.color.log_warning;
} else if(priority == ERROR) {
id = R.color.log_error;
} else if(priority == INFO) {
id = R.color.log_info;
}
return ContextCompat.getColor(ctx, id);
}
}
@NonNull
@Override
public String toString() {
return this.formatted ? String.format("%-6s %-13s %-6s %-6s %-2s %-40s %s", date, time, pid, tid, priorityChar, tag, message) : this.unformatted;
}
@NonNull
public static LogLine forLogString(@NonNull final String log) {
try {
final var split = log.split("\\s", 7);
return new LogLine(
split[0], // date
split[1], // time
split[2], // process id
split[3], // thread id
split[4], // priority
split[5], // tag
split[6] // message
);
} catch (Throwable th) {
return new LogLine(log);
}
}
private int parsePriority(String s) {
if (s == null || s.trim().length() > 1) {
return DEBUG;
}
char c = s.toLowerCase(Locale.US).charAt(0);
if (c == 'w') {
return WARNING;
} else if (c == 'e') {
return ERROR;
} else if (c == 'i') {
return INFO;
} else {
return DEBUG;
}
}
public int getColor(Context ctx) {
int id = R.color.log_normal;
if (priority == WARNING) {
id = R.color.log_warning;
} else if (priority == ERROR) {
id = R.color.log_error;
} else if (priority == INFO) {
id = R.color.log_info;
}
return ContextCompat.getColor(ctx, id);
}
@NonNull
@Override
public String toString() {
return this.formatted
? String.format(
"%-6s %-13s %-6s %-6s %-2s %-40s %s",
date, time, pid, tid, priorityChar, tag, message)
: this.unformatted;
}
}
@@ -1,71 +1,71 @@
/************************************************************************************
* This file is part of AndroidIDE.
*
*
* AndroidIDE is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
*
* AndroidIDE is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
*
* You should have received a copy of the GNU General Public License
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
*
**************************************************************************************/
**************************************************************************************/
package com.itsaky.androidide.models;
public class NewProjectDetails {
public String name;
public String packageName;
public int minSdk;
public int targetSdk;
public NewProjectDetails(){}
public NewProjectDetails(String name, String packageName, int minSdk, int targetSdk) {
this.name = name;
this.packageName = packageName;
this.minSdk = minSdk;
this.targetSdk = targetSdk;
}
public String name;
public String packageName;
public int minSdk;
public int targetSdk;
public NewProjectDetails setName(String name) {
this.name = name;
return this;
}
public NewProjectDetails() {}
public String getName() {
return name;
}
public NewProjectDetails(String name, String packageName, int minSdk, int targetSdk) {
this.name = name;
this.packageName = packageName;
this.minSdk = minSdk;
this.targetSdk = targetSdk;
}
public NewProjectDetails setPackageName(String packageName) {
this.packageName = packageName;
return this;
}
public NewProjectDetails setName(String name) {
this.name = name;
return this;
}
public String getPackageName() {
return packageName;
}
public String getName() {
return name;
}
public NewProjectDetails setMinSdk(int minSdk) {
this.minSdk = minSdk;
return this;
}
public NewProjectDetails setPackageName(String packageName) {
this.packageName = packageName;
return this;
}
public int getMinSdk() {
return minSdk;
}
public String getPackageName() {
return packageName;
}
public NewProjectDetails setTargetSdk(int targetSdk) {
this.targetSdk = targetSdk;
return this;
}
public NewProjectDetails setMinSdk(int minSdk) {
this.minSdk = minSdk;
return this;
}
public int getTargetSdk() {
return targetSdk;
}
public int getMinSdk() {
return minSdk;
}
public NewProjectDetails setTargetSdk(int targetSdk) {
this.targetSdk = targetSdk;
return this;
}
public int getTargetSdk() {
return targetSdk;
}
}
@@ -25,31 +25,32 @@ import com.itsaky.lsp.java.models.DefaultJavaServerSettings;
/**
* Server settings for the java language server.
*
* @author Akash Yadav
*/
public class PrefBasedJavaServerSettings extends DefaultJavaServerSettings {
public static final String KEY_JAVA_PREF_MATCH_LOWER = "idepref_editor_java_matchLower";
private static PrefBasedJavaServerSettings instance;
private final PreferenceManager prefs;
public PrefBasedJavaServerSettings () {
this.prefs = StudioApp.getInstance ().getPrefManager ();
public PrefBasedJavaServerSettings() {
this.prefs = StudioApp.getInstance().getPrefManager();
}
@NonNull
public static PrefBasedJavaServerSettings getInstance () {
public static PrefBasedJavaServerSettings getInstance() {
if (instance == null) {
instance = new PrefBasedJavaServerSettings ();
instance = new PrefBasedJavaServerSettings();
}
return instance;
}
@Override
public boolean shouldMatchAllLowerCase () {
return prefs.getBoolean (KEY_JAVA_PREF_MATCH_LOWER, false);
public boolean shouldMatchAllLowerCase() {
return prefs.getBoolean(KEY_JAVA_PREF_MATCH_LOWER, false);
}
}
@@ -1,86 +1,85 @@
/************************************************************************************
* This file is part of AndroidIDE.
*
*
* AndroidIDE is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
*
* AndroidIDE is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
*
* You should have received a copy of the GNU General Public License
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
*
**************************************************************************************/
**************************************************************************************/
package com.itsaky.androidide.models;
import android.content.Context;
import androidx.annotation.DrawableRes;
import androidx.annotation.StringRes;
import android.content.Context;
public class ProjectTemplate {
private int id;
private String name;
private String description;
@DrawableRes
private int imageId;
public ProjectTemplate() {
}
public ProjectTemplate(int id, String name, String description, int imageId) {
this.id = id;
this.name = name;
this.description = description;
this.imageId = imageId;
}
public ProjectTemplate setId(int id) {
this.id = id;
return this;
}
private int id;
private String name;
private String description;
public int getId() {
return id;
}
public ProjectTemplate setName(Context ctx, @StringRes int name) {
return setName(ctx.getString(name));
}
@DrawableRes private int imageId;
public ProjectTemplate setName(String name) {
this.name = name;
return this;
}
public ProjectTemplate() {}
public String getName() {
return name;
}
public ProjectTemplate setDescription(Context ctx, @StringRes int desc) {
return setDescription(ctx.getString(desc));
}
public ProjectTemplate(int id, String name, String description, int imageId) {
this.id = id;
this.name = name;
this.description = description;
this.imageId = imageId;
}
public ProjectTemplate setDescription(String description) {
this.description = description;
return this;
}
public ProjectTemplate setId(int id) {
this.id = id;
return this;
}
public String getDescription() {
return description;
}
public int getId() {
return id;
}
public ProjectTemplate setImageId(@DrawableRes int imageId) {
this.imageId = imageId;
return this;
}
public ProjectTemplate setName(Context ctx, @StringRes int name) {
return setName(ctx.getString(name));
}
public int getImageId() {
return imageId;
}
public ProjectTemplate setName(String name) {
this.name = name;
return this;
}
public String getName() {
return name;
}
public ProjectTemplate setDescription(Context ctx, @StringRes int desc) {
return setDescription(ctx.getString(desc));
}
public ProjectTemplate setDescription(String description) {
this.description = description;
return this;
}
public String getDescription() {
return description;
}
public ProjectTemplate setImageId(@DrawableRes int imageId) {
this.imageId = imageId;
return this;
}
public int getImageId() {
return imageId;
}
}
@@ -1,20 +1,20 @@
/************************************************************************************
* This file is part of AndroidIDE.
*
*
* AndroidIDE is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
*
* AndroidIDE is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
*
* You should have received a copy of the GNU General Public License
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
*
**************************************************************************************/
**************************************************************************************/
package com.itsaky.androidide.models;
import com.itsaky.lsp.models.Range;
@@ -25,9 +25,9 @@ public class SearchResult extends Range {
public File file;
public String line;
public String match;
public SearchResult(Range src, File file, String line, String match) {
super(src.getStart (), src.getEnd ());
super(src.getStart(), src.getEnd());
this.file = file;
this.line = line;
this.match = match;
@@ -26,8 +26,7 @@ import androidx.annotation.DrawableRes;
import java.util.Objects;
/**
* Holds data about the widget item in widgets list shown in
* UI Designer screen.
* Holds data about the widget item in widgets list shown in UI Designer screen.
*
* @author Akash Yadav
*/
@@ -38,7 +37,7 @@ public class UIWidget implements IconTextListItem, Parcelable {
private final String clazz;
private UIWidget (Parcel in) {
private UIWidget(Parcel in) {
this.name = in.readString();
this.icon = in.readInt();
this.clazz = in.readString();
@@ -50,17 +49,18 @@ public class UIWidget implements IconTextListItem, Parcelable {
this.clazz = clazz.getName();
}
public static final Creator<UIWidget> CREATOR = new Creator<UIWidget>() {
@Override
public UIWidget createFromParcel(Parcel in) {
return new UIWidget(in);
}
public static final Creator<UIWidget> CREATOR =
new Creator<UIWidget>() {
@Override
public UIWidget createFromParcel(Parcel in) {
return new UIWidget(in);
}
@Override
public UIWidget[] newArray(int size) {
return new UIWidget[size];
}
};
@Override
public UIWidget[] newArray(int size) {
return new UIWidget[size];
}
};
public String getName() {
return name;
@@ -75,7 +75,9 @@ public class UIWidget implements IconTextListItem, Parcelable {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
UIWidget widget = (UIWidget) o;
return getIcon() == widget.getIcon() && getName().equals(widget.getName()) && clazz.equals(widget.clazz);
return getIcon() == widget.getIcon()
&& getName().equals(widget.getName())
&& clazz.equals(widget.clazz);
}
@Override
@@ -85,15 +87,16 @@ public class UIWidget implements IconTextListItem, Parcelable {
/**
* Get the class of this widget.
*
* @return The class that this widget represents
* @throws ClassNotFoundException If there is no class with this widget's class name.
* However, this will never be thrown.
* @throws ClassNotFoundException If there is no class with this widget's class name. However,
* this will never be thrown.
*/
public Class<? extends View> asClass () throws ClassNotFoundException {
public Class<? extends View> asClass() throws ClassNotFoundException {
return getClass().getClassLoader().loadClass(this.clazz).asSubclass(View.class);
}
public String getWidgetClassName () {
public String getWidgetClassName() {
return this.clazz;
}
@@ -20,7 +20,6 @@ package com.itsaky.androidide.models;
import android.os.Parcel;
import android.os.Parcelable;
import androidx.annotation.DrawableRes;
import androidx.annotation.NonNull;
import org.jetbrains.annotations.Contract;
@@ -30,28 +29,22 @@ import java.util.List;
import java.util.Objects;
/**
* Model class for holding data about a group that will be displayed
* in Widgets list in UI Designer screen.
* Model class for holding data about a group that will be displayed in Widgets list in UI Designer
* screen.
*
* A UIWidgetGroup contains multiple {@link UIWidget}.
* <p>A UIWidgetGroup contains multiple {@link UIWidget}.
*
* @author Akash Yadav
*/
public class UIWidgetGroup implements IconTextListItem, Parcelable {
/**
* Name of this group.
*/
/** Name of this group. */
private final String name;
/**
* Children of this group.
*/
/** Children of this group. */
private final List<UIWidget> children;
/**
* Is this group currently selected in the group list?
*/
/** Is this group currently selected in the group list? */
private boolean selected;
public UIWidgetGroup(String name) {
@@ -66,25 +59,27 @@ public class UIWidgetGroup implements IconTextListItem, Parcelable {
this.selected = in.readByte() != 0;
}
public static final Creator<UIWidgetGroup> CREATOR = new Creator<UIWidgetGroup>() {
public static final Creator<UIWidgetGroup> CREATOR =
new Creator<UIWidgetGroup>() {
@NonNull
@Contract("_ -> new")
@Override
public UIWidgetGroup createFromParcel(Parcel in) {
return new UIWidgetGroup(in);
}
@NonNull
@Contract("_ -> new")
@Override
public UIWidgetGroup createFromParcel(Parcel in) {
return new UIWidgetGroup(in);
}
@NonNull
@Contract(value = "_ -> new", pure = true)
@Override
public UIWidgetGroup[] newArray(int size) {
return new UIWidgetGroup[size];
}
};
@NonNull
@Contract(value = "_ -> new", pure = true)
@Override
public UIWidgetGroup[] newArray(int size) {
return new UIWidgetGroup[size];
}
};
/**
* Get the name of this group.
*
* @return The name of this group.
*/
public String getName() {
@@ -93,6 +88,7 @@ public class UIWidgetGroup implements IconTextListItem, Parcelable {
/**
* Get the children of this group.
*
* @return The children.
*/
@NonNull
@@ -110,12 +106,13 @@ public class UIWidgetGroup implements IconTextListItem, Parcelable {
/**
* Add this widget to this group. Does nothing if the child is already present.
*
* @param child The child to add.
* @return This instance for chained method calls.
*/
public UIWidgetGroup addChild (UIWidget child) {
public UIWidgetGroup addChild(UIWidget child) {
if (children.contains(child)) {
return this;
return this;
}
this.children.add(child);
return this;
@@ -123,11 +120,12 @@ public class UIWidgetGroup implements IconTextListItem, Parcelable {
/**
* Get the child at the given index.
*
* @param index The index of the child.
* @return The child at the index.
* @throws IndexOutOfBoundsException If the given index is not valid.
*/
public UIWidget getChild (int index ){
public UIWidget getChild(int index) {
return children.get(index);
}
@@ -136,7 +134,9 @@ public class UIWidgetGroup implements IconTextListItem, Parcelable {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
UIWidgetGroup group = (UIWidgetGroup) o;
return isSelected() == group.isSelected() && getName().equals(group.getName()) && getChildren().equals(group.getChildren());
return isSelected() == group.isSelected()
&& getName().equals(group.getName())
&& getChildren().equals(group.getChildren());
}
@Override
@@ -38,135 +38,141 @@ import java.util.Objects;
* @author Akash Yadav
*/
public class XMLAttribute extends UiAttribute implements Parcelable {
public static final Creator<XMLAttribute> CREATOR = new Creator<> () {
@NonNull
@Contract("_ -> new")
@Override
public XMLAttribute createFromParcel (Parcel in) {
return new XMLAttribute (in);
}
@NonNull
@Contract(value = "_ -> new", pure = true)
@Override
public XMLAttribute[] newArray (int size) {
return new XMLAttribute[size];
}
};
public static final Creator<XMLAttribute> CREATOR =
new Creator<>() {
@NonNull
@Contract("_ -> new")
@Override
public XMLAttribute createFromParcel(Parcel in) {
return new XMLAttribute(in);
}
@NonNull
@Contract(value = "_ -> new", pure = true)
@Override
public XMLAttribute[] newArray(int size) {
return new XMLAttribute[size];
}
};
public Attr attr;
/**
* Is this attribute applied to any view?
*/
/** Is this attribute applied to any view? */
private boolean isApplied;
/**
* Format of this attribute. May be stored here so we don't
* have to look in {@link com.itsaky.attrinfo.AttrInfo AttrInfo}
* again and again.
* Format of this attribute. May be stored here so we don't have to look in {@link
* com.itsaky.attrinfo.AttrInfo AttrInfo} again and again.
*/
private int format = -1;
public XMLAttribute (IAttribute attribute) {
this (attribute, true);
public XMLAttribute(IAttribute attribute) {
this(attribute, true);
}
public XMLAttribute (@NonNull IAttribute attribute, boolean isApplied) {
this (attribute.getNamespace (), attribute.getAttributeName (), attribute.getValue (), isApplied);
public XMLAttribute(@NonNull IAttribute attribute, boolean isApplied) {
this(
attribute.getNamespace(),
attribute.getAttributeName(),
attribute.getValue(),
isApplied);
}
public XMLAttribute (INamespace namespace, String name, String value, boolean isApplied) {
super (namespace, name, value);
public XMLAttribute(INamespace namespace, String name, String value, boolean isApplied) {
super(namespace, name, value);
this.isApplied = isApplied;
}
private XMLAttribute (@NonNull Parcel in) {
this (in.readParcelable (INamespace.class.getClassLoader ()), in.readString (), in.readString (), in.readByte () != 0);
private XMLAttribute(@NonNull Parcel in) {
this(
in.readParcelable(INamespace.class.getClassLoader()),
in.readString(),
in.readString(),
in.readByte() != 0);
}
/**
* Create a new attribute instance with the android namespace.
* {@link #isApplied()} will return true by default.
* Create a new attribute instance with the android namespace. {@link #isApplied()} will return
* true by default.
*
* @param name The name of the attribute.
* @param name The name of the attribute.
* @param value The value of the attribute.
* @return A new instance of this class with the provided data.
*/
@NonNull
@Contract("_, _ -> new")
public static XMLAttribute newAndroidAttribute (String name, String value) {
return new XMLAttribute (INamespace.ANDROID, name, value, true);
public static XMLAttribute newAndroidAttribute(String name, String value) {
return new XMLAttribute(INamespace.ANDROID, name, value, true);
}
/**
* Create a new attribute instance with the android namespace.
* The {@link #isApplied()} will return {@code false} by default.
* Create a new attribute instance with the android namespace. The {@link #isApplied()} will
* return {@code false} by default.
*
* @param name The name of the attribute.
* @return The new attribute instance.
*/
@NonNull
@Contract("_ -> new")
public static XMLAttribute newAndroidAttribute (String name) {
return new XMLAttribute (INamespace.ANDROID, name, "", false);
public static XMLAttribute newAndroidAttribute(String name) {
return new XMLAttribute(INamespace.ANDROID, name, "", false);
}
public int getFormat () {
public int getFormat() {
return format;
}
public void setFormat (int format) {
public void setFormat(int format) {
this.format = format;
}
public int findFormat () {
if (getFormat () > 0) {
return getFormat ();
public int findFormat() {
if (getFormat() > 0) {
return getFormat();
}
final var attrs = StudioApp.getInstance ().attrInfo ();
this.attr = attrs.getAttribute (getAttributeName ());
final var attrs = StudioApp.getInstance().attrInfo();
this.attr = attrs.getAttribute(getAttributeName());
if (this.attr != null) {
this.format = this.attr.format;
}
return getFormat ();
return getFormat();
}
public boolean hasFormat (int format) {
public boolean hasFormat(int format) {
return (this.format & format) != 0;
}
public Attr getAttr () {
public Attr getAttr() {
return attr;
}
public void setAttr (Attr attr) {
Objects.requireNonNull (attr);
public void setAttr(Attr attr) {
Objects.requireNonNull(attr);
this.attr = attr;
setFormat (attr.format);
setFormat(attr.format);
}
@Override
public void apply (String value) {
super.apply (value);
public void apply(String value) {
super.apply(value);
isApplied = true;
}
public boolean isApplied () {
public boolean isApplied() {
return isApplied;
}
@Override
public int describeContents () {
public int describeContents() {
return 0;
}
@Override
public void writeToParcel (@NonNull Parcel dest, int flags) {
dest.writeParcelable (getNamespace (), flags);
dest.writeString (getAttributeName ());
dest.writeString (getValue ());
dest.writeByte ((byte) (isApplied ? 1 : 0));
public void writeToParcel(@NonNull Parcel dest, int flags) {
dest.writeParcelable(getNamespace(), flags);
dest.writeString(getAttributeName());
dest.writeString(getValue());
dest.writeByte((byte) (isApplied ? 1 : 0));
}
}
@@ -1,87 +1,88 @@
/************************************************************************************
* This file is part of AndroidIDE.
*
*
*
*
* AndroidIDE is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
*
* AndroidIDE is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
*
* You should have received a copy of the GNU General Public License
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
*
**************************************************************************************/
**************************************************************************************/
package com.itsaky.androidide.project;
import android.os.Parcel;
import android.os.Parcelable;
import com.itsaky.androidide.app.StudioApp;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
public class AndroidProject implements Parcelable {
private String appName;
private String packageName;
private String iconPath;
private String projectPath;
private String mainModule;
private String mainSourcePath;
private String appName;
private String packageName;
private String iconPath;
private String projectPath;
private String mainModule;
private String mainSourcePath;
private List<String> modulePaths = new ArrayList<>();
private List<String> sourcePaths = new ArrayList<>();
private List<String> classPaths = new ArrayList<>();
public AndroidProject() {
addSource(StudioApp.getInstance().getLogSenderDir().getAbsolutePath());
}
public AndroidProject() {
addSource(StudioApp.getInstance().getLogSenderDir().getAbsolutePath());
}
protected AndroidProject(Parcel in) {
appName = in.readString();
packageName = in.readString();
iconPath = in.readString();
projectPath = in.readString();
mainModule = in.readString();
mainSourcePath = in.readString();
protected AndroidProject(Parcel in) {
appName = in.readString();
packageName = in.readString();
iconPath = in.readString();
projectPath = in.readString();
mainModule = in.readString();
mainSourcePath = in.readString();
in.readList(modulePaths, String.class.getClassLoader());
in.readList(sourcePaths, String.class.getClassLoader());
in.readList(classPaths, String.class.getClassLoader());
}
public File getBuildDir() {
return new File(getMainModulePath(), "build");
}
}
public AndroidProject setMainModule(String mainModule) {
this.mainModule = mainModule;
return this;
}
public File getBuildDir() {
return new File(getMainModulePath(), "build");
}
public String getMainModule() {
return mainModule;
}
public String getMainModulePath() {
return new File(getProjectPath(), getMainModule()).getAbsolutePath();
}
public AndroidProject setMainModule(String mainModule) {
this.mainModule = mainModule;
return this;
}
public AndroidProject setMainSourcePath(String mainModulePath) {
this.mainSourcePath = mainModulePath;
return this;
}
public String getMainModule() {
return mainModule;
}
public String getMainSourcePath() {
return mainSourcePath;
}
public String getMainModulePath() {
return new File(getProjectPath(), getMainModule()).getAbsolutePath();
}
public AndroidProject setMainSourcePath(String mainModulePath) {
this.mainSourcePath = mainModulePath;
return this;
}
public String getMainSourcePath() {
return mainSourcePath;
}
public AndroidProject setModulePaths(List<String> modulePaths) {
this.modulePaths = modulePaths;
@@ -94,8 +95,7 @@ public class AndroidProject implements Parcelable {
}
public AndroidProject addModule(String path) {
if (!getModulePaths().contains(path))
getModulePaths().add(path);
if (!getModulePaths().contains(path)) getModulePaths().add(path);
return this;
}
@@ -110,8 +110,7 @@ public class AndroidProject implements Parcelable {
}
public AndroidProject addSource(String path) {
if (!getSourcePaths().contains(path))
getSourcePaths().add(path);
if (!getSourcePaths().contains(path)) getSourcePaths().add(path);
return this;
}
@@ -126,91 +125,101 @@ public class AndroidProject implements Parcelable {
}
public AndroidProject addClasspath(String path) {
if (!getClassPaths().contains(path))
getClassPaths().add(path);
if (!getClassPaths().contains(path)) getClassPaths().add(path);
return this;
}
public AndroidProject setAppName(String appName) {
this.appName = appName;
public AndroidProject setAppName(String appName) {
this.appName = appName;
return this;
}
}
public String getAppName() {
return appName;
}
public String getAppName() {
return appName;
}
public AndroidProject setPackageName(String packageName) {
this.packageName = packageName;
public AndroidProject setPackageName(String packageName) {
this.packageName = packageName;
return this;
}
}
public String getPackageName() {
return packageName;
}
public String getPackageName() {
return packageName;
}
public AndroidProject setIconPath(String iconPath) {
this.iconPath = iconPath;
public AndroidProject setIconPath(String iconPath) {
this.iconPath = iconPath;
return this;
}
}
public String getIconPath() {
return iconPath;
}
public String getIconPath() {
return iconPath;
}
public AndroidProject setProjectPath(String projectPath) {
this.projectPath = projectPath;
public AndroidProject setProjectPath(String projectPath) {
this.projectPath = projectPath;
return this;
}
}
public String getProjectPath() {
return projectPath;
}
public String getProjectPath() {
return projectPath;
}
@Override
public int describeContents() {
return 0;
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel p1, int p2) {
p1.writeString(appName);
p1.writeString(packageName);
p1.writeString(iconPath);
p1.writeString(projectPath);
p1.writeString(mainModule);
p1.writeString(mainSourcePath);
@Override
public void writeToParcel(Parcel p1, int p2) {
p1.writeString(appName);
p1.writeString(packageName);
p1.writeString(iconPath);
p1.writeString(projectPath);
p1.writeString(mainModule);
p1.writeString(mainSourcePath);
p1.writeList(modulePaths);
p1.writeList(sourcePaths);
p1.writeList(classPaths);
}
}
@Override
public String toString() {
return "["
+ "\nappName=" + getAppName()
+ "\npackageName=" + getPackageName()
+ "\niconPath=" + getIconPath()
+ "\nprojectPath=" + getProjectPath()
+ "\nmainModule=" + getMainModule()
+ "\nmainModulePath=" + getMainModulePath()
+ "\nmainModuleSourcePath=" + getMainSourcePath()
+ "\nmodules=" + getModulePaths()
+ "\nsources=" + getSourcePaths()
+ "\nclasspaths=" + getClassPaths()
+ "\n]";
}
@Override
public String toString() {
return "["
+ "\nappName="
+ getAppName()
+ "\npackageName="
+ getPackageName()
+ "\niconPath="
+ getIconPath()
+ "\nprojectPath="
+ getProjectPath()
+ "\nmainModule="
+ getMainModule()
+ "\nmainModulePath="
+ getMainModulePath()
+ "\nmainModuleSourcePath="
+ getMainSourcePath()
+ "\nmodules="
+ getModulePaths()
+ "\nsources="
+ getSourcePaths()
+ "\nclasspaths="
+ getClassPaths()
+ "\n]";
}
public static final Creator<AndroidProject> CREATOR = new Creator<AndroidProject>(){
public static final Creator<AndroidProject> CREATOR =
new Creator<AndroidProject>() {
@Override
public AndroidProject createFromParcel(Parcel p1) {
return new AndroidProject(p1);
}
@Override
public AndroidProject createFromParcel(Parcel p1) {
return new AndroidProject(p1);
}
@Override
public AndroidProject[] newArray(int p1) {
return new AndroidProject[p1];
}
};
@Override
public AndroidProject[] newArray(int p1) {
return new AndroidProject[p1];
}
};
}
@@ -1,20 +1,20 @@
/************************************************************************************
* This file is part of AndroidIDE.
*
*
* AndroidIDE is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
*
* AndroidIDE is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
*
* You should have received a copy of the GNU General Public License
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
*
**************************************************************************************/
**************************************************************************************/
package com.itsaky.androidide.project;
import com.google.gson.annotations.SerializedName;
@@ -22,65 +22,47 @@ import com.google.gson.annotations.SerializedName;
/**
* Represents a module (subproject) of a root project.
*
* In Android projects, a module can either be an application project,
* or a library project.
* <p>In Android projects, a module can either be an application project, or a library project.
*/
public class IDEModule extends IDEProject {
/**
* Build tools version of this project
*/
/** Build tools version of this project */
@SerializedName("buildToolsVersion")
public String buildToolsVersion;
/**
* Compile SDK version of this project
*
* Representation: 'android-31', 'android-30', 'android-29', etc
* <p>Representation: 'android-31', 'android-30', 'android-29', etc
*/
@SerializedName("compileSdkVersion")
public String compileSdkVersion;
/**
* Minimum SDK version of this project
*/
/** Minimum SDK version of this project */
@SerializedName("minSdk")
public SDK minSdk;
/**
* Target SDK version of this project
*/
/** Target SDK version of this project */
@SerializedName("targetSdk")
public SDK targetSdk;
/**
* Version code of this project
*/
/** Version code of this project */
@SerializedName("versionCode")
public int versionCode = 0;
/**
* Version name of this project
*/
/** Version name of this project */
@SerializedName("versionName")
public String versionName = "Not defined";
/**
* Is this project an Android library project?
*/
/** Is this project an Android library project? */
@SerializedName("isLibrary")
public boolean isLibrary = true;
/**
* Does this module has viewBinding enabled?
*/
/** Does this module has viewBinding enabled? */
@SerializedName("viewBindingEnabled")
public boolean viewBindingEnabled;
/**
* Library projects don't have applicationId, application projects do.
*/
/** Library projects don't have applicationId, application projects do. */
@SerializedName("applicationId")
public String applicationId;
}
@@ -5,84 +5,66 @@
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
*
* AndroidIDE is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
*
* You should have received a copy of the GNU General Public License
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
*
**************************************************************************************/
**************************************************************************************/
package com.itsaky.androidide.project;
import com.google.gson.annotations.SerializedName;
import java.util.List;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
/**
* A model that contains data of a project currently opened in AndroidIDE
*/
/** A model that contains data of a project currently opened in AndroidIDE */
public class IDEProject {
/**
* Name of this project
*/
/** Name of this project */
@SerializedName("name")
public String name = "";
/**
* Name of this project which should be displayed to user
*/
/** Name of this project which should be displayed to user */
@SerializedName("displayName")
public String displayName = "";
/**
* Description of this project. Maybe null.
*/
/** Description of this project. Maybe null. */
@SerializedName("description")
public String description = "";
/**
* Path of this project. This is NOT the directory path of this project.
*
* For example, ':app or ':module:subModule'
*
* <p>For example, ':app or ':module:subModule'
*/
@SerializedName("path")
public String path = "";
/**
* Path of the directory containing this project
*/
/** Path of the directory containing this project */
@SerializedName("projectDir")
public String projectDir;
/**
* File path of the application's icon
* We get this manually by parsing AndroidManifest.xml
*/
/** File path of the application's icon We get this manually by parsing AndroidManifest.xml */
public String iconPath;
/**
* Subprojects of this project
*/
/** Subprojects of this project */
@SerializedName("modules")
public List<IDEModule> modules = new ArrayList<>();
/**
* Tasks included in this project
*/
/** Tasks included in this project */
@SerializedName("tasks")
public List<IDETask> tasks = new ArrayList<>();
/**
* Path of jar file of dependencies of this project
*/
/** Path of jar file of dependencies of this project */
@SerializedName("dependencies")
public List<String> dependencies = new ArrayList<>();
/**
* Get the module by its path
*
@@ -90,29 +72,21 @@ public class IDEProject {
* @return An Optional containing the IDEModule or Optional.empty()
*/
public Optional<IDEModule> getModuleByPath(String path) {
if(modules != null && modules.size() > 0) {
for(IDEModule module : modules) {
if(module == null) continue;
if(module.path.trim().equals(path.trim()))
return Optional.of(module);
if (modules != null && modules.size() > 0) {
for (IDEModule module : modules) {
if (module == null) continue;
if (module.path.trim().equals(path.trim())) return Optional.of(module);
}
}
return Optional.empty();
}
/**
* Stores information about a SDK. Like,
* • Codename
* • API string
* • API level
*/
/** Stores information about a SDK. Like, • Codename • API string • API level */
public static class SDK {
@SerializedName("codename")
public String codename;
@SerializedName("apiString")
public String apiString;
@@ -1,54 +1,44 @@
/************************************************************************************
* This file is part of AndroidIDE.
*
*
* AndroidIDE is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
*
* AndroidIDE is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
*
* You should have received a copy of the GNU General Public License
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
*
**************************************************************************************/
**************************************************************************************/
package com.itsaky.androidide.project;
import com.google.gson.annotations.SerializedName;
/**
* Represents a Gradle task
*/
/** Represents a Gradle task */
public class IDETask {
/**
* Name of this task
*/
/** Name of this task */
@SerializedName("name")
public String name;
/**
* Description of this task
*/
/** Description of this task */
@SerializedName("description")
public String description;
/**
* Name of the group that this task belongs to
*/
/** Name of the group that this task belongs to */
@SerializedName("group")
public String group;
/**
* Path of this task
*
* For example, ':app:assembleDebug', ':app:build'
*
* <p>For example, ':app:assembleDebug', ':app:build'
*/
@SerializedName("path")
public String path;
}
@@ -17,30 +17,22 @@
package com.itsaky.androidide.project;
import android.os.Build;
import android.os.FileObserver;
import androidx.annotation.NonNull;
import androidx.annotation.RequiresApi;
import java.io.File;
import java.util.List;
/**
* A file observer that observer files in project directories.
* Sub classes need to provide the file extensions that they want to get
* notified about.
* A file observer that observer files in project directories. Sub classes need to provide the file
* extensions that they want to get notified about.
*
* @author Akash Yadav
*/
public abstract class ProjectFileObserver extends FileObserver {
// Cannot use constructors that accept java.io.File
// They were added in API 29
public ProjectFileObserver (String path) {
this (path, ALL_EVENTS);
public ProjectFileObserver(String path) {
this(path, ALL_EVENTS);
}
public ProjectFileObserver (String path, int mask) {
super (path, mask);
public ProjectFileObserver(String path, int mask) {
super(path, mask);
}
}
@@ -27,226 +27,227 @@ import java.io.File;
import java.io.FileFilter;
public class ProjectResourceTable implements IResourceTable {
private File resDir;
private File color;
private File layout;
private File[] drawables;
private File[] mipmaps;
private static final Logger LOG = Logger.instance ("ProjectResourceFinder");
private static final Logger LOG = Logger.instance("ProjectResourceFinder");
@Override
public File findDrawable (@NonNull String name) {
public File findDrawable(@NonNull String name) {
for (File drawable : drawables) {
final File file = findFileWithName (drawable.listFiles (), name);
final File file = findFileWithName(drawable.listFiles(), name);
if (file != null) {
return file;
}
}
return null;
}
@Override
public File findLayout (@NonNull String name) {
return findFileWithName (this.layout.listFiles (), name);
public File findLayout(@NonNull String name) {
return findFileWithName(this.layout.listFiles(), name);
}
@Override
public String findString (@NonNull String name) {
final var table = ValuesTableFactory.getTable (resDir);
public String findString(@NonNull String name) {
final var table = ValuesTableFactory.getTable(resDir);
if (table == null) {
return name;
}
final var resource = table.findString (name);
final var resource = table.findString(name);
if (resource == null) {
return name;
}
final var value = resource.getValue ();
if (value.startsWith ("@string/")) {
var ref = value.substring ("@string/".length ());
if (name.equals (ref)) {
final var value = resource.getValue();
if (value.startsWith("@string/")) {
var ref = value.substring("@string/".length());
if (name.equals(ref)) {
return name;
}
}
return value;
}
@Override
public String findBoolean (@NonNull String name) {
final var table = ValuesTableFactory.getTable (resDir);
public String findBoolean(@NonNull String name) {
final var table = ValuesTableFactory.getTable(resDir);
if (table == null) {
return name;
}
final var resource = table.findBoolean (name);
final var resource = table.findBoolean(name);
if (resource == null) {
return name;
}
final var val = resource.getValue ();
if (val.startsWith ("@bool/")) {
final var ref = val.substring ("@bool/".length ());
if (ref.equals (name)) {
final var val = resource.getValue();
if (val.startsWith("@bool/")) {
final var ref = val.substring("@bool/".length());
if (ref.equals(name)) {
// recursive reference
return name;
}
}
return val;
}
@Override
public String findInteger (@NonNull String name) {
final var table = ValuesTableFactory.getTable (resDir);
public String findInteger(@NonNull String name) {
final var table = ValuesTableFactory.getTable(resDir);
if (table == null) {
return name;
}
final var resource = table.findInteger (name);
final var resource = table.findInteger(name);
if (resource == null) {
return name;
}
final var val = resource.getValue ();
if (val.startsWith ("@integer/")) {
final var ref = val.substring ("@integer/".length ());
if (ref.equals (name)) {
final var val = resource.getValue();
if (val.startsWith("@integer/")) {
final var ref = val.substring("@integer/".length());
if (ref.equals(name)) {
// recursive reference
return name;
}
}
return val;
}
@Override
public String findColor (@NonNull String name) {
final var table = ValuesTableFactory.getTable (resDir);
public String findColor(@NonNull String name) {
final var table = ValuesTableFactory.getTable(resDir);
if (table == null) {
return name;
}
final var resource = table.findColor (name);
final var resource = table.findColor(name);
if (resource == null) {
return name;
}
final var value = resource.getValue ();
if (value.startsWith ("@color/")) {
var ref = value.substring ("@color/".length ());
if (name.equals (ref)) {
final var value = resource.getValue();
if (value.startsWith("@color/")) {
var ref = value.substring("@color/".length());
if (name.equals(ref)) {
return name;
}
}
return value;
}
@Override
public String[] findArray (@NonNull String name) {
final var table = ValuesTableFactory.getTable (resDir);
public String[] findArray(@NonNull String name) {
final var table = ValuesTableFactory.getTable(resDir);
if (table == null) {
return new String[]{name};
return new String[] {name};
}
final var resource = table.findArray (name);
final var resource = table.findArray(name);
if (resource == null) {
return new String[]{name};
return new String[] {name};
}
return resource.getValues ();
return resource.getValues();
}
@Override
public String findDimension (@NonNull String name) {
final var table = ValuesTableFactory.getTable (resDir);
public String findDimension(@NonNull String name) {
final var table = ValuesTableFactory.getTable(resDir);
if (table == null) {
return name;
}
final var resource = table.findDimension (name);
final var resource = table.findDimension(name);
if (resource == null) {
return name;
}
final var value = resource.getValue ();
if (value.startsWith ("@dimen/")) {
var ref = value.substring ("@dimen/".length ());
if (name.equals (ref)) {
final var value = resource.getValue();
if (value.startsWith("@dimen/")) {
var ref = value.substring("@dimen/".length());
if (name.equals(ref)) {
return name;
}
}
return value;
}
@Override
public void setInflatingFile (@NonNull File file) {
if (file.getParentFile () == null) {
throw new IllegalArgumentException ("Invalid inflating file");
public void setInflatingFile(@NonNull File file) {
if (file.getParentFile() == null) {
throw new IllegalArgumentException("Invalid inflating file");
}
setupDirectories (file // layout file
.getParentFile () // layout dir
.getParentFile ()); // res dir
setupDirectories(
file // layout file
.getParentFile() // layout dir
.getParentFile()); // res dir
}
private void setupDirectories (File resDir) {
private void setupDirectories(File resDir) {
if (resDir == null) {
LOG.error ("Null resource directory passed to resource finder. Ignoring.");
LOG.error("Null resource directory passed to resource finder. Ignoring.");
return;
}
this.resDir = resDir;
this.color = new File (resDir, "color");
this.layout = new File (resDir, "layout");
final File[] drawables = resDir.listFiles (new NameStartsWith ("drawable"));
this.color = new File(resDir, "color");
this.layout = new File(resDir, "layout");
final File[] drawables = resDir.listFiles(new NameStartsWith("drawable"));
this.drawables = drawables == null ? new File[0] : drawables;
final File[] mipmaps = resDir.listFiles (new NameStartsWith ("mipmap"));
final File[] mipmaps = resDir.listFiles(new NameStartsWith("mipmap"));
this.mipmaps = mipmaps == null ? new File[0] : mipmaps;
}
private File findFileWithName (File[] files, String name) {
private File findFileWithName(File[] files, String name) {
if (files == null || name == null) {
return null;
}
for (File file : files) {
var simpleName = file.getName ();
if (simpleName.startsWith (name)) {
var simpleName = file.getName();
if (simpleName.startsWith(name)) {
return file;
}
}
return null;
}
private static class NameStartsWith implements FileFilter {
private final String name;
public NameStartsWith (String name) {
public NameStartsWith(String name) {
this.name = name;
}
@Override
public boolean accept (@NonNull File file) {
return file.getName ().startsWith (name);
public boolean accept(@NonNull File file) {
return file.getName().startsWith(name);
}
}
}
@@ -1,70 +1,67 @@
/************************************************************************************
* This file is part of AndroidIDE.
*
*
*
*
* AndroidIDE is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
*
* AndroidIDE is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
*
* You should have received a copy of the GNU General Public License
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
*
**************************************************************************************/
**************************************************************************************/
package com.itsaky.androidide.services;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import com.itsaky.androidide.models.LogLine;
import com.itsaky.androidide.utils.Logger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class LogReceiver extends BroadcastReceiver {
private LogListener listener;
public static final String APPEND_LOG = "com.itsaky.androidide.logs.APPEND_LOG";
public static final String EXTRA_LINE = "log_line";
private final Logger LOG = Logger.instance ("LogReceiver");
public LogReceiver setLogListener(LogListener listener) {
this.listener = listener;
return this;
}
@Override
public void onReceive(Context context, Intent intent) {
if(intent.getAction().equals(APPEND_LOG) && intent.hasExtra(EXTRA_LINE)) {
String line = intent.getStringExtra(EXTRA_LINE);
if(line == null) return;
try {
sendLogLine(line);
} catch (Throwable th) {
LOG.error ("Unable to parse log line from app.", th);
}
}
}
private void sendLogLine(String line) {
final var log = LogLine.forLogString (line);
if(listener != null) {
private LogListener listener;
public static final String APPEND_LOG = "com.itsaky.androidide.logs.APPEND_LOG";
public static final String EXTRA_LINE = "log_line";
private final Logger LOG = Logger.instance("LogReceiver");
public LogReceiver setLogListener(LogListener listener) {
this.listener = listener;
return this;
}
@Override
public void onReceive(Context context, Intent intent) {
if (intent.getAction().equals(APPEND_LOG) && intent.hasExtra(EXTRA_LINE)) {
String line = intent.getStringExtra(EXTRA_LINE);
if (line == null) return;
try {
sendLogLine(line);
} catch (Throwable th) {
LOG.error("Unable to parse log line from app.", th);
}
}
}
private void sendLogLine(String line) {
final var log = LogLine.forLogString(line);
if (listener != null) {
listener.appendLogLine(log);
}
}
public interface LogListener {
void appendLogLine (LogLine line);
}
}
public interface LogListener {
void appendLogLine(LogLine line);
}
}
@@ -1,138 +1,140 @@
/************************************************************************************
* This file is part of AndroidIDE.
*
*
*
*
* AndroidIDE is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
*
* AndroidIDE is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
*
* You should have received a copy of the GNU General Public License
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
*
**************************************************************************************/
**************************************************************************************/
package com.itsaky.androidide.services;
import android.app.PendingIntent;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.provider.Settings;
import androidx.core.app.NotificationCompat;
import androidx.core.app.NotificationManagerCompat;
import com.google.firebase.messaging.FirebaseMessagingService;
import com.google.firebase.messaging.RemoteMessage;
import com.itsaky.androidide.app.StudioApp;
import com.itsaky.androidide.BuildConfig;
import com.itsaky.androidide.SplashActivity;
import com.itsaky.androidide.app.StudioApp;
import java.util.Map;
import android.provider.Settings;
import androidx.core.app.NotificationManagerCompat;
import android.annotation.SuppressLint;
public class MessagingService extends FirebaseMessagingService {
public static final String TOPIC_UPDATE= "app_update";
public static final String TOPIC_DEV_MSGS = "developer_notifications";
private final String KEY_TITLE = "title";
private final String KEY_MESSAGE = "message";
private final String KEY_VERSION = "version";
private final String KEY_CLICK_ACTION = "click_action";
private final String ACTION_OPEN_APP = "open_app";
private final String ACTION_OPEN_URL = "open_url:";
public static final String TOPIC_UPDATE = "app_update";
public static final String TOPIC_DEV_MSGS = "developer_notifications";
private final String KEY_TITLE = "title";
private final String KEY_MESSAGE = "message";
private final String KEY_VERSION = "version";
private final String KEY_CLICK_ACTION = "click_action";
private final String ACTION_OPEN_APP = "open_app";
private final String ACTION_OPEN_URL = "open_url:";
@Override
public void onMessageReceived(RemoteMessage message) {
if(message != null && message.getData() != null)
processNotification(message.getFrom(), message.getData());
}
public void onMessageReceived(RemoteMessage message) {
if (message != null && message.getData() != null)
processNotification(message.getFrom(), message.getData());
}
@Override
public void onNewToken(String token) {
super.onNewToken(token);
}
private void processNotification(String topic, Map<String, String> data) {
if(topic != null && topic.contains("/")) {
topic = topic.substring(topic.lastIndexOf("/") + 1);
if(topic.equals(TOPIC_UPDATE) && checkHasUpdateData(data)) {
final String title = data.get(KEY_TITLE);
final String msg = data.get(KEY_MESSAGE);
final String version = data.get(KEY_VERSION);
final String action = data.get(KEY_CLICK_ACTION);
final int l = stringAsInt(version);
if(l > BuildConfig.VERSION_CODE) {
showNotificationWithAction(action, title, msg);
}
} else if(topic.equals(TOPIC_DEV_MSGS) && checkHasTitleAndMessage(data)) {
final String title = data.get(KEY_TITLE);
final String msg = data.get(KEY_MESSAGE);
final String action = data.get(KEY_CLICK_ACTION);
showNotificationWithAction(action, title, msg);
}
}
}
private void showNotificationWithAction(String action, String title, String msg) {
Intent i = null;
int flag = PendingIntent.FLAG_IMMUTABLE;
if (action.equals(ACTION_OPEN_APP)) {
i = StudioApp.getInstance().getPackageManager().getLaunchIntentForPackage(StudioApp.getInstance().getPackageName());
} else if (action.startsWith(ACTION_OPEN_URL)) {
i = createOpenUrlIntent(action.substring(ACTION_OPEN_URL.length()));
flag |= Intent.FLAG_ACTIVITY_NEW_TASK;
}
@Override
public void onNewToken(String token) {
super.onNewToken(token);
}
if (i != null) {
PendingIntent intent = PendingIntent.getActivity(this, 0, i, flag);
showNotification(title, msg, intent, StudioApp.NOTIFICATION_ID_UPDATE);
}
}
private void showNotification(String title, String msg, PendingIntent i, String channelId) {
NotificationCompat.Builder b = new NotificationCompat.Builder(this, channelId);
b.setContentTitle(title)
.setContentText(msg)
.setAutoCancel(true)
.setSound(Settings.System.DEFAULT_NOTIFICATION_URI)
.setContentIntent(i);
NotificationManagerCompat.from(StudioApp.getInstance()).notify(0, b.build());
}
private Intent createOpenAppIntent() {
Intent i = new Intent(this, SplashActivity.class);
i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK|Intent.FLAG_ACTIVITY_CLEAR_TASK);
return i;
}
private Intent createOpenUrlIntent(String url) {
return new Intent(Intent.ACTION_VIEW, android.net.Uri.parse(url));
}
private int stringAsInt(String str) {
try {
return Integer.valueOf(str);
} catch (Throwable th) {
return -1;
}
}
private boolean checkHasUpdateData(Map<String, String> data) {
return checkHasTitleAndMessage(data)
&& data.containsKey(KEY_VERSION)
&& data.containsKey(KEY_CLICK_ACTION);
}
private boolean checkHasTitleAndMessage(Map<String, String> data) {
return data.containsKey(KEY_TITLE)
&& data.containsKey(KEY_MESSAGE);
}
private void processNotification(String topic, Map<String, String> data) {
if (topic != null && topic.contains("/")) {
topic = topic.substring(topic.lastIndexOf("/") + 1);
if (topic.equals(TOPIC_UPDATE) && checkHasUpdateData(data)) {
final String title = data.get(KEY_TITLE);
final String msg = data.get(KEY_MESSAGE);
final String version = data.get(KEY_VERSION);
final String action = data.get(KEY_CLICK_ACTION);
final int l = stringAsInt(version);
if (l > BuildConfig.VERSION_CODE) {
showNotificationWithAction(action, title, msg);
}
} else if (topic.equals(TOPIC_DEV_MSGS) && checkHasTitleAndMessage(data)) {
final String title = data.get(KEY_TITLE);
final String msg = data.get(KEY_MESSAGE);
final String action = data.get(KEY_CLICK_ACTION);
showNotificationWithAction(action, title, msg);
}
}
}
private void showNotificationWithAction(String action, String title, String msg) {
Intent i = null;
int flag = PendingIntent.FLAG_IMMUTABLE;
if (action.equals(ACTION_OPEN_APP)) {
i =
StudioApp.getInstance()
.getPackageManager()
.getLaunchIntentForPackage(StudioApp.getInstance().getPackageName());
} else if (action.startsWith(ACTION_OPEN_URL)) {
i = createOpenUrlIntent(action.substring(ACTION_OPEN_URL.length()));
flag |= Intent.FLAG_ACTIVITY_NEW_TASK;
}
if (i != null) {
PendingIntent intent = PendingIntent.getActivity(this, 0, i, flag);
showNotification(title, msg, intent, StudioApp.NOTIFICATION_ID_UPDATE);
}
}
private void showNotification(String title, String msg, PendingIntent i, String channelId) {
NotificationCompat.Builder b = new NotificationCompat.Builder(this, channelId);
b.setContentTitle(title)
.setContentText(msg)
.setAutoCancel(true)
.setSound(Settings.System.DEFAULT_NOTIFICATION_URI)
.setContentIntent(i);
NotificationManagerCompat.from(StudioApp.getInstance()).notify(0, b.build());
}
private Intent createOpenAppIntent() {
Intent i = new Intent(this, SplashActivity.class);
i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
return i;
}
private Intent createOpenUrlIntent(String url) {
return new Intent(Intent.ACTION_VIEW, android.net.Uri.parse(url));
}
private int stringAsInt(String str) {
try {
return Integer.valueOf(str);
} catch (Throwable th) {
return -1;
}
}
private boolean checkHasUpdateData(Map<String, String> data) {
return checkHasTitleAndMessage(data)
&& data.containsKey(KEY_VERSION)
&& data.containsKey(KEY_CLICK_ACTION);
}
private boolean checkHasTitleAndMessage(Map<String, String> data) {
return data.containsKey(KEY_TITLE) && data.containsKey(KEY_MESSAGE);
}
}
@@ -1,34 +1,31 @@
/************************************************************************************
* This file is part of AndroidIDE.
*
*
* AndroidIDE is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
*
* AndroidIDE is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
*
* You should have received a copy of the GNU General Public License
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
*
**************************************************************************************/
**************************************************************************************/
package com.itsaky.androidide.services.builder;
import com.itsaky.androidide.project.IDEModule;
import com.itsaky.androidide.project.IDEProject;
import com.itsaky.androidide.tasks.GradleTask;
import java.util.Optional;
/**
* A client should implement this interface to get callbacks about a build
*/
/** A client should implement this interface to get callbacks about a build */
public interface BuildListener {
/**
* Called to notify the client about the modification of build/generated files
*/
/** Called to notify the client about the modification of build/generated files */
void onBuildModified();
/**
@@ -70,9 +67,7 @@ public interface BuildListener {
*/
void onBuildFailed(GradleTask task, String msg);
/**
* Called to notify client to save any opened files
*/
/** Called to notify client to save any opened files */
void saveFiles();
/**
@@ -83,8 +78,6 @@ public interface BuildListener {
*/
void appendOutput(GradleTask task, CharSequence text);
/**
* Called to notify the client to prepare for a build
*/
/** Called to notify the client to prepare for a build */
void prepareBuild();
}
@@ -5,12 +5,12 @@
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
*
* AndroidIDE is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
*
* You should have received a copy of the GNU General Public License
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
*
@@ -64,104 +64,105 @@ import java.util.concurrent.CompletableFuture;
import java.util.stream.Collectors;
public class IDEService {
private BuildListener listener;
private GradleTask currentTask;
private IDEProject mIDEProject;
private IDEModule mAppModule;
private final File projectRoot;
private final IProcessExecutor processExecutor;
private boolean isBuilding = false;
public static final int TASK_SHOW_DEPENDENCIES = GradleTask.TASK_SHOW_DEPENDENCIES;
public static final int TASK_ASSEMBLE_DEBUG = GradleTask.TASK_ASSEMBLE_DEBUG;
public static final int TASK_ASSEMBLE_RELEASE = GradleTask.TASK_ASSEMBLE_RELEASE;
public static final int TASK_BUILD = GradleTask.TASK_BUILD;
public static final int TASK_BUNDLE = GradleTask.TASK_BUNDLE;
public static final int TASK_CLEAN = GradleTask.TASK_CLEAN;
public static final int TASK_CLEAN_BUILD = GradleTask.TASK_CLEAN_BUILD;
public static final int TASK_DEX = GradleTask.TASK_DEX;
public static final int TASK_LINT = GradleTask.TASK_LINT;
public static final int TASK_LINT_DEBUG = GradleTask.TASK_LINT_DEBUG;
public static final int TASK_LINT_RELEASE = GradleTask.TASK_LINT_RELEASE;
public static final int TASK_SHOW_DEPENDENCIES = GradleTask.TASK_SHOW_DEPENDENCIES;
public static final int TASK_ASSEMBLE_DEBUG = GradleTask.TASK_ASSEMBLE_DEBUG;
public static final int TASK_ASSEMBLE_RELEASE = GradleTask.TASK_ASSEMBLE_RELEASE;
public static final int TASK_BUILD = GradleTask.TASK_BUILD;
public static final int TASK_BUNDLE = GradleTask.TASK_BUNDLE;
public static final int TASK_CLEAN = GradleTask.TASK_CLEAN;
public static final int TASK_CLEAN_BUILD = GradleTask.TASK_CLEAN_BUILD;
public static final int TASK_DEX = GradleTask.TASK_DEX;
public static final int TASK_LINT = GradleTask.TASK_LINT;
public static final int TASK_LINT_DEBUG = GradleTask.TASK_LINT_DEBUG;
public static final int TASK_LINT_RELEASE = GradleTask.TASK_LINT_RELEASE;
public static final Logger LOG = Logger.instance("IDEService");
private final InputStreamLineReader.OnReadListener mOutputReadListener = this::onBuildOutput;
private final IProcessExitListener mProcessExitListener = new IProcessExitListener() {
@Override
public void onExit(int code) {
isBuilding = false;
if (listener != null) {
final String msg = getString(R.string.msg_gradle_terminated, code);
if (code == 0) {
onBuildSuccessful(currentTask, msg);
} else {
onBuildFailed(currentTask, msg);
private final IProcessExitListener mProcessExitListener =
new IProcessExitListener() {
@Override
public void onExit(int code) {
isBuilding = false;
if (listener != null) {
final String msg = getString(R.string.msg_gradle_terminated, code);
if (code == 0) {
onBuildSuccessful(currentTask, msg);
} else {
onBuildFailed(currentTask, msg);
}
}
}
}
}
};
};
public IDEService(File projectRoot) {
this.projectRoot = projectRoot;
this.processExecutor = ProcessExecutorFactory.commonExecutor();
}
public IDEService setListener(final BuildListener listener) {
this.listener = listener == null ? null : new MainThreadBuildListener(listener);
return this;
}
protected void onBuildOutput(String line) {
if(listener == null || line == null) return;
if (listener == null || line == null) return;
line = line.trim();
String PROJECT_INITIALIZED = ">>> PROJECT INITIALIZED <<<";
if(line.contains(PROJECT_INITIALIZED)) {
if (line.contains(PROJECT_INITIALIZED)) {
readIdeProject();
return;
}
String text = line;
final var app = StudioApp.getInstance ();
final var app = StudioApp.getInstance();
// Try to shorten common file paths
if (line.contains(app.getRootDir().getAbsolutePath())) {
text = line.replace(app.getRootDir().getAbsolutePath(), "HOME");
}
if (line.contains(Objects.requireNonNull (app.getRootDir ().getParentFile ()).getAbsolutePath())) {
if (line.contains(
Objects.requireNonNull(app.getRootDir().getParentFile()).getAbsolutePath())) {
text = line.replace(app.getRootDir().getParentFile().getAbsolutePath(), "ROOT");
}
listener.appendOutput(currentTask, text);
String RUN_TASK = "> Task";
String STARTING_DAEMON = "Starting a ";
if(line.startsWith(RUN_TASK)) {
if (line.startsWith(RUN_TASK)) {
listener.onRunTask(currentTask, line.trim());
} else if(line.startsWith(STARTING_DAEMON)) {
} else if (line.startsWith(STARTING_DAEMON)) {
listener.onStartingGradleDaemon(currentTask);
}
}
protected void onBuildSuccessful (GradleTask task, String msg) {
if(currentTask != null && currentTask.getTaskID() != TASK_SHOW_DEPENDENCIES) {
}
protected void onBuildSuccessful(GradleTask task, String msg) {
if (currentTask != null && currentTask.getTaskID() != TASK_SHOW_DEPENDENCIES) {
listener.onBuildSuccessful(task, msg.trim());
}
appendOutputSeparator();
if(currentTask != null && currentTask.affectsGeneratedSources()) {
if (currentTask != null && currentTask.affectsGeneratedSources()) {
notifyExternalSourceChange();
}
}
protected void onBuildFailed (GradleTask task, @NonNull String msg) {
protected void onBuildFailed(GradleTask task, @NonNull String msg) {
isBuilding = false;
listener.onBuildFailed(task, msg.trim());
appendOutputSeparator();
@@ -169,25 +170,32 @@ public class IDEService {
private void readIdeProject() {
// Parsing project data is meaningless if there is no one listening...
if(listener == null) {
if (listener == null) {
return;
}
final BuildListener local = listener;
new Thread(() -> {
mIDEProject = new Gson()
.fromJson(
FileIOUtils.readFile2String(Environment.PROJECT_DATA_FILE),
IDEProject.class
);
if(mIDEProject != null) {
Optional<IDEModule> appModule = mIDEProject.getModuleByPath(":app");
appModule.ifPresent (ideModule -> mAppModule = ideModule);
local.onProjectLoaded(mIDEProject, appModule);
} else {
StudioApp.getInstance ().toast(com.itsaky.androidide.R.string.msg_init_project_failed, Toaster.Type.ERROR);
}
}).start();
new Thread(
() -> {
mIDEProject =
new Gson()
.fromJson(
FileIOUtils.readFile2String(
Environment.PROJECT_DATA_FILE),
IDEProject.class);
if (mIDEProject != null) {
Optional<IDEModule> appModule = mIDEProject.getModuleByPath(":app");
appModule.ifPresent(ideModule -> mAppModule = ideModule);
local.onProjectLoaded(mIDEProject, appModule);
} else {
StudioApp.getInstance()
.toast(
com.itsaky.androidide.R.string
.msg_init_project_failed,
Toaster.Type.ERROR);
}
})
.start();
}
private void appendOutputSeparator() {
@@ -208,40 +216,40 @@ public class IDEService {
}
private String getString(@StringRes int id) {
return StudioApp.getInstance ().getString(id);
return StudioApp.getInstance().getString(id);
}
private String getString(@StringRes int id, Object... format) {
return StudioApp.getInstance ().getString(id, format);
return StudioApp.getInstance().getString(id, format);
}
private void notifyExternalSourceChange() {
// TODO Watch for file changes using a file observer
// instead of depending on the build
if(listener != null) {
if (listener != null) {
listener.onBuildModified();
}
}
@NonNull
private String[] getArguments(List<String> tasks) {
final PreferenceManager prefs = StudioApp.getInstance ().getPrefManager();
final PreferenceManager prefs = StudioApp.getInstance().getPrefManager();
final List<String> args = new ArrayList<>();
// 'bash' shell is always preferred
if (Environment.SHELL.exists ()) {
if (Environment.SHELL.exists()) {
args.add(Environment.SHELL.getAbsolutePath());
} else {
args.add (Environment.BUSYBOX.getAbsolutePath ());
args.add ("sh");
args.add(Environment.BUSYBOX.getAbsolutePath());
args.add("sh");
}
args.add(new File (projectRoot, "gradlew").getAbsolutePath());
args.add(new File(projectRoot, "gradlew").getAbsolutePath());
args.addAll(asAppTasks(tasks));
args.add("--init-script");
args.add(Environment.INIT_SCRIPT.getAbsolutePath());
if (prefs.isStackTraceEnabled ()) {
if (prefs.isStackTraceEnabled()) {
args.add("--stacktrace");
}
if (prefs.isGradleInfoEnabled()) {
@@ -263,16 +271,16 @@ public class IDEService {
private Collection<? extends String> asAppTasks(@NonNull List<String> tasks) {
return tasks.stream()
.filter(Objects::nonNull)
.map(this::asAppModuleTask)
.collect(Collectors.toList());
.filter(Objects::nonNull)
.map(this::asAppModuleTask)
.collect(Collectors.toList());
}
@Contract(pure = true)
private String asAppModuleTask (@NonNull String name) {
private String asAppModuleTask(@NonNull String name) {
return name.startsWith(":app:") ? name : ":app:" + name;
}
public void execTask(GradleTask task) {
execTask(task, false);
}
@@ -283,103 +291,126 @@ public class IDEService {
} else if (isBuilding()) {
return;
}
execTask(task, isGradleWrapperAvailable() ? CompletableFuture.completedFuture(Boolean.TRUE) : installWrapper());
}
private void execTask (GradleTask task, CompletableFuture<Boolean> installing) {
final Runnable taskRunner = () -> {
if(installing != null) {
Boolean result;
String failure = "";
try {
result = installing.get();
} catch (Throwable th) {
result = Boolean.FALSE;
failure = th.getMessage();
}
if(result.equals(Boolean.FALSE)) {
if(listener != null) {
listener.appendOutput(task, "-------------------- ERROR --------------------");
listener.appendOutput(task, getString(R.string.msg_gradlew_installation_failed, failure));
listener.appendOutput(task, "-----------------------------------------------");
}
return;
}
}
if (listener != null) {
final ProcessStreamsHolder holder = new ProcessStreamsHolder();
listener.saveFiles();
currentTask = task;
listener.appendOutput(task, getString(R.string.msg_task_begin, currentTime(), task.getName()));
try {
Environment.mkdirIfNotExits (Environment.TMP_DIR);
processExecutor.execAsync(holder, mProcessExitListener, projectRoot.getAbsolutePath(), true, getArguments(task.getTasks()));
isBuilding = true;
startReading (holder.in);
listener.prepareBuild();
} catch (Throwable e) {
listener.onBuildFailed(task, e.getMessage());
}
}
};
execTask(
task,
isGradleWrapperAvailable()
? CompletableFuture.completedFuture(Boolean.TRUE)
: installWrapper());
}
private void execTask(GradleTask task, CompletableFuture<Boolean> installing) {
final Runnable taskRunner =
() -> {
if (installing != null) {
Boolean result;
String failure = "";
try {
result = installing.get();
} catch (Throwable th) {
result = Boolean.FALSE;
failure = th.getMessage();
}
if (result.equals(Boolean.FALSE)) {
if (listener != null) {
listener.appendOutput(
task, "-------------------- ERROR --------------------");
listener.appendOutput(
task,
getString(
R.string.msg_gradlew_installation_failed, failure));
listener.appendOutput(
task, "-----------------------------------------------");
}
return;
}
}
if (listener != null) {
final ProcessStreamsHolder holder = new ProcessStreamsHolder();
listener.saveFiles();
currentTask = task;
listener.appendOutput(
task,
getString(R.string.msg_task_begin, currentTime(), task.getName()));
try {
Environment.mkdirIfNotExits(Environment.TMP_DIR);
processExecutor.execAsync(
holder,
mProcessExitListener,
projectRoot.getAbsolutePath(),
true,
getArguments(task.getTasks()));
isBuilding = true;
startReading(holder.in);
listener.prepareBuild();
} catch (Throwable e) {
listener.onBuildFailed(task, e.getMessage());
}
}
};
final Thread thread = new Thread(taskRunner, "GradleTaskExecutor");
thread.setDaemon(true);
thread.start();
}
private void startReading(InputStream in) {
final Thread reader = new Thread (new InputStreamLineReader (in, mOutputReadListener), "GradleProcessOutputReader");
final Thread reader =
new Thread(
new InputStreamLineReader(in, mOutputReadListener),
"GradleProcessOutputReader");
reader.setDaemon(true);
reader.start();
}
private CompletableFuture<Boolean> installWrapper() {
if(listener != null) {
if (listener != null) {
listener.appendOutput(null, "-------------------- NOTE --------------------");
listener.appendOutput(null, getString(R.string.msg_installing_gradlew));
listener.appendOutput(null, "----------------------------------------------");
}
return CompletableFuture.supplyAsync (() -> {
final File extracted = new File(Environment.TMP_DIR, "gradle-wrapper.zip");
if(ResourceUtils.copyFileFromAssets(getCommonAsset("gradle-wrapper.zip"), extracted.getAbsolutePath())) {
try {
final List<File> files = ZipUtils.unzipFile(extracted, projectRoot);
if(files != null && !files.isEmpty()) {
return Boolean.TRUE;
return CompletableFuture.supplyAsync(
() -> {
final File extracted = new File(Environment.TMP_DIR, "gradle-wrapper.zip");
if (ResourceUtils.copyFileFromAssets(
getCommonAsset("gradle-wrapper.zip"), extracted.getAbsolutePath())) {
try {
final List<File> files = ZipUtils.unzipFile(extracted, projectRoot);
if (files != null && !files.isEmpty()) {
return Boolean.TRUE;
}
} catch (IOException e) {
return Boolean.FALSE;
}
}
} catch (IOException e) {
return Boolean.FALSE;
}
}
return Boolean.FALSE;
});
});
}
public boolean isGradleWrapperAvailable () {
public boolean isGradleWrapperAvailable() {
if (projectRoot == null || !projectRoot.exists()) {
return false;
}
final File gradlew = new File(projectRoot, "gradlew");
final File gradleWrapperJar = new File(projectRoot, "gradle/wrapper/gradle-wrapper.jar");
final File gradleWrapperProps = new File(projectRoot, "gradle/wrapper/gradle-wrapper.properties");
return gradlew.exists () && gradleWrapperJar.exists () && gradleWrapperProps.exists ();
final File gradleWrapperProps =
new File(projectRoot, "gradle/wrapper/gradle-wrapper.properties");
return gradlew.exists() && gradleWrapperJar.exists() && gradleWrapperProps.exists();
}
public void assembleDebug(boolean installApk) {
execTask(((AssembleDebug) BaseGradleTasks.ASSEMBLE_DEBUG).setInstallApk(installApk));
}
@@ -395,7 +426,7 @@ public class IDEService {
public void bundle() {
execTask(BaseGradleTasks.BUNDLE);
}
public void clean() {
execTask(BaseGradleTasks.CLEAN);
}
@@ -405,9 +436,15 @@ public class IDEService {
}
public void stopAllDaemons() {
StudioApp.getInstance ().newShell(null).bgAppend(String.format(Locale.US, "cd '%s' && sh gradlew --stop", projectRoot.getAbsolutePath()));
if(isBuilding()) {
if(listener != null)
StudioApp.getInstance()
.newShell(null)
.bgAppend(
String.format(
Locale.US,
"cd '%s' && sh gradlew --stop",
projectRoot.getAbsolutePath()));
if (isBuilding()) {
if (listener != null)
listener.onBuildFailed(currentTask, getString(R.string.msg_daemons_stopped));
isBuilding = false;
}
@@ -424,15 +461,16 @@ public class IDEService {
public void lintRelease() {
execTask(BaseGradleTasks.LINT_RELEASE);
}
public void updateResourceClasses() {
execTask(new UpdateResourceClassesTask(mAppModule != null && mAppModule.viewBindingEnabled));
execTask(
new UpdateResourceClassesTask(mAppModule != null && mAppModule.viewBindingEnabled));
}
public boolean isBuilding() {
return isBuilding;
}
public String typeString(int type) {
switch (type) {
case TASK_ASSEMBLE_DEBUG:
@@ -445,11 +483,12 @@ public class IDEService {
return getString(R.string.clean_amp_build);
case TASK_CLEAN:
return getString(R.string.clean_project);
case TASK_BUNDLE :
case TASK_BUNDLE:
return getString(R.string.create_aab);
case TASK_DEX:
return getString(R.string.compiling);
default: return "";
default:
return "";
}
}
}
}
@@ -1,16 +1,16 @@
/*
* This file is part of AndroidIDE.
*
*
* AndroidIDE is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
*
* AndroidIDE is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
*
* You should have received a copy of the GNU General Public License
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
*
@@ -27,12 +27,12 @@ import com.itsaky.androidide.tasks.GradleTask;
import java.util.Objects;
import java.util.Optional;
class MainThreadBuildListener implements BuildListener {
class MainThreadBuildListener implements BuildListener {
private final BuildListener listener;
public MainThreadBuildListener(@NonNull final BuildListener listener) {
Objects.requireNonNull (listener, "Build listener cannot be null");
Objects.requireNonNull(listener, "Build listener cannot be null");
this.listener = listener;
}
@@ -23,16 +23,15 @@ import io.github.rosemoe.sora.lang.styling.TextStyle;
import io.github.rosemoe.sora.widget.schemes.EditorColorScheme;
/**
* Base class for editor color schemes in AndroidIDE.
* If you're implementing a color scheme for the IDE,
* this should be the base class instead of {@link EditorColorScheme}.
* Base class for editor color schemes in AndroidIDE. If you're implementing a color scheme for the
* IDE, this should be the base class instead of {@link EditorColorScheme}.
*
* @author Akash Yadav
*/
public class SchemeAndroidIDE extends EditorColorScheme {
private static int current = EditorColorScheme.END_COLOR_ID;
public static final int LOG_INFO = current++;
public static final int LOG_DEBUG = current++;
public static final int LOG_ERROR = current++;
@@ -63,115 +62,117 @@ public class SchemeAndroidIDE extends EditorColorScheme {
public static final int LOCAL_VARIABLE = current++;
public static final int TODO_COMMENT = current++;
public static final int FIXME_COMMENT = current++;
@Override
public void applyDefault () {
public void applyDefault() {
// Apply default colors
super.applyDefault ();
super.applyDefault();
// Apply customized colors
setColor (WHOLE_BACKGROUND, 0xff212121);
setColor (LINE_NUMBER_BACKGROUND, 0xff212121);
setColor (LINE_DIVIDER, Color.TRANSPARENT);
setColor (LINE_NUMBER, 0xffaaaaaa );
setColor (LINE_NUMBER_PANEL, 0xff000000);
setColor (LINE_NUMBER_PANEL_TEXT, 0xffffffff);
setColor (TEXT_NORMAL, 0xfff5f5f5);
setColor (SELECTED_TEXT_BACKGROUND, 0xff757575);
setColor (TEXT_SELECTED, 0xff424242);
setColor (SELECTION_INSERT, 0xffff5252);
setColor (SELECTION_HANDLE, 0xffff5252);
setColor (CURRENT_LINE, 0xff313131);
setColor (UNDERLINE, 0xffffffff);
setColor (SCROLL_BAR_THUMB, 0xff757575);
setColor (SCROLL_BAR_THUMB_PRESSED, 0xffff5252);
setColor (SCROLL_BAR_TRACK, 0xffbdbdbd);
setColor (BLOCK_LINE, 0xff424242);
setColor (BLOCK_LINE_CURRENT, 0xffffffff);
setColor (AUTO_COMP_PANEL_BG, 0xff757575);
setColor (AUTO_COMP_PANEL_CORNER, 0xff9e9e9e);
setColor (MATCHED_TEXT_BACKGROUND, 0xffFF8F00);
setColor (NON_PRINTABLE_CHAR, 0xffdddddd);
setColor (KEYWORD, 0xffff6060);
setColor (OPERATOR, 0xff4fc3f7);
setColor (LITERAL, 0xff8bc34a);
setColor (TYPE_NAME, 0xff4fc3f7);
setColor (LOCAL_VARIABLE, 0xfff5f5f5);
setColor (RESOURCE_VARIABLE, 0xfff5f5f5);
setColor (PARAMETER, 0xfff5f5f5);
setColor (EXCEPTION_PARAM, 0xfff5f5f5);
setColor (METHOD_DECLARATION, 0xfff5f5f5);
setColor (METHOD_INVOCATION, 0xfff5f5f5);
setColor (CONSTRUCTOR, 0xfff5f5f5);
setColor (ANNOTATION, 0xff4fc3f7);
setColor (PACKAGE_NAME, 0xfff0be4b);
setColor (FIELD, 0xfff0be4b);
setColor (STATIC_INIT, 0xfff0be4b);
setColor (INSTANCE_INIT, 0xfff0be4b);
setColor (TYPE_PARAM, 0xfff0be4b);
setColor (STATIC_FIELD, 0xffffb300);
setColor (ENUM_TYPE, 0xff4fc3f7);
setColor (INTERFACE, 0xff4fc3f7);
setColor (ENUM, 0xffffb300);
setColor (XML_TAG, 0xffff6060);
setColor (DIAGNOSTIC_ERROR, 0xfff44336);
setColor (DIAGNOSTIC_WARNING, 0xffFFEB3B);
setColor (DIAGNOSTIC_INFO, 0xff4CAF50);
setColor (DIAGNOSTIC_HINT, 0xffffffff);
setColor (STDERR, 0xfff44336);
setColor (STDOUT, 0xff4CAF50);
setColor (TODO_COMMENT, 0xffffc400);
setColor (FIXME_COMMENT, 0xffffab00);
setColor (COMMENT, 0xffbdbdbd);
setColor(WHOLE_BACKGROUND, 0xff212121);
setColor(LINE_NUMBER_BACKGROUND, 0xff212121);
setColor(LINE_DIVIDER, Color.TRANSPARENT);
setColor(LINE_NUMBER, 0xffaaaaaa);
setColor(LINE_NUMBER_PANEL, 0xff000000);
setColor(LINE_NUMBER_PANEL_TEXT, 0xffffffff);
setColor(TEXT_NORMAL, 0xfff5f5f5);
setColor(SELECTED_TEXT_BACKGROUND, 0xff757575);
setColor(TEXT_SELECTED, 0xff424242);
setColor(SELECTION_INSERT, 0xffff5252);
setColor(SELECTION_HANDLE, 0xffff5252);
setColor(CURRENT_LINE, 0xff313131);
setColor(UNDERLINE, 0xffffffff);
setColor(SCROLL_BAR_THUMB, 0xff757575);
setColor(SCROLL_BAR_THUMB_PRESSED, 0xffff5252);
setColor(SCROLL_BAR_TRACK, 0xffbdbdbd);
setColor(BLOCK_LINE, 0xff424242);
setColor(BLOCK_LINE_CURRENT, 0xffffffff);
setColor(AUTO_COMP_PANEL_BG, 0xff757575);
setColor(AUTO_COMP_PANEL_CORNER, 0xff9e9e9e);
setColor(MATCHED_TEXT_BACKGROUND, 0xffFF8F00);
setColor(NON_PRINTABLE_CHAR, 0xffdddddd);
setColor(KEYWORD, 0xffff6060);
setColor(OPERATOR, 0xff4fc3f7);
setColor(LITERAL, 0xff8bc34a);
setColor(TYPE_NAME, 0xff4fc3f7);
setColor(LOCAL_VARIABLE, 0xfff5f5f5);
setColor(RESOURCE_VARIABLE, 0xfff5f5f5);
setColor(PARAMETER, 0xfff5f5f5);
setColor(EXCEPTION_PARAM, 0xfff5f5f5);
setColor(METHOD_DECLARATION, 0xfff5f5f5);
setColor(METHOD_INVOCATION, 0xfff5f5f5);
setColor(CONSTRUCTOR, 0xfff5f5f5);
setColor(ANNOTATION, 0xff4fc3f7);
setColor(PACKAGE_NAME, 0xfff0be4b);
setColor(FIELD, 0xfff0be4b);
setColor(STATIC_INIT, 0xfff0be4b);
setColor(INSTANCE_INIT, 0xfff0be4b);
setColor(TYPE_PARAM, 0xfff0be4b);
setColor(STATIC_FIELD, 0xffffb300);
setColor(ENUM_TYPE, 0xff4fc3f7);
setColor(INTERFACE, 0xff4fc3f7);
setColor(ENUM, 0xffffb300);
setColor(XML_TAG, 0xffff6060);
setColor(DIAGNOSTIC_ERROR, 0xfff44336);
setColor(DIAGNOSTIC_WARNING, 0xffFFEB3B);
setColor(DIAGNOSTIC_INFO, 0xff4CAF50);
setColor(DIAGNOSTIC_HINT, 0xffffffff);
setColor(STDERR, 0xfff44336);
setColor(STDOUT, 0xff4CAF50);
setColor(TODO_COMMENT, 0xffffc400);
setColor(FIXME_COMMENT, 0xffffab00);
setColor(COMMENT, 0xffbdbdbd);
}
/**
* Delegates to {@link TextStyle#makeStyle(int)}
*
* @param id The color id.
* @return The style flags.
*/
public static long get (int id) {
return TextStyle.makeStyle (id);
public static long get(int id) {
return TextStyle.makeStyle(id);
}
/**
* Create style for keywords. Convenient method to avoid calling {@link TextStyle#makeStyle(int, int, boolean, boolean, boolean)}
* Create style for keywords. Convenient method to avoid calling {@link TextStyle#makeStyle(int,
* int, boolean, boolean, boolean)}
*
* @return The default style for keywords.
*/
public static long forKeyword () {
return TextStyle.makeStyle (KEYWORD, 0, true, false, false);
public static long forKeyword() {
return TextStyle.makeStyle(KEYWORD, 0, true, false, false);
}
/**
* Create style for string literals. The returned style sets the {@link TextStyle#NO_COMPLETION_BIT}.
* Create style for string literals. The returned style sets the {@link
* TextStyle#NO_COMPLETION_BIT}.
*
* @return The style for string literals.
*/
public static long forString () {
return TextStyle.makeStyle (LITERAL, true);
public static long forString() {
return TextStyle.makeStyle(LITERAL, true);
}
/**
* Create color style for default comments.
*
* @return The style for {@link #COMMENT}.
*/
public static long forComment () {
return TextStyle.makeStyle (COMMENT, true);
public static long forComment() {
return TextStyle.makeStyle(COMMENT, true);
}
/**
* Create style for the given id without completions.
*
* @param id The id to create style for.
* @return The style for the id.
*/
public static long withoutCompletion (int id) {
return TextStyle.makeStyle (id, true);
public static long withoutCompletion(int id) {
return TextStyle.makeStyle(id, true);
}
}
@@ -1,31 +1,31 @@
/************************************************************************************
* This file is part of AndroidIDE.
*
*
*
*
* AndroidIDE is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
*
* AndroidIDE is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
*
* You should have received a copy of the GNU General Public License
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
*
**************************************************************************************/
**************************************************************************************/
package com.itsaky.androidide.syntax.highlighters;
import android.text.SpannableStringBuilder;
/**
* A highlighter is used to highlight a small piece of code
*
* Implementations of these class shouldn't be used in CodeEditor
* For CodeEditor usage, implement an EditorLanguage instead
*
* <p>Implementations of these class shouldn't be used in CodeEditor For CodeEditor usage, implement
* an EditorLanguage instead
*/
public interface Highlighter {
SpannableStringBuilder highlight(String code, String match) throws Exception;
@@ -5,12 +5,12 @@
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
*
* AndroidIDE is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
*
* You should have received a copy of the GNU General Public License
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
*/
@@ -19,15 +19,18 @@ package com.itsaky.androidide.syntax.highlighters;
import android.text.SpannableStringBuilder;
import android.text.style.BackgroundColorSpan;
import android.text.style.ForegroundColorSpan;
import com.itsaky.androidide.lexers.java.JavaLexer;
import com.itsaky.androidide.syntax.colorschemes.SchemeAndroidIDE;
import java.io.StringReader;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import io.github.rosemoe.sora.widget.schemes.EditorColorScheme;
import org.antlr.v4.runtime.CharStreams;
import org.antlr.v4.runtime.Token;
import io.github.rosemoe.sora.widget.schemes.EditorColorScheme;
import java.io.StringReader;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class JavaHighlighter implements Highlighter {
@@ -37,11 +40,10 @@ public class JavaHighlighter implements Highlighter {
final SpannableStringBuilder sb = new SpannableStringBuilder();
final EditorColorScheme scheme = new SchemeAndroidIDE();
Token token;
while((token = lexer.nextToken()) != null) {
if(token.getType() == JavaLexer.EOF)
break;
switch(token.getType()) {
case JavaLexer.WS :
while ((token = lexer.nextToken()) != null) {
if (token.getType() == JavaLexer.EOF) break;
switch (token.getType()) {
case JavaLexer.WS:
sb.append(token.getText());
break;
case JavaLexer.ABSTRACT:
@@ -85,7 +87,10 @@ public class JavaHighlighter implements Highlighter {
case JavaLexer.VOID:
case JavaLexer.VOLATILE:
case JavaLexer.WHILE:
sb.append(token.getText(), new ForegroundColorSpan(scheme.getColor(EditorColorScheme.KEYWORD)), SpannableStringBuilder.SPAN_EXCLUSIVE_EXCLUSIVE);
sb.append(
token.getText(),
new ForegroundColorSpan(scheme.getColor(EditorColorScheme.KEYWORD)),
SpannableStringBuilder.SPAN_EXCLUSIVE_EXCLUSIVE);
break;
case JavaLexer.DECIMAL_LITERAL:
case JavaLexer.HEX_LITERAL:
@@ -96,75 +101,90 @@ public class JavaHighlighter implements Highlighter {
case JavaLexer.BOOL_LITERAL:
case JavaLexer.CHAR_LITERAL:
case JavaLexer.NULL_LITERAL:
case JavaLexer.STRING_LITERAL :
sb.append(token.getText(), new ForegroundColorSpan(scheme.getColor(EditorColorScheme.LITERAL)), SpannableStringBuilder.SPAN_EXCLUSIVE_EXCLUSIVE);
case JavaLexer.STRING_LITERAL:
sb.append(
token.getText(),
new ForegroundColorSpan(scheme.getColor(EditorColorScheme.LITERAL)),
SpannableStringBuilder.SPAN_EXCLUSIVE_EXCLUSIVE);
break;
case JavaLexer.LPAREN :
case JavaLexer.RPAREN :
case JavaLexer.LBRACK :
case JavaLexer.RBRACK :
case JavaLexer.SEMI :
case JavaLexer.COMMA :
case JavaLexer.ASSIGN :
case JavaLexer.GT :
case JavaLexer.LT :
case JavaLexer.BANG :
case JavaLexer.TILDE :
case JavaLexer.QUESTION :
case JavaLexer.COLON :
case JavaLexer.EQUAL :
case JavaLexer.GE :
case JavaLexer.LE :
case JavaLexer.NOTEQUAL :
case JavaLexer.AND :
case JavaLexer.OR :
case JavaLexer.INC :
case JavaLexer.DEC :
case JavaLexer.ADD :
case JavaLexer.SUB :
case JavaLexer.MUL :
case JavaLexer.DIV :
case JavaLexer.BITAND :
case JavaLexer.BITOR :
case JavaLexer.CARET :
case JavaLexer.MOD :
case JavaLexer.ADD_ASSIGN :
case JavaLexer.SUB_ASSIGN :
case JavaLexer.MUL_ASSIGN :
case JavaLexer.DIV_ASSIGN :
case JavaLexer.AND_ASSIGN :
case JavaLexer.OR_ASSIGN :
case JavaLexer.XOR_ASSIGN :
case JavaLexer.MOD_ASSIGN :
case JavaLexer.LSHIFT_ASSIGN :
case JavaLexer.RSHIFT_ASSIGN :
case JavaLexer.URSHIFT_ASSIGN :
case JavaLexer.ARROW :
case JavaLexer.COLONCOLON :
case JavaLexer.ELLIPSIS :
case JavaLexer.DOT :
case JavaLexer.LBRACE :
case JavaLexer.RBRACE :
case JavaLexer.AT :
sb.append(token.getText(), new ForegroundColorSpan(scheme.getColor(EditorColorScheme.OPERATOR)), SpannableStringBuilder.SPAN_EXCLUSIVE_EXCLUSIVE);
case JavaLexer.LPAREN:
case JavaLexer.RPAREN:
case JavaLexer.LBRACK:
case JavaLexer.RBRACK:
case JavaLexer.SEMI:
case JavaLexer.COMMA:
case JavaLexer.ASSIGN:
case JavaLexer.GT:
case JavaLexer.LT:
case JavaLexer.BANG:
case JavaLexer.TILDE:
case JavaLexer.QUESTION:
case JavaLexer.COLON:
case JavaLexer.EQUAL:
case JavaLexer.GE:
case JavaLexer.LE:
case JavaLexer.NOTEQUAL:
case JavaLexer.AND:
case JavaLexer.OR:
case JavaLexer.INC:
case JavaLexer.DEC:
case JavaLexer.ADD:
case JavaLexer.SUB:
case JavaLexer.MUL:
case JavaLexer.DIV:
case JavaLexer.BITAND:
case JavaLexer.BITOR:
case JavaLexer.CARET:
case JavaLexer.MOD:
case JavaLexer.ADD_ASSIGN:
case JavaLexer.SUB_ASSIGN:
case JavaLexer.MUL_ASSIGN:
case JavaLexer.DIV_ASSIGN:
case JavaLexer.AND_ASSIGN:
case JavaLexer.OR_ASSIGN:
case JavaLexer.XOR_ASSIGN:
case JavaLexer.MOD_ASSIGN:
case JavaLexer.LSHIFT_ASSIGN:
case JavaLexer.RSHIFT_ASSIGN:
case JavaLexer.URSHIFT_ASSIGN:
case JavaLexer.ARROW:
case JavaLexer.COLONCOLON:
case JavaLexer.ELLIPSIS:
case JavaLexer.DOT:
case JavaLexer.LBRACE:
case JavaLexer.RBRACE:
case JavaLexer.AT:
sb.append(
token.getText(),
new ForegroundColorSpan(scheme.getColor(EditorColorScheme.OPERATOR)),
SpannableStringBuilder.SPAN_EXCLUSIVE_EXCLUSIVE);
break;
case JavaLexer.BOOLEAN:
case JavaLexer.BYTE :
case JavaLexer.BYTE:
case JavaLexer.CHAR:
case JavaLexer.DOUBLE:
case JavaLexer.ENUM :
case JavaLexer.ENUM:
case JavaLexer.FLOAT:
case JavaLexer.INT :
case JavaLexer.INT:
case JavaLexer.LONG:
case JavaLexer.SHORT:
sb.append(token.getText(), new ForegroundColorSpan(scheme.getColor(SchemeAndroidIDE.TYPE_NAME)), SpannableStringBuilder.SPAN_EXCLUSIVE_EXCLUSIVE);
sb.append(
token.getText(),
new ForegroundColorSpan(scheme.getColor(SchemeAndroidIDE.TYPE_NAME)),
SpannableStringBuilder.SPAN_EXCLUSIVE_EXCLUSIVE);
break;
case JavaLexer.BLOCK_COMMENT :
case JavaLexer.LINE_COMMENT :
sb.append(token.getText(), new ForegroundColorSpan(scheme.getColor(EditorColorScheme.COMMENT)), SpannableStringBuilder.SPAN_EXCLUSIVE_EXCLUSIVE);
case JavaLexer.BLOCK_COMMENT:
case JavaLexer.LINE_COMMENT:
sb.append(
token.getText(),
new ForegroundColorSpan(scheme.getColor(EditorColorScheme.COMMENT)),
SpannableStringBuilder.SPAN_EXCLUSIVE_EXCLUSIVE);
break;
default:
sb.append(token.getText(), new ForegroundColorSpan(scheme.getColor(EditorColorScheme.TEXT_NORMAL)), SpannableStringBuilder.SPAN_EXCLUSIVE_EXCLUSIVE);
sb.append(
token.getText(),
new ForegroundColorSpan(scheme.getColor(EditorColorScheme.TEXT_NORMAL)),
SpannableStringBuilder.SPAN_EXCLUSIVE_EXCLUSIVE);
break;
}
}
@@ -173,9 +193,17 @@ public class JavaHighlighter implements Highlighter {
Pattern pattern = Pattern.compile(Pattern.quote(match));
Matcher matcher = pattern.matcher(code);
while(matcher.find()) {
sb.setSpan(bg, matcher.start(), matcher.end(), SpannableStringBuilder.SPAN_EXCLUSIVE_EXCLUSIVE);
sb.setSpan(fg, matcher.start(), matcher.end(), SpannableStringBuilder.SPAN_EXCLUSIVE_EXCLUSIVE);
while (matcher.find()) {
sb.setSpan(
bg,
matcher.start(),
matcher.end(),
SpannableStringBuilder.SPAN_EXCLUSIVE_EXCLUSIVE);
sb.setSpan(
fg,
matcher.start(),
matcher.end(),
SpannableStringBuilder.SPAN_EXCLUSIVE_EXCLUSIVE);
}
return sb;
@@ -1,23 +1,22 @@
/************************************************************************************
* This file is part of AndroidIDE.
*
*
*
*
* AndroidIDE is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
*
* AndroidIDE is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
*
* You should have received a copy of the GNU General Public License
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
*
**************************************************************************************/
**************************************************************************************/
package com.itsaky.androidide.tasks;
@@ -29,19 +28,19 @@ public abstract class BaseGradleTask implements GradleTask {
public boolean affectsGeneratedSources() {
return false;
}
@Override
public boolean shouldSaveFiles() {
return getType() == Type.BUILD
|| getType() == Type.LINT
|| getTaskID() == IDEService.TASK_DEX;
}
@Override
public boolean equals(Object obj) {
return obj != null
&& obj instanceof GradleTask
&& getTaskID() == ((GradleTask) obj).getTaskID()
&& getType() == ((GradleTask) obj).getType();
}
@Override
public boolean shouldSaveFiles() {
return getType() == Type.BUILD
|| getType() == Type.LINT
|| getTaskID() == IDEService.TASK_DEX;
}
@Override
public boolean equals(Object obj) {
return obj != null
&& obj instanceof GradleTask
&& getTaskID() == ((GradleTask) obj).getTaskID()
&& getType() == ((GradleTask) obj).getType();
}
}
@@ -1,64 +1,68 @@
/************************************************************************************
* This file is part of AndroidIDE.
*
*
*
*
* AndroidIDE is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
*
* AndroidIDE is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
*
* You should have received a copy of the GNU General Public License
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
*
**************************************************************************************/
**************************************************************************************/
package com.itsaky.androidide.tasks;
import java.io.File;
import java.util.List;
public interface GradleTask {
public String getName();
public String getName();
public String getCommands();
public List<String> getTasks();
public int getTaskID();
public int getTaskID();
public boolean affectsGeneratedSources();
public boolean shouldSaveFiles();
public boolean canOutput();
public Type getType();
public static enum Type {
ANDROIDIDE_PLUGIN,
BUILD,
LINT,
HELP,
OTHER
public boolean shouldSaveFiles();
public boolean canOutput();
public Type getType();
public static enum Type {
ANDROIDIDE_PLUGIN,
BUILD,
LINT,
HELP,
OTHER
}
public static final int TASK_SHOW_DEPENDENCIES = 0;
public static final int TASK_ASSEMBLE_DEBUG = 1;
public static final int TASK_ASSEMBLE_RELEASE = 2;
public static final int TASK_BUILD = 3;
public static final int TASK_BUNDLE = 4;
public static final int TASK_CLEAN = 5;
public static final int TASK_CLEAN_BUILD = 6;
public static final int TASK_COMPILE_JAVA = 7;
public static final int TASK_DEX = 8;
public static final int TASK_LINT = 9;
public static final int TASK_LINT_DEBUG = 10;
public static final int TASK_LINT_RELEASE = 11;
public static final int TASK_TASKS = 12;
public static final int TASK_INIT_PROJECT = 13;
public static final int TASK_PROCESS_DEBUG_RESOURCES = 14;
public static final int DATABINDING_GEN_BASE_CLASSES_DEBUG = 15;
public static final int TASK_UPDATE_RESOURCE_CLASSES = 16;
public static final int TASK_SHOW_DEPENDENCIES = 0;
public static final int TASK_ASSEMBLE_DEBUG = 1;
public static final int TASK_ASSEMBLE_RELEASE = 2;
public static final int TASK_BUILD = 3;
public static final int TASK_BUNDLE = 4;
public static final int TASK_CLEAN = 5;
public static final int TASK_CLEAN_BUILD = 6;
public static final int TASK_COMPILE_JAVA = 7;
public static final int TASK_DEX = 8;
public static final int TASK_LINT = 9;
public static final int TASK_LINT_DEBUG = 10;
public static final int TASK_LINT_RELEASE = 11;
public static final int TASK_TASKS = 12;
public static final int TASK_INIT_PROJECT = 13;
public static final int TASK_PROCESS_DEBUG_RESOURCES = 14;
public static final int DATABINDING_GEN_BASE_CLASSES_DEBUG = 15;
public static final int TASK_UPDATE_RESOURCE_CLASSES = 16;
}
@@ -1,79 +1,83 @@
/************************************************************************************
* This file is part of AndroidIDE.
*
*
*
*
* AndroidIDE is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
*
* AndroidIDE is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
*
* You should have received a copy of the GNU General Public License
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
*
**************************************************************************************/
**************************************************************************************/
package com.itsaky.androidide.tasks;
import android.os.Handler;
import android.os.Looper;
import com.blankj.utilcode.util.ThrowableUtils;
import com.itsaky.androidide.utils.Logger;
import java.util.concurrent.Callable;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
import com.itsaky.androidide.utils.Logger;
public class TaskExecutor {
private final Executor executor = Executors.newSingleThreadExecutor();
private final Handler handler = new Handler(Looper.getMainLooper());
private final Logger LOG = Logger.instance ("TaskExecutor");
private final Logger LOG = Logger.instance("TaskExecutor");
public interface Callback<R> {
void complete(R result);
}
public interface CallbackWithError<R> {
void complete(R result, Throwable error);
}
public <R> void executeAsync(Callable<R> callable, Callback<R> callback) {
executor.execute(() -> {
try {
final R result = callable.call();
handler.post(() -> {
callback.complete(result);
});
} catch (Throwable th) {
LOG.error ("Callable task was not able to finish", th);
}
});
executor.execute(
() -> {
try {
final R result = callable.call();
handler.post(
() -> {
callback.complete(result);
});
} catch (Throwable th) {
LOG.error("Callable task was not able to finish", th);
}
});
}
public <R> void executeAsyncProvideError(Callable<R> callable, CallbackWithError<R> callback) {
executor.execute(() -> {
Throwable error = null;
R result = null;
try {
result = callable.call();
} catch (Throwable th) {
LOG.error ("Callable task was not able to finish", th);
error = th;
}
final R resultCopied = result;
final Throwable errorCopied = error;
handler.post(() -> {
callback.complete(resultCopied, errorCopied);
});
});
executor.execute(
() -> {
Throwable error = null;
R result = null;
try {
result = callable.call();
} catch (Throwable th) {
LOG.error("Callable task was not able to finish", th);
error = th;
}
final R resultCopied = result;
final Throwable errorCopied = error;
handler.post(
() -> {
callback.complete(resultCopied, errorCopied);
});
});
}
}
}
@@ -1,29 +1,30 @@
/************************************************************************************
* This file is part of AndroidIDE.
*
*
*
*
* AndroidIDE is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
*
* AndroidIDE is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
*
* You should have received a copy of the GNU General Public License
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
*
**************************************************************************************/
**************************************************************************************/
package com.itsaky.androidide.tasks.callables;
import android.content.Context;
import com.itsaky.androidide.adapters.viewholders.FileTreeViewHolder;
import com.unnamed.b.atv.model.TreeNode;
import java.io.File;
import java.io.FileFilter;
import java.util.Arrays;
@@ -31,56 +32,53 @@ import java.util.Comparator;
import java.util.concurrent.Callable;
public class FileTreeCallable implements Callable<Boolean> {
private final Context ctx;
private TreeNode parent;
private File file;
private final Context ctx;
private TreeNode parent;
private File file;
public FileTreeCallable(Context ctx, TreeNode parent, File file) {
this.ctx = ctx;
this.parent = parent;
this.file = file;
}
public FileTreeCallable(Context ctx, TreeNode parent, File file) {
this.ctx = ctx;
this.parent = parent;
this.file = file;
}
@Override
public Boolean call() throws Exception {
getNodeFromArray(file.listFiles(/*new HiddenFilesFilter()*/), parent);
return true;
}
@Override
public Boolean call() throws Exception {
getNodeFromArray(file.listFiles(/*new HiddenFilesFilter()*/ ), parent);
return true;
}
private void getNodeFromArray(File[] files, TreeNode parent) {
Arrays.sort(files, new SortFileName());
Arrays.sort(files, new SortFolder());
for (File file : files) {
TreeNode node = new TreeNode(file);
node.setViewHolder(new FileTreeViewHolder(ctx));
parent.addChild(node);
}
}
private void getNodeFromArray(File[] files, TreeNode parent) {
Arrays.sort(files, new SortFileName());
Arrays.sort(files, new SortFolder());
for (File file : files) {
TreeNode node = new TreeNode(file);
node.setViewHolder(new FileTreeViewHolder(ctx));
parent.addChild(node);
}
}
public static class HiddenFilesFilter implements FileFilter {
public static class HiddenFilesFilter implements FileFilter {
@Override
public boolean accept(File p1) {
return !p1.getName().startsWith(".");
}
}
@Override
public boolean accept(File p1) {
return !p1.getName().startsWith(".");
}
}
public static class SortFileName implements Comparator<File> {
@Override
public int compare(File f1, File f2) {
return f1.getName().compareTo(f2.getName());
}
}
public static class SortFileName implements Comparator<File> {
@Override
public int compare(File f1, File f2) {
return f1.getName().compareTo(f2.getName());
}
}
public static class SortFolder implements Comparator<File> {
@Override
public int compare(File f1, File f2) {
if (f1.isDirectory() == f2.isDirectory())
return 0;
else if (f1.isDirectory() && !f2.isDirectory())
return -1;
else
return 1;
}
}
public static class SortFolder implements Comparator<File> {
@Override
public int compare(File f1, File f2) {
if (f1.isDirectory() == f2.isDirectory()) return 0;
else if (f1.isDirectory() && !f2.isDirectory()) return -1;
else return 1;
}
}
}
@@ -1,52 +1,54 @@
/************************************************************************************
* This file is part of AndroidIDE.
*
*
*
*
* AndroidIDE is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
*
* AndroidIDE is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
*
* You should have received a copy of the GNU General Public License
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
*
**************************************************************************************/
**************************************************************************************/
package com.itsaky.androidide.tasks.callables;
import android.content.Context;
import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;
import com.itsaky.androidide.models.License;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.util.List;
import java.util.concurrent.Callable;
public class LicenseReader implements Callable<List<License>> {
private final Context ctx;
public LicenseReader(Context ctx) {
this.ctx = ctx;
}
@Override
public List<License> call() throws Exception {
BufferedReader reader = new BufferedReader(new InputStreamReader(ctx.getAssets().open("licenses.json")));
BufferedReader reader =
new BufferedReader(new InputStreamReader(ctx.getAssets().open("licenses.json")));
String line;
StringBuilder sb = new StringBuilder();
while((line = reader.readLine()) != null) {
while ((line = reader.readLine()) != null) {
sb.append(line);
}
TypeToken<List<License>> token = new TypeToken<List<License>>(){};
TypeToken<List<License>> token = new TypeToken<List<License>>() {};
return new Gson().fromJson(sb.toString(), token.getType());
}
}
@@ -1,49 +1,53 @@
/************************************************************************************
* This file is part of AndroidIDE.
*
*
*
*
* AndroidIDE is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
*
* AndroidIDE is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
*
* You should have received a copy of the GNU General Public License
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
*
**************************************************************************************/
**************************************************************************************/
package com.itsaky.androidide.tasks.callables;
import android.text.TextUtils;
import com.blankj.utilcode.util.FileUtils;
import java.io.File;
import java.io.FileFilter;
public class ListDirectoryCallable implements java.util.concurrent.Callable<String> {
private File file;
public ListDirectoryCallable(File file) {
this.file = file;
}
@Override
public String call() throws Exception {
return TextUtils.join("\n", FileUtils.listFilesInDirWithFilter(file, ARCHIVE_FILTER, false));
}
private final FileFilter ARCHIVE_FILTER = new FileFilter(){
private File file;
@Override
public boolean accept(File p1) {
return p1.isFile() && (p1.getName().endsWith(".tar.xz") || p1.getName().endsWith(".zip"));
}
};
public ListDirectoryCallable(File file) {
this.file = file;
}
@Override
public String call() throws Exception {
return TextUtils.join(
"\n", FileUtils.listFilesInDirWithFilter(file, ARCHIVE_FILTER, false));
}
private final FileFilter ARCHIVE_FILTER =
new FileFilter() {
@Override
public boolean accept(File p1) {
return p1.isFile()
&& (p1.getName().endsWith(".tar.xz") || p1.getName().endsWith(".zip"));
}
};
}
@@ -1,47 +1,48 @@
/************************************************************************************
* This file is part of AndroidIDE.
*
*
*
*
* AndroidIDE is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
*
* AndroidIDE is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
*
* You should have received a copy of the GNU General Public License
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
*
**************************************************************************************/
**************************************************************************************/
package com.itsaky.androidide.tasks.callables;
import com.itsaky.androidide.models.NewProjectDetails;
import com.itsaky.androidide.utils.ProjectWriter;
import java.util.concurrent.Callable;
import com.itsaky.androidide.models.ProjectTemplate;
import com.itsaky.androidide.interfaces.ProjectWriterCallback;
import com.itsaky.androidide.models.NewProjectDetails;
import com.itsaky.androidide.models.ProjectTemplate;
import com.itsaky.androidide.utils.ProjectWriter;
import java.util.concurrent.Callable;
public class ProjectCreatorCallable implements Callable<Void> {
private ProjectTemplate template;
private NewProjectDetails details;
private ProjectWriterCallback callback;
private ProjectTemplate template;
private NewProjectDetails details;
private ProjectWriterCallback callback;
public ProjectCreatorCallable(ProjectTemplate template, NewProjectDetails details, ProjectWriterCallback callback) {
this.template = template;
this.details = details;
this.callback = callback;
}
@Override
public Void call() throws Exception {
ProjectWriter.write(template, details, callback);
return null;
}
public ProjectCreatorCallable(
ProjectTemplate template, NewProjectDetails details, ProjectWriterCallback callback) {
this.template = template;
this.details = details;
this.callback = callback;
}
@Override
public Void call() throws Exception {
ProjectWriter.write(template, details, callback);
return null;
}
}
@@ -1,43 +1,43 @@
/************************************************************************************
* This file is part of AndroidIDE.
*
*
*
*
* AndroidIDE is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
*
* AndroidIDE is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
*
* You should have received a copy of the GNU General Public License
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
*
**************************************************************************************/
**************************************************************************************/
package com.itsaky.androidide.tasks.callables;
import com.blankj.utilcode.util.ZipUtils;
import java.io.File;
import java.util.List;
import java.util.concurrent.Callable;
public final class UnzipCallable implements Callable<List<File>> {
private File src;
private File dest;
private File src;
private File dest;
public UnzipCallable(File src, File dest) {
this.src = src;
this.dest = dest;
}
public UnzipCallable(File src, File dest) {
this.src = src;
this.dest = dest;
}
@Override
public List<File> call() throws Exception {
return ZipUtils.unzipFile(src, dest);
}
@Override
public List<File> call() throws Exception {
return ZipUtils.unzipFile(src, dest);
}
}
@@ -1,23 +1,22 @@
/************************************************************************************
* This file is part of AndroidIDE.
*
*
*
*
* AndroidIDE is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
*
* AndroidIDE is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
*
* You should have received a copy of the GNU General Public License
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
*
**************************************************************************************/
**************************************************************************************/
package com.itsaky.androidide.tasks.gradle;
@@ -35,21 +34,16 @@ import com.itsaky.androidide.tasks.gradle.plugin.InitializeIDEProject;
import com.itsaky.androidide.tasks.gradle.resources.ProcessDebugResources;
public class BaseGradleTasks {
public static final BaseGradleTask
BUILD = new Build(),
ASSEMBLE_DEBUG = new AssembleDebug(),
ASSEMBLE_RELEASE = new AssembleRelease(),
BUNDLE = new Bundle(),
CLEAN = new Clean(),
CLEAN_BUILD = new CleanBuild(),
LINT = new Lint(),
LINT_DEBUG = new LintDebug(),
LINT_RELEASE = new LintRelease(),
PROCESS_DEBUG_RESOURCES = new ProcessDebugResources(),
INITIALIZE_IDE_PROJECT = new InitializeIDEProject();
public static final BaseGradleTask BUILD = new Build(),
ASSEMBLE_DEBUG = new AssembleDebug(),
ASSEMBLE_RELEASE = new AssembleRelease(),
BUNDLE = new Bundle(),
CLEAN = new Clean(),
CLEAN_BUILD = new CleanBuild(),
LINT = new Lint(),
LINT_DEBUG = new LintDebug(),
LINT_RELEASE = new LintRelease(),
PROCESS_DEBUG_RESOURCES = new ProcessDebugResources(),
INITIALIZE_IDE_PROJECT = new InitializeIDEProject();
}

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