mirror of
https://github.com/TonyJiangWJ/Auto.js.git
synced 2026-06-21 21:01:43 +08:00
feat: java class searching and import; fix: editor crash on edit when file is too large
This commit is contained in:
parent
fc0f5c479e
commit
02c64cd7b1
Binary file not shown.
@ -8,8 +8,8 @@ android {
|
||||
applicationId "org.autojs.autojs"
|
||||
minSdkVersion 17
|
||||
targetSdkVersion 23
|
||||
versionCode 413
|
||||
versionName "4.0.2 Alpha8"
|
||||
versionCode 414
|
||||
versionName "4.0.2 Alpha9"
|
||||
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
|
||||
multiDexEnabled true
|
||||
ndk {
|
||||
|
||||
1
app/src/main/assets/indices/all_android_classes.json
Normal file
1
app/src/main/assets/indices/all_android_classes.json
Normal file
File diff suppressed because one or more lines are too long
@ -30,7 +30,6 @@ import org.autojs.autojs.ui.error.ErrorReportActivity;
|
||||
|
||||
import com.stardust.theme.ThemeColor;
|
||||
import com.stardust.theme.ThemeColorManager;
|
||||
import com.tencent.bugly.Bugly;
|
||||
import com.tencent.bugly.crashreport.CrashReport;
|
||||
|
||||
|
||||
@ -77,7 +76,7 @@ public class App extends MultiDexApplication {
|
||||
|
||||
CrashReport.initCrashReport(getApplicationContext(), BUGLY_APP_ID, false, strategy);
|
||||
|
||||
crashHandler.setDefaultHandler(Thread.getDefaultUncaughtExceptionHandler());
|
||||
crashHandler.setBuglyHandler(Thread.getDefaultUncaughtExceptionHandler());
|
||||
Thread.setDefaultUncaughtExceptionHandler(crashHandler);
|
||||
}
|
||||
|
||||
|
||||
@ -8,6 +8,7 @@ import org.autojs.autojs.ui.widget.SimpleTextWatcher;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
@ -20,6 +21,7 @@ public class AnyWordsCompletion implements SimpleTextWatcher.AfterTextChangedLis
|
||||
private static final String PATTERN = "[\\W]";
|
||||
private ExecutorService mExecutorService;
|
||||
private volatile DictionaryTree<String> mDictionaryTree;
|
||||
private AtomicInteger mExecuteId = new AtomicInteger();
|
||||
|
||||
public AnyWordsCompletion(ExecutorService executorService) {
|
||||
mExecutorService = executorService;
|
||||
@ -27,13 +29,21 @@ public class AnyWordsCompletion implements SimpleTextWatcher.AfterTextChangedLis
|
||||
|
||||
@Override
|
||||
public void afterTextChanged(Editable s) {
|
||||
mExecutorService.execute(() -> splitWords(s.toString()));
|
||||
String str = s.toString();
|
||||
int id = mExecuteId.incrementAndGet();
|
||||
mExecutorService.execute(() -> splitWords(id, str));
|
||||
}
|
||||
|
||||
private void splitWords(String s) {
|
||||
private void splitWords(int id, String s) {
|
||||
if(id != mExecuteId.get()){
|
||||
return;
|
||||
}
|
||||
DictionaryTree<String> tree = new DictionaryTree<>();
|
||||
String[] words = s.split(PATTERN);
|
||||
for (String word : words) {
|
||||
if(id != mExecuteId.get()){
|
||||
return;
|
||||
}
|
||||
tree.putWord(word, word);
|
||||
}
|
||||
mDictionaryTree = tree;
|
||||
|
||||
@ -1,18 +1,22 @@
|
||||
package org.autojs.autojs.model.autocomplete;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.widget.EditText;
|
||||
|
||||
import org.autojs.autojs.model.indices.Module;
|
||||
import org.autojs.autojs.model.indices.Modules;
|
||||
import org.autojs.autojs.model.indices.Property;
|
||||
import org.autojs.autojs.ui.widget.SimpleTextWatcher;
|
||||
import org.mozilla.javascript.ast.Loop;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
@ -39,6 +43,8 @@ public class AutoCompletion {
|
||||
private AutoCompleteCallback mAutoCompleteCallback;
|
||||
private ExecutorService mExecutorService = Executors.newSingleThreadExecutor();
|
||||
private AnyWordsCompletion mAnyWordsCompletion;
|
||||
private AtomicInteger mExecuteId = new AtomicInteger();
|
||||
private Handler mHandler = new Handler(Looper.getMainLooper());
|
||||
|
||||
public AutoCompletion(Context context, EditText editText) {
|
||||
buildDictionaryTree(context);
|
||||
@ -80,10 +86,19 @@ public class AutoCompletion {
|
||||
if (mPropertyPrefill == null && module == null)
|
||||
return;
|
||||
String prefill = mPropertyPrefill;
|
||||
int id = mExecuteId.incrementAndGet();
|
||||
mExecutorService.execute(() -> {
|
||||
if (id != mExecuteId.get())
|
||||
return;
|
||||
List<CodeCompletion> completions = findCodeCompletion(module, prefill);
|
||||
CodeCompletions codeCompletions = new CodeCompletions(cursor, completions);
|
||||
mAutoCompleteCallback.updateCodeCompletion(codeCompletions);
|
||||
if (id != mExecuteId.get())
|
||||
return;
|
||||
mHandler.post(() -> {
|
||||
if (id != mExecuteId.get())
|
||||
return;
|
||||
mAutoCompleteCallback.updateCodeCompletion(codeCompletions);
|
||||
});
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
@ -0,0 +1,37 @@
|
||||
package org.autojs.autojs.model.indices;
|
||||
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
|
||||
public class AndroidClass {
|
||||
|
||||
@SerializedName("class_name")
|
||||
private String mClassName;
|
||||
@SerializedName("package_name")
|
||||
private String mPackageName;
|
||||
@SerializedName("full_name")
|
||||
private String mFullName;
|
||||
|
||||
public String getClassName() {
|
||||
return mClassName;
|
||||
}
|
||||
|
||||
public void setClassName(String className) {
|
||||
mClassName = className;
|
||||
}
|
||||
|
||||
public String getPackageName() {
|
||||
return mPackageName;
|
||||
}
|
||||
|
||||
public void setPackageName(String packageName) {
|
||||
mPackageName = packageName;
|
||||
}
|
||||
|
||||
public String getFullName() {
|
||||
return mFullName;
|
||||
}
|
||||
|
||||
public void setFullName(String fullName) {
|
||||
mFullName = fullName;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,137 @@
|
||||
package org.autojs.autojs.model.indices;
|
||||
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.Context;
|
||||
import android.text.TextUtils;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
|
||||
import org.autojs.autojs.tool.EmptyObservers;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.Reader;
|
||||
import java.lang.reflect.Type;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.LinkedBlockingQueue;
|
||||
import java.util.concurrent.ThreadPoolExecutor;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import io.reactivex.Observable;
|
||||
import io.reactivex.Single;
|
||||
import io.reactivex.android.schedulers.AndroidSchedulers;
|
||||
import io.reactivex.schedulers.Schedulers;
|
||||
|
||||
public class AndroidClassIndices {
|
||||
|
||||
static class LoadException extends RuntimeException {
|
||||
|
||||
LoadException(Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressLint("StaticFieldLeak")
|
||||
private static AndroidClassIndices sInstance;
|
||||
|
||||
private static Type ANDROID_CLASS_LIST_TYPE = new TypeToken<List<AndroidClass>>() {
|
||||
}.getType();
|
||||
//packageName -> package classes
|
||||
private LinkedHashMap<String, ArrayList<ClassSearchingItem>> mPackages = new LinkedHashMap<>();
|
||||
private Context mContext;
|
||||
private ExecutorService mSingleThreadExecutor = new ThreadPoolExecutor(1, 1,
|
||||
2, TimeUnit.MINUTES, new LinkedBlockingQueue<>());
|
||||
private Throwable mLoadThrowable;
|
||||
|
||||
|
||||
protected AndroidClassIndices(Context context) {
|
||||
mContext = context.getApplicationContext();
|
||||
load("indices/all_android_classes.json");
|
||||
}
|
||||
|
||||
public static AndroidClassIndices getInstance(Context context) {
|
||||
if (sInstance == null) {
|
||||
sInstance = new AndroidClassIndices(context);
|
||||
}
|
||||
return sInstance;
|
||||
}
|
||||
|
||||
public Single<List<ClassSearchingItem>> search(String keywords) {
|
||||
return Single.fromCallable(() -> {
|
||||
if (mPackages.isEmpty() && mLoadThrowable != null) {
|
||||
throw new LoadException(mLoadThrowable);
|
||||
}
|
||||
return mPackages.values();
|
||||
})
|
||||
.map(packages -> search(packages, keywords))
|
||||
.subscribeOn(Schedulers.from(mSingleThreadExecutor));
|
||||
}
|
||||
|
||||
private List<ClassSearchingItem> search(Collection<ArrayList<ClassSearchingItem>> packages, String keywords) {
|
||||
List<ClassSearchingItem> result = new ArrayList<>();
|
||||
if (TextUtils.isEmpty(keywords)) {
|
||||
for (ArrayList<ClassSearchingItem> packageClasses : packages) {
|
||||
result.addAll(packageClasses);
|
||||
}
|
||||
} else {
|
||||
for (ArrayList<ClassSearchingItem> packageClasses : packages) {
|
||||
for (ClassSearchingItem item : packageClasses) {
|
||||
if (item.matches(keywords)) {
|
||||
result.add(item);
|
||||
}
|
||||
}
|
||||
Collections.sort(result);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@SuppressLint("CheckResult")
|
||||
private void load(String assetsPath) {
|
||||
Observable.just(assetsPath)
|
||||
.map(path -> mContext.getAssets().open(path))
|
||||
.map(InputStreamReader::new)
|
||||
.doOnNext(this::load)
|
||||
.subscribeOn(Schedulers.from(mSingleThreadExecutor))
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(EmptyObservers.consumer(), t -> {
|
||||
mLoadThrowable = t;
|
||||
t.printStackTrace();
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
private void load(Reader reader) throws IOException {
|
||||
try {
|
||||
Gson gson = new Gson();
|
||||
ArrayList<AndroidClass> classes = gson.fromJson(reader, ANDROID_CLASS_LIST_TYPE);
|
||||
load(classes);
|
||||
} catch (RuntimeException e) {
|
||||
throw e;
|
||||
} finally {
|
||||
reader.close();
|
||||
}
|
||||
}
|
||||
|
||||
private void load(ArrayList<AndroidClass> classes) {
|
||||
mPackages.clear();
|
||||
for (AndroidClass clazz : classes) {
|
||||
String packageName = clazz.getPackageName();
|
||||
ArrayList<ClassSearchingItem> packageClasses = mPackages.get(packageName);
|
||||
if (packageClasses == null) {
|
||||
packageClasses = new ArrayList<>();
|
||||
mPackages.put(packageName, packageClasses);
|
||||
packageClasses.add(new ClassSearchingItem.PackageItem(packageName));
|
||||
}
|
||||
packageClasses.add(new ClassSearchingItem.ClassItem(clazz));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,160 @@
|
||||
package org.autojs.autojs.model.indices;
|
||||
|
||||
import android.support.annotation.NonNull;
|
||||
import android.util.Log;
|
||||
|
||||
public abstract class ClassSearchingItem implements Comparable<ClassSearchingItem> {
|
||||
|
||||
static final String BASE_URL = "http://www.android-doc.com/reference/";
|
||||
|
||||
protected int rank;
|
||||
|
||||
public abstract boolean matches(String keywords);
|
||||
|
||||
public abstract String getLabel();
|
||||
|
||||
public abstract String getUrl();
|
||||
|
||||
@Override
|
||||
public int compareTo(@NonNull ClassSearchingItem o) {
|
||||
return Integer.compare(o.rank, rank);
|
||||
}
|
||||
|
||||
protected int rank(String words, String keywords) {
|
||||
int length = words.length();
|
||||
int i = words.indexOf(keywords);
|
||||
if (i < 0) {
|
||||
return 0;
|
||||
}
|
||||
//full matches
|
||||
if (i == 0 && keywords.length() == length) {
|
||||
return 10;
|
||||
}
|
||||
//words ends with keywords
|
||||
if (i + keywords.length() == length) {
|
||||
if (i > 0 && words.charAt(i - 1) == '.') {
|
||||
return 9;
|
||||
}
|
||||
return 8;
|
||||
}
|
||||
//package starts with keywords
|
||||
if (i > 0 && words.charAt(i - 1) == '.') {
|
||||
//package equals keywords
|
||||
if (i < length - 1 && words.charAt(i + 1) == '.') {
|
||||
return 7;
|
||||
}
|
||||
return 6;
|
||||
}
|
||||
//package ends with keywords
|
||||
if (i < length - 1 && words.charAt(i + 1) == '.') {
|
||||
return 6;
|
||||
}
|
||||
if (i == 0) {
|
||||
return 5;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "ClassSearchingItem{" + getLabel() + "}";
|
||||
}
|
||||
|
||||
public abstract String getImportText();
|
||||
|
||||
public static class ClassItem extends ClassSearchingItem {
|
||||
|
||||
private final AndroidClass mAndroidClass;
|
||||
|
||||
public ClassItem(AndroidClass androidClass) {
|
||||
mAndroidClass = androidClass;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean matches(String keywords) {
|
||||
rank = rank(mAndroidClass.getFullName(), keywords);
|
||||
Log.d("ClassSearching", "rank = " + rank + ", word = " + mAndroidClass.getFullName());
|
||||
return rank > 0;
|
||||
}
|
||||
|
||||
|
||||
public String getLabel() {
|
||||
return String.format("%s (%s)", mAndroidClass.getClassName(), mAndroidClass.getPackageName());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUrl() {
|
||||
return BASE_URL + mAndroidClass.getPackageName().replace('.', '/')
|
||||
+ "/" + mAndroidClass.getClassName() + ".html";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getImportText() {
|
||||
return String.format("importClass(%s)", mAndroidClass.getFullName());
|
||||
}
|
||||
|
||||
public AndroidClass getAndroidClass() {
|
||||
return mAndroidClass;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
ClassItem classItem = (ClassItem) o;
|
||||
return mAndroidClass.equals(classItem.mAndroidClass);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return mAndroidClass.hashCode();
|
||||
}
|
||||
}
|
||||
|
||||
public static class PackageItem extends ClassSearchingItem {
|
||||
|
||||
private final String mPackageName;
|
||||
|
||||
public PackageItem(String packageName) {
|
||||
mPackageName = packageName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean matches(String keywords) {
|
||||
rank = rank(mPackageName, keywords);
|
||||
return rank > 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getLabel() {
|
||||
return mPackageName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUrl() {
|
||||
return BASE_URL + mPackageName.replace('.', '/') + "/package-summary.html";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getImportText() {
|
||||
return String.format("importPackage(%s)", mPackageName);
|
||||
}
|
||||
|
||||
public String getPackageName() {
|
||||
return mPackageName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
PackageItem that = (PackageItem) o;
|
||||
return mPackageName.equals(that.mPackageName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return mPackageName.hashCode();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -9,25 +9,16 @@ import android.content.Intent;
|
||||
import android.os.Build;
|
||||
import android.os.Looper;
|
||||
import android.util.Log;
|
||||
import android.view.View;
|
||||
import android.view.WindowManager;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.stardust.app.GlobalAppContext;
|
||||
|
||||
import org.autojs.autojs.App;
|
||||
import org.autojs.autojs.BuildConfig;
|
||||
import org.autojs.autojs.R;
|
||||
import org.mozilla.javascript.RhinoException;
|
||||
|
||||
import com.stardust.util.IntentUtil;
|
||||
import com.stardust.view.accessibility.AccessibilityService;
|
||||
import com.tencent.bugly.Bugly;
|
||||
import com.tencent.bugly.crashreport.BuglyLog;
|
||||
import com.tencent.bugly.crashreport.CrashReport;
|
||||
|
||||
import java.io.PrintWriter;
|
||||
import java.io.StringWriter;
|
||||
import java.lang.Thread.UncaughtExceptionHandler;
|
||||
import java.util.Map;
|
||||
|
||||
@ -36,21 +27,24 @@ public class CrashHandler extends CrashReport.CrashHandleCallback implements Unc
|
||||
private static int crashCount = 0;
|
||||
private static long firstCrashMillis = 0;
|
||||
private final Class<?> mErrorReportClass;
|
||||
private UncaughtExceptionHandler mDefaultHandler;
|
||||
private UncaughtExceptionHandler mBuglyHandler;
|
||||
private UncaughtExceptionHandler mSystemHandler;
|
||||
|
||||
public CrashHandler(Class<?> errorReportClass) {
|
||||
this.mErrorReportClass = errorReportClass;
|
||||
mDefaultHandler = Thread.getDefaultUncaughtExceptionHandler();
|
||||
mSystemHandler = Thread.getDefaultUncaughtExceptionHandler();
|
||||
}
|
||||
|
||||
public void setDefaultHandler(UncaughtExceptionHandler defaultHandler) {
|
||||
mDefaultHandler = defaultHandler;
|
||||
public void setBuglyHandler(UncaughtExceptionHandler buglyHandler) {
|
||||
mBuglyHandler = buglyHandler;
|
||||
}
|
||||
|
||||
public void uncaughtException(Thread thread, Throwable ex) {
|
||||
Log.e(TAG, "Uncaught Exception", ex);
|
||||
if (thread != Looper.getMainLooper().getThread()) {
|
||||
CrashReport.postCatchedException(ex, thread);
|
||||
if(!(ex instanceof RhinoException)){
|
||||
CrashReport.postCatchedException(ex, thread);
|
||||
}
|
||||
return;
|
||||
}
|
||||
AccessibilityService service = AccessibilityService.getInstance();
|
||||
@ -60,7 +54,11 @@ public class CrashHandler extends CrashReport.CrashHandleCallback implements Unc
|
||||
} else {
|
||||
BuglyLog.d(TAG, "cannot disable service: " + service);
|
||||
}
|
||||
mDefaultHandler.uncaughtException(thread, ex);
|
||||
if (BuildConfig.DEBUG) {
|
||||
mSystemHandler.uncaughtException(thread, ex);
|
||||
} else {
|
||||
mBuglyHandler.uncaughtException(thread, ex);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@ -0,0 +1,133 @@
|
||||
package org.autojs.autojs.ui.edit;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.Context;
|
||||
import android.os.Handler;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.v7.widget.LinearLayoutManager;
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
import android.view.ContextThemeWrapper;
|
||||
import android.view.View;
|
||||
import android.widget.EditText;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.afollestad.materialdialogs.MaterialDialog;
|
||||
|
||||
import org.autojs.autojs.R;
|
||||
import org.autojs.autojs.model.indices.AndroidClassIndices;
|
||||
import org.autojs.autojs.model.indices.ClassSearchingItem;
|
||||
import org.autojs.autojs.theme.dialog.ThemeColorMaterialDialogBuilder;
|
||||
import org.autojs.autojs.tool.MaterialDialogFactory;
|
||||
import org.autojs.autojs.ui.widget.AutoAdapter;
|
||||
import org.autojs.autojs.ui.widget.BindableViewHolder;
|
||||
import org.autojs.autojs.ui.widget.SimpleTextWatcher;
|
||||
|
||||
import io.reactivex.android.schedulers.AndroidSchedulers;
|
||||
import me.zhanghai.android.materialprogressbar.MaterialProgressBar;
|
||||
|
||||
public class ClassSearchDialogBuilder extends ThemeColorMaterialDialogBuilder {
|
||||
|
||||
public interface OnItemClickListener {
|
||||
void onItemClick(MaterialDialog dialog, ClassSearchingItem item, int position);
|
||||
}
|
||||
|
||||
private AutoAdapter<ClassSearchingItem> mResultListAdapter;
|
||||
private MaterialProgressBar mProgressBar;
|
||||
private EditText mKeywords;
|
||||
private OnItemClickListener mOnItemClickListener;
|
||||
private int mSearchId = 0;
|
||||
private final Handler mHandler;
|
||||
private MaterialDialog mDialog;
|
||||
|
||||
|
||||
public ClassSearchDialogBuilder(@NonNull Context context) {
|
||||
super(new ContextThemeWrapper(context, R.style.AppTheme));
|
||||
mHandler = new Handler();
|
||||
initViews(getContext());
|
||||
AndroidClassIndices.getInstance(getContext());
|
||||
}
|
||||
|
||||
public ClassSearchDialogBuilder setQuery(String text) {
|
||||
mKeywords.setText(text);
|
||||
return this;
|
||||
}
|
||||
|
||||
public ClassSearchDialogBuilder itemClick(OnItemClickListener listener) {
|
||||
mOnItemClickListener = listener;
|
||||
return this;
|
||||
}
|
||||
|
||||
private void initViews(Context context) {
|
||||
View view = View.inflate(context, R.layout.dialog_class_search, null);
|
||||
customView(view, true);
|
||||
mKeywords = view.findViewById(R.id.keywords);
|
||||
mKeywords.addTextChangedListener(new SimpleTextWatcher(this::postSearch));
|
||||
mProgressBar = view.findViewById(R.id.progress_bar);
|
||||
initResultList(view, context);
|
||||
}
|
||||
|
||||
private void initResultList(View view, Context context) {
|
||||
RecyclerView resultList = view.findViewById(R.id.result_list);
|
||||
resultList.setLayoutManager(new LinearLayoutManager(context));
|
||||
mResultListAdapter = new AutoAdapter<>(ClassSearchingItemViewHolder::new,
|
||||
R.layout.item_class_searching_result_list);
|
||||
resultList.setAdapter(mResultListAdapter);
|
||||
}
|
||||
|
||||
@Override
|
||||
public MaterialDialog build() {
|
||||
mDialog = super.build();
|
||||
return mDialog;
|
||||
}
|
||||
|
||||
private void postSearch(CharSequence s) {
|
||||
mSearchId++;
|
||||
int searchId = mSearchId;
|
||||
mHandler.postDelayed(() -> {
|
||||
if (searchId == mSearchId) {
|
||||
search(s);
|
||||
}
|
||||
}, 300);
|
||||
}
|
||||
|
||||
@SuppressLint("CheckResult")
|
||||
private void search(CharSequence s) {
|
||||
mResultListAdapter.removeAll();
|
||||
mProgressBar.setVisibility(View.VISIBLE);
|
||||
String keywords = s.toString();
|
||||
AndroidClassIndices.getInstance(getContext())
|
||||
.search(keywords)
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(result -> {
|
||||
mResultListAdapter.notifyDataSetChanged(result);
|
||||
mProgressBar.setVisibility(View.GONE);
|
||||
}, t -> {
|
||||
t.printStackTrace();
|
||||
mProgressBar.setVisibility(View.GONE);
|
||||
Toast.makeText(context, t.getMessage(), Toast.LENGTH_LONG).show();
|
||||
});
|
||||
}
|
||||
|
||||
private class ClassSearchingItemViewHolder extends BindableViewHolder<ClassSearchingItem> {
|
||||
|
||||
private final TextView mLabel;
|
||||
|
||||
ClassSearchingItemViewHolder(View itemView) {
|
||||
super(itemView);
|
||||
mLabel = itemView.findViewById(R.id.label);
|
||||
itemView.setOnClickListener(v -> {
|
||||
int pos = getAdapterPosition();
|
||||
if (mOnItemClickListener != null) {
|
||||
mOnItemClickListener.onItemClick(mDialog, mResultListAdapter.get(pos), pos);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bind(ClassSearchingItem data, int position) {
|
||||
mLabel.setText(data.getLabel());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@ -5,6 +5,7 @@ import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.view.ActionMode;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
@ -43,7 +44,8 @@ import static org.autojs.autojs.ui.edit.EditorView.EXTRA_READ_ONLY;
|
||||
* Created by Stardust on 2017/1/29.
|
||||
*/
|
||||
@EActivity(R.layout.activity_edit)
|
||||
public class EditActivity extends BaseActivity implements OnActivityResultDelegate.DelegateHost, PermissionRequestProxyActivity {
|
||||
public class
|
||||
EditActivity extends BaseActivity implements OnActivityResultDelegate.DelegateHost, PermissionRequestProxyActivity {
|
||||
|
||||
private OnActivityResultDelegate.Mediator mMediator = new OnActivityResultDelegate.Mediator();
|
||||
|
||||
@ -84,6 +86,18 @@ public class EditActivity extends BaseActivity implements OnActivityResultDelega
|
||||
setUpToolbar();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public ActionMode onWindowStartingActionMode(ActionMode.Callback callback) {
|
||||
return super.onWindowStartingActionMode(callback);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public ActionMode onWindowStartingActionMode(ActionMode.Callback callback, int type) {
|
||||
return super.onWindowStartingActionMode(callback, type);
|
||||
}
|
||||
|
||||
private void onLoadFileError(String message) {
|
||||
new ThemeColorMaterialDialogBuilder(this)
|
||||
.title(getString(R.string.text_cannot_read_file))
|
||||
@ -117,6 +131,13 @@ public class EditActivity extends BaseActivity implements OnActivityResultDelega
|
||||
return super.onPrepareOptionsMenu(menu);
|
||||
}
|
||||
|
||||
public boolean onPrepareActionMode(Menu menu) {
|
||||
boolean isScriptRunning = mEditorView.getScriptExecutionId() != ScriptExecution.NO_ID;
|
||||
MenuItem forceStopItem = menu.findItem(R.id.action_force_stop);
|
||||
forceStopItem.setEnabled(isScriptRunning);
|
||||
return super.onPrepareOptionsMenu(menu);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActionModeStarted(ActionMode mode) {
|
||||
Menu menu = mode.getMenu();
|
||||
|
||||
@ -1,15 +1,21 @@
|
||||
package org.autojs.autojs.ui.edit;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.Context;
|
||||
import android.support.design.widget.Snackbar;
|
||||
import android.text.InputType;
|
||||
import android.text.TextUtils;
|
||||
import android.view.MenuItem;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.afollestad.materialdialogs.MaterialDialog;
|
||||
import com.stardust.autojs.script.JavaScriptSource;
|
||||
import com.stardust.autojs.script.ScriptSource;
|
||||
import com.stardust.pio.PFiles;
|
||||
|
||||
import org.autojs.autojs.R;
|
||||
import org.autojs.autojs.model.indices.AndroidClass;
|
||||
import org.autojs.autojs.model.indices.ClassSearchingItem;
|
||||
import org.autojs.autojs.ui.build.BuildActivity;
|
||||
import org.autojs.autojs.ui.build.BuildActivity_;
|
||||
import org.autojs.autojs.ui.common.NotAskAgainDialog;
|
||||
@ -18,6 +24,7 @@ import org.autojs.autojs.ui.log.LogActivity_;
|
||||
import org.autojs.autojs.theme.dialog.ThemeColorMaterialDialogBuilder;
|
||||
|
||||
import com.stardust.util.ClipboardUtil;
|
||||
import com.stardust.util.IntentUtil;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
@ -28,6 +35,7 @@ import io.reactivex.android.schedulers.AndroidSchedulers;
|
||||
* Created by Stardust on 2017/9/28.
|
||||
*/
|
||||
|
||||
@SuppressLint("CheckResult")
|
||||
public class EditorMenu {
|
||||
|
||||
private EditorView mEditorView;
|
||||
@ -112,6 +120,9 @@ public class EditorMenu {
|
||||
case R.id.action_console:
|
||||
showConsole();
|
||||
return true;
|
||||
case R.id.action_import_java_class:
|
||||
importJavaPackageOrClass();
|
||||
return true;
|
||||
case R.id.action_editor_text_size:
|
||||
mEditorView.selectTextSize();
|
||||
return true;
|
||||
@ -132,6 +143,52 @@ public class EditorMenu {
|
||||
return false;
|
||||
}
|
||||
|
||||
private void importJavaPackageOrClass() {
|
||||
mEditor.getSelection()
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(s ->
|
||||
new ClassSearchDialogBuilder(mContext)
|
||||
.setQuery(s)
|
||||
.itemClick((dialog, item, pos) -> showClassSearchingItem(dialog, item))
|
||||
.title(R.string.text_search_java_class)
|
||||
.show()
|
||||
);
|
||||
}
|
||||
|
||||
private void showClassSearchingItem(MaterialDialog dialog, ClassSearchingItem item) {
|
||||
String title;
|
||||
String desc;
|
||||
if (item instanceof ClassSearchingItem.ClassItem) {
|
||||
AndroidClass androidClass = ((ClassSearchingItem.ClassItem) item).getAndroidClass();
|
||||
title = androidClass.getClassName();
|
||||
desc = androidClass.getFullName();
|
||||
} else {
|
||||
title = ((ClassSearchingItem.PackageItem) item).getPackageName();
|
||||
desc = title;
|
||||
}
|
||||
new ThemeColorMaterialDialogBuilder(mContext)
|
||||
.title(title)
|
||||
.content(desc)
|
||||
.positiveText(R.string.text_copy)
|
||||
.negativeText(R.string.text_en_import)
|
||||
.neutralText(R.string.text_view_docs)
|
||||
.onPositive((ignored, which) -> {
|
||||
ClipboardUtil.setClip(mContext, desc);
|
||||
Toast.makeText(mContext, R.string.text_already_copy_to_clip, Toast.LENGTH_SHORT).show();
|
||||
dialog.dismiss();
|
||||
})
|
||||
.onNegative((ignored, which) -> {
|
||||
if (mEditor.getText().startsWith(JavaScriptSource.EXECUTION_MODE_UI_PREFIX)) {
|
||||
mEditor.insert(1, item.getImportText() + ";\n");
|
||||
} else {
|
||||
mEditor.insert(0, item.getImportText() + ";\n");
|
||||
}
|
||||
})
|
||||
.onNeutral((ignored, which) -> IntentUtil.browse(mContext, item.getUrl()))
|
||||
.onAny((ignored, which) -> dialog.dismiss())
|
||||
.show();
|
||||
}
|
||||
|
||||
private void startBuildApkActivity() {
|
||||
BuildActivity_.intent(mContext)
|
||||
.extra(BuildActivity.EXTRA_SOURCE_FILE, mEditorView.getFile().getPath())
|
||||
|
||||
@ -60,7 +60,7 @@ public class CodeEditText extends AppCompatEditText {
|
||||
protected HVScrollView mParentScrollView;
|
||||
|
||||
private final CopyOnWriteArrayList<CodeEditor.CursorChangeCallback> mCursorChangeCallbacks = new CopyOnWriteArrayList<>();
|
||||
private volatile JavaScriptHighlighter.HighlightTokens mHighlightTokens;
|
||||
private JavaScriptHighlighter.HighlightTokens mHighlightTokens;
|
||||
private Theme mTheme;
|
||||
private TimingLogger mLogger = new TimingLogger(LOG_TAG, "draw");
|
||||
private Paint mLineHighlightPaint = new Paint();
|
||||
@ -195,7 +195,7 @@ public class CodeEditText extends AppCompatEditText {
|
||||
Layout layout = getLayout();
|
||||
int lineCount = getLineCount();
|
||||
int textLength = highlightTokens == null ? 0 : highlightTokens.getText().length();
|
||||
Editable text = getText();
|
||||
String text = highlightTokens == null ? "" : highlightTokens.getText();
|
||||
int paddingLeft = getPaddingLeft();
|
||||
int scrollX = Math.max(getRealScrollX() - paddingLeft, 0);
|
||||
Paint paint = getPaint();
|
||||
@ -260,6 +260,12 @@ public class CodeEditText extends AppCompatEditText {
|
||||
}
|
||||
paint.setColor(previousColor);
|
||||
float offsetX = paint.measureText(text, lineStart, previousColorPos);
|
||||
if(previousColorPos < 0 || visibleCharEnd > textLength || previousColorPos >= visibleCharEnd){
|
||||
Log.e(LOG_TAG, "IndexOutOfBounds: previousColorPos = " + previousColorPos + ", visibleCharEnd = "
|
||||
+visibleCharEnd + ", textLength = " + textLength);
|
||||
postInvalidate();
|
||||
return;
|
||||
}
|
||||
canvas.drawText(text, previousColorPos, visibleCharEnd, paddingLeft + offsetX, lineBaseline, paint);
|
||||
if (DEBUG) {
|
||||
mLogger.addSplit("draw line " + line + " (" + (visibleCharEnd - visibleCharStart) + ") ");
|
||||
@ -407,8 +413,10 @@ public class CodeEditText extends AppCompatEditText {
|
||||
|
||||
|
||||
public void updateHighlightTokens(JavaScriptHighlighter.HighlightTokens highlightTokens) {
|
||||
mHighlightTokens = highlightTokens;
|
||||
postInvalidate();
|
||||
post(() -> {
|
||||
mHighlightTokens = highlightTokens;
|
||||
invalidate();
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@ -317,6 +317,11 @@ public class CodeEditor extends HVScrollView {
|
||||
mCodeEditText.getText().insert(selection, insertText);
|
||||
}
|
||||
|
||||
public void insert(int line, String insertText) {
|
||||
int selection = mCodeEditText.getLayout().getLineStart(line);
|
||||
mCodeEditText.getText().insert(selection, insertText);
|
||||
}
|
||||
|
||||
public void moveCursor(int dCh) {
|
||||
mCodeEditText.setSelection(mCodeEditText.getSelectionStart() + dCh);
|
||||
}
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
package org.autojs.autojs.ui.main;
|
||||
|
||||
import android.Manifest;
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.os.Build;
|
||||
@ -14,6 +15,7 @@ import android.support.v4.view.ViewPager;
|
||||
import android.support.v4.widget.DrawerLayout;
|
||||
import android.support.v7.app.ActionBarDrawerToggle;
|
||||
import android.support.v7.widget.Toolbar;
|
||||
import android.util.Log;
|
||||
import android.view.Gravity;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
@ -32,6 +34,7 @@ import org.autojs.autojs.BuildConfig;
|
||||
import org.autojs.autojs.Pref;
|
||||
import org.autojs.autojs.R;
|
||||
import org.autojs.autojs.autojs.AutoJs;
|
||||
import org.autojs.autojs.model.indices.AndroidClassIndices;
|
||||
import org.autojs.autojs.storage.file.StorageFileProvider;
|
||||
import org.autojs.autojs.timing.TimedTaskManager;
|
||||
import org.autojs.autojs.tool.AccessibilityServiceTool;
|
||||
@ -68,6 +71,8 @@ import org.opencv.core.Core;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
import io.reactivex.android.schedulers.AndroidSchedulers;
|
||||
|
||||
@EActivity(R.layout.activity_main)
|
||||
public class MainActivity extends BaseActivity implements OnActivityResultDelegate.DelegateHost, BackPressedHandler.HostActivity, PermissionRequestProxyActivity {
|
||||
|
||||
|
||||
@ -3,6 +3,8 @@ package org.autojs.autojs.ui.widget;
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import org.autojs.autojs.model.indices.ClassSearchingItem;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
@ -76,4 +78,15 @@ public class AutoAdapter<DT> extends RecyclerView.Adapter<BindableViewHolder<DT>
|
||||
public List<DT> getData() {
|
||||
return mData;
|
||||
}
|
||||
|
||||
public void removeAll() {
|
||||
mData.clear();
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
|
||||
public void notifyDataSetChanged(List<DT> result) {
|
||||
mData.clear();
|
||||
mData.addAll(result);
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
}
|
||||
|
||||
35
app/src/main/res/layout/dialog_class_search.xml
Normal file
35
app/src/main/res/layout/dialog_class_search.xml
Normal file
@ -0,0 +1,35 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical">
|
||||
|
||||
<android.support.design.widget.TextInputLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<android.support.design.widget.TextInputEditText
|
||||
android:id="@+id/keywords"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:hint="@string/text_class_or_package_name"/>
|
||||
</android.support.design.widget.TextInputLayout>
|
||||
|
||||
<FrameLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<android.support.v7.widget.RecyclerView
|
||||
android:id="@+id/result_list"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="400dp"/>
|
||||
|
||||
<me.zhanghai.android.materialprogressbar.MaterialProgressBar
|
||||
android:id="@+id/progress_bar"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:visibility="gone"/>
|
||||
|
||||
</FrameLayout>
|
||||
</LinearLayout>
|
||||
21
app/src/main/res/layout/item_class_searching_result_list.xml
Normal file
21
app/src/main/res/layout/item_class_searching_result_list.xml
Normal file
@ -0,0 +1,21 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:paddingBottom="10dp"
|
||||
android:background="?selectableItemBackground"
|
||||
android:clickable="true"
|
||||
android:focusable="true"
|
||||
android:paddingLeft="6dp"
|
||||
android:paddingRight="6dp"
|
||||
android:paddingTop="10dp">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/label"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:singleLine="true"
|
||||
android:textColor="#B2B3B7"
|
||||
android:textSize="15sp"/>
|
||||
</LinearLayout>
|
||||
@ -119,6 +119,12 @@
|
||||
android:id="@+id/action_console"
|
||||
android:title="@string/text_console"
|
||||
app:showAsAction="never"/>
|
||||
|
||||
<item
|
||||
android:id="@+id/action_import_java_class"
|
||||
android:title="@string/text_search_java_class"
|
||||
app:showAsAction="never"/>
|
||||
|
||||
<item
|
||||
android:id="@+id/action_info"
|
||||
android:title="@string/text_info"
|
||||
|
||||
@ -383,4 +383,8 @@
|
||||
<string name="error_connect_to_remote">连接失败: %s</string>
|
||||
<string name="text_are_you_sure_to_delete">确定要删除%s吗</string>
|
||||
<string name="text_run_on_boot">开机时运行</string>
|
||||
<string name="text_search_java_class">搜索Java包/类</string>
|
||||
<string name="text_class_or_package_name">类/包名</string>
|
||||
<string name="text_view_docs">查看文档</string>
|
||||
<string name="text_en_import">import</string>
|
||||
</resources>
|
||||
|
||||
@ -17,6 +17,8 @@ public abstract class JavaScriptSource extends ScriptSource {
|
||||
|
||||
public static final String ENGINE = "com.stardust.autojs.script.JavaScriptSource.Engine";
|
||||
|
||||
public static final String EXECUTION_MODE_UI_PREFIX = "\"ui\";";
|
||||
|
||||
public static final int EXECUTION_MODE_NORMAL = 0;
|
||||
public static final int EXECUTION_MODE_UI = 0x00000001;
|
||||
public static final int EXECUTION_MODE_AUTO = 0x00000002;
|
||||
|
||||
@ -1 +1 @@
|
||||
[{"outputType":{"type":"APK"},"apkInfo":{"type":"MAIN","splits":[],"versionCode":413},"path":"commonRelease-4.0.2 Alpha8.apk","properties":{"packageId":"org.autojs.autojs","split":"","minSdkVersion":"17"}}]
|
||||
[{"outputType":{"type":"APK"},"apkInfo":{"type":"MAIN","splits":[],"versionCode":414},"path":"commonRelease-4.0.2 Alpha9.apk","properties":{"packageId":"org.autojs.autojs","split":"","minSdkVersion":"17"}}]
|
||||
Loading…
Reference in New Issue
Block a user