diff --git a/.gitignore b/.gitignore index af31524e..6b04ae92 100644 --- a/.gitignore +++ b/.gitignore @@ -7,4 +7,5 @@ /build /captures .externalNativeBuild -*.apk \ No newline at end of file +*.apk +*.exe \ No newline at end of file diff --git a/.idea/dictionaries/Stardust.xml b/.idea/dictionaries/Stardust.xml index 8244557f..792fd648 100644 --- a/.idea/dictionaries/Stardust.xml +++ b/.idea/dictionaries/Stardust.xml @@ -1,8 +1,10 @@ + dismissable interruptible scriptable + tasker \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml index 935de354..c44fe40c 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -2,13 +2,14 @@ - + - - - - - + + + + + + diff --git a/app/build.gradle b/app/build.gradle index 91d4b6ff..8a664f4d 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -63,7 +63,7 @@ dependencies { compile 'moe.feng:AlipayZeroSdk:1.1' compile 'com.jraska:console:0.4.3' compile 'com.jraska:console-timber-tree:0.4.3' - compile 'com.heinrichreimersoftware:android-issue-reporter:eae4c73cbeb91e98da83fe79a3dea088a8b97448' + compile 'com.heinrichreimersoftware:android-issue-reporter:1.3.1' compile 'de.codecrafters.tableview:tableview:2.5.0' compile group: "pl.openrnd.android", name: "multi-level-listview", version: "1.0.1" compile 'de.psdev.licensesdialog:licensesdialog:1.8.1' @@ -72,6 +72,7 @@ dependencies { compile 'com.bignerdranch.android:expandablerecyclerview:3.0.0-RC1' compile 'com.ashokvarma.android:bottom-navigation-bar:1.4.1' compile 'com.github.hyb1996:node-android-lib:1.0.13' + compile 'com.yqritc:recyclerview-flexibledivider:1.4.0' compile 'me.zhanghai.android.materialprogressbar:library:1.3.0' compile group:'com.twofortyfouram', name:'android-plugin-client-sdk-for-locale', version:'[4.0.2, 5.0[' compile(name: 'libtermexec-release', ext: 'aar') diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index c3c5c2f8..e5e601a1 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -77,8 +77,8 @@ + android:name=".ui.error.IssueReporterActivity" + android:theme="@style/IssueReporterTheme"/> diff --git a/app/src/main/java/com/stardust/scriptdroid/Pref.java b/app/src/main/java/com/stardust/scriptdroid/Pref.java index 08016160..6c0363cd 100644 --- a/app/src/main/java/com/stardust/scriptdroid/Pref.java +++ b/app/src/main/java/com/stardust/scriptdroid/Pref.java @@ -13,8 +13,6 @@ import com.stardust.scriptdroid.autojs.AutoJs; public class Pref { private static final SharedPreferences DISPOSABLE_BOOLEAN = App.getApp().getSharedPreferences("DISPOSABLE_BOOLEAN", Context.MODE_PRIVATE); - public static final String KEY_DRAWER_HEADER_IMAGE_PATH = "KEY_DRAWER_HEADER_IMAGE_PATH"; - public static final String KEY_APP_BAR_IMAGE_PATH = "KEY_APP_BAR_IMAGE_PATH"; private static SharedPreferences.OnSharedPreferenceChangeListener onSharedPreferenceChangeListener = new SharedPreferences.OnSharedPreferenceChangeListener() { @Override public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) { @@ -84,22 +82,6 @@ public class Pref { return getDisposableBoolean("isFirstUsing", true); } - public static String getDrawerHeaderImagePath() { - return def().getString(KEY_DRAWER_HEADER_IMAGE_PATH, null); - } - - public static void setDrawerHeaderImagePath(String path) { - def().edit().putString(KEY_DRAWER_HEADER_IMAGE_PATH, path).apply(); - } - - public static String getAppBarImagePath() { - return def().getString(KEY_APP_BAR_IMAGE_PATH, null); - } - - public static void setAppBarImagePath(String path) { - def().edit().putString(KEY_APP_BAR_IMAGE_PATH, path).apply(); - } - static { def().registerOnSharedPreferenceChangeListener(onSharedPreferenceChangeListener); } diff --git a/app/src/main/java/com/stardust/scriptdroid/external/CommonUtils.java b/app/src/main/java/com/stardust/scriptdroid/external/CommonUtils.java index 8cd456ee..829dd885 100644 --- a/app/src/main/java/com/stardust/scriptdroid/external/CommonUtils.java +++ b/app/src/main/java/com/stardust/scriptdroid/external/CommonUtils.java @@ -1,5 +1,7 @@ package com.stardust.scriptdroid.external; +import android.os.Bundle; + /** * Created by Stardust on 2017/4/1. */ @@ -10,5 +12,8 @@ public class CommonUtils { public static final String EXTRA_KEY_PREPARE_SCRIPT = "script"; + public static boolean isTaskerBundleValid(Bundle bundle) { + return bundle.containsKey(CommonUtils.EXTRA_KEY_PATH); + } } diff --git a/app/src/main/java/com/stardust/scriptdroid/external/tasker/FireSettingReceiver.java b/app/src/main/java/com/stardust/scriptdroid/external/tasker/FireSettingReceiver.java index 8267ed32..16723242 100644 --- a/app/src/main/java/com/stardust/scriptdroid/external/tasker/FireSettingReceiver.java +++ b/app/src/main/java/com/stardust/scriptdroid/external/tasker/FireSettingReceiver.java @@ -23,19 +23,16 @@ public class FireSettingReceiver extends AbstractPluginSettingReceiver { @Override protected boolean isBundleValid(@NonNull Bundle bundle) { - Log.v(TAG, "isBundleValid: " + bundle); - return bundle.containsKey(CommonUtils.EXTRA_KEY_PATH); + return CommonUtils.isTaskerBundleValid(bundle); } @Override protected boolean isAsync() { - Log.v(TAG, "isAsync"); return true; } @Override protected void firePluginSetting(@NonNull Context context, @NonNull Bundle bundle) { - Log.v(TAG, "firePluginSetting:" + bundle); context.startActivity(new Intent(App.getApp(), RunIntentActivity.class) .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) .putExtra(CommonUtils.EXTRA_KEY_PATH, bundle.getString(CommonUtils.EXTRA_KEY_PATH)) diff --git a/app/src/main/java/com/stardust/scriptdroid/external/tasker/TaskPrefEditActivity.java b/app/src/main/java/com/stardust/scriptdroid/external/tasker/TaskPrefEditActivity.java index 4b4b19c9..37ad1217 100644 --- a/app/src/main/java/com/stardust/scriptdroid/external/tasker/TaskPrefEditActivity.java +++ b/app/src/main/java/com/stardust/scriptdroid/external/tasker/TaskPrefEditActivity.java @@ -9,7 +9,8 @@ import android.util.Log; import com.stardust.scriptdroid.scripts.ScriptFile; import com.stardust.scriptdroid.R; import com.stardust.scriptdroid.external.CommonUtils; -import com.stardust.scriptdroid.ui.main.my_script_list.ScriptAndFolderListRecyclerView; +import com.stardust.scriptdroid.ui.main.script_list.ScriptAndFolderListRecyclerView; +import com.stardust.scriptdroid.ui.main.script_list.ScriptListWithProgressBarView; import com.twofortyfouram.locale.sdk.client.ui.activity.AbstractPluginActivity; /** @@ -18,20 +19,19 @@ import com.twofortyfouram.locale.sdk.client.ui.activity.AbstractPluginActivity; public class TaskPrefEditActivity extends AbstractPluginActivity { - private static final String TAG = "TaskPrefEditActivity"; private String mSelectedScriptFilePath; @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); - Log.v(TAG, "onCreate"); setContentView(R.layout.activity_tasker_edit); initScriptListRecyclerView(); } private void initScriptListRecyclerView() { - ScriptAndFolderListRecyclerView scriptListRecyclerView = (ScriptAndFolderListRecyclerView) findViewById(R.id.script_list); - scriptListRecyclerView.setOnItemClickListener(new ScriptAndFolderListRecyclerView.OnScriptFileClickListener() { + ScriptListWithProgressBarView scriptListWithProgressBarView = (ScriptListWithProgressBarView) findViewById(R.id.script_list); + scriptListWithProgressBarView.setScriptFileOperationEnabled(false); + scriptListWithProgressBarView.setOnItemClickListener(new ScriptAndFolderListRecyclerView.OnScriptFileClickListener() { @Override public void onClick(ScriptFile file) { mSelectedScriptFilePath = file.getPath(); @@ -43,22 +43,17 @@ public class TaskPrefEditActivity extends AbstractPluginActivity { @Override public boolean isBundleValid(@NonNull Bundle bundle) { - boolean valid = bundle.getString(CommonUtils.EXTRA_KEY_PATH) != null; - Log.v(TAG, "isBundleValid: " + valid); - Context context; - return valid; + return CommonUtils.isTaskerBundleValid(bundle); } @Override public void onPostCreateWithPreviousResult(@NonNull Bundle bundle, @NonNull String s) { - Log.v(TAG, "onPostCreateWithPreviousResult: bundle=" + bundle + " str=" + s); mSelectedScriptFilePath = bundle.getString(CommonUtils.EXTRA_KEY_PATH); } @Nullable @Override public Bundle getResultBundle() { - Log.v(TAG, "getResultBundle"); Bundle bundle = new Bundle(); bundle.putString(CommonUtils.EXTRA_KEY_PATH, mSelectedScriptFilePath); return bundle; @@ -67,7 +62,6 @@ public class TaskPrefEditActivity extends AbstractPluginActivity { @NonNull @Override public String getResultBlurb(@NonNull Bundle bundle) { - Log.v(TAG, "getResultBlurb"); return bundle.getString(CommonUtils.EXTRA_KEY_PATH, getString(R.string.text_path_is_empty)); } } diff --git a/app/src/main/java/com/stardust/scriptdroid/layout_inspector/view/LayoutHierarchyView.java b/app/src/main/java/com/stardust/scriptdroid/layout_inspector/view/LayoutHierarchyView.java index fad16d98..343e68ff 100644 --- a/app/src/main/java/com/stardust/scriptdroid/layout_inspector/view/LayoutHierarchyView.java +++ b/app/src/main/java/com/stardust/scriptdroid/layout_inspector/view/LayoutHierarchyView.java @@ -178,7 +178,7 @@ public class LayoutHierarchyView extends MultiLevelListView { NodeInfo nodeInfo = (NodeInfo) object; ViewHolder viewHolder; if (convertView == null) { - convertView = LayoutInflater.from(getContext()).inflate(R.layout.layout_hierarchy_view_item, null); + convertView = LayoutInflater.from(getContext()).inflate(R.layout.layout_hierarchy_view_item, LayoutHierarchyView.this, false); viewHolder = new ViewHolder(convertView); convertView.setTag(viewHolder); } else { diff --git a/app/src/main/java/com/stardust/scriptdroid/scripts/StorageScriptProvider.java b/app/src/main/java/com/stardust/scriptdroid/scripts/StorageScriptProvider.java index 1cde9310..011be748 100644 --- a/app/src/main/java/com/stardust/scriptdroid/scripts/StorageScriptProvider.java +++ b/app/src/main/java/com/stardust/scriptdroid/scripts/StorageScriptProvider.java @@ -6,9 +6,12 @@ import com.stardust.scriptdroid.App; import com.stardust.scriptdroid.R; import com.stardust.util.FileSorter; import com.stardust.util.LimitedHashMap; +import com.stardust.util.MapEntries; import org.greenrobot.eventbus.EventBus; +import java.util.Map; + /** * Created by Stardust on 2017/3/31. */ @@ -29,11 +32,19 @@ public class StorageScriptProvider { public static final ScriptFile DEFAULT_DIRECTORY = new ScriptFile(DEFAULT_DIRECTORY_PATH); - - private static StorageScriptProvider instance = new StorageScriptProvider(); + private static StorageScriptProvider defaultProvider = new StorageScriptProvider(); public static StorageScriptProvider getDefault() { - return instance; + return defaultProvider; + } + + private static StorageScriptProvider externalStorageProvider; + + public static StorageScriptProvider getExternalStorageProvider() { + if (externalStorageProvider == null) { + externalStorageProvider = new StorageScriptProvider(Environment.getExternalStorageDirectory().getPath(), 5); + } + return externalStorageProvider; } private EventBus mDirectoryEventBus = new EventBus(); @@ -47,7 +58,6 @@ public class StorageScriptProvider { public StorageScriptProvider(ScriptFile initialDirectory, int cacheSize) { mInitialDirectory = initialDirectory; - mInitialDirectoryScriptFiles = getInitialDirectoryScriptFilesInner(); mScriptFileCache = new LimitedHashMap<>(cacheSize); } @@ -57,7 +67,7 @@ public class StorageScriptProvider { public void notifyDirectoryChanged(ScriptFile directory) { if (directory.equals(mInitialDirectory)) { - mInitialDirectoryScriptFiles = getInitialDirectoryScriptFilesInner(); + mInitialDirectoryScriptFiles = getInitialDirectoryScriptFiles(); } else { clearCache(directory); } @@ -66,15 +76,28 @@ public class StorageScriptProvider { public void notifyStoragePermissionGranted() { mScriptFileCache.clear(); - mInitialDirectoryScriptFiles = getInitialDirectoryScriptFilesInner(); + mInitialDirectoryScriptFiles = null; mDirectoryEventBus.post(new DirectoryChangeEvent(mInitialDirectory)); } + public void refreshAll() { + Map files = (Map) mScriptFileCache.clone(); + mScriptFileCache.clear(); + mInitialDirectoryScriptFiles = null; + mDirectoryEventBus.post(new DirectoryChangeEvent(mInitialDirectory)); + for (Map.Entry file : files.entrySet()) { + mDirectoryEventBus.post(new DirectoryChangeEvent(new ScriptFile(file.getKey()))); + } + } + public ScriptFile getInitialDirectory() { return mInitialDirectory; } public ScriptFile[] getInitialDirectoryScriptFiles() { + if (mInitialDirectoryScriptFiles == null) { + mInitialDirectoryScriptFiles = getInitialDirectoryScriptFilesInner(); + } return mInitialDirectoryScriptFiles; } @@ -84,7 +107,7 @@ public class StorageScriptProvider { public ScriptFile[] getDirectoryScriptFiles(ScriptFile directory) { if (directory.equals(mInitialDirectory)) { - return mInitialDirectoryScriptFiles; + return getInitialDirectoryScriptFiles(); } ScriptFile[] scriptFiles = getScriptFilesFromCache(directory); if (scriptFiles == null) { @@ -93,6 +116,10 @@ public class StorageScriptProvider { return scriptFiles; } + public void clearCacheExceptInitialDirectory() { + mScriptFileCache.clear(); + } + private void clearCache(ScriptFile directory) { mScriptFileCache.remove(directory.getPath()); } @@ -119,7 +146,8 @@ public class StorageScriptProvider { public void registerDirectoryChangeListener(Object subscriber) { - mDirectoryEventBus.register(subscriber); + if (!mDirectoryEventBus.isRegistered(subscriber)) + mDirectoryEventBus.register(subscriber); } public void unregisterDirectoryChangeListener(Object subscriber) { diff --git a/app/src/main/java/com/stardust/scriptdroid/tool/DrawableSaver.java b/app/src/main/java/com/stardust/scriptdroid/tool/DrawableSaver.java new file mode 100644 index 00000000..54f69067 --- /dev/null +++ b/app/src/main/java/com/stardust/scriptdroid/tool/DrawableSaver.java @@ -0,0 +1,141 @@ +package com.stardust.scriptdroid.tool; + +import android.app.Activity; +import android.content.Context; +import android.graphics.drawable.BitmapDrawable; +import android.graphics.drawable.Drawable; +import android.view.View; +import android.widget.ImageView; + +import com.stardust.app.OnActivityResultDelegate; +import com.stardust.pio.PFile; + +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.concurrent.Callable; + +/** + * Created by Stardust on 2017/4/3. + */ + +public abstract class DrawableSaver { + + private static final String PREFIX = "saved_drawable_"; + + protected Drawable mOriginalDrawble; + private Context mContext; + private String mName; + + public DrawableSaver(Context context, String name, Drawable originalDrawble) { + mContext = context; + mName = PREFIX + name; + mOriginalDrawble = originalDrawble; + readImageAndApply(); + } + + private void readImageAndApply() { + new Thread(new Runnable() { + @Override + public void run() { + try { + InputStream inputStream = mContext.openFileInput(mName); + Drawable drawable = BitmapDrawable.createFromStream(inputStream, mName); + applyDrawableToView(drawable); + } catch (FileNotFoundException e) { + + } + } + }).start(); + } + + public void setDrawable(InputStream inputStream) { + saveDrawable(inputStream, new Runnable() { + @Override + public void run() { + readImageAndApply(); + } + }); + } + + private void saveDrawable(final InputStream inputStream, final Runnable callback) { + new Thread(new Runnable() { + @Override + public void run() { + try { + OutputStream outputStream = mContext.openFileOutput(mName, Context.MODE_PRIVATE); + PFile.write(inputStream, outputStream); + outputStream.close(); + inputStream.close(); + callback.run(); + } catch (IOException e) { + e.printStackTrace(); + } + } + }).start(); + + } + + public void select(Activity activity, final OnActivityResultDelegate.Intermediary intermediary) { + new ImageSelector(activity, intermediary, new ImageSelector.ImageSelectorCallback() { + @Override + public void onImageSelected(ImageSelector selector, InputStream inputStream) { + if (inputStream != null) + setDrawable(inputStream); + intermediary.removeDelegate(selector); + } + }).select(); + } + + public void reset() { + applyDrawableToView(mOriginalDrawble); + mContext.deleteFile(mName); + } + + public static void reset(Context context, String name) { + context.deleteFile(PREFIX + name); + } + + protected abstract void applyDrawableToView(Drawable drawable); + + public static class ViewBackgroundSaver extends DrawableSaver { + + private View mView; + + public ViewBackgroundSaver(Context activity, String name, View view) { + super(activity, name, view.getBackground()); + mView = view; + } + + @Override + protected void applyDrawableToView(final Drawable drawable) { + mView.post(new Runnable() { + @Override + public void run() { + mView.setBackground(drawable); + } + }); + } + } + + public static class ImageSaver extends DrawableSaver { + + private ImageView mView; + + public ImageSaver(Context activity, String name, ImageView view) { + super(activity, name, view.getDrawable()); + mView = view; + } + + @Override + protected void applyDrawableToView(final Drawable drawable) { + mView.post(new Runnable() { + @Override + public void run() { + mView.setImageDrawable(drawable); + } + }); + } + } +} diff --git a/app/src/main/java/com/stardust/scriptdroid/tool/ImageSelector.java b/app/src/main/java/com/stardust/scriptdroid/tool/ImageSelector.java index e41030d2..cf5347fc 100644 --- a/app/src/main/java/com/stardust/scriptdroid/tool/ImageSelector.java +++ b/app/src/main/java/com/stardust/scriptdroid/tool/ImageSelector.java @@ -9,6 +9,9 @@ import android.provider.MediaStore; import com.stardust.app.OnActivityResultDelegate; import com.stardust.scriptdroid.R; +import java.io.FileNotFoundException; +import java.io.InputStream; + /** * Created by Stardust on 2017/3/5. */ @@ -16,7 +19,7 @@ import com.stardust.scriptdroid.R; public class ImageSelector implements OnActivityResultDelegate { public interface ImageSelectorCallback { - void onImageSelected(ImageSelector selector, String path); + void onImageSelected(ImageSelector selector, InputStream path); } private static final int REQUEST_CODE = "LOVE EATING".hashCode() >> 16; @@ -38,19 +41,16 @@ public class ImageSelector implements OnActivityResultDelegate { @Override public void onActivityResult(int requestCode, int resultCode, Intent data) { - if (data == null) + if (data == null) { + mCallback.onImageSelected(this, null); return; - Uri selectedImageUri = data.getData(); - String[] filePathColumn = {MediaStore.Images.Media.DATA}; - Cursor cursor = mActivity.getContentResolver().query(selectedImageUri, filePathColumn, null, null, null); - if (cursor != null) { - cursor.moveToFirst(); - int columnIndex = cursor.getColumnIndex(filePathColumn[0]); - String filePath = cursor.getString(columnIndex); - cursor.close(); - mCallback.onImageSelected(this, filePath); } - + try { + InputStream inputStream = mActivity.getContentResolver().openInputStream(data.getData()); + mCallback.onImageSelected(this, inputStream); + } catch (FileNotFoundException e) { + mCallback.onImageSelected(this, null); + } } diff --git a/app/src/main/java/com/stardust/scriptdroid/ui/BaseActivity.java b/app/src/main/java/com/stardust/scriptdroid/ui/BaseActivity.java index 068b9818..ca302f32 100644 --- a/app/src/main/java/com/stardust/scriptdroid/ui/BaseActivity.java +++ b/app/src/main/java/com/stardust/scriptdroid/ui/BaseActivity.java @@ -24,7 +24,7 @@ import static android.content.pm.PackageManager.PERMISSION_GRANTED; * Created by Stardust on 2017/1/23. */ -public class BaseActivity extends AppCompatActivity { +public abstract class BaseActivity extends AppCompatActivity { protected static final int PERMISSION_REQUEST_CODE = 11186; private BackPressedHandler.Observer mBackPressObserver = new BackPressedHandler.Observer(); diff --git a/app/src/main/java/com/stardust/scriptdroid/ui/edit/editor920/Editor920Activity.java b/app/src/main/java/com/stardust/scriptdroid/ui/edit/editor920/Editor920Activity.java index 214e4736..2b8443b7 100644 --- a/app/src/main/java/com/stardust/scriptdroid/ui/edit/editor920/Editor920Activity.java +++ b/app/src/main/java/com/stardust/scriptdroid/ui/edit/editor920/Editor920Activity.java @@ -17,7 +17,7 @@ import java.util.Map; /** * Created by Stardust on 2017/1/30. */ -public class Editor920Activity extends IActivity { +public abstract class Editor920Activity extends IActivity { @Override protected void onCreate(@Nullable Bundle savedInstanceState) { diff --git a/app/src/main/java/com/stardust/scriptdroid/ui/error/AbstractIssueReporterActivity.java b/app/src/main/java/com/stardust/scriptdroid/ui/error/AbstractIssueReporterActivity.java new file mode 100644 index 00000000..bb0d8ed8 --- /dev/null +++ b/app/src/main/java/com/stardust/scriptdroid/ui/error/AbstractIssueReporterActivity.java @@ -0,0 +1,642 @@ +package com.stardust.scriptdroid.ui.error; + +import android.app.Activity; +import android.app.Dialog; +import android.content.Context; +import android.content.DialogInterface; +import android.graphics.Color; +import android.graphics.PorterDuff; +import android.graphics.drawable.Drawable; +import android.os.AsyncTask; +import android.os.Build; +import android.os.Bundle; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.annotation.StringDef; +import android.support.annotation.StringRes; +import android.support.design.widget.FloatingActionButton; +import android.support.design.widget.TextInputEditText; +import android.support.design.widget.TextInputLayout; +import android.support.v4.app.NavUtils; +import android.support.v7.app.ActionBar; +import android.support.v7.app.AppCompatActivity; +import android.support.v7.widget.Toolbar; +import android.text.TextUtils; +import android.util.Log; +import android.view.KeyEvent; +import android.view.View; +import android.view.inputmethod.EditorInfo; +import android.widget.EditText; +import android.widget.ImageButton; +import android.widget.LinearLayout; +import android.widget.RadioButton; +import android.widget.TextView; +import android.widget.Toast; + +import com.afollestad.materialdialogs.DialogAction; +import com.afollestad.materialdialogs.MaterialDialog; +import com.github.aakira.expandablelayout.ExpandableRelativeLayout; +import com.heinrichreimersoftware.androidissuereporter.model.DeviceInfo; +import com.heinrichreimersoftware.androidissuereporter.model.Report; +import com.heinrichreimersoftware.androidissuereporter.model.github.ExtraInfo; +import com.heinrichreimersoftware.androidissuereporter.model.github.GithubLogin; +import com.heinrichreimersoftware.androidissuereporter.model.github.GithubTarget; +import com.heinrichreimersoftware.androidissuereporter.util.ColorUtils; +import com.heinrichreimersoftware.androidissuereporter.util.ThemeUtils; + +import org.eclipse.egit.github.core.Issue; +import org.eclipse.egit.github.core.client.GitHubClient; +import org.eclipse.egit.github.core.client.RequestException; +import org.eclipse.egit.github.core.service.IssueService; + +import java.io.IOException; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.ref.WeakReference; + +import static android.util.Patterns.EMAIL_ADDRESS; + +import com.heinrichreimersoftware.androidissuereporter.R; +import com.stardust.theme.ThemeColorManager; + +/** + * Created by Stardust on 2017/4/3. + */ + +public abstract class AbstractIssueReporterActivity extends AppCompatActivity { + + private static final String TAG = AbstractIssueReporterActivity.class.getSimpleName(); + + private static final int STATUS_BAD_CREDENTIALS = 401; + private static final int STATUS_ISSUES_NOT_ENABLED = 410; + private boolean mCrash = false; + private boolean mReportFailed = true; + + @StringDef({RESULT_OK, RESULT_BAD_CREDENTIALS, RESULT_INVALID_TOKEN, RESULT_ISSUES_NOT_ENABLED, + RESULT_UNKNOWN}) + @Retention(RetentionPolicy.SOURCE) + private @interface Result { + } + + private static final String RESULT_OK = "RESULT_OK"; + private static final String RESULT_BAD_CREDENTIALS = "RESULT_BAD_CREDENTIALS"; + private static final String RESULT_INVALID_TOKEN = "RESULT_INVALID_TOKEN"; + private static final String RESULT_ISSUES_NOT_ENABLED = "RESULT_ISSUES_NOT_ENABLED"; + private static final String RESULT_UNKNOWN = "RESULT_UNKNOWN"; + private boolean emailRequired = false; + private int bodyMinChar = 0; + private Toolbar toolbar; + private TextInputEditText inputTitle; + private TextInputEditText inputDescription; + private TextView textDeviceInfo; + private ImageButton buttonDeviceInfo; + private ExpandableRelativeLayout layoutDeviceInfo; + private ExpandableRelativeLayout layoutAnonymous; + private TextInputEditText inputUsername; + private TextInputEditText inputPassword; + private TextInputEditText inputEmail; + private RadioButton optionUseAccount; + private RadioButton optionAnonymous; + private ExpandableRelativeLayout layoutLogin; + private FloatingActionButton buttonSend; + + private Drawable optionUseAccountButtonDrawable = null; + + private String token; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + if (TextUtils.isEmpty(getTitle())) + setTitle(R.string.air_title_report_issue); + + setContentView(com.stardust.scriptdroid.R.layout.air_activity_issue_reporter); + findViews(); + + //noinspection deprecation + token = getGuestToken(); + + initViews(); + + + DeviceInfo deviceInfo = new DeviceInfo(this); + textDeviceInfo.setText(deviceInfo.toString()); + + handleIntent(); + + optionAnonymous.post(new Runnable() { + @Override + public void run() { + optionAnonymous.performClick(); + } + }); + } + + private void handleIntent() { + final String errorDetail = getIntent().getStringExtra("error"); + if (errorDetail != null) { + inputDescription.setText(errorDetail); + String title = getFirstLine(errorDetail); + inputTitle.setText(title); + mCrash = true; + } + } + + private String getFirstLine(String str) { + int i = str.indexOf('\n'); + if (i < 0) + return str; + return str.substring(0, i); + } + + private void findViews() { + toolbar = (Toolbar) findViewById(R.id.air_toolbar); + + inputTitle = (TextInputEditText) findViewById(R.id.air_inputTitle); + inputDescription = (TextInputEditText) findViewById(R.id.air_inputDescription); + textDeviceInfo = (TextView) findViewById(R.id.air_textDeviceInfo); + buttonDeviceInfo = (ImageButton) findViewById(R.id.air_buttonDeviceInfo); + layoutDeviceInfo = (ExpandableRelativeLayout) findViewById(R.id.air_layoutDeviceInfo); + + inputUsername = (TextInputEditText) findViewById(R.id.air_inputUsername); + inputPassword = (TextInputEditText) findViewById(R.id.air_inputPassword); + inputEmail = (TextInputEditText) findViewById(R.id.air_inputEmail); + optionUseAccount = (RadioButton) findViewById(R.id.air_optionUseAccount); + optionAnonymous = (RadioButton) findViewById(R.id.air_optionAnonymous); + layoutLogin = (ExpandableRelativeLayout) findViewById(R.id.air_layoutLogin); + layoutAnonymous = (ExpandableRelativeLayout) findViewById(R.id.air_layoutGuest); + + buttonSend = (FloatingActionButton) findViewById(R.id.air_buttonSend); + } + + private void initViews() { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + getWindow().setStatusBarColor(ThemeColorManager.getColorPrimary()); + } + setSupportActionBar(toolbar); + + ActionBar actionBar = getSupportActionBar(); + if (actionBar != null) { + actionBar.setDisplayHomeAsUpEnabled(true); + actionBar.setTitle(com.stardust.scriptdroid.R.string.text_issue_report); + } + toolbar.setBackgroundColor(ThemeColorManager.getColorPrimary()); + + + toolbar.setNavigationOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + finish(); + } + }); + + buttonDeviceInfo.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + layoutDeviceInfo.toggle(); + } + }); + + + inputPassword.setOnEditorActionListener(new TextView.OnEditorActionListener() { + @Override + public boolean onEditorAction(TextView textView, int actionId, KeyEvent event) { + if (actionId == EditorInfo.IME_ACTION_SEND) { + reportIssue(); + return true; + } + return false; + } + }); + + updateGuestTokenViews(); + + buttonSend.setImageResource(ColorUtils.isDark(ThemeUtils.getColorAccent(this)) ? + R.drawable.air_ic_send_dark : R.drawable.air_ic_send_light); + buttonSend.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + try { + reportIssue(); + } catch (Exception e) { + e.printStackTrace(); + mReportFailed = true; + finish(); + } + } + }); + + } + + private void setOptionUseAccountMarginStart(int marginStart) { + LinearLayout.LayoutParams layoutParams = (LinearLayout.LayoutParams) optionUseAccount.getLayoutParams(); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { + layoutParams.setMarginStart(marginStart); + } else { + layoutParams.leftMargin = marginStart; + } + optionUseAccount.setLayoutParams(layoutParams); + } + + private void updateGuestTokenViews() { + if (TextUtils.isEmpty(token)) { + int baseline = getResources().getDimensionPixelSize(R.dimen.air_baseline); + int radioButtonPaddingStart = getResources().getDimensionPixelSize(R.dimen.air_radio_button_padding_start); + setOptionUseAccountMarginStart(-2 * baseline - radioButtonPaddingStart); + optionUseAccount.setEnabled(false); + optionAnonymous.setVisibility(View.GONE); + } else { + setOptionUseAccountMarginStart(0); + optionUseAccount.setEnabled(true); + optionUseAccount.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + layoutLogin.expand(); + layoutAnonymous.collapse(); + inputUsername.setEnabled(true); + inputPassword.setEnabled(true); + } + }); + optionAnonymous.setVisibility(View.VISIBLE); + optionAnonymous.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + layoutLogin.collapse(); + layoutAnonymous.expand(); + inputUsername.setEnabled(false); + inputPassword.setEnabled(false); + } + }); + } + } + + private void reportIssue() { + + if (!validateInput()) return; + + if (optionUseAccount.isChecked()) { + String username = inputUsername.getText().toString(); + String password = inputPassword.getText().toString(); + sendBugReport(new GithubLogin(username, password), null); + } else { + if (TextUtils.isEmpty(token)) + throw new IllegalStateException("You must provide a GitHub API Token."); + + String email = null; + if (!TextUtils.isEmpty(inputEmail.getText()) && + EMAIL_ADDRESS.matcher(inputEmail.getText().toString()).matches()) { + email = inputEmail.getText().toString(); + } + + sendBugReport(new GithubLogin(token), email); + } + } + + private boolean validateInput() { + boolean hasErrors = false; + + if (optionUseAccount.isChecked()) { + if (TextUtils.isEmpty(inputUsername.getText())) { + setError(inputUsername, R.string.air_error_no_username); + hasErrors = true; + } else { + removeError(inputUsername); + } + + if (TextUtils.isEmpty(inputPassword.getText())) { + setError(inputPassword, R.string.air_error_no_password); + hasErrors = true; + } else { + removeError(inputPassword); + } + } else { + if (emailRequired) { + if (TextUtils.isEmpty(inputEmail.getText()) || + !EMAIL_ADDRESS.matcher(inputEmail.getText().toString()).matches()) { + setError(inputEmail, R.string.air_error_no_email); + hasErrors = true; + } else { + removeError(inputEmail); + } + } + } + + if (TextUtils.isEmpty(inputTitle.getText())) { + setError(inputTitle, R.string.air_error_no_title); + hasErrors = true; + } else { + removeError(inputTitle); + } + + if (TextUtils.isEmpty(inputDescription.getText())) { + setError(inputDescription, R.string.air_error_no_description); + hasErrors = true; + } else { + if (bodyMinChar > 0) { + if (inputDescription.getText().toString().length() < bodyMinChar) { + setError(inputDescription, getResources().getQuantityString(R.plurals.air_error_short_description, bodyMinChar, bodyMinChar)); + hasErrors = true; + } else { + removeError(inputDescription); + } + } else + removeError(inputDescription); + } + return !hasErrors; + } + + private void setError(TextInputEditText editText, @StringRes int errorRes) { + try { + View layout = (View) editText.getParent(); + while (!layout.getClass().getSimpleName().equals(TextInputLayout.class.getSimpleName())) + layout = (View) layout.getParent(); + TextInputLayout realLayout = (TextInputLayout) layout; + realLayout.setError(getString(errorRes)); + } catch (ClassCastException | NullPointerException e) { + Log.e(TAG, "Issue while setting error UI.", e); + } + } + + private void setError(TextInputEditText editText, String error) { + try { + View layout = (View) editText.getParent(); + while (!layout.getClass().getSimpleName().equals(TextInputLayout.class.getSimpleName())) + layout = (View) layout.getParent(); + TextInputLayout realLayout = (TextInputLayout) layout; + realLayout.setError(error); + } catch (ClassCastException | NullPointerException e) { + Log.e(TAG, "Issue while setting error UI.", e); + } + } + + private void removeError(TextInputEditText editText) { + try { + View layout = (View) editText.getParent(); + while (!layout.getClass().getSimpleName().equals(TextInputLayout.class.getSimpleName())) + layout = (View) layout.getParent(); + TextInputLayout realLayout = (TextInputLayout) layout; + realLayout.setError(null); + } catch (ClassCastException | NullPointerException e) { + Log.e(TAG, "Issue while removing error UI.", e); + } + } + + private void sendBugReport(GithubLogin login, String email) { + if (!validateInput()) return; + + String bugTitle = inputTitle.getText().toString(); + String bugDescription = inputDescription.getText().toString(); + + DeviceInfo deviceInfo = new DeviceInfo(this); + + ExtraInfo extraInfo = new ExtraInfo(); + onSaveExtraInfo(extraInfo); + + Report report = new Report(bugTitle, bugDescription, deviceInfo, extraInfo, email); + GithubTarget target = getTarget(); + + report(this, report, target, login); + } + + protected final void setGuestEmailRequired(boolean required) { + this.emailRequired = required; + if (required) { + optionAnonymous.setText(R.string.air_label_use_email); + ((TextInputLayout) findViewById(R.id.air_inputEmailParent)).setHint(getString(R.string.air_label_email)); + } else { + optionAnonymous.setText(R.string.air_label_use_guest); + ((TextInputLayout) findViewById(R.id.air_inputEmailParent)).setHint(getString(R.string.air_label_email_optional)); + } + } + + protected final void setMinimumDescriptionLength(int length) { + this.bodyMinChar = length; + } + + protected void onSaveExtraInfo(ExtraInfo extraInfo) { + } + + protected abstract GithubTarget getTarget(); + + @Deprecated + protected String getGuestToken() { + return null; + } + + protected final void setGuestToken(String token) { + this.token = token; + Log.d(TAG, "GuestToken: " + token); + updateGuestTokenViews(); + } + + @Override + public void finish() { + if (mCrash) { + if (!mReportFailed) { + Toast.makeText(this, com.stardust.scriptdroid.R.string.text_report_succeed, Toast.LENGTH_SHORT).show(); + } else { + Toast.makeText(this, com.stardust.scriptdroid.R.string.text_report_fail, Toast.LENGTH_SHORT).show(); + } + finishAffinity(); + } else { + super.finish(); + } + } + + private void report(Activity activity, Report report, GithubTarget target, + GithubLogin login) { + new ReportIssueTask(activity, report, target, login).execute(); + } + + private class ReportIssueTask extends DialogAsyncTask { + private final Report report; + private final GithubTarget target; + private final GithubLogin login; + + private ReportIssueTask(Activity activity, Report report, GithubTarget target, + GithubLogin login) { + super(activity); + this.report = report; + this.target = target; + this.login = login; + } + + + @Override + protected Dialog createDialog(@NonNull Context context) { + return new MaterialDialog.Builder(context) + .progress(true, 0) + .progressIndeterminateStyle(true) + .title(R.string.air_dialog_title_loading) + .show(); + } + + @Override + @Result + protected String doInBackground(Void... params) { + GitHubClient client; + if (login.shouldUseApiToken()) { + client = new GitHubClient().setOAuth2Token(login.getApiToken()); + } else { + client = new GitHubClient().setCredentials(login.getUsername(), login.getPassword()); + } + + Issue issue = new Issue().setTitle(report.getTitle()).setBody(report.getDescription()); + try { + new IssueService(client).createIssue(target.getUsername(), target.getRepository(), issue); + return RESULT_OK; + } catch (RequestException e) { + switch (e.getStatus()) { + case STATUS_BAD_CREDENTIALS: + if (login.shouldUseApiToken()) + return RESULT_INVALID_TOKEN; + return RESULT_BAD_CREDENTIALS; + case STATUS_ISSUES_NOT_ENABLED: + return RESULT_ISSUES_NOT_ENABLED; + default: + e.printStackTrace(); + return RESULT_UNKNOWN; + } + } catch (IOException e) { + e.printStackTrace(); + return RESULT_UNKNOWN; + } + } + + @Override + protected void onPostExecute(@Result String result) { + super.onPostExecute(result); + + Context context = getContext(); + if (context == null) return; + + switch (result) { + case RESULT_OK: + mReportFailed = false; + tryToFinishActivity(); + break; + case RESULT_BAD_CREDENTIALS: + new MaterialDialog.Builder(context) + .title(R.string.air_dialog_title_failed) + .content(R.string.air_dialog_description_failed_wrong_credentials) + .positiveText(R.string.air_dialog_action_failed) + .show(); + break; + case RESULT_INVALID_TOKEN: + new MaterialDialog.Builder(context) + .title(R.string.air_dialog_title_failed) + .content(R.string.air_dialog_description_failed_invalid_token) + .positiveText(R.string.air_dialog_action_failed) + .show(); + break; + case RESULT_ISSUES_NOT_ENABLED: + new MaterialDialog.Builder(context) + .title(R.string.air_dialog_title_failed) + .content(R.string.air_dialog_description_failed_issues_not_available) + .positiveText(R.string.air_dialog_action_failed) + .show(); + break; + default: + new MaterialDialog.Builder(context) + .title(R.string.air_dialog_title_failed) + .content(R.string.air_dialog_description_failed_unknown) + .positiveText(R.string.air_dialog_action_failed) + .onPositive(new MaterialDialog.SingleButtonCallback() { + @Override + public void onClick(@NonNull MaterialDialog dialog, + @NonNull DialogAction which) { + tryToFinishActivity(); + } + }) + .cancelListener(new DialogInterface.OnCancelListener() { + @Override + public void onCancel(DialogInterface dialog) { + tryToFinishActivity(); + } + }) + .show(); + break; + } + } + + private void tryToFinishActivity() { + Context context = getContext(); + if (context instanceof Activity && !((Activity) context).isFinishing()) { + ((Activity) context).finish(); + } + } + } + + private static abstract class DialogAsyncTask extends AsyncTask { + private WeakReference contextWeakReference; + private WeakReference dialogWeakReference; + + private boolean supposedToBeDismissed; + + private DialogAsyncTask(Context context) { + contextWeakReference = new WeakReference<>(context); + dialogWeakReference = new WeakReference<>(null); + } + + @Override + protected void onPreExecute() { + super.onPreExecute(); + Context context = getContext(); + if (!supposedToBeDismissed && context != null) { + Dialog dialog = createDialog(context); + dialogWeakReference = new WeakReference<>(dialog); + dialog.show(); + } + } + + @SuppressWarnings("unchecked") + @Override + protected final void onProgressUpdate(Pr... values) { + super.onProgressUpdate(values); + Dialog dialog = getDialog(); + if (dialog != null) { + onProgressUpdate(dialog, values); + } + } + + @SuppressWarnings("unchecked") + private void onProgressUpdate(@NonNull Dialog dialog, Pr... values) { + } + + @Nullable + Context getContext() { + return contextWeakReference.get(); + } + + @Nullable + Dialog getDialog() { + return dialogWeakReference.get(); + } + + @Override + protected void onCancelled(Re result) { + super.onCancelled(result); + tryToDismiss(); + } + + @Override + protected void onPostExecute(Re result) { + super.onPostExecute(result); + tryToDismiss(); + } + + private void tryToDismiss() { + supposedToBeDismissed = true; + try { + Dialog dialog = getDialog(); + if (dialog != null) + dialog.dismiss(); + } catch (Exception e) { + e.printStackTrace(); + } + } + + protected abstract Dialog createDialog(@NonNull Context context); + } +} diff --git a/app/src/main/java/com/stardust/scriptdroid/ui/error/ErrorReportActivity.java b/app/src/main/java/com/stardust/scriptdroid/ui/error/ErrorReportActivity.java index 4818f196..b301b2fb 100644 --- a/app/src/main/java/com/stardust/scriptdroid/ui/error/ErrorReportActivity.java +++ b/app/src/main/java/com/stardust/scriptdroid/ui/error/ErrorReportActivity.java @@ -79,7 +79,7 @@ public class ErrorReportActivity extends BaseActivity { } private void startIssueReportActivity() { - Intent intent = new Intent(this, IssueReportActivity.class); + Intent intent = new Intent(this, IssueReporterActivity.class); intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP); intent.putExtras(getIntent()); startActivity(intent); diff --git a/app/src/main/java/com/stardust/scriptdroid/ui/error/IssueReportActivity.java b/app/src/main/java/com/stardust/scriptdroid/ui/error/IssueReportActivity.java deleted file mode 100644 index 5b0ed9b0..00000000 --- a/app/src/main/java/com/stardust/scriptdroid/ui/error/IssueReportActivity.java +++ /dev/null @@ -1,145 +0,0 @@ -package com.stardust.scriptdroid.ui.error; - -import android.os.Bundle; -import android.support.design.widget.FloatingActionButton; -import android.support.v7.widget.Toolbar; -import android.util.Base64; -import android.view.View; -import android.widget.EditText; -import android.widget.Toast; - -import com.heinrichreimersoftware.androidissuereporter.IssueReporterActivity; -import com.heinrichreimersoftware.androidissuereporter.model.github.GithubTarget; -import com.stardust.scriptdroid.R; - -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; - -/** - * Created by Stardust on 2017/2/13. - */ - -public class IssueReportActivity extends IssueReporterActivity { - - private boolean mCrash = false; - private Method mReportIssue, mValidateInput; - private boolean mReportFailed = false; - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - handleIntent(); - setUpToobar(); - hookSendClick(); - } - - private void hookSendClick() { - FloatingActionButton send = (FloatingActionButton) this.findViewById(com.heinrichreimersoftware.androidissuereporter.R.id.air_buttonSend); - send.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - try { - reportIssue(); - } catch (Exception e) { - mReportFailed = true; - e.printStackTrace(); - finish(); - } - } - }); - } - - private void setUpToobar() { - getSupportActionBar().setTitle(R.string.text_issue_report); - getSupportActionBar().setDisplayHomeAsUpEnabled(true); - Toolbar toolbar = (Toolbar) findViewById(R.id.air_toolbar); - toolbar.setNavigationOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - finish(); - } - }); - } - - private void handleIntent() { - final String errorDetail = getIntent().getStringExtra("error"); - if (errorDetail != null) { - ((EditText) findViewById(R.id.air_inputDescription)).setText(errorDetail); - String title = getFirstLine(errorDetail); - ((EditText) findViewById(R.id.air_inputTitle)).setText(title); - mCrash = true; - } - } - - private String getFirstLine(String str) { - int i = str.indexOf('\n'); - if(i < 0) - return str; - return str.substring(0, i); - } - - private boolean validateInput() { - if (mValidateInput == null) { - try { - mValidateInput = IssueReporterActivity.class.getDeclaredMethod("validateInput"); - mValidateInput.setAccessible(true); - } catch (NoSuchMethodException e) { - e.printStackTrace(); - } - } - try { - return mValidateInput != null && (Boolean) mValidateInput.invoke(this); - } catch (IllegalAccessException | InvocationTargetException e) { - e.printStackTrace(); - } - return false; - } - - private void reportIssue() { - if (mReportIssue == null) { - try { - mReportIssue = IssueReporterActivity.class.getDeclaredMethod("reportIssue"); - mReportIssue.setAccessible(true); - } catch (NoSuchMethodException e) { - e.printStackTrace(); - } - } - try { - mReportIssue.invoke(this); - } catch (IllegalAccessException | InvocationTargetException e) { - e.printStackTrace(); - } - } - - @Override - public void finish() { - if (mCrash) { - if (!mReportFailed) { - Toast.makeText(IssueReportActivity.this, R.string.text_report_succeed, Toast.LENGTH_SHORT).show(); - } else { - Toast.makeText(IssueReportActivity.this, R.string.text_report_fail, Toast.LENGTH_SHORT).show(); - } - finishAffinity(); - - } else { - super.finish(); - } - } - - @Override - protected GithubTarget getTarget() { - return new GithubTarget("hyb1996", "NoRootScriptDroid"); - } - - - @Override - protected String getGuestToken() { - //绕过github安全检查 - return decode("MzA5MWJhNWI0NDM2NWYzOGRmNDA4ZWRhM2Y5MTUxN2ZlNDVlZDBhZQ=="); - } - - public static String decode(String str) { - return new String(Base64.decode(str.getBytes(), Base64.DEFAULT)); - } - -} diff --git a/app/src/main/java/com/stardust/scriptdroid/ui/error/IssueReporterActivity.java b/app/src/main/java/com/stardust/scriptdroid/ui/error/IssueReporterActivity.java new file mode 100644 index 00000000..21aadca3 --- /dev/null +++ b/app/src/main/java/com/stardust/scriptdroid/ui/error/IssueReporterActivity.java @@ -0,0 +1,33 @@ +package com.stardust.scriptdroid.ui.error; + +import android.os.Bundle; +import android.util.Base64; + +import com.heinrichreimersoftware.androidissuereporter.model.github.GithubTarget; + +/** + * Created by Stardust on 2017/2/13. + */ + +public class IssueReporterActivity extends AbstractIssueReporterActivity { + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + //绕过GitHub安全检查 + String token = decode("MzA5MWJhNWI0NDM2NWYzOGRmNDA4ZWRhM2Y5MTUxN2ZlNDVlZDBhZQ=="); + setGuestToken(token); + } + + @Override + protected GithubTarget getTarget() { + return new GithubTarget("hyb1996", "NoRootScriptDroid"); + } + + + public static String decode(String str) { + return new String(Base64.decode(str.getBytes(), Base64.DEFAULT)); + } + + +} diff --git a/app/src/main/java/com/stardust/scriptdroid/ui/main/MainActivity.java b/app/src/main/java/com/stardust/scriptdroid/ui/main/MainActivity.java index 69aed01f..595c7fce 100644 --- a/app/src/main/java/com/stardust/scriptdroid/ui/main/MainActivity.java +++ b/app/src/main/java/com/stardust/scriptdroid/ui/main/MainActivity.java @@ -7,10 +7,7 @@ import android.content.ClipData; import android.content.ClipboardManager; import android.content.Context; import android.content.Intent; -import android.graphics.drawable.BitmapDrawable; -import android.graphics.drawable.Drawable; import android.os.Bundle; -import android.os.Environment; import android.support.annotation.NonNull; import android.support.design.widget.TabLayout; import android.support.v4.view.ViewPager; @@ -25,22 +22,23 @@ import android.widget.Toast; import com.afollestad.materialdialogs.DialogAction; import com.afollestad.materialdialogs.MaterialDialog; -import com.afollestad.materialdialogs.folderselector.FileChooserDialog; import com.stardust.app.FragmentPagerAdapterBuilder; import com.stardust.app.NotAskAgainDialog; import com.stardust.app.OnActivityResultDelegate; -import com.stardust.scriptdroid.Pref; +import com.stardust.scriptdroid.scripts.ScriptFile; import com.stardust.scriptdroid.scripts.StorageScriptProvider; import com.stardust.scriptdroid.service.AccessibilityWatchDogService; import com.stardust.scriptdroid.tool.AccessibilityServiceTool; -import com.stardust.scriptdroid.tool.ImageSelector; +import com.stardust.scriptdroid.tool.DrawableSaver; import com.stardust.scriptdroid.ui.BaseActivity; -import com.stardust.scriptdroid.ui.main.my_script_list.MyScriptListFragment; +import com.stardust.scriptdroid.ui.main.script_list.MyScriptListFragment; import com.stardust.scriptdroid.ui.main.sample_list.SampleScriptListFragment; +import com.stardust.scriptdroid.ui.main.script_list.ScriptFileChooserDialogBuilder; import com.stardust.scriptdroid.ui.main.task.TaskManagerFragment; import com.stardust.scriptdroid.ui.settings.SettingsActivity; import com.stardust.theme.dialog.ThemeColorMaterialDialogBuilder; import com.stardust.util.BackPressedHandler; +import com.stardust.util.MessageEvent; import com.stardust.view.ViewBinding; import com.stardust.widget.SlidingUpPanel; import com.stardust.scriptdroid.BuildConfig; @@ -48,10 +46,13 @@ import com.stardust.scriptdroid.R; import com.stardust.view.ViewBinder; import com.stardust.view.accessibility.AccessibilityServiceUtils; -import java.io.File; +import org.greenrobot.eventbus.EventBus; +import org.greenrobot.eventbus.Subscribe; -public class MainActivity extends BaseActivity implements FileChooserDialog.FileCallback { +public class MainActivity extends BaseActivity { + + public static final String MESSAGE_CLEAR_BACKGROUND_SETTINGS = "MESSAGE_CLEAR_BACKGROUND_SETTINGS"; private static final String EXTRA_ACTION = "EXTRA_ACTION"; @@ -66,6 +67,7 @@ public class MainActivity extends BaseActivity implements FileChooserDialog.File private FragmentPagerAdapterBuilder.StoredFragmentPagerAdapter mPagerAdapter; private OnActivityResultDelegate.Intermediary mActivityResultIntermediary = new OnActivityResultDelegate.Intermediary(); + private DrawableSaver mDrawerHeaderBackgroundSaver, mAppbarBackgroundSaver; @Override protected void onCreate(Bundle savedInstanceState) { @@ -74,6 +76,7 @@ public class MainActivity extends BaseActivity implements FileChooserDialog.File checkPermissions(); registerBackPressHandlers(); handleIntent(getIntent()); + EventBus.getDefault().register(this); } private void registerBackPressHandlers() { @@ -131,21 +134,7 @@ public class MainActivity extends BaseActivity implements FileChooserDialog.File private void setUpDrawerHeader() { TextView version = $(R.id.version); version.setText("Version " + BuildConfig.VERSION_NAME); - String path = Pref.getDrawerHeaderImagePath(); - if (path != null) { - setDrawerHeaderImage(path); - } - path = Pref.getAppBarImagePath(); - if (path != null) { - setAppBarImage(path); - } - } - - private void setDrawerHeaderImage(String path) { - Drawable d = BitmapDrawable.createFromPath(path); - if (d != null) { - ((ImageView) $(R.id.drawer_header_img)).setImageDrawable(d); - } + mDrawerHeaderBackgroundSaver = new DrawableSaver.ImageSaver(this, "drawer_header_background", (ImageView) $(R.id.drawer_header_img)); } private void setUpToolbar() { @@ -156,6 +145,7 @@ public class MainActivity extends BaseActivity implements FileChooserDialog.File R.string.text_drawer_close); drawerToggle.syncState(); mDrawerLayout.addDrawerListener(drawerToggle); + mAppbarBackgroundSaver = new DrawableSaver.ImageSaver(this, "appbar_background", (ImageView) $(R.id.app_bar_bg)); } private void setUpTabViewPager() { @@ -167,6 +157,7 @@ public class MainActivity extends BaseActivity implements FileChooserDialog.File .add(new TaskManagerFragment(), R.string.text_task_manage) .build(); viewPager.setAdapter(mPagerAdapter); + viewPager.setOffscreenPageLimit(mPagerAdapter.getCount()); tabLayout.setupWithViewPager(viewPager); } @@ -187,9 +178,30 @@ public class MainActivity extends BaseActivity implements FileChooserDialog.File @ViewBinding.Click(R.id.import_from_file) private void showFileChooser() { - new FileChooserDialog.Builder(this) - .initialPath(Environment.getExternalStorageDirectory().getPath()) - .extensionsFilter(".js", ".txt") + new ScriptFileChooserDialogBuilder(this) + .scriptProvider(StorageScriptProvider.getExternalStorageProvider()) + .fileCallback(new ScriptFileChooserDialogBuilder.FileCallback() { + @Override + public void onFileSelection(MaterialDialog dialog, ScriptFile file) { + dialog.dismiss(); + StorageScriptProvider.getExternalStorageProvider().clearCacheExceptInitialDirectory(); + getMyScriptListFragment().importFile(file.getPath()); + } + }) + .title(R.string.text_please_choose_file_to_import) + .autoDismiss(false) + .positiveText(R.string.cancel) + .neutralText(R.string.text_refresh) + .onAny(new MaterialDialog.SingleButtonCallback() { + @Override + public void onClick(@NonNull MaterialDialog dialog, @NonNull DialogAction which) { + if (which == DialogAction.POSITIVE) { + dialog.dismiss(); + } else { + StorageScriptProvider.getExternalStorageProvider().refreshAll(); + } + } + }) .show(); } @@ -205,19 +217,7 @@ public class MainActivity extends BaseActivity implements FileChooserDialog.File @ViewBinding.Click(R.id.drawer_header_img) public void selectHeaderImage() { - new ImageSelector(this, mActivityResultIntermediary, new ImageSelector.ImageSelectorCallback() { - @Override - public void onImageSelected(ImageSelector selector, String path) { - setDrawerHeaderImage(path); - Pref.setDrawerHeaderImagePath(path); - mActivityResultIntermediary.removeDelegate(selector); - } - }).select(); - } - - @Override - public void onFileSelection(@NonNull FileChooserDialog dialog, @NonNull File file) { - getMyScriptListFragment().importFile(file.getPath()); + mDrawerHeaderBackgroundSaver.select(this, mActivityResultIntermediary); } @Override @@ -275,7 +275,7 @@ public class MainActivity extends BaseActivity implements FileChooserDialog.File .putExtra(ARGUMENT_PATH, path)); } - private MyScriptListFragment getMyScriptListFragment() { + public MyScriptListFragment getMyScriptListFragment() { return ((MyScriptListFragment) mPagerAdapter.getStoredFragment(0)); } @@ -299,21 +299,20 @@ public class MainActivity extends BaseActivity implements FileChooserDialog.File @ViewBinding.Click(R.id.toolbar) public void OnToolbarClick() { - new ImageSelector(this, mActivityResultIntermediary, new ImageSelector.ImageSelectorCallback() { - @Override - public void onImageSelected(ImageSelector selector, String path) { - Pref.setAppBarImagePath(path); - setAppBarImage(path); - mActivityResultIntermediary.removeDelegate(selector); - } - }).select(); + mAppbarBackgroundSaver.select(this, mActivityResultIntermediary); } - private void setAppBarImage(String path) { - Drawable d = BitmapDrawable.createFromPath(path); - if (d != null) { - ((ImageView) $(R.id.app_bar_bg)).setImageDrawable(d); + @Subscribe + public void onMessageEvent(MessageEvent event) { + if (event.message.equals(MESSAGE_CLEAR_BACKGROUND_SETTINGS)) { + mAppbarBackgroundSaver.reset(); + mDrawerHeaderBackgroundSaver.reset(); } } + @Override + protected void onDestroy() { + super.onDestroy(); + EventBus.getDefault().unregister(this); + } } \ No newline at end of file diff --git a/app/src/main/java/com/stardust/scriptdroid/ui/main/sample_list/SampleScriptListFragment.java b/app/src/main/java/com/stardust/scriptdroid/ui/main/sample_list/SampleScriptListFragment.java index fc48963e..ccfd4ada 100644 --- a/app/src/main/java/com/stardust/scriptdroid/ui/main/sample_list/SampleScriptListFragment.java +++ b/app/src/main/java/com/stardust/scriptdroid/ui/main/sample_list/SampleScriptListFragment.java @@ -9,18 +9,13 @@ import android.view.ViewGroup; import com.afollestad.materialdialogs.MaterialDialog; import com.stardust.app.Fragment; -import com.stardust.scriptdroid.scripts.ScriptFile; -import com.stardust.scriptdroid.scripts.ScriptFileList; -import com.stardust.pio.PFile; import com.stardust.scriptdroid.R; -import com.stardust.scriptdroid.scripts.StorageScriptProvider; import com.stardust.scriptdroid.scripts.sample.Sample; import com.stardust.scriptdroid.scripts.sample.SampleFileManager; import com.stardust.scriptdroid.ui.edit.EditActivity; -import com.stardust.scriptdroid.ui.main.my_script_list.MyScriptListFragment; -import com.stardust.util.MessageEvent; +import com.stardust.scriptdroid.ui.main.MainActivity; -import org.greenrobot.eventbus.EventBus; +import java.io.IOException; /** * Created by Stardust on 2017/3/13. @@ -70,13 +65,12 @@ public class SampleScriptListFragment extends Fragment { } private void copySampleToMyScripts(Sample sample) { - String path = StorageScriptProvider.DEFAULT_DIRECTORY_PATH + sample.name + ".js"; - if (!ScriptFileList.getImpl().containsPath(path) && PFile.copyAsset(getActivity(), sample.path, path)) { - ScriptFileList.getImpl().add(new ScriptFile(path)); - EventBus.getDefault().post(new MessageEvent(MyScriptListFragment.MESSAGE_SCRIPT_FILE_ADDED)); - Snackbar.make(mSampleScriptListRecyclerView, R.string.text_import_succeed, Snackbar.LENGTH_SHORT).show(); - } else { - Snackbar.make(mSampleScriptListRecyclerView, R.string.text_file_exists, Snackbar.LENGTH_SHORT).show(); + try { + ((MainActivity) getActivity()).getMyScriptListFragment(). + importFile(sample.name, getActivity().getAssets().open(sample.path)); + } catch (IOException e) { + e.printStackTrace(); + Snackbar.make(mSampleScriptListRecyclerView, R.string.text_import_fail, Snackbar.LENGTH_SHORT).show(); } } diff --git a/app/src/main/java/com/stardust/scriptdroid/ui/main/my_script_list/MyScriptListFragment.java b/app/src/main/java/com/stardust/scriptdroid/ui/main/script_list/MyScriptListFragment.java similarity index 91% rename from app/src/main/java/com/stardust/scriptdroid/ui/main/my_script_list/MyScriptListFragment.java rename to app/src/main/java/com/stardust/scriptdroid/ui/main/script_list/MyScriptListFragment.java index 5696c58e..f32d73ab 100644 --- a/app/src/main/java/com/stardust/scriptdroid/ui/main/my_script_list/MyScriptListFragment.java +++ b/app/src/main/java/com/stardust/scriptdroid/ui/main/script_list/MyScriptListFragment.java @@ -1,4 +1,4 @@ -package com.stardust.scriptdroid.ui.main.my_script_list; +package com.stardust.scriptdroid.ui.main.script_list; import android.os.Bundle; import android.support.annotation.NonNull; @@ -24,6 +24,9 @@ import com.stardust.view.ViewBinding; import com.stardust.widget.SimpleAdapterDataObserver; import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.InputStream; /** * Created by Stardust on 2017/3/13. @@ -34,8 +37,8 @@ public class MyScriptListFragment extends Fragment { public static final String MESSAGE_SCRIPT_FILE_ADDED = "MESSAGE_SCRIPT_FILE_ADDED"; private ScriptAndFolderListRecyclerView mScriptListRecyclerView; + private ScriptListWithProgressBarView mScriptListWithProgressBarView; private View mNoScriptHint; - private View mProgressBar; private MaterialDialog mScriptFileOperationDialog; private MaterialDialog mDirectoryOperationDialog; private ScriptFile mSelectedScriptFile; @@ -57,14 +60,15 @@ public class MyScriptListFragment extends Fragment { @Override public void onViewCreated(View view, @Nullable Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); - mScriptListRecyclerView = $(R.id.script_list); + mScriptListWithProgressBarView = $(R.id.script_list); + mScriptListRecyclerView = mScriptListWithProgressBarView.getScriptAndFolderListRecyclerView(); mNoScriptHint = $(R.id.hint_no_script); - mProgressBar = $(R.id.progressBar); initScriptListRecyclerView(); initDialogs(); } private void initScriptListRecyclerView() { + mScriptListWithProgressBarView.setStorageScriptProvider(StorageScriptProvider.getDefault()); mScriptListRecyclerView.getAdapter().registerAdapterDataObserver(new SimpleAdapterDataObserver() { @Override public void onSomethingChanged() { @@ -141,14 +145,22 @@ public class MyScriptListFragment extends Fragment { } public void importFile(final String pathFrom) { - showFileNameInputDialog(PFile.getNameWithoutExtension(pathFrom), new MaterialDialog.InputCallback() { + try { + importFile(PFile.getNameWithoutExtension(pathFrom), new FileInputStream(pathFrom)); + } catch (FileNotFoundException e) { + showMessage(R.string.file_not_exists); + } + } + + public void importFile(String prefix, final InputStream inputStream) { + showFileNameInputDialog(PFile.getNameWithoutExtension(prefix), new MaterialDialog.InputCallback() { @Override public void onInput(@NonNull MaterialDialog dialog, CharSequence input) { final String pathTo = getCurrentDirectoryPath() + input + ".js"; new Thread(new Runnable() { @Override public void run() { - if (PFile.copy(pathFrom, pathTo)) { + if (PFile.copyStream(inputStream, pathTo)) { showMessage(R.string.text_import_succeed); } else { showMessage(R.string.text_import_fail); @@ -156,7 +168,6 @@ public class MyScriptListFragment extends Fragment { notifyScriptFileChanged(); } }).start(); - } }); } @@ -237,10 +248,10 @@ public class MyScriptListFragment extends Fragment { private void onScriptFileOperated() { mSelectedScriptFile = null; - mProgressBar.post(new Runnable() { + getView().post(new Runnable() { @Override public void run() { - mProgressBar.setVisibility(View.GONE); + mScriptListWithProgressBarView.hideProgressBar(); } }); } @@ -276,7 +287,7 @@ public class MyScriptListFragment extends Fragment { } private void doDeletingScriptFile() { - mProgressBar.setVisibility(View.VISIBLE); + mScriptListWithProgressBarView.showProgressBar(); new Thread(new Runnable() { @Override public void run() { diff --git a/app/src/main/java/com/stardust/scriptdroid/ui/main/my_script_list/ScriptAndFolderListRecyclerView.java b/app/src/main/java/com/stardust/scriptdroid/ui/main/script_list/ScriptAndFolderListRecyclerView.java similarity index 81% rename from app/src/main/java/com/stardust/scriptdroid/ui/main/my_script_list/ScriptAndFolderListRecyclerView.java rename to app/src/main/java/com/stardust/scriptdroid/ui/main/script_list/ScriptAndFolderListRecyclerView.java index 9270a497..6fccd29a 100644 --- a/app/src/main/java/com/stardust/scriptdroid/ui/main/my_script_list/ScriptAndFolderListRecyclerView.java +++ b/app/src/main/java/com/stardust/scriptdroid/ui/main/script_list/ScriptAndFolderListRecyclerView.java @@ -1,4 +1,4 @@ -package com.stardust.scriptdroid.ui.main.my_script_list; +package com.stardust.scriptdroid.ui.main.script_list; import android.content.Context; import android.os.Bundle; @@ -17,7 +17,6 @@ import android.widget.TextView; import com.stardust.autojs.script.FileScriptSource; import com.stardust.scriptdroid.autojs.AutoJs; import com.stardust.scriptdroid.scripts.ScriptFile; -import com.stardust.scriptdroid.App; import com.stardust.scriptdroid.R; import com.stardust.scriptdroid.scripts.StorageScriptProvider; import com.stardust.scriptdroid.ui.main.operation.ScriptFileOperation; @@ -40,6 +39,13 @@ public class ScriptAndFolderListRecyclerView extends RecyclerView { void onLongClick(ScriptFile file); } + public interface FileProcessListener { + + void onFilesListing(); + + void onFileListed(); + } + private OnScriptFileClickListener mOnItemClickListener; private OnScriptFileLongClickListener mOnItemLongClickListener; private final OnClickListener mOnItemClickListenerProxy = new OnClickListener() { @@ -86,13 +92,14 @@ public class ScriptAndFolderListRecyclerView extends RecyclerView { } }; - private ScriptFile[] mScriptFileList; - + private ScriptFile[] mScriptFileList = new ScriptFile[0]; private ScriptFile mCurrentFolder; private ScriptFile mRootFolder; private Adapter mAdapter; - private boolean mCanGoBack; - private StorageScriptProvider mStorageScriptProvider = StorageScriptProvider.getDefault(); + private boolean mCanGoBack = false; + private StorageScriptProvider mStorageScriptProvider; + private FileProcessListener mFileProcessListener; + private boolean mScriptFileOperationEnabled = true; public ScriptAndFolderListRecyclerView(Context context) { super(context); @@ -109,11 +116,25 @@ public class ScriptAndFolderListRecyclerView extends RecyclerView { init(); } - private void setCurrentFolder(ScriptFile folder, boolean canGoBack) { + private void setCurrentFolder(final ScriptFile folder, boolean canGoBack) { mCurrentFolder = folder; - mScriptFileList = mStorageScriptProvider.getDirectoryScriptFiles(folder); mCanGoBack = canGoBack; - mAdapter.notifyDataSetChanged(); + if (mFileProcessListener != null) { + mFileProcessListener.onFilesListing(); + } + new Thread(new Runnable() { + @Override + public void run() { + mScriptFileList = mStorageScriptProvider.getDirectoryScriptFiles(folder); + post(new Runnable() { + @Override + public void run() { + mFileProcessListener.onFileListed(); + mAdapter.notifyDataSetChanged(); + } + }); + } + }).start(); } private void setRootFolder(ScriptFile folder) { @@ -121,8 +142,16 @@ public class ScriptAndFolderListRecyclerView extends RecyclerView { setCurrentFolder(mRootFolder, false); } + public void setFileProcessListener(FileProcessListener fileProcessListener) { + mFileProcessListener = fileProcessListener; + } + public void setStorageScriptProvider(StorageScriptProvider storageScriptProvider) { + if (mStorageScriptProvider != null) + mStorageScriptProvider.unregisterDirectoryChangeListener(this); mStorageScriptProvider = storageScriptProvider; + mStorageScriptProvider.registerDirectoryChangeListener(this); + setRootFolder(mStorageScriptProvider.getInitialDirectory()); } public void setOnItemClickListener(OnScriptFileClickListener onItemClickListener) { @@ -137,6 +166,10 @@ public class ScriptAndFolderListRecyclerView extends RecyclerView { return mCurrentFolder; } + public void setScriptFileOperationEnabled(boolean scriptFileOperationEnabled) { + mScriptFileOperationEnabled = scriptFileOperationEnabled; + } + private void goBack() { ScriptFile parent = mCurrentFolder.getParentFile(); setCurrentFolder(parent, !parent.equals(mRootFolder)); @@ -147,7 +180,6 @@ public class ScriptAndFolderListRecyclerView extends RecyclerView { addItemDecoration(new DividerItemDecoration(getContext(), VERTICAL)); mAdapter = new Adapter(); setAdapter(mAdapter); - setRootFolder(mStorageScriptProvider.getInitialDirectory()); } @Override @@ -171,7 +203,8 @@ public class ScriptAndFolderListRecyclerView extends RecyclerView { @Override protected void onAttachedToWindow() { super.onAttachedToWindow(); - mStorageScriptProvider.registerDirectoryChangeListener(this); + if (mStorageScriptProvider != null) + mStorageScriptProvider.registerDirectoryChangeListener(this); } @Override @@ -268,8 +301,13 @@ public class ScriptAndFolderListRecyclerView extends RecyclerView { FileViewHolder(View itemView) { super(itemView); - itemView.findViewById(R.id.edit).setOnClickListener(mOnEditClickListener); - itemView.findViewById(R.id.run).setOnClickListener(mOnRunClickListener); + if (mScriptFileOperationEnabled) { + itemView.findViewById(R.id.edit).setOnClickListener(mOnEditClickListener); + itemView.findViewById(R.id.run).setOnClickListener(mOnRunClickListener); + } else { + itemView.findViewById(R.id.edit).setVisibility(GONE); + itemView.findViewById(R.id.run).setVisibility(GONE); + } } } diff --git a/app/src/main/java/com/stardust/scriptdroid/ui/main/script_list/ScriptFileChooserDialogBuilder.java b/app/src/main/java/com/stardust/scriptdroid/ui/main/script_list/ScriptFileChooserDialogBuilder.java new file mode 100644 index 00000000..52e0c173 --- /dev/null +++ b/app/src/main/java/com/stardust/scriptdroid/ui/main/script_list/ScriptFileChooserDialogBuilder.java @@ -0,0 +1,63 @@ +package com.stardust.scriptdroid.ui.main.script_list; + +import android.content.Context; +import android.support.annotation.NonNull; + +import com.afollestad.materialdialogs.MaterialDialog; +import com.stardust.scriptdroid.scripts.ScriptFile; +import com.stardust.scriptdroid.scripts.StorageScriptProvider; + +/** + * Created by Stardust on 2017/4/3. + */ + +public class ScriptFileChooserDialogBuilder extends MaterialDialog.Builder { + + public interface FileCallback { + void onFileSelection(MaterialDialog dialog, ScriptFile file); + } + + private ScriptListWithProgressBarView mScriptListWithProgressBarView; + private StorageScriptProvider mStorageScriptProvider; + private FileCallback mFileCallback; + + public ScriptFileChooserDialogBuilder(@NonNull Context context) { + super(context); + mScriptListWithProgressBarView = new ScriptListWithProgressBarView(context); + mScriptListWithProgressBarView.setMinimumHeight(800); + mScriptListWithProgressBarView.setScriptFileOperationEnabled(false); + } + + public ScriptFileChooserDialogBuilder fileCallback(final FileCallback fileCallback) { + mFileCallback = fileCallback; + return this; + } + + public ScriptFileChooserDialogBuilder initialPath(String initialPath) { + mStorageScriptProvider = new StorageScriptProvider(initialPath, 5); + return this; + } + + public ScriptFileChooserDialogBuilder scriptProvider(StorageScriptProvider storageScriptProvider) { + mStorageScriptProvider = storageScriptProvider; + return this; + } + + public MaterialDialog build() { + if (mStorageScriptProvider != null) { + mScriptListWithProgressBarView.setStorageScriptProvider(mStorageScriptProvider); + } + customView(mScriptListWithProgressBarView, false); + final MaterialDialog dialog = super.build(); + if (mFileCallback != null) { + mScriptListWithProgressBarView.setOnItemClickListener(new ScriptAndFolderListRecyclerView.OnScriptFileClickListener() { + @Override + public void onClick(ScriptFile file) { + mFileCallback.onFileSelection(dialog, file); + } + }); + } + return dialog; + } + +} diff --git a/app/src/main/java/com/stardust/scriptdroid/ui/main/my_script_list/ScriptListRecyclerView.java b/app/src/main/java/com/stardust/scriptdroid/ui/main/script_list/ScriptListRecyclerView.java similarity index 98% rename from app/src/main/java/com/stardust/scriptdroid/ui/main/my_script_list/ScriptListRecyclerView.java rename to app/src/main/java/com/stardust/scriptdroid/ui/main/script_list/ScriptListRecyclerView.java index fb3a15ed..cb1280e2 100644 --- a/app/src/main/java/com/stardust/scriptdroid/ui/main/my_script_list/ScriptListRecyclerView.java +++ b/app/src/main/java/com/stardust/scriptdroid/ui/main/script_list/ScriptListRecyclerView.java @@ -1,4 +1,4 @@ -package com.stardust.scriptdroid.ui.main.my_script_list; +package com.stardust.scriptdroid.ui.main.script_list; import android.content.Context; import android.support.annotation.Nullable; diff --git a/app/src/main/java/com/stardust/scriptdroid/ui/main/script_list/ScriptListWithProgressBarView.java b/app/src/main/java/com/stardust/scriptdroid/ui/main/script_list/ScriptListWithProgressBarView.java new file mode 100644 index 00000000..dc4cdddd --- /dev/null +++ b/app/src/main/java/com/stardust/scriptdroid/ui/main/script_list/ScriptListWithProgressBarView.java @@ -0,0 +1,92 @@ +package com.stardust.scriptdroid.ui.main.script_list; + +import android.content.Context; +import android.os.Build; +import android.support.annotation.AttrRes; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.annotation.RequiresApi; +import android.support.annotation.StyleRes; +import android.util.AttributeSet; +import android.view.View; +import android.widget.FrameLayout; + +import com.stardust.scriptdroid.R; +import com.stardust.scriptdroid.scripts.StorageScriptProvider; + +/** + * Created by Stardust on 2017/4/3. + */ + +public class ScriptListWithProgressBarView extends FrameLayout { + + private View mProgressBar; + private ScriptAndFolderListRecyclerView mScriptAndFolderListRecyclerView; + + public ScriptListWithProgressBarView(@NonNull Context context) { + super(context); + init(); + } + + public ScriptListWithProgressBarView(@NonNull Context context, @Nullable AttributeSet attrs) { + super(context, attrs); + init(); + } + + public ScriptListWithProgressBarView(@NonNull Context context, @Nullable AttributeSet attrs, @AttrRes int defStyleAttr) { + super(context, attrs, defStyleAttr); + init(); + } + + @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) + public ScriptListWithProgressBarView(@NonNull Context context, @Nullable AttributeSet attrs, @AttrRes int defStyleAttr, @StyleRes int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); + init(); + } + + private void init() { + inflate(getContext(), R.layout.script_and_folder_list_view, this); + mProgressBar = findViewById(R.id.progressBar); + mScriptAndFolderListRecyclerView = (ScriptAndFolderListRecyclerView) findViewById(R.id.script_list_recycler_view); + mScriptAndFolderListRecyclerView.setFileProcessListener(new ScriptAndFolderListRecyclerView.FileProcessListener() { + @Override + public void onFilesListing() { + showProgressBar(); + } + + @Override + public void onFileListed() { + hideProgressBar(); + } + }); + } + + + public ScriptAndFolderListRecyclerView getScriptAndFolderListRecyclerView() { + return mScriptAndFolderListRecyclerView; + } + + + public void setStorageScriptProvider(StorageScriptProvider storageScriptProvider) { + mScriptAndFolderListRecyclerView.setStorageScriptProvider(storageScriptProvider); + } + + public void setOnItemClickListener(ScriptAndFolderListRecyclerView.OnScriptFileClickListener onItemClickListener) { + mScriptAndFolderListRecyclerView.setOnItemClickListener(onItemClickListener); + } + + public void setScriptFileOperationEnabled(boolean enabled) { + mScriptAndFolderListRecyclerView.setScriptFileOperationEnabled(enabled); + } + + + public void showProgressBar() { + mProgressBar.setVisibility(VISIBLE); + } + + public void hideProgressBar() { + mProgressBar.setVisibility(GONE); + } + + +} diff --git a/app/src/main/java/com/stardust/scriptdroid/ui/settings/SettingsActivity.java b/app/src/main/java/com/stardust/scriptdroid/ui/settings/SettingsActivity.java index 1bba10e8..e2a81707 100644 --- a/app/src/main/java/com/stardust/scriptdroid/ui/settings/SettingsActivity.java +++ b/app/src/main/java/com/stardust/scriptdroid/ui/settings/SettingsActivity.java @@ -12,12 +12,16 @@ import android.widget.Toast; import com.stardust.scriptdroid.Pref; import com.stardust.scriptdroid.ui.BaseActivity; -import com.stardust.scriptdroid.ui.error.IssueReportActivity; +import com.stardust.scriptdroid.ui.error.IssueReporterActivity; +import com.stardust.scriptdroid.ui.main.MainActivity; import com.stardust.util.IntentUtil; import com.stardust.util.MapEntries; import com.stardust.scriptdroid.R; import com.stardust.theme.app.ColorSelectActivity; import com.stardust.theme.util.ListBuilder; +import com.stardust.util.MessageEvent; + +import org.greenrobot.eventbus.EventBus; import java.util.List; import java.util.Map; @@ -108,15 +112,13 @@ public class SettingsActivity extends BaseActivity { .entry(getString(R.string.text_reset_background), new Runnable() { @Override public void run() { - Pref.setAppBarImagePath(null); - Pref.setDrawerHeaderImagePath(null); - Toast.makeText(getActivity(), R.string.text_restart_app_to_apply, Toast.LENGTH_SHORT).show(); + EventBus.getDefault().post(new MessageEvent(MainActivity.MESSAGE_CLEAR_BACKGROUND_SETTINGS)); } }) .entry(getString(R.string.text_issue_report), new Runnable() { @Override public void run() { - startActivity(new Intent(getActivity(), IssueReportActivity.class).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)); + startActivity(new Intent(getActivity(), IssueReporterActivity.class).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)); } }) .entry(getString(R.string.text_join_qq_group), new Runnable() { diff --git a/app/src/main/res/layout/activity_error_report.xml b/app/src/main/res/layout/activity_error_report.xml index 29c80fe8..993b20d6 100644 --- a/app/src/main/res/layout/activity_error_report.xml +++ b/app/src/main/res/layout/activity_error_report.xml @@ -11,14 +11,14 @@ android:layout_height="wrap_content" android:theme="@style/AppTheme.AppBarOverlay"> - - + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/drawer.xml b/app/src/main/res/layout/drawer.xml index a217793f..ce5598df 100644 --- a/app/src/main/res/layout/drawer.xml +++ b/app/src/main/res/layout/drawer.xml @@ -63,6 +63,7 @@ - @@ -22,19 +21,4 @@ android:textSize="16sp" android:visibility="gone"/> - - - - - \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_scripts.xml b/app/src/main/res/layout/fragment_scripts.xml index 065023e1..20c00d1c 100644 --- a/app/src/main/res/layout/fragment_scripts.xml +++ b/app/src/main/res/layout/fragment_scripts.xml @@ -16,9 +16,6 @@ android:id="@+id/tab" android:layout_width="match_parent" android:layout_height="48dp" - android:elevation="10dp" - app:elevation="10dp" - android:outlineProvider="bounds" app:tabGravity="fill" app:tabIndicatorColor="@color/colorAccent" app:tabMode="fixed" diff --git a/app/src/main/res/layout/script_and_folder_list_view.xml b/app/src/main/res/layout/script_and_folder_list_view.xml new file mode 100644 index 00000000..2ed3a978 --- /dev/null +++ b/app/src/main/res/layout/script_and_folder_list_view.xml @@ -0,0 +1,26 @@ + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/raw/document.md b/app/src/main/res/raw/document.md deleted file mode 100644 index 44da54f3..00000000 --- a/app/src/main/res/raw/document.md +++ /dev/null @@ -1,85 +0,0 @@ -### 一、语法 -本软件使用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, i)` 点击第i个文本text所在的区域,并返回是否点击成功。i从0开始,当不指定时点击所有区域。当前界面没有出现该文本或者该文本所在区域不可点击时返回false。例如`click("发现")`,是点击"发现"。如果要点击"发现"直至点击成功可以用`while(!click("发现"))`。 -> 文本所在区域指的是,从文本处向其父视图寻找,直至发现一个可点击的部件为止。 -* `click(left, top, bottom, right)` -> 有些按钮或者部件是图标而不是文字(例如发送朋友圈的照相机图标以及QQ下方的消息、联系人、动态图标),这时不能通过`click(text)`来点击,只能通过描述图标所在的区域来点击。left, bottom, top, right描述的就是点击的区域。至于要定位点击的区域,可以在侧拉菜单开启"点击区域辅助"(或者安卓7.0以上在通知栏点击"修改"添加点击区域辅助快捷设定图标),之后每次点击或长按都会提示这次点击或长按的区域并自动保存,可以在编辑器的右侧拉菜单中插入。 - - 点击与长方形范围严格匹配的区域,并返回是否点击成功。其中left为长方形左边与屏幕左边的像素距离,top为上边与屏幕上边的像素距离,right为右边与**屏幕左边**的像素距离, bottom为下边与**屏幕上边**的距离。区域严格匹配。 -> 以下的longClick、select、scrollUp、scrollDown的参数均与click类似,不再赘述。 -* `longClick` 长按 -* `select` 选择 -* `scrollUp` 上滑。不加参数时会寻找"最大"的可滑动的控件上滑,例如微信消息列表等; 参数为一个整数i时会找到第i个可滑动控件滑动。 -* `scrollDown` 下滑。不加参数时与scrollUp类似。 -* `input(text)` 把所有输入框的文本都置为text。例如input("测试")。 -* `input(i, text)` 把第i个输入框的文本设为text。i从0开始。 -* `addAccessibilityDelegate(function)` 增加一个AccessibilityEvent处理代理。这可以实现很多强大的功能,但需要熟悉安卓的AccessibilityEvent。例如: -``` -addAccessibilityDelegate(function(service, event){ - //do something -}); -``` -### 三、其他函数 - -* `sleep(n)` 暂停执行n**毫秒**时间。 -* `toast(text)` 显示提示文本。 -* `launch(packageName)` 运行包名为packageName的应用。例如launch("com.tencent.mm")是运行微信,应用包名可以通过一些工具获取;获取通过函数launchApp代替 -* `launch(packageName, className)` 运行包名为packageName,类名(Activity)为className的应用。 -* `launchApp(appName)` 运存应用名称为appName的应用。例如launchApp("QQ")。不同应用名称可能相同,这时只运行其中某一个应用。 -* `notStopped` 若当前脚本处于运行状态时返回`true`, 否则返回`false`。对于某些循环, 例如`while(true)`,请用`while(notStopped())`代替,以免死循环造成的脚本无法正常停止。 -* `isStoppd` 若当前脚本处于停止状态时返回`true`, 否则返回`false`。 -* `shell(cmd, root=false)` 执行shell命令cmd, 其中参数root表示是否以root权限执行,默认为false。例如`shell("input keyevent 26", true); //锁屏`。 -* `getTexts()` 获取屏幕上的文字列表, 返回一个java.util.List。例如: -* `getPackageName` 获取正在运行的应用包名 -* `getActivityName` 获取正在运行的Activity名称 -* `setClip(text)` 设置剪贴板文本 -``` -launchApp("微信"); -while(!click("通讯录")); -var texts = getTexts(); -for(var i = 0; i < texts.size(); i++){ - log(texts.get(i)); -} -openConsole(); -``` - -### 四、全局变量 -* `context` ApplicationContext,参见安卓[android.content.Context](https://developer.android.com/reference/android/content/Context.html) -> 这里的context由于是ApplicationContext,是不可见的,不能用于dialog和其他UI相关。如果要显示弹窗或者视图,请启动UI模式(代码的第一行为`"ui";`既可)并使用activity代替。 -* `activity` UI模式下会启动一个Activity来运行脚本,并在UI线程下运行。可通过该变量来获取该activity。 -### 五、控制台 -* `openConsole()` 打开控制台。 -* `clearConsole()` 清空控制台。 -* `log(text)` 在控制台中输出日志,以例如`log("Hello world");`。 -* `err(text)` 在控制台中输出错误信息,以红色字体显示,例如: -``` -try{ - //do something -}catch(e){ - err("错误); - err(e); - openConsole(); -} -``` -### 六、运行模式 -* UI模式 在脚本第一行加上`"ui;"`。UI模式下会启动一个Activity来运行脚本,并在UI线程下运行。 -* 驻留模式 在脚本第一行加上`"stay;"`,驻留模式下代码运行结束后不会马上退出,只有"关闭所有正在运行的脚本"或者调用`stop()`才会停止。适用于诸如addAccessibilityDelegate的代理后台运行。 -### 七、在脚本中调用Java -使用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/values-en/strings.xml b/app/src/main/res/values-en/strings.xml index 13a978b2..17069fde 100644 --- a/app/src/main/res/values-en/strings.xml +++ b/app/src/main/res/values-en/strings.xml @@ -155,6 +155,8 @@ If the app responses slowly when running script, choose Thread Pool or Create Thread. But it may causes rebooting of the device ON OFF + 请选择要导入的脚本 + 刷新 None Volume Up diff --git a/app/src/main/res/values-zh/strings.xml b/app/src/main/res/values-zh/strings.xml index 32f19478..64f49526 100644 --- a/app/src/main/res/values-zh/strings.xml +++ b/app/src/main/res/values-zh/strings.xml @@ -29,7 +29,7 @@ Issues不可用,请联系软件开发者. 未知错误 OK - + 描述至少要 %d 个字. 描述至少要 %d 个字. diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 575b1886..4b2a0232 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -185,6 +185,8 @@ 导入失败 确定要删除吗 没有浏览器耶o(╯□╰)o快去安装一个吧 + 请选择要导入的脚本 + 刷新 diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml index 26ac379e..23afb3e1 100644 --- a/app/src/main/res/values/styles.xml +++ b/app/src/main/res/values/styles.xml @@ -1,11 +1,10 @@ - -