diff --git a/app/build.gradle b/app/build.gradle index 08974c25b..aba8f297f 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -29,7 +29,8 @@ android { disable "VectorPath", "NestedWeights", "UnusedResources", - "ContentDescription" + "ContentDescription", + "SmallSp" } buildFeatures.viewBinding true @@ -59,7 +60,7 @@ android { stringfog.enable false } release { - minifyEnabled false + minifyEnabled true proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' signingConfig signingConfigs.release stringfog.enable true @@ -75,9 +76,8 @@ android { } stringfog { - def isRelease = getGradle().getStartParameter().getTaskRequests().toString().contains("release") key 'WVV0aGMwaEFZVzVFVW05SlJFbGtaUT09' - enable false + enable true implementation 'com.github.megatronking.stringfog.xor.StringFogImpl' } @@ -101,9 +101,9 @@ dependencies { // AndroidX implementation 'androidx.annotation:annotation:1.2.0' - implementation 'androidx.appcompat:appcompat:1.3.0' + implementation 'androidx.appcompat:appcompat:1.3.1' implementation 'androidx.cardview:cardview:1.0.0' - implementation 'androidx.constraintlayout:constraintlayout:2.0.4' + implementation 'androidx.constraintlayout:constraintlayout:2.1.0' implementation 'androidx.coordinatorlayout:coordinatorlayout:1.1.0' implementation 'androidx.drawerlayout:drawerlayout:1.1.1' implementation 'androidx.multidex:multidex:2.0.1' @@ -121,9 +121,6 @@ dependencies { // Local projects implementation project(':treeview') - implementation project(':spinkit') - implementation project(':slidr') - implementation project(':javadocparser') implementation project(':lsp') implementation fileTree(dir: 'libs', include: ['*.jar']) diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro index 3860f99a1..d1c2252fc 100644 --- a/app/proguard-rules.pro +++ b/app/proguard-rules.pro @@ -1,4 +1,5 @@ --keepattributes SourceFile, LineNumberTable +-keepattributes Signature +-keepattributes *Annotation* -renamesourcefileattribute FuckYou -obfuscationdictionary dictionary.txt -packageobfuscationdictionary dictionary.txt @@ -8,10 +9,16 @@ -dontwarn -dontnote +-dontwarn sun.misc.** -dontwarn android.arch.** -dontwarn android.lifecycle.** -keep class android.arch.** { *; } -keep class android.lifecycle.** { *; } +-keep class com.google.gson.examples.android.model.** { ; } +-keep class * extends com.google.gson.TypeAdapter +-keep class * implements com.google.gson.TypeAdapterFactory +-keep class * implements com.google.gson.JsonSerializer +-keep class * implements com.google.gson.JsonDeserializer -dontwarn androidx.arch.** -dontwarn androidx.lifecycle.** @@ -19,10 +26,17 @@ -keep class androidx.lifecycle.** { *; } -keepclasseswithmembernames,includedescriptorclasses class * { native ; } +-keep,allowobfuscation,allowshrinking class com.google.gson.reflect.TypeToken +-keep,allowobfuscation,allowshrinking class * extends com.google.gson.reflect.TypeToken -keep public class * implements com.bumptech.glide.module.GlideModule -keep class * extends com.bumptech.glide.module.AppGlideModule { (...); } +-keep class com.bumptech.glide.load.data.ParcelFileDescriptorRewinder$InternalRewinder { *** rewind(); } + +-keepclassmembers,allowobfuscation class * { + @com.google.gson.annotations.SerializedName ; +} + -keep public enum com.bumptech.glide.load.ImageHeaderParser$** { **[] $VALUES; public *; } --keep class com.bumptech.glide.load.data.ParcelFileDescriptorRewinder$InternalRewinder { *** rewind(); } diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 98a7429b5..cc20f27db 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -11,6 +11,7 @@ + + diff --git a/app/src/main/assets/licenses.json b/app/src/main/assets/licenses.json new file mode 100644 index 000000000..091c968b2 --- /dev/null +++ b/app/src/main/assets/licenses.json @@ -0,0 +1,62 @@ +[ + { + "name": "AndroidX Libraries", + "license": "Apache License 2.0", + "url": "https://github.com/androidx/androidx" + }, + { + "name": "Material Components for Android", + "license": "Apache License 2.0", + "url": "https://github.com/material-components/material-components-android" + }, + { + "name": "Gson", + "license": "Apache License 2.0", + "url": "https://github.com/google/gson" + }, + { + "name": "Guava", + "license": "Apache License 2.0", + "url": "https://github.com/google/guava" + }, + { + "name": "ANTLR4", + "license": "The 3-Clause BSD License", + "url": "https://github.com/antlr/antlr4" + }, + { + "name": "Glide", + "license": "BSD, part MIT and Apache 2.0", + "url": "https://github.com/bumptech/glide" + }, + { + "name": "Android UtilCode", + "license": "Apache License 2.0", + "url": "https://github.com/Blankj/AndroidUtilCode" + }, + { + "name": "UnicornFilePicker", + "license": "Apache License 2.0", + "url": "https://github.com/abhishekti7/UnicornFilePicker" + }, + { + "name": "QuickAction", + "license": "Apache License 2.0", + "url": "https://github.com/piruin/quickaction" + }, + { + "name": "XmlToJson", + "license": "Apache License 2.0", + "url": "https://github.com/smart-fun/XmlToJson" + }, + { + "name": "AndroidTreeView", + "license": "Apache License 2.0", + "url": "https://github.com/bmelnychuk/AndroidTreeView" + }, + { + "name": "CodeEditor", + "license": "GPL-3.0 License", + "url": "https://github.com/Rosemoe/CodeEditor" + } +] \ No newline at end of file diff --git a/app/src/main/java/com/itsaky/androidide/AboutActivity.java b/app/src/main/java/com/itsaky/androidide/AboutActivity.java new file mode 100644 index 000000000..8de0eafd7 --- /dev/null +++ b/app/src/main/java/com/itsaky/androidide/AboutActivity.java @@ -0,0 +1,61 @@ +package com.itsaky.androidide; + +import android.os.Bundle; +import android.view.View; +import com.itsaky.androidide.app.StudioActivity; +import com.itsaky.androidide.databinding.ActivityAboutBinding; +import com.itsaky.androidide.fragments.sheets.OptionsListFragment; +import com.itsaky.androidide.models.License; +import com.itsaky.androidide.tasks.TaskExecutor; +import com.itsaky.androidide.tasks.callables.LicenseReader; +import java.util.List; + +public class AboutActivity extends StudioActivity { + + private ActivityAboutBinding binding; + + @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()); + + new TaskExecutor().executeAsync(new LicenseReader(this), __ -> { + setupLicenses(__); + }); + } + + private void setupLicenses(List licenses) { + if(licenses == null || licenses.size() <= 0) { + binding.items.licenses.setText(getString(R.string.msg_about_no_licenses)); + } else { + StringBuilder sb = new StringBuilder(); + for(License license : licenses) { + sb.append("\u2022 "); + sb.append(license.name); + sb.append("\n"); + sb.append(getString(R.string.msg_about_licensed_under, license.license)); + sb.append("\n"); + sb.append(getString(R.string.msg_about_license_source, license.url)); + sb.append("\n\n"); + } + binding.items.licenses.setText(sb); + } + } + + private String getFooter() { + final String arch = android.os.Build.SUPPORTED_ABIS[0]; + try { + 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); + } + } +} diff --git a/app/src/main/java/com/itsaky/androidide/EditorActivity.java b/app/src/main/java/com/itsaky/androidide/EditorActivity.java index 979c4cb09..97b8abcbe 100644 --- a/app/src/main/java/com/itsaky/androidide/EditorActivity.java +++ b/app/src/main/java/com/itsaky/androidide/EditorActivity.java @@ -1121,6 +1121,7 @@ public class EditorActivity extends StudioActivity implements FileTreeFragment.F if(i >= 0 && !mBinding.tabs.getTabAt(i).isSelected()) mBinding.tabs.getTabAt(i).select(); mBinding.editorDrawerLayout.closeDrawer(GravityCompat.END); + invalidateOptionsMenu(); } @Override @@ -1521,5 +1522,7 @@ public class EditorActivity extends StudioActivity implements FileTreeFragment.F DidCloseTextDocumentParams p = new DidCloseTextDocumentParams(); p.textDocument = id; didClose(p); + + invalidateOptionsMenu(); } } diff --git a/app/src/main/java/com/itsaky/androidide/PreferencesActivity.java b/app/src/main/java/com/itsaky/androidide/PreferencesActivity.java index bfb1eab00..6dc08a282 100644 --- a/app/src/main/java/com/itsaky/androidide/PreferencesActivity.java +++ b/app/src/main/java/com/itsaky/androidide/PreferencesActivity.java @@ -28,7 +28,6 @@ public class PreferencesActivity extends StudioActivity { setSupportActionBar(binding.toolbar); getSupportActionBar().setTitle(R.string.ide_preferences); getSupportActionBar().setDisplayHomeAsUpEnabled(true); - getSupportActionBar().setHomeButtonEnabled(true); loadFragment(getPrefsFragment()); } diff --git a/app/src/main/java/com/itsaky/androidide/adapters/SymbolInputAdapter.java b/app/src/main/java/com/itsaky/androidide/adapters/SymbolInputAdapter.java index e450c2b88..123626120 100644 --- a/app/src/main/java/com/itsaky/androidide/adapters/SymbolInputAdapter.java +++ b/app/src/main/java/com/itsaky/androidide/adapters/SymbolInputAdapter.java @@ -47,7 +47,7 @@ public class SymbolInputAdapter extends RecyclerView.Adapter channel.insertSymbol(symbol.commit, symbol.offset)); } diff --git a/app/src/main/java/com/itsaky/androidide/adapters/viewholders/FileTreeViewHolder.java b/app/src/main/java/com/itsaky/androidide/adapters/viewholders/FileTreeViewHolder.java index 34a8f3ae3..fabe2f733 100644 --- a/app/src/main/java/com/itsaky/androidide/adapters/viewholders/FileTreeViewHolder.java +++ b/app/src/main/java/com/itsaky/androidide/adapters/viewholders/FileTreeViewHolder.java @@ -7,7 +7,6 @@ import android.view.LayoutInflater; import android.view.View; import android.widget.ImageView; import androidx.core.content.ContextCompat; -import com.github.ybq.android.spinkit.style.Circle; import com.itsaky.androidide.R; import com.itsaky.androidide.app.StudioActivity; import com.itsaky.androidide.databinding.LayoutFiletreeItemBinding; @@ -60,7 +59,6 @@ public class FileTreeViewHolder extends TreeNode.BaseNodeViewHolder { } public void setLoading() { - chevron.setImageDrawable(new Circle()); chevron.getDrawable().setColorFilter(ContextCompat.getColor(context, R.color.secondaryLightColor), PorterDuff.Mode.SRC_ATOP); } diff --git a/app/src/main/java/com/itsaky/androidide/app/StudioApp.java b/app/src/main/java/com/itsaky/androidide/app/StudioApp.java index a26926d33..38098096e 100644 --- a/app/src/main/java/com/itsaky/androidide/app/StudioApp.java +++ b/app/src/main/java/com/itsaky/androidide/app/StudioApp.java @@ -75,7 +75,8 @@ public class StudioApp extends MultiDexApplication public static final String TELEGRAM_GROUP_URL = "https://t.me/androidide_discussions"; public static final String SUGGESTIONS_URL = "https://github.com/itsaky/AndroidIDE"; - + public static final String WEBSITE = "https://androidide.com"; + private Thread.UncaughtExceptionHandler uncaughtExceptionHandler; @Override @@ -338,6 +339,7 @@ public class StudioApp extends MultiDexApplication return new File(getFilesDir(), "framework"); } + @SuppressLint("SdCardPath") public File getIDEDataDir() { return Environment.mkdirIfNotExits(new File("/data/data/com.itsaky.androidide/files")); } @@ -489,4 +491,12 @@ public class StudioApp extends MultiDexApplication open.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); startActivity(open); } + + public void openWebsite() { + Intent open = new Intent(); + open.setAction(Intent.ACTION_VIEW); + open.setData(Uri.parse(StudioApp.WEBSITE)); + open.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + startActivity(open); + } } diff --git a/app/src/main/java/com/itsaky/androidide/fragments/EditorFragment.java b/app/src/main/java/com/itsaky/androidide/fragments/EditorFragment.java index f42855316..645643821 100644 --- a/app/src/main/java/com/itsaky/androidide/fragments/EditorFragment.java +++ b/app/src/main/java/com/itsaky/androidide/fragments/EditorFragment.java @@ -310,13 +310,14 @@ public class EditorFragment extends BaseFragment implements EditorEventListener @Override public void afterDelete(CodeEditor editor, CharSequence content, int startLine, int startColumn, int endLine, int endColumn, CharSequence deletedContent) { isModified = true; + requestSignature(); } @Override public void afterInsert(CodeEditor editor, CharSequence content, int startLine, int startColumn, int endLine, int endColumn, CharSequence insertedContent) { isModified = true; - if(getFile() != null && getFile().getName().endsWith(".java")) { + if(getFile() != null && getFile().getName().endsWith(EXT_XML)) { boolean isOpen = false; try { isOpen = editor.getText().charAt(editor.getCursor().getLeft() - 2) == '<'; @@ -326,10 +327,7 @@ public class EditorFragment extends BaseFragment implements EditorEventListener } } - char c = insertedContent.charAt(0); - if(c == '(' || c == ',') { - requestSignature(); - } + requestSignature(); } private void requestSignature() { diff --git a/app/src/main/java/com/itsaky/androidide/fragments/preferences/IDEPreferences.java b/app/src/main/java/com/itsaky/androidide/fragments/preferences/IDEPreferences.java index 85b931607..9803eefce 100644 --- a/app/src/main/java/com/itsaky/androidide/fragments/preferences/IDEPreferences.java +++ b/app/src/main/java/com/itsaky/androidide/fragments/preferences/IDEPreferences.java @@ -1,5 +1,6 @@ package com.itsaky.androidide.fragments.preferences; +import android.content.Intent; import android.graphics.Typeface; import android.os.Bundle; import android.text.Spannable; @@ -13,13 +14,13 @@ import androidx.core.content.ContextCompat; import androidx.preference.Preference; import androidx.preference.PreferenceScreen; import com.blankj.utilcode.util.ResourceUtils; -import com.blankj.utilcode.util.ThrowableUtils; +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.app.StudioApp; import org.json.JSONArray; import org.json.JSONObject; -import com.blankj.utilcode.util.SizeUtils; public class IDEPreferences extends BasePreferenceFragment { @@ -35,6 +36,7 @@ public class IDEPreferences extends BasePreferenceFragment { 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) { @@ -48,7 +50,8 @@ public class IDEPreferences extends BasePreferenceFragment { final Preference telegram = new Preference(getContext()); final Preference issueTracker = new Preference(getContext()); final Preference changelog = new Preference(getContext()); - + final Preference about = new Preference(getContext()); + appearance.setKey(KEY_APPEARANCE); appearance.setIconSpaceReserved(false); appearance.setFragment(getAppearanceFrag().getClass().getName()); @@ -81,17 +84,24 @@ public class IDEPreferences extends BasePreferenceFragment { changelog.setIconSpaceReserved(false); changelog.setTitle(R.string.pref_changelog); changelog.setSummary(R.string.pref_changelog_summary); + + about.setKey(KEY_ABOUT); + about.setIconSpaceReserved(false); + about.setTitle(R.string.idepref_about_title); +// about.setSummary(R.string.pref_changelog_summary); screen.addPreference(editor); screen.addPreference(build); screen.addPreference(issueTracker); screen.addPreference(telegram); screen.addPreference(changelog); + screen.addPreference(about); final Preference.OnPreferenceClickListener listener = getListener(); issueTracker.setOnPreferenceClickListener(listener); telegram.setOnPreferenceClickListener(listener); changelog.setOnPreferenceClickListener(listener); + about.setOnPreferenceClickListener(listener); } private AppearancePreferences getAppearanceFrag() { @@ -161,7 +171,9 @@ public class IDEPreferences extends BasePreferenceFragment { StudioApp.getInstance().openTelegramGroup(); } else if(key.equals(KEY_ISSUES)) { StudioApp.getInstance().openIssueTracker(); - } + } else if(key.equals(KEY_ABOUT)) { + startActivity(new Intent(getContext(), AboutActivity.class)); + } return true; } }; diff --git a/app/src/main/java/com/itsaky/androidide/fragments/sheets/BaseBottomSheetFragment.java b/app/src/main/java/com/itsaky/androidide/fragments/sheets/BaseBottomSheetFragment.java index a7be2ab28..80c2b0152 100644 --- a/app/src/main/java/com/itsaky/androidide/fragments/sheets/BaseBottomSheetFragment.java +++ b/app/src/main/java/com/itsaky/androidide/fragments/sheets/BaseBottomSheetFragment.java @@ -13,16 +13,13 @@ import android.content.Context; import android.content.DialogInterface; import android.view.MotionEvent; import android.graphics.Rect; -import com.r0adkll.slidr.model.SlidrInterface; -import com.r0adkll.slidr.Slidr; -import com.r0adkll.slidr.model.SlidrConfig; -import com.r0adkll.slidr.model.SlidrPosition; public abstract class BaseBottomSheetFragment extends BottomSheetDialogFragment { protected Dialog mDialog; private LayoutSheetBinding binding; protected boolean shadowEnabled = true; + protected boolean titleEnabled = true; @Override public Dialog onCreateDialog(Bundle savedInstanceState) { @@ -78,7 +75,7 @@ public abstract class BaseBottomSheetFragment extends BottomSheetDialogFragment } protected boolean shouldHideTitle() { - return false; + return !titleEnabled; } protected String getTitle() { @@ -88,6 +85,10 @@ public abstract class BaseBottomSheetFragment extends BottomSheetDialogFragment public void setShowShadow(boolean enabled) { this.shadowEnabled = enabled; } + + public void setShowTitle(boolean enabled) { + this.titleEnabled = enabled; + } protected void onShow() {} protected abstract void bind(LinearLayout container); diff --git a/app/src/main/java/com/itsaky/androidide/fragments/sheets/LogSheet.java b/app/src/main/java/com/itsaky/androidide/fragments/sheets/LogSheet.java index cc7bd9173..12e54d9f7 100644 --- a/app/src/main/java/com/itsaky/androidide/fragments/sheets/LogSheet.java +++ b/app/src/main/java/com/itsaky/androidide/fragments/sheets/LogSheet.java @@ -10,6 +10,7 @@ import android.widget.TextView; import com.blankj.utilcode.util.ThrowableUtils; import com.itsaky.androidide.R; import com.itsaky.androidide.models.LogLine; +import android.annotation.SuppressLint; public class LogSheet extends BaseBottomSheetFragment { @@ -51,6 +52,7 @@ public class LogSheet extends BaseBottomSheetFragment { } } + @SuppressLint("SetTextI18n") protected TextView createLogTextView(LogLine line) { TextView text = new TextView(getContext()); text.setTextSize(TypedValue.COMPLEX_UNIT_SP, 14); diff --git a/app/src/main/java/com/itsaky/androidide/fragments/sheets/OptionsListFragment.java b/app/src/main/java/com/itsaky/androidide/fragments/sheets/OptionsListFragment.java index 2390ea04d..71b3125b6 100644 --- a/app/src/main/java/com/itsaky/androidide/fragments/sheets/OptionsListFragment.java +++ b/app/src/main/java/com/itsaky/androidide/fragments/sheets/OptionsListFragment.java @@ -16,6 +16,8 @@ public class OptionsListFragment extends BaseBottomSheetFragment { private RecyclerView mList; private OnOptionsClickListener listener; private final List mOptions = new ArrayList<>(); + + protected boolean dismissOnItemClick = true; @Override protected void bind(LinearLayout container) { @@ -28,7 +30,12 @@ public class OptionsListFragment extends BaseBottomSheetFragment { public void onViewCreated(View view, Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); mList.setLayoutManager(new LinearLayoutManager(getContext())); - mList.setAdapter(new OptionsSheetAdapter(mOptions, __ -> { dismiss(); if(listener != null) listener.onOptionsClick(__);})); + mList.setAdapter(new OptionsSheetAdapter(mOptions, __ -> { + if(dismissOnItemClick) + dismiss(); + if(listener != null) + listener.onOptionsClick(__); + })); } public OptionsListFragment setOnOptionsClickListener(OnOptionsClickListener listener) { @@ -52,6 +59,11 @@ public class OptionsListFragment extends BaseBottomSheetFragment { mOptions.remove(option); return this; } + + public OptionsListFragment setDismissOnItemClick(boolean dissmiss) { + this.dismissOnItemClick = dissmiss; + return this; + } @Override protected String getTitle() { diff --git a/app/src/main/java/com/itsaky/androidide/fragments/sheets/ProgressSheet.java b/app/src/main/java/com/itsaky/androidide/fragments/sheets/ProgressSheet.java index 8962a33c9..a5f1258ca 100644 --- a/app/src/main/java/com/itsaky/androidide/fragments/sheets/ProgressSheet.java +++ b/app/src/main/java/com/itsaky/androidide/fragments/sheets/ProgressSheet.java @@ -5,7 +5,6 @@ import android.os.Bundle; import android.view.LayoutInflater; import android.view.View; import android.widget.LinearLayout; -import com.github.ybq.android.spinkit.style.FoldingCube; import com.itsaky.androidide.databinding.LayoutProgressSheetBinding; import com.blankj.utilcode.util.SizeUtils; import android.widget.RelativeLayout; @@ -13,7 +12,7 @@ import android.widget.RelativeLayout; public class ProgressSheet extends BaseBottomSheetFragment { private LayoutProgressSheetBinding binding; - private Drawable drawable = new FoldingCube(); + private Drawable drawable = null; private String message = ""; private String subMessage = ""; private boolean subMessageEnabled = false; diff --git a/app/src/main/java/com/itsaky/androidide/fragments/sheets/TextSheetFragment.java b/app/src/main/java/com/itsaky/androidide/fragments/sheets/TextSheetFragment.java index 6a5564eda..67694f66d 100644 --- a/app/src/main/java/com/itsaky/androidide/fragments/sheets/TextSheetFragment.java +++ b/app/src/main/java/com/itsaky/androidide/fragments/sheets/TextSheetFragment.java @@ -44,7 +44,7 @@ public class TextSheetFragment extends BaseBottomSheetFragment { @Override protected void onShow() { super.onShow(); - setToEditor(SpannableString.valueOf(getOutputBuilder())); + editor.setText(getOutputBuilder()); } private Void scrollToBottom() { diff --git a/app/src/main/java/com/itsaky/androidide/language/java/server/JavaLanguageServer.java b/app/src/main/java/com/itsaky/androidide/language/java/server/JavaLanguageServer.java index 571696cbe..31b613f74 100644 --- a/app/src/main/java/com/itsaky/androidide/language/java/server/JavaLanguageServer.java +++ b/app/src/main/java/com/itsaky/androidide/language/java/server/JavaLanguageServer.java @@ -48,6 +48,7 @@ import java.util.concurrent.BlockingQueue; import java.util.concurrent.ConcurrentHashMap; import com.itsaky.lsp.DidChangeWatchedFilesParams; import com.itsaky.lsp.SignatureHelp; +import android.annotation.SuppressLint; public class JavaLanguageServer implements ShellServer.Callback { @@ -136,6 +137,7 @@ public class JavaLanguageServer implements ShellServer.Callback { return server; } + @SuppressLint("NewApi") protected void sendInit() { InitializeParams p = new InitializeParams(); p.rootUri = new File(project.getProjectPath()).toURI(); @@ -267,41 +269,12 @@ public class JavaLanguageServer implements ShellServer.Callback { @Override public void output(CharSequence charSequence) { - writeOut(charSequence.toString()); - } - - private BufferedOutputStream os; - private BufferedOutputStream os1; - - private void writeOut(String data) { - try { - if(os == null) { - os = new BufferedOutputStream(new FileOutputStream(new File(FileUtil.getExternalStorageDir(), "ide_xlog/out.txt"), true)); - } - os.write(data.getBytes()); - os.flush(); - } catch (Throwable th) { - logger.e(ThrowableUtils.getFullStackTrace(th)); - } - } - - private void writeOut2(String data) { - try { - if(os1 == null) { - os1 = new BufferedOutputStream(new FileOutputStream(new File(FileUtil.getExternalStorageDir(), "ide_xlog/verbose.txt"), true)); - } - os1.write(data.getBytes()); - os1.flush(); - } catch (Throwable th) { - logger.e(ThrowableUtils.getFullStackTrace(th)); - } } /** * This method is called on UI Thread, do not perform time consuming or networking tasks */ private void onServerOut(String line) { - writeOut2(line + "\n"); if(line.contains(CONTENT_LENGTH)) line = line.substring(0, line.lastIndexOf(CONTENT_LENGTH)); if(this.client == null) return; try { diff --git a/app/src/main/java/com/itsaky/androidide/models/CompletionItemWrapper.java b/app/src/main/java/com/itsaky/androidide/models/CompletionItemWrapper.java index fe4cb2c8e..d5812b059 100644 --- a/app/src/main/java/com/itsaky/androidide/models/CompletionItemWrapper.java +++ b/app/src/main/java/com/itsaky/androidide/models/CompletionItemWrapper.java @@ -13,6 +13,7 @@ import com.itsaky.lsp.TextEdit; import io.github.rosemoe.editor.text.Cursor; import io.github.rosemoe.editor.widget.CodeEditor; import java.util.List; +import com.itsaky.androidide.language.java.manager.JavaCharacter; public class CompletionItemWrapper implements SuggestItem, Comparable { @@ -58,12 +59,21 @@ public class CompletionItemWrapper implements SuggestItem, Comparable { try { final Cursor cursor = editor.getCursor(); if(cursor.isSelected()) return; - final int length = getInsertLength(); + final int line = cursor.getLeftLine(); + int col = cursor.getLeftColumn(); + while(true) { + col--; + if(col == 0) + break; + if(!JavaCharacter.isJavaIdentifierPart(editor.getText().charAt(line, col))) + break; + } + col++; String text = getInsertText(); final boolean shiftLeft = text.contains("$0"); final boolean atEnd = text.endsWith("$0"); text = text.replace("$0", ""); - editor.getText().delete(cursor.getLeftLine(), cursor.getLeftColumn() - length, cursor.getLeftLine(), cursor.getLeftColumn()); + editor.getText().delete(line, col, line, cursor.getLeftColumn()); cursor.onCommitText(text); if(shiftLeft && !atEnd) { diff --git a/app/src/main/java/com/itsaky/androidide/models/License.java b/app/src/main/java/com/itsaky/androidide/models/License.java new file mode 100644 index 000000000..a7dafb9ab --- /dev/null +++ b/app/src/main/java/com/itsaky/androidide/models/License.java @@ -0,0 +1,14 @@ +package com.itsaky.androidide.models; + +import com.google.gson.annotations.SerializedName; + +public class License { + @SerializedName("name") + public String name; + + @SerializedName("license") + public String license; + + @SerializedName("url") + public String url; +} diff --git a/app/src/main/java/com/itsaky/androidide/models/LogLine.java b/app/src/main/java/com/itsaky/androidide/models/LogLine.java index 23acd4777..14eee61a2 100644 --- a/app/src/main/java/com/itsaky/androidide/models/LogLine.java +++ b/app/src/main/java/com/itsaky/androidide/models/LogLine.java @@ -47,7 +47,7 @@ public class LogLine { if(s == null || s.trim().length() > 1) { return DEBUG; } - char c = s.toLowerCase().charAt(0); + char c = s.toLowerCase(Locale.US).charAt(0); if(c == 'w') { return WARNING; } else if(c == 'e') { diff --git a/app/src/main/java/com/itsaky/androidide/syntax/lexer/impls/java/JavaLexerImpl.java b/app/src/main/java/com/itsaky/androidide/syntax/lexer/impls/java/JavaLexerImpl.java index c94066e60..cb8da03ce 100644 --- a/app/src/main/java/com/itsaky/androidide/syntax/lexer/impls/java/JavaLexerImpl.java +++ b/app/src/main/java/com/itsaky/androidide/syntax/lexer/impls/java/JavaLexerImpl.java @@ -15,6 +15,9 @@ import java.io.StringReader; import java.util.ArrayList; import java.util.List; import org.antlr.v4.runtime.CharStreams; +import java.util.Map; +import java.util.HashMap; +import com.itsaky.lsp.Position; public class JavaLexerImpl extends BaseJavaLexer implements Lexer { @@ -30,7 +33,7 @@ public class JavaLexerImpl extends BaseJavaLexer implements Lexer { this.isFirst = true; builtinTypes = new ArrayList<>(); - + builtinTypes.add(JavaLexer.BOOLEAN); builtinTypes.add(JavaLexer.BYTE); builtinTypes.add(JavaLexer.CHAR); @@ -154,6 +157,11 @@ public class JavaLexerImpl extends BaseJavaLexer implements Lexer { type = TokenType.STRING_LITERAL; colors.addIfNeeded(line, column, EditorColorScheme.LITERAL); wasClassName = false; + + Position start = new Position(line, column); + Position end = new Position(line, column + currentToken.getText().length()); + colors.addStringRange(line, new Range(start, end)); + addHexColorIfPresent(); break; case JavaLexer.LPAREN : diff --git a/app/src/main/java/com/itsaky/androidide/syntax/lexer/tokens/Token.java b/app/src/main/java/com/itsaky/androidide/syntax/lexer/tokens/Token.java index dd8b66c44..59508277c 100644 --- a/app/src/main/java/com/itsaky/androidide/syntax/lexer/tokens/Token.java +++ b/app/src/main/java/com/itsaky/androidide/syntax/lexer/tokens/Token.java @@ -5,6 +5,7 @@ import java.util.logging.Level; import java.util.logging.Logger; import java.util.ArrayList; import java.util.HashMap; +import java.util.Locale; public class Token implements Serializable { @@ -76,9 +77,9 @@ public class Token implements Serializable { @Override public String toString() { if (pairValue == 0) { - return String.format("%s (%d, %d) %d", type, line, column, length); + return String.format(Locale.US, "%s (%d, %d) %d", type, line, column, length); } else { - return String.format("%s (%d, %d) (%d) %d", type, line, column, length, pairValue); + return String.format(Locale.US, "%s (%d, %d) (%d) %d", type, line, column, length, pairValue); } } diff --git a/app/src/main/java/com/itsaky/androidide/tasks/callables/LicenseReader.java b/app/src/main/java/com/itsaky/androidide/tasks/callables/LicenseReader.java new file mode 100644 index 000000000..4a139a915 --- /dev/null +++ b/app/src/main/java/com/itsaky/androidide/tasks/callables/LicenseReader.java @@ -0,0 +1,31 @@ +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> { + + private final Context ctx; + + public LicenseReader(Context ctx) { + this.ctx = ctx; + } + + @Override + public List call() throws Exception { + BufferedReader reader = new BufferedReader(new InputStreamReader(ctx.getAssets().open("licenses.json"))); + String line; + StringBuilder sb = new StringBuilder(); + while((line = reader.readLine()) != null) { + sb.append(line); + } + TypeToken> token = new TypeToken>(){}; + return new Gson().fromJson(sb.toString(), token.getType()); + } +} diff --git a/app/src/main/java/com/itsaky/androidide/views/TooltipLayout.java b/app/src/main/java/com/itsaky/androidide/views/TooltipLayout.java index 26f39cab7..9e991b47c 100644 --- a/app/src/main/java/com/itsaky/androidide/views/TooltipLayout.java +++ b/app/src/main/java/com/itsaky/androidide/views/TooltipLayout.java @@ -12,6 +12,8 @@ import android.graphics.Color; public class TooltipLayout extends LinearLayout { private final float dp8; + private final Path path; + private final Paint paint; public TooltipLayout (Context ctx) { this(ctx, null); @@ -25,14 +27,20 @@ public class TooltipLayout extends LinearLayout { super(ctx, attrs, style); dp8 = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 8, getResources().getDisplayMetrics()); + path = new Path(); + + paint = new Paint(); + paint.setAntiAlias(true); + paint.setStyle(Paint.Style.FILL); + paint.setColor(Color.parseColor("#424242")); } - + @Override protected void onDraw(Canvas canvas) { final int width = getWidth(); final int height = getHeight(); final float midX = width / 2; - Path path = new Path(); + path.moveTo(0, 0); path.lineTo(width, 0); path.lineTo(width, height - dp8); @@ -42,10 +50,6 @@ public class TooltipLayout extends LinearLayout { path.lineTo(0, height - dp8); path.close(); - Paint paint = new Paint(); - paint.setAntiAlias(true); - paint.setStyle(Paint.Style.FILL); - paint.setColor(Color.parseColor("#424242")); canvas.drawPath(path, paint); super.onDraw(canvas); } diff --git a/app/src/main/java/io/github/rosemoe/editor/text/Content.java b/app/src/main/java/io/github/rosemoe/editor/text/Content.java index 72a6c55c0..232219ba2 100644 --- a/app/src/main/java/io/github/rosemoe/editor/text/Content.java +++ b/app/src/main/java/io/github/rosemoe/editor/text/Content.java @@ -20,6 +20,7 @@ import java.util.List; import io.github.rosemoe.editor.annotations.Experimental; import io.github.rosemoe.struct.BlockLinkedList; +import io.github.rosemoe.editor.widget.CodeEditor; /** * This class saves the text content for editor and maintains line widths @@ -45,6 +46,8 @@ public class Content implements CharSequence { private UndoManager mUndoManager; private Cursor mCursor; private LineRemoveListener mLineListener; + + private final CodeEditor editor; /** * Use a BlockLinkedList instead of ArrayList. @@ -60,8 +63,8 @@ public class Content implements CharSequence { /** * This constructor will create a Content object with no text */ - public Content() { - this(null); + public Content(CodeEditor editor) { + this(editor, null); } /** @@ -70,10 +73,11 @@ public class Content implements CharSequence { * * @param src The source of Content */ - public Content(CharSequence src) { + public Content(CodeEditor editor, CharSequence src) { if (src == null) { src = ""; } + this.editor = editor; mTextLength = 0; mNestedBatchEdit = 0; if (!useBlock) @@ -607,7 +611,7 @@ public class Content implements CharSequence { * @return sub sequence of this Content */ public Content subContent(int startLine, int startColumn, int endLine, int endColumn) { - Content c = new Content(); + Content c = new Content(this.editor); c.setUndoEnabled(false); if (startLine == endLine) { c.insert(0, 0, mLines.get(startLine).subSequence(startColumn, endColumn)); @@ -691,7 +695,7 @@ public class Content implements CharSequence { */ public Cursor getCursor() { if (mCursor == null) { - mCursor = new Cursor(this); + mCursor = new Cursor(this.editor, this); } return mCursor; } diff --git a/app/src/main/java/io/github/rosemoe/editor/text/Cursor.java b/app/src/main/java/io/github/rosemoe/editor/text/Cursor.java index 45876ccfa..1cda8d2e3 100644 --- a/app/src/main/java/io/github/rosemoe/editor/text/Cursor.java +++ b/app/src/main/java/io/github/rosemoe/editor/text/Cursor.java @@ -20,6 +20,7 @@ import io.github.rosemoe.editor.interfaces.EditorLanguage; import io.github.rosemoe.editor.util.IntPair; import java.util.List; import java.util.ArrayList; +import io.github.rosemoe.editor.widget.CodeEditor; /** * @author Rose @@ -35,6 +36,8 @@ public final class Cursor { private EditorLanguage mLanguage; private int mTabWidth; + private final CodeEditor editor; + private final List pairs; /** @@ -42,7 +45,8 @@ public final class Cursor { * * @param content Target content */ - public Cursor(Content content) { + public Cursor(CodeEditor editor, Content content) { + this.editor = editor; mContent = content; mIndexer = new CachedIndexer(content); mLeft = new CharPosition().zero(); @@ -269,7 +273,12 @@ public final class Cursor { && text.charAt(0) == mContent.charAt(getLeftLine(), getLeftColumn()) && pairs.contains(text.charAt(0))) { - set(getLeftLine(), getLeftColumn() + 1); + // Moving the cursor to right through editor is instant + // If we don't really have an instance of the editor, fall back to 'less efficient' method + if(this.editor != null) + this.editor.moveSelectionRight(); + else + set(getLeftLine(), getLeftColumn() + 1); } else { if (mAutoIndentEnabled && text.length() != 0 && applyAutoIndent) { char first = text.charAt(0); @@ -311,18 +320,8 @@ public final class Cursor { * @return Generated space string */ private String createIndent(int p) { - int tab = 0; - int space; -// if (mLanguage.useTab()) { -// tab = p / mTabWidth; -// space = p % mTabWidth; -// } else { - space = p; -// } + int space = p; StringBuilder s = new StringBuilder(); - for (int i = 0; i < tab; i++) { - s.append('\t'); - } for (int i = 0; i < space; i++) { s.append(' '); } diff --git a/app/src/main/java/io/github/rosemoe/editor/text/TextAnalyzeResult.java b/app/src/main/java/io/github/rosemoe/editor/text/TextAnalyzeResult.java index 02c034bc8..35b8fc21e 100644 --- a/app/src/main/java/io/github/rosemoe/editor/text/TextAnalyzeResult.java +++ b/app/src/main/java/io/github/rosemoe/editor/text/TextAnalyzeResult.java @@ -24,6 +24,8 @@ import io.github.rosemoe.editor.struct.Span; import io.github.rosemoe.editor.widget.EditorColorScheme; import java.util.HashMap; import io.github.rosemoe.editor.struct.HexColor; +import java.util.Map; +import com.itsaky.lsp.Range; /** * The result of analysis @@ -37,6 +39,8 @@ public class TextAnalyzeResult { protected List mLabels; protected Span mLast; protected int mSuppressSwitch = Integer.MAX_VALUE; + + private final Map> stringMap = new HashMap<>(); /** * Create a new result @@ -47,6 +51,22 @@ public class TextAnalyzeResult { mBlocks = new ArrayList<>(1024); mHexColor = new HashMap<>(); } + + public Map> getStringMap() { + return stringMap; + } + + public void addStringRange(int line, Range range) { + if(stringMap.containsKey(line)) { + List ranges = stringMap.get(line); + ranges.add(range); + stringMap.put(line, ranges); + } else { + List ranges = new ArrayList<>(); + ranges.add(range); + stringMap.put(line, ranges); + } + } public void addHexColor(HexColor index, int color) { mHexColor.put(index, color); diff --git a/app/src/main/java/io/github/rosemoe/editor/widget/CodeEditor.java b/app/src/main/java/io/github/rosemoe/editor/widget/CodeEditor.java index 28f20a12a..bfd08f3dd 100644 --- a/app/src/main/java/io/github/rosemoe/editor/widget/CodeEditor.java +++ b/app/src/main/java/io/github/rosemoe/editor/widget/CodeEditor.java @@ -262,6 +262,7 @@ public class CodeEditor extends View implements ContentListener, TextAnalyzer.Ca private Map mLineColors; private Map mDiagnostics; + private Map> stringMap; private File currentFile; @@ -597,6 +598,10 @@ public class CodeEditor extends View implements ContentListener, TextAnalyzer.Ca diags = new HashMap<>(); this.mDiagnostics = diags; } + + public void setStringMap(Map> map) { + this.stringMap = map; + } public CodeEditor setLineColors(HashMap map) { this.mLineColors = map; @@ -3703,7 +3708,7 @@ public class CodeEditor extends View implements ContentListener, TextAnalyzer.Ca String t = text.toString(); t = t.replace("\t", " "); - mText = new Content(t); + mText = new Content(this, t); mCursor = mText.getCursor(); mCursor.setAutoIndent(mAutoIndentEnabled); mCursor.setLanguage(mLanguage); @@ -4421,7 +4426,8 @@ public class CodeEditor extends View implements ContentListener, TextAnalyzer.Ca exitSelectModeIfNeeded(); // Auto completion - if (isAutoCompletionEnabled()) { + // Trigger only when autocompletion is enabled and cursor is not in a string token + if (isAutoCompletionEnabled() && !isInString()) { if ((mConnection.mComposingLine == -1 || mCompletionOnComposing) && endColumn != 0 && startLine == endLine) { int end = endColumn; while (endColumn > 0) { @@ -4461,6 +4467,20 @@ public class CodeEditor extends View implements ContentListener, TextAnalyzer.Ca } } + private boolean isInString() { + final int line = getCursor().getLeftLine(); + final int column = getCursor().getLeftColumn(); + if(stringMap == null || stringMap.isEmpty() || !stringMap.containsKey(line)) return false; + List strings = stringMap.get(line); + if(strings == null || strings.isEmpty()) return false; + for(int i=0;i 0) { setLineColors(mSpanner.getResult().getHexColors()); } + if(mSpanner.getResult().getStringMap().size() > 0) { + setStringMap(mSpanner.getResult().getStringMap()); + } if (mHighlightCurrentBlock) { mCursorPosition = findCursorBlock(); } diff --git a/app/src/main/java/io/github/rosemoe/editor/widget/SymbolChannel.java b/app/src/main/java/io/github/rosemoe/editor/widget/SymbolChannel.java index 91e7f2be9..d9c48cfb2 100644 --- a/app/src/main/java/io/github/rosemoe/editor/widget/SymbolChannel.java +++ b/app/src/main/java/io/github/rosemoe/editor/widget/SymbolChannel.java @@ -66,13 +66,12 @@ public class SymbolChannel { && text.charAt(0) == mEditor.getText().charAt(cur.getLeftLine(), cur.getLeftColumn()) && pairs.contains(text.charAt(0))) { mEditor.moveSelectionRight(); - } - - mEditor.getText().insert(cur.getRightLine(), cur.getRightColumn(), text); - - mEditor.notifyExternalCursorChange(); - if (selectionOffset != text.length()) { - mEditor.setSelection(cur.getRightLine(), cur.getRightColumn() - (text.length() - selectionOffset)); + } else { + mEditor.getText().insert(cur.getRightLine(), cur.getRightColumn(), text); + mEditor.notifyExternalCursorChange(); + if (selectionOffset != text.length()) { + mEditor.setSelection(cur.getRightLine(), cur.getRightColumn() - (text.length() - selectionOffset)); + } } } diff --git a/app/src/main/res/drawable/ic_email.xml b/app/src/main/res/drawable/ic_email.xml new file mode 100644 index 000000000..ef28e36ee --- /dev/null +++ b/app/src/main/res/drawable/ic_email.xml @@ -0,0 +1,11 @@ + + + + + diff --git a/app/src/main/res/drawable/ic_github.xml b/app/src/main/res/drawable/ic_github.xml new file mode 100644 index 000000000..99e60bf79 --- /dev/null +++ b/app/src/main/res/drawable/ic_github.xml @@ -0,0 +1,11 @@ + + + + + diff --git a/app/src/main/res/drawable/ic_website.xml b/app/src/main/res/drawable/ic_website.xml new file mode 100644 index 000000000..cd54f775c --- /dev/null +++ b/app/src/main/res/drawable/ic_website.xml @@ -0,0 +1,11 @@ + + + + + diff --git a/app/src/main/res/layout/activity_about.xml b/app/src/main/res/layout/activity_about.xml new file mode 100644 index 000000000..1594fcc74 --- /dev/null +++ b/app/src/main/res/layout/activity_about.xml @@ -0,0 +1,81 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/activity_editor.xml b/app/src/main/res/layout/activity_editor.xml index 9ef5f0936..1ae9513b6 100644 --- a/app/src/main/res/layout/activity_editor.xml +++ b/app/src/main/res/layout/activity_editor.xml @@ -2,6 +2,7 @@ + android:paddingBottom="4dp" + tools:ignore="MissingConstraints"/> - - + android:paddingStart="4dp" + android:paddingEnd="4dp"> @@ -49,10 +32,9 @@ + android:layout_marginStart="8dp"/> diff --git a/app/src/main/res/layout/layout_about_items.xml b/app/src/main/res/layout/layout_about_items.xml new file mode 100644 index 000000000..0ad60e3c4 --- /dev/null +++ b/app/src/main/res/layout/layout_about_items.xml @@ -0,0 +1,225 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/layout_completion_window.xml b/app/src/main/res/layout/layout_completion_window.xml index e21b55e7b..05d71dc2a 100644 --- a/app/src/main/res/layout/layout_completion_window.xml +++ b/app/src/main/res/layout/layout_completion_window.xml @@ -2,6 +2,8 @@ diff --git a/app/src/main/res/layout/layout_diagnostic_group.xml b/app/src/main/res/layout/layout_diagnostic_group.xml index 1c66a1f4c..e499b2c8e 100644 --- a/app/src/main/res/layout/layout_diagnostic_group.xml +++ b/app/src/main/res/layout/layout_diagnostic_group.xml @@ -1,6 +1,8 @@ + android:paddingStart="16dp" + android:paddingEnd="16dp"/> diff --git a/app/src/main/res/layout/layout_diagnostic_item.xml b/app/src/main/res/layout/layout_diagnostic_item.xml index b0c02d250..c56c560a2 100644 --- a/app/src/main/res/layout/layout_diagnostic_item.xml +++ b/app/src/main/res/layout/layout_diagnostic_item.xml @@ -1,6 +1,8 @@ diff --git a/app/src/main/res/layout/layout_projects_item.xml b/app/src/main/res/layout/layout_projects_item.xml index 5d3123ef7..24f4c888b 100644 --- a/app/src/main/res/layout/layout_projects_item.xml +++ b/app/src/main/res/layout/layout_projects_item.xml @@ -2,6 +2,7 @@ diff --git a/app/src/main/res/layout/text_compose_panel.xml b/app/src/main/res/layout/text_compose_panel.xml index 9a8a4ebe3..667474283 100644 --- a/app/src/main/res/layout/text_compose_panel.xml +++ b/app/src/main/res/layout/text_compose_panel.xml @@ -1,76 +1,53 @@ - - - + android:layout_width="wrap_content" + android:fillViewport="true"> - + android:layout_width="wrap_content" + android:orientation="horizontal" + android:gravity="center_vertical"> - + android:text="@string/selectAll" + android:textColor="#FF000000" + android:textSize="12sp" + android:id="@+id/panel_btn_select_all"/> -