diff --git a/.idea/markdown-navigator.xml b/.idea/markdown-navigator.xml new file mode 100644 index 00000000..7787b9a9 --- /dev/null +++ b/.idea/markdown-navigator.xml @@ -0,0 +1,68 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/markdown-navigator/profiles_settings.xml b/.idea/markdown-navigator/profiles_settings.xml new file mode 100644 index 00000000..674a5916 --- /dev/null +++ b/.idea/markdown-navigator/profiles_settings.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/app/build.gradle b/app/build.gradle index a0c46c61..c776f33b 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -47,6 +47,7 @@ dependencies { compile('de.psdev.licensesdialog:licensesdialog:1.8.1') compile 'com.squareup.duktape:duktape-android:1.1.0' compile 'com.furture.react:DuktapeJava:1.1.0' + compile 'com.zzhoujay.markdown:markdown:1.0.2' compile files('libs/JSTransformer.jar') diff --git a/app/src/androidTest/java/com/stardust/scriptdroid/ExampleInstrumentedTest.java b/app/src/androidTest/java/com/stardust/scriptdroid/ExampleInstrumentedTest.java deleted file mode 100644 index 11c68b43..00000000 --- a/app/src/androidTest/java/com/stardust/scriptdroid/ExampleInstrumentedTest.java +++ /dev/null @@ -1,26 +0,0 @@ -package com.stardust.scriptdroid; - -import android.content.Context; -import android.support.test.InstrumentationRegistry; -import android.support.test.runner.AndroidJUnit4; - -import org.junit.Test; -import org.junit.runner.RunWith; - -import static org.junit.Assert.*; - -/** - * Instrumentation test, which will execute on an Android device. - * - * @see Testing documentation - */ -@RunWith(AndroidJUnit4.class) -public class ExampleInstrumentedTest { - @Test - public void useAppContext() throws Exception { - // Context of the app under test. - Context appContext = InstrumentationRegistry.getTargetContext(); - - assertEquals("com.stardust.scriptdroid", appContext.getPackageName()); - } -} diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 4c1d2fbc..d5e86975 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -73,6 +73,8 @@ + + \ No newline at end of file diff --git a/app/src/main/java/com/stardust/scriptdroid/DocumentActivity.java b/app/src/main/java/com/stardust/scriptdroid/DocumentActivity.java new file mode 100644 index 00000000..5b9b7ee6 --- /dev/null +++ b/app/src/main/java/com/stardust/scriptdroid/DocumentActivity.java @@ -0,0 +1,50 @@ +package com.stardust.scriptdroid; + +import android.os.Bundle; +import android.support.v7.widget.Toolbar; +import android.view.View; + +import com.stardust.scriptdroid.file.FileUtils; +import com.stardust.view.MarkdownView; + +/** + * Created by Stardust on 2017/2/1. + */ + +public class DocumentActivity extends BaseActivity { + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setUpUI(); + } + + private void setUpUI() { + setContentView(R.layout.activity_document); + setUpToolbar(); + loadDocument(); + } + + private void loadDocument() { + MarkdownView markdownView = $(R.id.markdown); + try { + markdownView.loadMarkdown(FileUtils.readString(getResources().openRawResource(R.raw.document))); + } catch (Exception e) { + e.printStackTrace(); + markdownView.setText("文档加载失败/(ㄒoㄒ)/~~"); + } + } + + private void setUpToolbar() { + Toolbar toolbar = $(R.id.toolbar); + toolbar.setTitle(R.string.text_syntax_and_api); + setSupportActionBar(toolbar); + getSupportActionBar().setDisplayHomeAsUpEnabled(true); + toolbar.setNavigationOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + finish(); + } + }); + } +} diff --git a/app/src/main/java/com/stardust/scriptdroid/droid/Droid.java b/app/src/main/java/com/stardust/scriptdroid/droid/Droid.java index 55204137..e069fd90 100644 --- a/app/src/main/java/com/stardust/scriptdroid/droid/Droid.java +++ b/app/src/main/java/com/stardust/scriptdroid/droid/Droid.java @@ -45,31 +45,24 @@ public class Droid { public void runScriptFile(File file, OnRunFinishedListener listener) { checkFile(file); - runScript(FileUtils.readString(file), listener); + runScript(FileUtils.readString(file), listener, RunningConfig.getDefault()); } private void runScript(String script) { - runScript(script, null); + runScript(script, null, RunningConfig.getDefault()); } public void runScriptFile(String path) { runScriptFile(new File(path)); } - public void runScript(final String script, OnRunFinishedListener listener) { - if (listener == null) - listener = DEFAULT_LISTENER; - final OnRunFinishedListener finalListener = listener; - new Thread(new Runnable() { - @Override - public void run() { - try { - finalListener.onRunFinished(JAVA_SCRIPT_ENGINE.execute(script), null); - } catch (Exception e) { - finalListener.onRunFinished(null, e); - } - } - }).start(); + public void runScript(final String script, OnRunFinishedListener listener, RunningConfig config) { + if (config.runInNewThread) { + new Thread(new RunScriptRunnable(script, listener, config)).start(); + }else { + new RunScriptRunnable(script, listener, config).run(); + } + } @@ -90,4 +83,23 @@ public class Droid { } + private static class RunScriptRunnable implements Runnable { + + private final String mScript; + private OnRunFinishedListener mOnRunFinishedListener; + + public RunScriptRunnable(String script, OnRunFinishedListener listener, RunningConfig config) { + mOnRunFinishedListener = listener == null ? DEFAULT_LISTENER : listener; + mScript = script; + } + + @Override + public void run() { + try { + mOnRunFinishedListener.onRunFinished(JAVA_SCRIPT_ENGINE.execute(mScript), null); + } catch (Exception e) { + mOnRunFinishedListener.onRunFinished(null, e); + } + } + } } diff --git a/app/src/main/java/com/stardust/scriptdroid/droid/RunningConfig.java b/app/src/main/java/com/stardust/scriptdroid/droid/RunningConfig.java new file mode 100644 index 00000000..17959630 --- /dev/null +++ b/app/src/main/java/com/stardust/scriptdroid/droid/RunningConfig.java @@ -0,0 +1,38 @@ +package com.stardust.scriptdroid.droid; + +import android.app.Activity; +import android.content.Context; + +/** + * Created by Stardust on 2017/2/1. + */ +public class RunningConfig { + + private static final RunningConfig RUNNING_CONFIG = new RunningConfig(); + + public static RunningConfig getDefault() { + return RUNNING_CONFIG; + } + + public boolean runInNewThread = true; + public Activity activity; + public Context context; + + public RunningConfig runInNewThread(boolean runInNewThread) { + this.runInNewThread = runInNewThread; + return this; + } + + public RunningConfig activity(Activity activity) { + this.activity = activity; + this.context = activity; + return this; + } + + public RunningConfig context(Context context) { + this.context = context; + return this; + } + + +} diff --git a/app/src/main/java/com/stardust/scriptdroid/droid/script/file/ScriptFileList.java b/app/src/main/java/com/stardust/scriptdroid/droid/script/file/ScriptFileList.java index ab9fb6dd..08c1cd95 100644 --- a/app/src/main/java/com/stardust/scriptdroid/droid/script/file/ScriptFileList.java +++ b/app/src/main/java/com/stardust/scriptdroid/droid/script/file/ScriptFileList.java @@ -14,7 +14,7 @@ public abstract class ScriptFileList { public abstract void remove(int i); - public abstract void rename(int position, String newName); + public abstract void rename(int position, String newName, boolean renameFile); public abstract int size(); diff --git a/app/src/main/java/com/stardust/scriptdroid/droid/script/file/SharedPrefScriptFileList.java b/app/src/main/java/com/stardust/scriptdroid/droid/script/file/SharedPrefScriptFileList.java index 5cf70955..62a589cd 100644 --- a/app/src/main/java/com/stardust/scriptdroid/droid/script/file/SharedPrefScriptFileList.java +++ b/app/src/main/java/com/stardust/scriptdroid/droid/script/file/SharedPrefScriptFileList.java @@ -6,6 +6,7 @@ import android.content.SharedPreferences; import com.google.gson.Gson; import com.google.gson.reflect.TypeToken; import com.stardust.scriptdroid.App; +import com.stardust.scriptdroid.file.FileUtils; import java.lang.reflect.Type; import java.util.ArrayList; @@ -38,7 +39,6 @@ public class SharedPrefScriptFileList extends ScriptFileList { } - private void readFromSharedPref() { Type type = new TypeToken>() { }.getType(); @@ -75,8 +75,12 @@ public class SharedPrefScriptFileList extends ScriptFileList { } @Override - public void rename(int position, String newName) { + public void rename(int position, String newName, boolean renameFile) { mScriptName.set(position, newName); + if (renameFile) { + String newPath = FileUtils.renameWithoutExtension(mScriptPath.get(position), newName); + mScriptPath.set(position, newPath); + } syncWithSharedPref(); } diff --git a/app/src/main/java/com/stardust/scriptdroid/file/FileUtils.java b/app/src/main/java/com/stardust/scriptdroid/file/FileUtils.java index dab20a66..9b0c5f7c 100644 --- a/app/src/main/java/com/stardust/scriptdroid/file/FileUtils.java +++ b/app/src/main/java/com/stardust/scriptdroid/file/FileUtils.java @@ -8,6 +8,7 @@ import com.stardust.scriptdroid.App; import java.io.File; import java.io.FileInputStream; +import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; @@ -78,7 +79,7 @@ public class FileUtils { if (i >= 0) { String folder = path.substring(0, i); File file = new File(folder); - if(file.exists()) + if (file.exists()) return true; return file.mkdirs(); } else { @@ -88,11 +89,8 @@ public class FileUtils { public static String readString(File file, String encoding) { try { - FileInputStream fis = new FileInputStream(file); - byte[] bytes = new byte[fis.available()]; - fis.read(bytes); - return new String(bytes, encoding); - } catch (IOException e) { + return readString(new FileInputStream(file), encoding); + } catch (FileNotFoundException e) { e.printStackTrace(); throw new RuntimeException(e); } @@ -102,6 +100,21 @@ public class FileUtils { return readString(file, "utf-8"); } + public static String readString(InputStream is, String encoding) { + try { + byte[] bytes = new byte[is.available()]; + is.read(bytes); + return new String(bytes, encoding); + } catch (IOException e) { + e.printStackTrace(); + throw new RuntimeException(e); + } + } + + public static String readString(InputStream inputStream) { + return readString(inputStream, "utf-8"); + } + public static boolean copy(int rawId, String path) { InputStream is = App.getApp().getResources().openRawResource(rawId); return copy(is, path); @@ -147,4 +160,20 @@ public class FileUtils { return false; } } + + public static String renameWithoutExtension(String path, String newName) { + File file = new File(path); + File newFile = new File(file.getParent(), newName + "." + getExtension(file.getName())); + file.renameTo(newFile); + return newFile.getAbsolutePath(); + } + + public static String getExtension(String fileName) { + int i = fileName.lastIndexOf('.'); + if (i < 0) + return ""; + return fileName.substring(i + 1); + } + + } \ No newline at end of file diff --git a/app/src/main/java/com/stardust/scriptdroid/ui/ScriptFileOperation.java b/app/src/main/java/com/stardust/scriptdroid/ui/ScriptFileOperation.java index e97a296b..c695cf65 100644 --- a/app/src/main/java/com/stardust/scriptdroid/ui/ScriptFileOperation.java +++ b/app/src/main/java/com/stardust/scriptdroid/ui/ScriptFileOperation.java @@ -112,10 +112,7 @@ public abstract class ScriptFileOperation { .input("输入新名称", oldName, new MaterialDialog.InputCallback() { @Override public void onInput(@NonNull MaterialDialog dialog, CharSequence input) { - scriptFileList.rename(position, input.toString()); - if(dialog.isPromptCheckBoxChecked()){ - scriptFileList.get(position).rename(input); - } + scriptFileList.rename(position, input.toString(), dialog.isPromptCheckBoxChecked()); recyclerView.getAdapter().notifyItemChanged(position); } }) diff --git a/app/src/main/java/com/stardust/scriptdroid/ui/SlideMenuFragment.java b/app/src/main/java/com/stardust/scriptdroid/ui/SlideMenuFragment.java index 04d6392d..6043ef06 100644 --- a/app/src/main/java/com/stardust/scriptdroid/ui/SlideMenuFragment.java +++ b/app/src/main/java/com/stardust/scriptdroid/ui/SlideMenuFragment.java @@ -1,5 +1,6 @@ package com.stardust.scriptdroid.ui; +import android.content.Intent; import android.os.Bundle; import android.support.annotation.Nullable; import android.support.design.widget.Snackbar; @@ -12,6 +13,7 @@ import android.widget.CompoundButton; import android.widget.Toast; import com.stardust.app.Fragment; +import com.stardust.scriptdroid.DocumentActivity; import com.stardust.scriptdroid.R; import com.stardust.scriptdroid.droid.Droid; import com.stardust.scriptdroid.droid.runtime.action.ActionPerformService; @@ -94,8 +96,7 @@ public class SlideMenuFragment extends Fragment { @ViewBinding.Click(R.id.syntax_and_api) private void startSyntaxHelpActivity() { - // TODO: 2017/1/30 startSyntaxHelpActivity - Toast.makeText(getContext(), "暂无", Toast.LENGTH_LONG).show(); + startActivity(new Intent(getContext(), DocumentActivity.class)); } @ViewBinding.Click(R.id.about_app) diff --git a/app/src/main/java/com/stardust/view/MarkdownView.java b/app/src/main/java/com/stardust/view/MarkdownView.java new file mode 100644 index 00000000..bb94c9e0 --- /dev/null +++ b/app/src/main/java/com/stardust/view/MarkdownView.java @@ -0,0 +1,61 @@ +package com.stardust.view; + +import android.content.Context; +import android.os.Build; +import android.support.annotation.RequiresApi; +import android.text.Html; +import android.text.Spanned; +import android.text.method.ScrollingMovementMethod; +import android.util.AttributeSet; +import android.widget.TextView; + +import com.zzhoujay.markdown.MarkDown; + +/** + * Created by Stardust on 2017/2/1. + */ + +public class MarkdownView extends TextView { + public MarkdownView(Context context) { + super(context); + init(); + } + + public MarkdownView(Context context, AttributeSet attrs) { + super(context, attrs); + init(); + } + + public MarkdownView(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + init(); + } + + @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) + public MarkdownView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); + init(); + } + + private void init() { + setMovementMethod(ScrollingMovementMethod.getInstance()); + setVerticalScrollBarEnabled(true); + setTextIsSelectable(true); + setClickable(true); + } + + public void loadMarkdown(final String text, final Html.ImageGetter imageGetter) { + post(new Runnable() { + @Override + public void run() { + Spanned spanned = MarkDown.fromMarkdown(text, imageGetter, MarkdownView.this); + setText(spanned); + } + }); + } + + public void loadMarkdown(final String text) { + loadMarkdown(text, null); + } + +} diff --git a/app/src/main/res/layout/activity_document.xml b/app/src/main/res/layout/activity_document.xml new file mode 100644 index 00000000..d3588f4c --- /dev/null +++ b/app/src/main/res/layout/activity_document.xml @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/raw/document.md b/app/src/main/res/raw/document.md new file mode 100644 index 00000000..02ebcd07 --- /dev/null +++ b/app/src/main/res/raw/document.md @@ -0,0 +1,37 @@ + +###一、语法 +本软件使用JavaScript语言([ECMAscript E5/E5.1](http://www.ecma-international.org/ecma-262/5.1/)),基于[Duktape](http://www.duktape.org/)引擎拓展一些自动操作(点击、长按、滑动等)函数。因而语法参见JavaScript(例如[w3cschool教程](http://www.w3school.com.cn/js/))。 +###二、自动操作函数 +**自动操作的函数都需要开启"自动操作服务"才能执行,否则执行到相应函数时脚本会停止运行。** +* `click(text)` 点击文本text所在的区域,并返回是否点击成功。当前界面没有出现该文本或者该文本所在区域不可点击时返回false。例如`click("发现")`,是点击"发现"。如果要点击"发现"直至点击成功可以用`while(!click("发现"))`。 +> 文本所在区域指的是,从文本处向上寻找,直至发现一个可点击的部件为止。 +* `click(left, top, bottom, right)` 点击与长方形范围严格匹配的区域,并返回是否点击成功。其中left为长方形左边与屏幕左边的像素距离,top为上边与屏幕上边的像素距离,right为右边与**屏幕左边**的像素距离, bottom为下边与**屏幕下边**的距离。区域严格匹配,至于要确定要点击的区域在屏幕上的左边位置,可以在侧拉菜单开启"脚本辅助"(或者安卓7.0以上在通知栏点击"修改"添加脚本辅助快捷设定图标),之后每次点击或长按事件都会提示这次点击或长按的区域并自动保存,可以在编辑器中插入。 +> 以下的longClick、select、scrollUp、scrollDown的参数均与click类似,不再赘述。 +* `longClick` 长按 +* `select` 选择 +* `scrollUp` 上滑。不加参数时会寻找"最大"的可滑动的控件下滑,例如微信消息列表等。 +* `scrollDown` 下滑。不加参数时与scrollUp类似。 +* `input(string)` 把所有输入框的文本都置为string。 +###三、其他函数 +* `sleep(n)` 暂停执行n**毫秒**时间。 +* `toast(string)` 显示提示文本。 +* `launch(packageName)` 运行包名为packageName的应用。例如launch("com.tencent.mm")是运行微信,应用包名可以通过一些工具获取;获取通过函数launchApp代替 +* `launch(packageName, className)` 运行包名为packageName,类名(Activity)为className的应用。 +* `launchApp(appName)` 运存应用名称为appName的应用。例如launchApp("QQ")。不同应用名称可能相同,这时只运行其中某一个应用。 +###四、在脚本中调用安卓 +使用importClass来引入要使用的库,例如: +```javascript +importClass("android.view.View.OnClickListener") +view.setOnClickListener(new OnClickListener(function(){ + Toast.makeText(activity, "Button1 Clicked", Toast.LENGTH_SHORT).show(); + var intent = new Intent(activity, "com.furture.react.activity.DetailActivity"); + activity.startActivity(intent); +})); + +view2.setOnClickListener(new OnClickListener({ + onClick: function(){ + Toast.makeText(activity, "Button2 Clicked", Toast.LENGTH_SHORT).show(); + } +})); +``` +详细文档参见[DuktapeJava Java Docs](http://gubaojian.github.io/DuktapeJava/javadoc/)。 diff --git a/app/src/main/res/raw/sample_open_running_services.js b/app/src/main/res/raw/sample_open_running_services.js deleted file mode 100644 index 2c989d37..00000000 --- a/app/src/main/res/raw/sample_open_running_services.js +++ /dev/null @@ -1,23 +0,0 @@ - -function openRunningServices(){ - launch("com.android.settings"); - var i = 0; - while(!click("开发者选项")){ - i++; - if(i == 10){ - toast("开发者选项未开启"); - return; - } - scrollDown(); - } - while(!click("正在运行的服务")); -} - - -importClass("android.os.Build.VERSION"); - -if(VERSION.SDK_INT < 23){ - toast("本代码只适用于Android6.0以上"); -}else{ - openRunningServices(); -} \ No newline at end of file diff --git a/app/src/main/res/raw/sample_open_wechat_moment.js b/app/src/main/res/raw/sample_open_wechat_moment.js deleted file mode 100644 index 82fa1cca..00000000 --- a/app/src/main/res/raw/sample_open_wechat_moment.js +++ /dev/null @@ -1,5 +0,0 @@ -launchApp("微信"); -sleep(500); -click("发现"); -sleep(500); -click("朋友圈"); \ No newline at end of file diff --git a/app/src/main/res/raw/sample_simple_calculator.js b/app/src/main/res/raw/sample_simple_calculator.js deleted file mode 100644 index 1ed33d7b..00000000 --- a/app/src/main/res/raw/sample_simple_calculator.js +++ /dev/null @@ -1,7 +0,0 @@ - -importClass("com.afollestad.materialdialogs.MaterialDialog"); - -new MaterialDialog.Builder(context) - .title("请输入算式") - .contenet("测试") - .show(); \ No newline at end of file