diff --git a/build.gradle b/build.gradle index d576ffd4..3d780546 100644 --- a/build.gradle +++ b/build.gradle @@ -11,7 +11,6 @@ buildscript { jcenter() google() mavenCentral() - maven { url "https://plugins.gradle.org/m2/" } maven { url "https://maven.google.com" } } diff --git a/local.properties b/local.properties index 4929a5a2..2bf3c26a 100644 --- a/local.properties +++ b/local.properties @@ -4,6 +4,10 @@ # Location of the SDK. This is only used by Gradle. # For customization when using a Version Control System, please read the # header note. -#Thu Aug 02 10:23:49 CST 2018 -ndk.dir=/Users/yanhecun/Library/Android/sdk/ndk-bundle -sdk.dir=/Users/yanhecun/Library/Android/sdk +#Mon Apr 13 14:53:44 CST 2020 +#ndk.dir=/Users/yanhecun/Library/Android/sdk/ndk-bundle +#ndk.dir=D\:\\android-sdk-windows\\ndk\\20.1.5948944 +#sdk.dir=D\:\\android-sdk-windows + +sdk.dir=/Users/river/Library/Android/sdk +ndk.dir=/Users/river/Library/Android/sdk/ndk/21.3.6528147 diff --git a/qpysdk/src/main/libs/armeabi-v7a/libSDL2.so b/qpysdk/src/main/libs/armeabi-v7a/libSDL2.so deleted file mode 100755 index 5338e91e..00000000 Binary files a/qpysdk/src/main/libs/armeabi-v7a/libSDL2.so and /dev/null differ diff --git a/qpysdk/src/main/libs/armeabi-v7a/libSDL2_gfx.so b/qpysdk/src/main/libs/armeabi-v7a/libSDL2_gfx.so deleted file mode 100755 index 80d01e32..00000000 Binary files a/qpysdk/src/main/libs/armeabi-v7a/libSDL2_gfx.so and /dev/null differ diff --git a/qpysdk/src/main/libs/armeabi-v7a/libSDL2_image.so b/qpysdk/src/main/libs/armeabi-v7a/libSDL2_image.so deleted file mode 100755 index 03484635..00000000 Binary files a/qpysdk/src/main/libs/armeabi-v7a/libSDL2_image.so and /dev/null differ diff --git a/qpysdk/src/main/libs/armeabi-v7a/libSDL2_mixer.so b/qpysdk/src/main/libs/armeabi-v7a/libSDL2_mixer.so deleted file mode 100755 index c48ebb56..00000000 Binary files a/qpysdk/src/main/libs/armeabi-v7a/libSDL2_mixer.so and /dev/null differ diff --git a/qpysdk/src/main/libs/armeabi-v7a/libSDL2_ttf.so b/qpysdk/src/main/libs/armeabi-v7a/libSDL2_ttf.so deleted file mode 100755 index 98aeea15..00000000 Binary files a/qpysdk/src/main/libs/armeabi-v7a/libSDL2_ttf.so and /dev/null differ diff --git a/qpysdk/src/main/libs/armeabi-v7a/libmain.so b/qpysdk/src/main/libs/armeabi-v7a/libmain.so deleted file mode 100755 index 44643b21..00000000 Binary files a/qpysdk/src/main/libs/armeabi-v7a/libmain.so and /dev/null differ diff --git a/qpysdk/src/main/libs/armeabi-v7a/libpng16.so b/qpysdk/src/main/libs/armeabi-v7a/libpng16.so deleted file mode 100755 index a16e3512..00000000 Binary files a/qpysdk/src/main/libs/armeabi-v7a/libpng16.so and /dev/null differ diff --git a/qpysdk/src/main/libs/armeabi-v7a/libpython2.7.so b/qpysdk/src/main/libs/armeabi-v7a/libpython2.7.so deleted file mode 100755 index 4b3fbaa5..00000000 Binary files a/qpysdk/src/main/libs/armeabi-v7a/libpython2.7.so and /dev/null differ diff --git a/qpysdk/src/main/libs/armeabi-v7a/libqpysdk.so b/qpysdk/src/main/libs/armeabi-v7a/libqpysdk.so deleted file mode 100755 index 1b189d79..00000000 Binary files a/qpysdk/src/main/libs/armeabi-v7a/libqpysdk.so and /dev/null differ diff --git a/qpython/build.gradle b/qpython/build.gradle index 738e1a9e..f54574d5 100644 --- a/qpython/build.gradle +++ b/qpython/build.gradle @@ -14,8 +14,8 @@ android { defaultConfig { minSdkVersion rootProject.ext.minSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion - versionCode 304 - versionName "3.0.0" + versionCode 310 + versionName "3.1.0" multiDexEnabled true vectorDrawables.useSupportLibrary = true @@ -68,7 +68,12 @@ android { } debug { - signingConfig signingConfigs.release +// signingConfig signingConfigs.release + debuggable true + minifyEnabled false + jniDebuggable true + signingConfig signingConfigs.debug + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } } @@ -162,6 +167,9 @@ dependencies { api 'org.litepal.android:core:1.3.1' api 'me.dm7.barcodescanner:zxing:1.9' api 'com.android.support:multidex:1.0.1' + implementation ('com.gyf.cactus:cactus-support:1.1.3-beta09'){ + exclude group: 'com.google.guava' + } api rootProject.ext.libOkHttp3 api rootProject.ext.libOkHttp3Log @@ -176,6 +184,12 @@ dependencies { api rootProject.ext.libSupportCardView api rootProject.ext.libSupportPreference + odApi rootProject.ext.firebaseCore + odApi rootProject.ext.firebaseMsg + odApi rootProject.ext.firebaseAuth + odApi rootProject.ext.firebaseDatabase + odApi rootProject.ext.googlePlayServiceAuth + osApi rootProject.ext.firebaseCore osApi rootProject.ext.firebaseMsg osApi rootProject.ext.firebaseAuth @@ -192,7 +206,7 @@ dependencies { api rootProject.ext.retrofitCoverterGson api rootProject.ext.retrofitAdapterRxjava - api 'com.android.support.constraint:constraint-layout:1.0.2' + api 'com.android.support.constraint:constraint-layout:1.1.3' // 微信 opApi('com.tencent.mm.opensdk:wechat-sdk-android-with-mta:1.4.0') { diff --git a/qpython/src/main/AndroidManifest.xml b/qpython/src/main/AndroidManifest.xml index db2647f9..9a4f5b38 100644 --- a/qpython/src/main/AndroidManifest.xml +++ b/qpython/src/main/AndroidManifest.xml @@ -19,8 +19,6 @@ - - @@ -35,6 +33,22 @@ + + + + + + + + + + + + + + + + @@ -68,6 +82,8 @@ + + diff --git a/qpython/src/main/java/org/qpython/qpy/console/shortcuts/ShortcutReceiver.java b/qpython/src/main/java/org/qpython/qpy/console/shortcuts/ShortcutReceiver.java new file mode 100644 index 00000000..848230e7 --- /dev/null +++ b/qpython/src/main/java/org/qpython/qpy/console/shortcuts/ShortcutReceiver.java @@ -0,0 +1,16 @@ +package org.qpython.qpy.console.shortcuts; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; + +import org.qpython.qsl4a.qsl4a.LogUtil; + +public class ShortcutReceiver extends BroadcastReceiver { + + + @Override + public void onReceive(Context context, Intent intent) { + LogUtil.e("111111111" + intent); + } +} diff --git a/qpython/src/main/java/org/qpython/qpy/main/activity/AppListActivity.java b/qpython/src/main/java/org/qpython/qpy/main/activity/AppListActivity.java index e964f622..a5f9dbe6 100644 --- a/qpython/src/main/java/org/qpython/qpy/main/activity/AppListActivity.java +++ b/qpython/src/main/java/org/qpython/qpy/main/activity/AppListActivity.java @@ -1,18 +1,31 @@ package org.qpython.qpy.main.activity; -import android.app.Activity; +import android.Manifest; +import android.app.ActivityManager; import android.app.AlertDialog; import android.app.LoaderManager; +import android.app.PendingIntent; import android.content.Context; import android.content.Intent; +import android.content.IntentFilter; import android.content.Loader; +import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; +import android.content.pm.ShortcutInfo; +import android.content.pm.ShortcutManager; +import android.graphics.drawable.Icon; +import android.os.Build; import android.os.Bundle; +import android.os.Handler; +import android.support.annotation.NonNull; import android.support.annotation.Nullable; -import android.support.v7.app.AppCompatActivity; +import android.support.v4.app.ActivityCompat; +import android.support.v4.content.ContextCompat; import android.support.v7.widget.GridLayoutManager; import android.support.v7.widget.LinearLayoutManager; import android.support.v7.widget.RecyclerView; import android.widget.TextView; +import android.widget.Toast; import com.quseit.util.FileHelper; import com.quseit.util.FolderUtils; @@ -23,12 +36,15 @@ import org.greenrobot.eventbus.ThreadMode; import org.qpython.qpy.R; import org.qpython.qpy.console.ScriptExec; +import org.qpython.qpy.console.shortcuts.ShortcutReceiver; import org.qpython.qpy.main.adapter.AppListAdapter; import org.qpython.qpy.main.event.AppsLoader; import org.qpython.qpy.main.model.AppModel; import org.qpython.qpy.main.model.QPyScriptModel; +import org.qpython.qpy.utils.ShortcutUtil; import org.qpython.qpysdk.QPyConstants; import org.qpython.qpysdk.utils.Utils; +import org.qpython.qsl4a.qsl4a.LogUtil; import java.io.File; import java.io.IOException; @@ -36,6 +52,8 @@ import java.util.Arrays; import java.util.List; +import static org.qpython.qpy.R2.string.show; + /** * Local App list * Created by Hmei on 2017-05-22. @@ -43,10 +61,13 @@ public class AppListActivity extends BaseActivity implements LoaderManager.LoaderCallbacks> { public static final String TYPE_SCRIPT = "script"; + private static final int REQUEST_INSTALL_SHORTCUT = 0; private List dataList; private AppListAdapter adapter; + ShortcutReceiver receiver; + public static void start(Context context, String type) { Intent starter = new Intent(context, AppListActivity.class); starter.putExtra("type", type); @@ -56,12 +77,25 @@ public static void start(Context context, String type) { @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); + initShortcutReceiver(); runShortcut(); setContentView(R.layout.activity_local_app); initView(); EventBus.getDefault().register(this); } + private void initShortcutReceiver() { + IntentFilter filter = new IntentFilter(); + filter.addAction(Intent.ACTION_CREATE_SHORTCUT); + filter.addAction("com.android.launcher.action.INSTALL_SHORTCUT"); + filter.addAction("android.content.pm.action.CONFIRM_PIN_SHORTCUT"); + filter.addAction(Intent.ACTION_VIEW); + + receiver = new ShortcutReceiver(); + registerReceiver(receiver,filter); + + } + @Override protected void onResume() { super.onResume(); @@ -74,6 +108,9 @@ protected void onResume() { @Override protected void onDestroy() { super.onDestroy(); + if (receiver != null){ + unregisterReceiver(receiver); + } EventBus.getDefault().unregister(this); } @@ -90,6 +127,7 @@ private void runShortcut() { } } + QPyScriptModel mBean; private void initView() { dataList = new ArrayList<>(); adapter = new AppListAdapter(dataList, getIntent().getStringExtra("type"), this); @@ -104,6 +142,16 @@ public void runProject(QPyScriptModel item) { ScriptExec.getInstance().playProject(AppListActivity.this, item.getPath(), false); } + @Override + public void createShortcut(QPyScriptModel item) { + mBean = item; +// if (!checkPermission()){ +// return; +// } + createShortcutOnThis(); +// test(); + } + @Override public void exit() { AppListActivity.this.finish(); @@ -116,9 +164,113 @@ public void exit() { appsView.setAdapter(adapter); ((TextView) findViewById(R.id.tv_folder_name)).setText(R.string.qpy_app); + findViewById(R.id.iv_back).setOnClickListener(view -> AppListActivity.this.finish()); + getScriptList(); } +// private boolean checkPermission() { +// if (Build.VERSION.SDK_INT >= 23) { +// int checkPermission = ContextCompat.checkSelfPermission(this, Manifest.permission.INSTALL_SHORTCUT); +// LogUtil.e("checkPermission" + checkPermission); +// LogUtil.e("checkPermission" + PackageManager.PERMISSION_GRANTED); +// if (checkPermission != PackageManager.PERMISSION_GRANTED) { +// ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.INSTALL_SHORTCUT}, REQUEST_INSTALL_SHORTCUT); +// return false; +// } else { +// return true; +// } +// } else { +// return true; +// } +// } + +// @Override +// public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { +// if (requestCode == REQUEST_INSTALL_SHORTCUT) { +// if (grantResults[0] != PackageManager.PERMISSION_GRANTED) { +// Toast.makeText(this, R.string.toast_read_permission_deny, Toast.LENGTH_SHORT).show(); +// } else { +// createShortcutOnThis(); +// } +// } +// } + + private void test(){ + judgeShortcutNameV2("org.qpython.qpy"); + } + + private void createShortcutOnThis(){ + if (mBean == null){ + return; + } + + Intent intent = new Intent(); + intent.setClass(this, AppListActivity.class); + intent.setAction(Intent.ACTION_VIEW); + intent.putExtra("type", "script"); + intent.putExtra("path", mBean.getPath()); + intent.putExtra("isProj", mBean.isProj()); + + if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + ShortcutManager mShortcutManager = getSystemService(ShortcutManager.class); + if (mShortcutManager.isRequestPinShortcutSupported()) { + ShortcutInfo pinShortcutInfo = + new ShortcutInfo.Builder(this, mBean.getLabel()) + .setShortLabel(mBean.getLabel()) + .setLongLabel(mBean.getLabel()) + .setIcon(Icon.createWithResource(this, mBean.getIconRes())) + .setIntent(intent) + .build(); + Intent pinnedShortcutCallbackIntent = + mShortcutManager.createShortcutResultIntent(pinShortcutInfo); + PendingIntent successCallback = PendingIntent.getBroadcast(this, 0, + pinnedShortcutCallbackIntent, 0); + LogUtil.e("createShortcut: " + "111111111111"); + mShortcutManager.requestPinShortcut(pinShortcutInfo, + successCallback.getIntentSender()); + LogUtil.e("createShortcut: " + mBean.getLabel()); + new Handler().postDelayed(new Runnable() { + @Override + public void run() { + judgeShortcutNameV2("org.qpython.qpy"); +// judgeShortcutName(mBean.getLabel()); + } + },200); + } + } else { + //Adding shortcut for MainActivity + //on Home screen + Intent addIntent = new Intent(); + addIntent.putExtra(Intent.EXTRA_SHORTCUT_INTENT, intent); + addIntent.putExtra(Intent.EXTRA_SHORTCUT_NAME, mBean.getLabel()); + addIntent.putExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE, + Intent.ShortcutIconResource.fromContext(getApplicationContext(), + mBean.getIconRes())); + addIntent.setAction("com.android.launcher.action.INSTALL_SHORTCUT"); + getApplicationContext().sendBroadcast(addIntent); + Toast.makeText(this, getString(R.string.shortcut_create_suc, mBean.getLabel()), Toast.LENGTH_SHORT).show(); + } + } + +// private void judgeShortcutName(String name) { +// int shortcutNum = 0; +// for (String packageName : ShortcutUtil.getAllTheLauncher(getApplicationContext())) { +// LogUtil.e("packageName111111: " + packageName); +// if (name.equals(packageName)) { +// shortcutNum ++; +// } +// } +// LogUtil.e("packageName222222: " + shortcutNum); +// } + + private void judgeShortcutNameV2(String name) { + if (!ShortcutUtil.getShortcutInfo(getApplicationContext()).isEmpty()){ + return; + } + Toast.makeText(this, getString(R.string.shortcut_create_fail), Toast.LENGTH_SHORT).show(); + } + private void getScriptList() { try { diff --git a/qpython/src/main/java/org/qpython/qpy/main/activity/HomeMainActivity.java b/qpython/src/main/java/org/qpython/qpy/main/activity/HomeMainActivity.java index c058e0cd..e605741a 100644 --- a/qpython/src/main/java/org/qpython/qpy/main/activity/HomeMainActivity.java +++ b/qpython/src/main/java/org/qpython/qpy/main/activity/HomeMainActivity.java @@ -19,6 +19,7 @@ import android.view.View; import android.widget.Toast; +import com.gyf.cactus.Cactus; import com.quseit.util.NAction; import com.quseit.util.Utils; @@ -58,6 +59,7 @@ public class HomeMainActivity extends BaseActivity { private static final int LOGIN_REQUEST_CODE = 136; private ActivityMainBinding binding; + private SharedPreferences preferences; public static void start(Context context) { Intent starter = new Intent(context, HomeMainActivity.class); @@ -73,6 +75,7 @@ public static void start(Context context, String userName) { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); + preferences = PreferenceManager.getDefaultSharedPreferences(this); binding = DataBindingUtil.setContentView(this, R.layout.activity_main); //App.setActivity(this); startMain(); @@ -88,6 +91,8 @@ private void initIcon() { case "2.x": binding.icon.setImageResource(R.drawable.img_home_logo); break; + default: + break; } } @@ -101,7 +106,7 @@ private void initUser() { private void startMain() { initListener(); - startPyService(); +// startPyService(); Bus.getDefault().register(this); init(); if (ActivityCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED) { @@ -146,6 +151,8 @@ private void initListener() { .setTitle(R.string.choose_action) .setItems(chars, (dialog, which) -> { switch (which) { + default: + break; case 0: // Create Shortcut TermActivity.startActivity(HomeMainActivity.this); break; @@ -210,6 +217,11 @@ public void onClick(DialogInterface dialogInterface, int i) { protected void onDestroy() { super.onDestroy(); Bus.getDefault().unregister(this); + boolean isKeepAlive = preferences.getBoolean(getString(R.string.key_alive), false); + if (!isKeepAlive){ + return; + } + Cactus.getInstance().unregister(this); } private void handlePython3(Intent intent) { @@ -227,7 +239,7 @@ private void handlePython3(Intent intent) { } private void handleNotification(Bundle bundle) { - if (bundle == null) return; + if (bundle == null) {return;} if (!bundle.getBoolean("force") && !PreferenceManager.getDefaultSharedPreferences(this).getBoolean(getString(R.string.key_hide_push), true)) { return; } @@ -244,6 +256,7 @@ private void handleNotification(Bundle bundle) { Intent starter = new Intent(Intent.ACTION_VIEW, Uri.parse(link)); startActivity(starter); break; + default:break; } } } @@ -270,6 +283,7 @@ private void handleNotification() { Intent starter = new Intent(Intent.ACTION_VIEW, Uri.parse(link)); startActivity(starter); break; + default:break; } sharedPreferences.edit().clear().apply(); } catch (JSONException e) { @@ -310,11 +324,11 @@ protected void onPause() { super.onPause(); } - private void startPyService() { - Log.d(TAG, "startPyService"); - Intent intent = new Intent(this, QPyScriptService.class); - startService(intent); - } +// private void startPyService() { +// Log.d(TAG, "startPyService"); +// Intent intent = new Intent(this, QPyScriptService.class); +// startService(intent); +// } private void openQpySDK() { Log.d("HomeMainActivity", "openQpySDK"); @@ -470,4 +484,5 @@ public static class StartQrCodeActivityEvent { private void sendEvent(String evenName) { } + } diff --git a/qpython/src/main/java/org/qpython/qpy/main/activity/NotebookActivity.java b/qpython/src/main/java/org/qpython/qpy/main/activity/NotebookActivity.java index 38dbfe29..88f6b2b0 100644 --- a/qpython/src/main/java/org/qpython/qpy/main/activity/NotebookActivity.java +++ b/qpython/src/main/java/org/qpython/qpy/main/activity/NotebookActivity.java @@ -436,6 +436,7 @@ public boolean OnClickListener(String name) { } } + @Override public void toast(String msg) { Toast.makeText(this, msg, Toast.LENGTH_SHORT).show(); } diff --git a/qpython/src/main/java/org/qpython/qpy/main/adapter/AppListAdapter.java b/qpython/src/main/java/org/qpython/qpy/main/adapter/AppListAdapter.java index 920a5d19..a5cb6aa3 100644 --- a/qpython/src/main/java/org/qpython/qpy/main/adapter/AppListAdapter.java +++ b/qpython/src/main/java/org/qpython/qpy/main/adapter/AppListAdapter.java @@ -28,6 +28,8 @@ import org.qpython.qpy.main.model.QPyScriptModel; import org.qpython.qpy.texteditor.EditorActivity; import org.qpython.qpy.texteditor.ui.view.EnterDialog; +import org.qpython.qsl4a.qsl4a.LogUtil; + import android.support.v7.app.AlertDialog; @@ -114,6 +116,8 @@ public void onBindViewHolder(MyViewHolder holder, int positi case 2: openToEdit(position); dialog.dismiss(); + default: + break; } }).setNegativeButton("CLOSE", new DialogInterface.OnClickListener() { @@ -146,6 +150,8 @@ public interface Callback { void runProject(QPyScriptModel item); + void createShortcut(QPyScriptModel item); + void exit(); } @@ -156,43 +162,46 @@ private boolean createShortCut(int position) { } // Create shortcut QPyScriptModel qPyScriptModel = (QPyScriptModel) dataList.get(position); - Intent intent = new Intent(); - intent.setClass(context, AppListActivity.class); - intent.setAction(Intent.ACTION_VIEW); - intent.putExtra("type", "script"); - intent.putExtra("path", qPyScriptModel.getPath()); - intent.putExtra("isProj", qPyScriptModel.isProj()); - - if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - ShortcutManager mShortcutManager = context.getSystemService(ShortcutManager.class); - if (mShortcutManager.isRequestPinShortcutSupported()) { - ShortcutInfo pinShortcutInfo = - new ShortcutInfo.Builder(context, dataList.get(position).getLabel()) - .setShortLabel(dataList.get(position).getLabel()) - .setLongLabel(dataList.get(position).getLabel()) - .setIcon(Icon.createWithResource(context, dataList.get(position).getIconRes())) - .setIntent(intent) - .build(); - Intent pinnedShortcutCallbackIntent = - mShortcutManager.createShortcutResultIntent(pinShortcutInfo); - PendingIntent successCallback = PendingIntent.getBroadcast(context, 0, - pinnedShortcutCallbackIntent, 0); - mShortcutManager.requestPinShortcut(pinShortcutInfo, - successCallback.getIntentSender()); - } - } else { - //Adding shortcut for MainActivity - //on Home screen - Intent addIntent = new Intent(); - addIntent.putExtra(Intent.EXTRA_SHORTCUT_INTENT, intent); - addIntent.putExtra(Intent.EXTRA_SHORTCUT_NAME, dataList.get(position).getLabel()); - addIntent.putExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE, - Intent.ShortcutIconResource.fromContext(context.getApplicationContext(), - dataList.get(position).getIconRes())); - addIntent.setAction("com.android.launcher.action.INSTALL_SHORTCUT"); - context.getApplicationContext().sendBroadcast(addIntent); - Toast.makeText(context, context.getString(R.string.shortcut_create_suc, dataList.get(position).getLabel()), Toast.LENGTH_SHORT).show(); - } +// Intent intent = new Intent(); +// intent.setClass(context, AppListActivity.class); +// intent.setAction(Intent.ACTION_VIEW); +// intent.putExtra("type", "script"); +// intent.putExtra("path", qPyScriptModel.getPath()); +// intent.putExtra("isProj", qPyScriptModel.isProj()); +// +// if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { +// ShortcutManager mShortcutManager = context.getSystemService(ShortcutManager.class); +// if (mShortcutManager.isRequestPinShortcutSupported()) { +// ShortcutInfo pinShortcutInfo = +// new ShortcutInfo.Builder(context, dataList.get(position).getLabel()) +// .setShortLabel(dataList.get(position).getLabel()) +// .setLongLabel(dataList.get(position).getLabel()) +// .setIcon(Icon.createWithResource(context, dataList.get(position).getIconRes())) +// .setIntent(intent) +// .build(); +// Intent pinnedShortcutCallbackIntent = +// mShortcutManager.createShortcutResultIntent(pinShortcutInfo); +// PendingIntent successCallback = PendingIntent.getBroadcast(context, 0, +// pinnedShortcutCallbackIntent, 0); +// +// boolean aaa = mShortcutManager.requestPinShortcut(pinShortcutInfo, +// successCallback.getIntentSender()); +// LogUtil.e("11111111111" + aaa); +// } +// } else { +// //Adding shortcut for MainActivity +// //on Home screen +// Intent addIntent = new Intent(); +// addIntent.putExtra(Intent.EXTRA_SHORTCUT_INTENT, intent); +// addIntent.putExtra(Intent.EXTRA_SHORTCUT_NAME, dataList.get(position).getLabel()); +// addIntent.putExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE, +// Intent.ShortcutIconResource.fromContext(context.getApplicationContext(), +// dataList.get(position).getIconRes())); +// addIntent.setAction("com.android.launcher.action.INSTALL_SHORTCUT"); +// context.getApplicationContext().sendBroadcast(addIntent); +// Toast.makeText(context, context.getString(R.string.shortcut_create_suc, dataList.get(position).getLabel()), Toast.LENGTH_SHORT).show(); +// } + callback.createShortcut(qPyScriptModel); return true; } diff --git a/qpython/src/main/java/org/qpython/qpy/main/app/App.java b/qpython/src/main/java/org/qpython/qpy/main/app/App.java index 95ea64e2..8ea092d0 100644 --- a/qpython/src/main/java/org/qpython/qpy/main/app/App.java +++ b/qpython/src/main/java/org/qpython/qpy/main/app/App.java @@ -1,18 +1,25 @@ package org.qpython.qpy.main.app; +import android.app.ActivityManager; import android.content.Context; +import android.content.Intent; import android.content.SharedPreferences; import android.content.res.Configuration; import android.content.res.Resources; import android.os.Build; +import android.preference.PreferenceManager; import android.support.multidex.MultiDex; import android.support.v7.app.AppCompatActivity; +import android.util.Log; import com.google.gson.Gson; +import com.gyf.cactus.Cactus; +import com.gyf.cactus.callback.CactusCallback; import com.quseit.common.CrashHandler; import com.quseit.common.updater.downloader.DefaultDownloader; import com.squareup.leakcanary.LeakCanary; +import org.qpython.qpy.R; import org.qpython.qpy.main.server.Service; import org.qpython.qpy.main.server.gist.Api; import org.qpython.qpy.main.server.gist.TokenManager; @@ -20,7 +27,9 @@ import org.qpython.qpy.main.server.http.Retrofitor; import org.qpython.qpy.utils.NotebookUtil; import org.qpython.qpysdk.QPyConstants; +import org.qpython.qsl4a.QPyScriptService; import org.qpython.qsl4a.QSL4APP; +import org.qpython.qsl4a.qsl4a.LogUtil; import java.util.ArrayList; import java.util.HashMap; @@ -35,21 +44,22 @@ import retrofit2.adapter.rxjava.RxJavaCallAdapterFactory; import retrofit2.converter.gson.GsonConverterFactory; -public class App extends QSL4APP { +public class App extends QSL4APP implements CactusCallback{ + private static final String TAG = "APP"; private static Context sContext; - private static String sScriptPath; - private static String sProjectPath; + private static String sScriptPath; + private static String sProjectPath; //private static AppCompatActivity sActivity; - private static OkHttpClient okHttpClient; + private static OkHttpClient okHttpClient; private static HttpLoggingInterceptor interceptor; - private static Gson gson; + private static Gson gson; private static DefaultDownloader downloader; //保存user信息 - private static SharedPreferences mPreferences; + private static SharedPreferences mPreferences; private static Retrofit.Builder retrofitBuilder; @@ -87,7 +97,7 @@ public static Context getContext() { return sContext; } -// public static AppCompatActivity getActivity() { + // public static AppCompatActivity getActivity() { // return sActivity; // } // @@ -197,7 +207,7 @@ public void onCreate() { .getInstance() .setTimeOut(Retrofitor.DEFAULT_TIMEOUT) // .openDebug(BuildConfig.DEBUG) - // .supportSSL(!BuildConfig.DEBUG) + // .supportSSL(!BuildConfig.DEBUG) .addHeaders(header) .init(Api.BASE_URL); @@ -213,8 +223,30 @@ public void onCreate() { NotebookUtil.killNBSrv(this); NotebookUtil.startNotebookService2(this); } + + initCactus(); } + private void initCactus() { + boolean isKeepAlive = PreferenceManager.getDefaultSharedPreferences(this).getBoolean(getString(R.string.key_alive), false); + LogUtil.e("isKeepAlive:" + isKeepAlive); + if (!isKeepAlive){ + LogUtil.e("doWork0000000"); + if (!isRunService( "org.qpython.qsl4a.QPyScriptService")) { + startPyService(); + } + return; + } + Cactus.getInstance() + .isDebug(true) + .setTitle("QPython") + .setContent("QPython service is alive") + .setLargeIcon(R.drawable.ic_launcher) + .setSmallIcon(R.drawable.ic_launcher) + .hideNotificationAfterO(false) + .addCallback(this) + .register(this); + } private void initLayoutDir() { if (Build.VERSION.SDK_INT >= 17) { @@ -225,4 +257,45 @@ private void initLayoutDir() { resources.updateConfiguration(config, resources.getDisplayMetrics()); } } + + /** + * 保活的回调接口 + * @param i + */ + @Override + public void doWork(int i) { + LogUtil.e("doWork1111111"); + if (!isRunService( "org.qpython.qsl4a.QPyScriptService")) { + startPyService(); + } + } + + /** + * 保活的回调接口 + */ + @Override + public void onStop() {} + + private void startPyService() { + LogUtil.e("doWork22222"); + Log.d(TAG, "startPyService"); + Intent intent = new Intent(this, QPyScriptService.class); + startService(intent); + } + + /** + * 判断服务是否在运行 + * + * @param serviceName + * @return 服务名称为全路径 例如com.ghost.WidgetUpdateService + */ + public boolean isRunService(String serviceName) { + ActivityManager manager = (ActivityManager) getSystemService(ACTIVITY_SERVICE); + for (ActivityManager.RunningServiceInfo service : manager.getRunningServices(Integer.MAX_VALUE)) { + if (serviceName.equals(service.service.getClassName())) { + return true; + } + } + return false; + } } diff --git a/qpython/src/main/java/org/qpython/qpy/main/fragment/SettingFragment.java b/qpython/src/main/java/org/qpython/qpy/main/fragment/SettingFragment.java index f5624c51..0bd6186c 100644 --- a/qpython/src/main/java/org/qpython/qpy/main/fragment/SettingFragment.java +++ b/qpython/src/main/java/org/qpython/qpy/main/fragment/SettingFragment.java @@ -12,6 +12,7 @@ import android.os.Build; import android.os.Bundle; import android.os.Environment; +import android.os.Handler; import android.preference.CheckBoxPreference; import android.preference.Preference; import android.preference.PreferenceFragment; @@ -69,12 +70,12 @@ public class SettingFragment extends PreferenceFragment { private LoadingDialog mLoadingDialog; private SharedPreferences settings; - private Resources resources; - private Preference mPassWordPref, username_pref, portnum_pref, chroot_pref, lastlog; - private CheckBoxPreference sl4a, running_state, root, display_pwd, notebook_run; + private Resources resources; + private Preference mPassWordPref, username_pref, portnum_pref, chroot_pref, lastlog; + private CheckBoxPreference sl4a, running_state, root, display_pwd, notebook_run, keepAliveBox; - private PreferenceScreen py_inter,notebook_page; - private Preference py3,py2; //notebook_res, py2compatible + private PreferenceScreen py_inter, notebook_page; + private Preference py3, py2; //notebook_res, py2compatible //private Preference update_qpy3,update_qpy2compatible; private SwitchPreference log, app; @@ -99,7 +100,7 @@ public void onReceive(Context context, Intent intent) { static private String transformPassword(String password) { StringBuilder sb = new StringBuilder(password.length()); for (int i = 0; i < password.length(); ++i) - sb.append('*'); + {sb.append('*');} return sb.toString(); } @@ -140,7 +141,7 @@ private void initSettings() { ip = null; } - if (ip!=null) { + if (ip != null) { ipaddress.setSummary(ip.getHostAddress()); } else { ipaddress.setSummary(R.string.ip_address_need_wifi); @@ -153,7 +154,7 @@ private void initSettings() { notebook_page.setSummary(NotebookUtil.isNotebookLibInstall(getActivity()) ? R.string.notebook_installed : R.string.notebook_not_started); } else { - notebook_page.setSummary( R.string.notebook_py3_support); + notebook_page.setSummary(R.string.notebook_py3_support); } @@ -169,6 +170,7 @@ private void initSettings() { root = (CheckBoxPreference) findPreference(resources.getString(R.string.key_root)); + keepAliveBox = (CheckBoxPreference) findPreference(resources.getString(R.string.key_alive)); sl4a = (CheckBoxPreference) findPreference(resources.getString(R.string.key_sl4a)); app = (SwitchPreference) findPreference(getString(R.string.key_hide_push)); log = (SwitchPreference) findPreference(resources.getString(R.string.key_hide_noti)); @@ -185,6 +187,11 @@ private void initSettings() { root.setChecked(isRoot); root.setSummary(isRoot ? R.string.enable_root : R.string.disable_root); + boolean isKeepAlive; + isKeepAlive = settings.getBoolean(getString(R.string.key_alive), false); + keepAliveBox.setChecked(isKeepAlive); + keepAliveBox.setSummary(isKeepAlive ? R.string.enable_keep_alive : R.string.disable_keep_alive); + isRunning = isMyServiceRunning(QPyScriptService.class); sl4a.setChecked(isRunning); sl4a.setSummary(isRunning ? R.string.sl4a_running : R.string.sl4a_un_running); @@ -211,6 +218,7 @@ private void initSettings() { SharedPreferences.Editor editor = settings.edit(); editor.putBoolean(getString(R.string.key_root), root.isChecked()); + editor.putBoolean(getString(R.string.key_alive), keepAliveBox.isChecked()); //editor.putString(getString(R.string.key_qpypi), qpypi.getSummary().toString()); editor.putString(getString(R.string.key_username), username_pref.getSummary().toString()); editor.putString(getString(R.string.key_ftp_pwd), settings.getString(mPassWordPref.getKey(), "ftp")); @@ -262,13 +270,13 @@ private void initListener() { notebook_run.setChecked(NotebookUtil.isNBSrvSet(getActivity())); notebook_run.setOnPreferenceChangeListener((preference, newValue) -> { - if ((boolean)newValue) { + if ((boolean) newValue) { NotebookUtil.startNotebookService2(getActivity()); } else { NotebookUtil.killNBSrv(getActivity()); } - notebook_page.setSummary(NotebookUtil.isNotebookEnable(getActivity())?R.string.notebook_installed : R.string.notebook_not_started); + notebook_page.setSummary(NotebookUtil.isNotebookEnable(getActivity()) ? R.string.notebook_installed : R.string.notebook_not_started); return true; }); @@ -305,6 +313,20 @@ private void initListener() { } }); + keepAliveBox.setOnPreferenceChangeListener((preference, newValue) -> + { + boolean isCheck = (boolean) newValue; + settings.edit().putBoolean(getString(R.string.key_alive), isCheck).apply(); + Toast.makeText(getActivity(), R.string.keep_alive_tips, Toast.LENGTH_SHORT).show(); + new Handler().postDelayed(new Runnable() { + @Override + public void run() { + restartAppV2(); + } + },2000); + return true; + }); + sl4a.setOnPreferenceChangeListener((preference, newValue) -> { @@ -333,32 +355,32 @@ private void initListener() { }); findPreference(resources.getString(R.string.key_reset)). - setOnPreferenceClickListener(preference -> - { - NAction.startInstalledAppDetailsActivity(getActivity()); - return false; - }); + setOnPreferenceClickListener(preference -> + { + NAction.startInstalledAppDetailsActivity(getActivity()); + return false; + }); findPreference(resources.getString(R.string.key_about)). - setOnPreferenceClickListener(preference -> - { - AboutActivity.start(getActivity()); - return true; - }); + setOnPreferenceClickListener(preference -> + { + AboutActivity.start(getActivity()); + return true; + }); findPreference("course"). - setOnPreferenceClickListener(preference -> - { - CourseActivity.start(getActivity()); - return true; - }); + setOnPreferenceClickListener(preference -> + { + CourseActivity.start(getActivity()); + return true; + }); findPreference("community"). - setOnPreferenceClickListener(preference -> - { - startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(URL_COMMUNITY+"?from="+NAction.getCode(this.getActivity())))); - return true; - }); + setOnPreferenceClickListener(preference -> + { + startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(URL_COMMUNITY + "?from=" + NAction.getCode(this.getActivity())))); + return true; + }); /* ====================FTP==================== */ running_state.setOnPreferenceChangeListener((preference, newValue) -> { @@ -475,6 +497,14 @@ private void initListener() { }); } + private void restartAppV2() { + Intent intent = getActivity().getPackageManager().getLaunchIntentForPackage(getActivity().getPackageName()); + PendingIntent restartIntent = PendingIntent.getActivity(getActivity().getApplicationContext(), 0, intent, PendingIntent.FLAG_ONE_SHOT); + AlarmManager mgr = (AlarmManager) getActivity().getSystemService(Context.ALARM_SERVICE); + mgr.set(AlarmManager.RTC, System.currentTimeMillis() + 1000, restartIntent); // 1秒钟后重启应用 + System.exit(0); + } + private void releaseNotebook(Preference preference) { Observable.create((Observable.OnSubscribe) subscriber -> { try { @@ -493,32 +523,32 @@ private void releaseNotebook(Preference preference) { } }) - .subscribeOn(Schedulers.io()) - .doOnSubscribe(() -> mLoadingDialog.show()) - .subscribeOn(AndroidSchedulers.mainThread()) - .observeOn(AndroidSchedulers.mainThread()) - .doOnTerminate(() -> mLoadingDialog.dismiss()) - .subscribe(new Observer() { - @Override - public void onCompleted() { + .subscribeOn(Schedulers.io()) + .doOnSubscribe(() -> mLoadingDialog.show()) + .subscribeOn(AndroidSchedulers.mainThread()) + .observeOn(AndroidSchedulers.mainThread()) + .doOnTerminate(() -> mLoadingDialog.dismiss()) + .subscribe(new Observer() { + @Override + public void onCompleted() { - } + } - @Override - public void onError(Throwable e) { + @Override + public void onError(Throwable e) { - } + } - @Override - public void onNext(Boolean aBoolean) { - Log.d(TAG, "onNext"); + @Override + public void onNext(Boolean aBoolean) { + Log.d(TAG, "onNext"); - NotebookUtil.startNotebookService2(getActivity()); - notebook_page.setSummary(NotebookUtil.isNotebookLibInstall(getActivity())?R.string.notebook_installed : R.string.notebook_not_started); + NotebookUtil.startNotebookService2(getActivity()); + notebook_page.setSummary(NotebookUtil.isNotebookLibInstall(getActivity()) ? R.string.notebook_installed : R.string.notebook_not_started); - } - }); + } + }); } private void installNotebook() { @@ -539,7 +569,7 @@ private void installNotebook() { private void extractNotebookRes(String path) { final String extarget = NotebookUtil.RELEASE_PATH; - if (path!=null && !path.equals("")) { + if (path != null && !path.equals("")) { File resf = new File(path); if (resf.exists()) { QPySDK qpySDK = new QPySDK(App.getContext(), getActivity()); @@ -552,7 +582,7 @@ private void extractNotebookRes(String path) { private void releaseQPycRes(String path) { final String extarget = QPyConstants.PY_CACHE_PATH; - if (path!=null && !path.equals("")) { + if (path != null && !path.equals("")) { File res = new File(path); if (res.exists()) { @@ -569,7 +599,7 @@ private void releasePython2Standard(Preference preference) { QPySDK qpysdk = new QPySDK(getActivity(), getActivity()); qpysdk.extractRes("private1", getActivity().getFilesDir(), true); qpysdk.extractRes("private2", getActivity().getFilesDir(), true); - qpysdk.extractRes("private3", getActivity().getFilesDir(),true); + qpysdk.extractRes("private3", getActivity().getFilesDir(), true); subscriber.onNext(true); subscriber.onCompleted(); @@ -578,30 +608,30 @@ private void releasePython2Standard(Preference preference) { subscriber.onError(new Throwable("Failed to release Py2 resources")); } }) - .subscribeOn(Schedulers.io()) - .doOnSubscribe(() -> mLoadingDialog.show()) - .subscribeOn(AndroidSchedulers.mainThread()) - .observeOn(AndroidSchedulers.mainThread()) - .doOnTerminate(() -> mLoadingDialog.dismiss()) - .subscribe(new Observer() { - @Override - public void onCompleted() { + .subscribeOn(Schedulers.io()) + .doOnSubscribe(() -> mLoadingDialog.show()) + .subscribeOn(AndroidSchedulers.mainThread()) + .observeOn(AndroidSchedulers.mainThread()) + .doOnTerminate(() -> mLoadingDialog.dismiss()) + .subscribe(new Observer() { + @Override + public void onCompleted() { - } + } - @Override - public void onError(Throwable e) { - Toast.makeText(getActivity(), "Faild to extract Py2 resource", Toast.LENGTH_SHORT).show(); - } + @Override + public void onError(Throwable e) { + Toast.makeText(getActivity(), "Faild to extract Py2 resource", Toast.LENGTH_SHORT).show(); + } - @Override - public void onNext(Boolean aBoolean) { - NAction.setQPyInterpreter(getActivity(), "2.x"); - py_inter.setSummary(R.string.py2_now); + @Override + public void onNext(Boolean aBoolean) { + NAction.setQPyInterpreter(getActivity(), "2.x"); + py_inter.setSummary(R.string.py2_now); - getActivity().recreate(); - } - }); + getActivity().recreate(); + } + }); } private void releasePython2Compatable(Preference preference) { @@ -619,30 +649,30 @@ private void releasePython2Compatable(Preference preference) { subscriber.onError(new Throwable("Failed to release Py2 resources")); } }) - .subscribeOn(Schedulers.io()) - .doOnSubscribe(() -> mLoadingDialog.show()) - .subscribeOn(AndroidSchedulers.mainThread()) - .observeOn(AndroidSchedulers.mainThread()) - .doOnTerminate(() -> mLoadingDialog.dismiss()) - .subscribe(new Observer() { - @Override - public void onCompleted() { + .subscribeOn(Schedulers.io()) + .doOnSubscribe(() -> mLoadingDialog.show()) + .subscribeOn(AndroidSchedulers.mainThread()) + .observeOn(AndroidSchedulers.mainThread()) + .doOnTerminate(() -> mLoadingDialog.dismiss()) + .subscribe(new Observer() { + @Override + public void onCompleted() { - } + } - @Override - public void onError(Throwable e) { - Toast.makeText(getActivity(), "Faild to extract Py2 resource", Toast.LENGTH_SHORT).show(); - } + @Override + public void onError(Throwable e) { + Toast.makeText(getActivity(), "Faild to extract Py2 resource", Toast.LENGTH_SHORT).show(); + } - @Override - public void onNext(Boolean aBoolean) { - NAction.setQPyInterpreter(getActivity(), "2.x"); - py_inter.setSummary(R.string.py2_now); + @Override + public void onNext(Boolean aBoolean) { + NAction.setQPyInterpreter(getActivity(), "2.x"); + py_inter.setSummary(R.string.py2_now); - getActivity().recreate(); - } - }); + getActivity().recreate(); + } + }); } @@ -650,12 +680,12 @@ private void releasePython3(Preference preference) { QPySDK qpysdk = new QPySDK(this.getActivity(), this.getActivity()); Observable.create((Observable.OnSubscribe) subscriber -> { try { - releaseQPycRes(NStorage.getSP(App.getContext(),QPyConstants.KEY_PY3_RES)); + releaseQPycRes(NStorage.getSP(App.getContext(), QPyConstants.KEY_PY3_RES)); //extractQPyCore(false); qpysdk.extractRes("private31", getActivity().getFilesDir(), true); qpysdk.extractRes("private32", getActivity().getFilesDir(), true); - qpysdk.extractRes("private33", getActivity().getFilesDir(),true); + qpysdk.extractRes("private33", getActivity().getFilesDir(), true); qpysdk.extractRes("notebook3", getActivity().getFilesDir(), true); File externalStorage = new File(Environment.getExternalStorageDirectory(), "qpython"); @@ -669,30 +699,30 @@ private void releasePython3(Preference preference) { subscriber.onError(new Throwable("Failed to release Py3 resources")); } }) - .subscribeOn(Schedulers.io()) - .doOnSubscribe(() -> mLoadingDialog.show()) - .subscribeOn(AndroidSchedulers.mainThread()) - .observeOn(AndroidSchedulers.mainThread()) - .doOnTerminate(() -> mLoadingDialog.dismiss()) - .subscribe(new Observer() { - @Override - public void onCompleted() { + .subscribeOn(Schedulers.io()) + .doOnSubscribe(() -> mLoadingDialog.show()) + .subscribeOn(AndroidSchedulers.mainThread()) + .observeOn(AndroidSchedulers.mainThread()) + .doOnTerminate(() -> mLoadingDialog.dismiss()) + .subscribe(new Observer() { + @Override + public void onCompleted() { - } + } - @Override - public void onError(Throwable e) { - Toast.makeText(getActivity(), "Faild to extract Py3 resource", Toast.LENGTH_SHORT).show(); - } + @Override + public void onError(Throwable e) { + Toast.makeText(getActivity(), "Faild to extract Py3 resource", Toast.LENGTH_SHORT).show(); + } - @Override - public void onNext(Boolean aBoolean) { - NAction.setQPyInterpreter(getActivity(), "3.x"); - py_inter.setSummary(R.string.py3_now); + @Override + public void onNext(Boolean aBoolean) { + NAction.setQPyInterpreter(getActivity(), "3.x"); + py_inter.setSummary(R.string.py3_now); - getActivity().recreate(); - } - }); + getActivity().recreate(); + } + }); } @@ -757,204 +787,207 @@ private void getNotebook() { mLoadingDialog.show(); QBaseApp.getInstance().getAsyncHttpClient().get(getActivity(), NotebookUtil.getNBLink(getActivity()), - null, new JsonHttpResponseHandler() { - @Override - public void onSuccess(int statusCode, Header[] headers, JSONObject result) { - final String KEY_RES = "setting.notebookresource.ver"; - - try { - - final String notebook_resource_ver = NStorage.getSP(getActivity(), KEY_RES); - final String url = result.getString("link"); - final String target = result.getString("target"); - final String vercode = result.getString("vercode"); - final String title = result.getString("title"); - final String vername = result.getString("vername"); - final String path = NotebookUtil.RELEASE_PATH+"/"+target; - - NStorage.setSP(App.getContext(), NotebookUtil.getNbResFk(getActivity()), path); - - Log.d(TAG, "getNotebook:onSuccess:"+notebook_resource_ver+"["+vercode+"]"); + null, new JsonHttpResponseHandler() { + @Override + public void onSuccess(int statusCode, Header[] headers, JSONObject result) { + final String KEY_RES = "setting.notebookresource.ver"; - if (notebook_resource_ver.equals(vercode) && new File(path).exists()) { - mLoadingDialog.dismiss(); + try { - new AlertDialog.Builder(getActivity(), R.style.MyDialog) - .setTitle(title) - .setMessage(R.string.newest_resource) - .setPositiveButton(R.string.ok, (dialog1, which) -> { - try { - extractNotebookRes(path); - } catch (Exception e) { + final String notebook_resource_ver = NStorage.getSP(getActivity(), KEY_RES); + final String url = result.getString("link"); + final String target = result.getString("target"); + final String vercode = result.getString("vercode"); + final String title = result.getString("title"); + final String vername = result.getString("vername"); + final String path = NotebookUtil.RELEASE_PATH + "/" + target; - } - dialog1.dismiss(); - }) - .create() - .show(); + NStorage.setSP(App.getContext(), NotebookUtil.getNbResFk(getActivity()), path); - } else { - App.getDownloader().download(getString(R.string.download_notebook), url, path, new Downloader.Callback() { + Log.d(TAG, "getNotebook:onSuccess:" + notebook_resource_ver + "[" + vercode + "]"); - @Override - public void pending(String name) { - mLoadingDialog.cancel(); + if (notebook_resource_ver.equals(vercode) && new File(path).exists()) { + mLoadingDialog.dismiss(); new AlertDialog.Builder(getActivity(), R.style.MyDialog) - .setTitle(R.string.notice) - .setMessage(R.string.download_progress_need_sometime) - .setPositiveButton(R.string.ok, (dialog1, which) -> { - }) - .create() - .show(); - } + .setTitle(title) + .setMessage(R.string.newest_resource) + .setPositiveButton(R.string.ok, (dialog1, which) -> { + try { + extractNotebookRes(path); + } catch (Exception e) { + + } + dialog1.dismiss(); + }) + .create() + .show(); + + } else { + App.getDownloader().download(getString(R.string.download_notebook), url, path, new Downloader.Callback() { + + @Override + public void pending(String name) { + mLoadingDialog.cancel(); + + new AlertDialog.Builder(getActivity(), R.style.MyDialog) + .setTitle(R.string.notice) + .setMessage(R.string.download_progress_need_sometime) + .setPositiveButton(R.string.ok, (dialog1, which) -> { + }) + .create() + .show(); + } - @Override - public void complete(String name, File installer) { + @Override + public void complete(String name, File installer) { - Log.d(TAG, "getNotebook:complete:" + name + "[" + installer.getAbsolutePath() + "]"); - mLoadingDialog.dismiss(); + Log.d(TAG, "getNotebook:complete:" + name + "[" + installer.getAbsolutePath() + "]"); + mLoadingDialog.dismiss(); - NStorage.setSP(App.getContext(),KEY_RES, vercode); + NStorage.setSP(App.getContext(), KEY_RES, vercode); - // UNZIP resources && install - try { - extractNotebookRes(installer.getAbsolutePath()); - Toast.makeText(App.getContext(), R.string.file_downloaded, Toast.LENGTH_SHORT).show(); + // UNZIP resources && install + try { + extractNotebookRes(installer.getAbsolutePath()); + Toast.makeText(App.getContext(), R.string.file_downloaded, Toast.LENGTH_SHORT).show(); - } catch (Exception e) { + } catch (Exception e) { - } - } + } + } - @Override - public void error(String err) { - mLoadingDialog.cancel(); - try { - Toast.makeText(getActivity(), err, Toast.LENGTH_SHORT).show(); - } catch (Exception e) { + @Override + public void error(String err) { + mLoadingDialog.cancel(); + try { + Toast.makeText(getActivity(), err, Toast.LENGTH_SHORT).show(); + } catch (Exception e) { - } + } + } + }); } - }); + } catch (JSONException e) { + e.printStackTrace(); + } } - } catch (JSONException e) { - e.printStackTrace(); - } - } - @Override - public void onFailure(int statusCode, Header[] headers, String responseString, Throwable throwable) { - // waitingWindow.dismiss(); - Log.d(TAG, "Error in checkConfUpdate:" + throwable.getMessage()); - } - }); + + @Override + public void onFailure(int statusCode, Header[] headers, String responseString, Throwable throwable) { + // waitingWindow.dismiss(); + Log.d(TAG, "Error in checkConfUpdate:" + throwable.getMessage()); + } + }); } + private void getQPYC(boolean ispy2compatible) { mLoadingDialog.show(); - String conf_url = (ispy2compatible?QPyConstants.QPYC2COMPATIBLE:QPyConstants.QPYC3)+"?"+NAction.getUserUrl(getActivity()); + String conf_url = (ispy2compatible ? QPyConstants.QPYC2COMPATIBLE : QPyConstants.QPYC3) + "?" + NAction.getUserUrl(getActivity()); QBaseApp.getInstance().getAsyncHttpClient().get(getActivity(), conf_url, - null, new JsonHttpResponseHandler() { - @Override - public void onSuccess(int statusCode, Header[] headers, JSONObject result) { - final String KEY_RES = ispy2compatible?QPyConstants.QPYC2COMPATIBLE_VER_KEY:QPyConstants.QPYC3_VER_KEY; - - try { + null, new JsonHttpResponseHandler() { + @Override + public void onSuccess(int statusCode, Header[] headers, JSONObject result) { + final String KEY_RES = ispy2compatible ? QPyConstants.QPYC2COMPATIBLE_VER_KEY : QPyConstants.QPYC3_VER_KEY; - final String py_resource_ver = NStorage.getSP(getActivity(), KEY_RES); + try { - final String url = result.getString("link"); - final String target = result.getString("target"); - final String vercode = result.getString("vercode"); - final String title = result.getString("title"); - final String vername = result.getString("vername"); - final String path = QPyConstants.PY_CACHE_PATH + "/" + target; + final String py_resource_ver = NStorage.getSP(getActivity(), KEY_RES); - NStorage.setSP(App.getContext(), QPyConstants.KEY_PY3_RES, path); + final String url = result.getString("link"); + final String target = result.getString("target"); + final String vercode = result.getString("vercode"); + final String title = result.getString("title"); + final String vername = result.getString("vername"); + final String path = QPyConstants.PY_CACHE_PATH + "/" + target; - Log.d(TAG, "getQPYC:onSuccess:"+py_resource_ver+"["+vercode+"]"); + NStorage.setSP(App.getContext(), QPyConstants.KEY_PY3_RES, path); - if (py_resource_ver.equals(vercode) && new File(path).exists()) { - mLoadingDialog.dismiss(); + Log.d(TAG, "getQPYC:onSuccess:" + py_resource_ver + "[" + vercode + "]"); - new AlertDialog.Builder(getActivity(), R.style.MyDialog) - .setTitle(title) - .setMessage(R.string.newest_resource) - .setPositiveButton(R.string.ok, (dialog1, which) -> { - try { - releaseQPycRes(path); - } catch (Exception e) { + if (py_resource_ver.equals(vercode) && new File(path).exists()) { + mLoadingDialog.dismiss(); + new AlertDialog.Builder(getActivity(), R.style.MyDialog) + .setTitle(title) + .setMessage(R.string.newest_resource) + .setPositiveButton(R.string.ok, (dialog1, which) -> { + try { + releaseQPycRes(path); + } catch (Exception e) { + + } + + dialog1.dismiss(); + }) + .create() + .show(); + + } else { + + App.getDownloader().download(getString(R.string.download_py), url, path, new Downloader.Callback() { + + @Override + public void pending(String name) { + mLoadingDialog.cancel(); + + new AlertDialog.Builder(getActivity(), R.style.MyDialog) + .setTitle(R.string.notice) + .setMessage(R.string.download_progress_need_sometime) + //.setNegativeButton(R.string.cancel, (dialog1, which) -> dialog1.dismiss()) + .setPositiveButton(R.string.ok, (dialog1, which) -> { + }) + .create() + .show(); } - dialog1.dismiss(); - }) - .create() - .show(); - - } else { - - App.getDownloader().download(getString(R.string.download_py), url, path, new Downloader.Callback() { - - @Override - public void pending(String name) { - mLoadingDialog.cancel(); + @Override + public void complete(String name, File installer) { - new AlertDialog.Builder(getActivity(), R.style.MyDialog) - .setTitle(R.string.notice) - .setMessage(R.string.download_progress_need_sometime) - //.setNegativeButton(R.string.cancel, (dialog1, which) -> dialog1.dismiss()) - .setPositiveButton(R.string.ok, (dialog1, which) -> { - }) - .create() - .show(); - } + Log.d(TAG, "getQPYC:complete:" + name + "[" + installer.getAbsolutePath() + "]"); + mLoadingDialog.dismiss(); - @Override - public void complete(String name, File installer) { + NStorage.setSP(App.getContext(), KEY_RES, vercode); + // UNZIP resources && install + try { + releaseQPycRes(installer.getAbsolutePath()); - Log.d(TAG, "getQPYC:complete:" + name + "[" + installer.getAbsolutePath() + "]"); - mLoadingDialog.dismiss(); - - NStorage.setSP(App.getContext(),KEY_RES, vercode); - // UNZIP resources && install - try { - releaseQPycRes(installer.getAbsolutePath()); - - } catch (Exception e) { + } catch (Exception e) { + } } - } - @Override - public void error(String err) { - mLoadingDialog.cancel(); - try { - Toast.makeText(getActivity(), err, Toast.LENGTH_SHORT).show(); - } catch (Exception e) { + @Override + public void error(String err) { + mLoadingDialog.cancel(); + try { + Toast.makeText(getActivity(), err, Toast.LENGTH_SHORT).show(); + } catch (Exception e) { + } } - } - }); + }); + } + } catch (JSONException e) { + e.printStackTrace(); } - } catch (JSONException e) { - e.printStackTrace(); - } - } - @Override - public void onFailure(int statusCode, Header[] headers, String responseString, Throwable throwable) { - // waitingWindow.dismiss(); - Log.d(TAG, "Error in getQPYC:" + throwable.getMessage()); - } - }); + } + + @Override + public void onFailure(int statusCode, Header[] headers, String responseString, Throwable throwable) { + // waitingWindow.dismiss(); + Log.d(TAG, "Error in getQPYC:" + throwable.getMessage()); + } + }); } private boolean isQPycRelease(boolean ispy2compatible) { boolean isRelease = true; - String[] py3Mp3File = getActivity().getResources().getStringArray(ispy2compatible?R.array.qpy2compatible_zip:R.array.qpy3_zip); + String[] py3Mp3File = getActivity().getResources().getStringArray(ispy2compatible ? R.array.qpy2compatible_zip : R.array.qpy3_zip); for (String s : py3Mp3File) { isRelease = isRelease && new File(QPyConstants.PY_CACHE_PATH + "/" + s).exists(); } @@ -964,8 +997,8 @@ private boolean isQPycRelease(boolean ispy2compatible) { private void removeQPyc2Core() { Log.d(TAG, "removeQPyc2Core"); String files = getActivity().getFilesDir().getAbsolutePath(); - String[] files2del = {files+"/lib/notebook.zip", files+"/lib/python27.zip", files+"/lib/python2.7"}; - for (int i=0;i getAllTheLauncher(Context context) { + List names = new ArrayList<>(); + List packs = context.getPackageManager().getInstalledPackages(0); + for (int i = 0; i < packs.size(); i++) { + PackageInfo p = packs.get(i); + + + if (p.versionName == null) { + continue; + } + names.add(p.packageName); +// newInfo.appname = p.applicationInfo.loadLabel(context.getPackageManager()).toString(); +// newInfo.pname = p.packageName; +// newInfo.classname = p.applicationInfo.className; +// newInfo.versionCode = p.versionCode; +// newInfo.icon = p.applicationInfo.loadIcon(context.getPackageManager()); +// List names = null; +// PackageManager pkgMgt = context.getPackageManager(); +// Intent it = new Intent(Intent.ACTION_MAIN); +// it.addCategory(Intent.CATEGORY_LAUNCHER); +// List ra = pkgMgt.queryIntentActivities(it, 0); +// if (ra.size() != 0) { +// names = new ArrayList(); +// } +// for (int i = 0; i < ra.size(); i++) { +// String packageName = ra.get(i).activityInfo.packageName; +// names.add(packageName); +// } + } + return names; + } + + @RequiresApi(api = Build.VERSION_CODES.N_MR1) + public static List getShortcutInfo(Context context){ + ShortcutManager mShortcutManager = context.getSystemService(ShortcutManager.class); + return mShortcutManager.getPinnedShortcuts(); + } +} diff --git a/qpython/src/main/res/layout/activity_local_app.xml b/qpython/src/main/res/layout/activity_local_app.xml index ef51f7e5..754f8318 100644 --- a/qpython/src/main/res/layout/activity_local_app.xml +++ b/qpython/src/main/res/layout/activity_local_app.xml @@ -1,8 +1,8 @@ - - + + + + + + + + android:background="@null" /> \ No newline at end of file diff --git a/qpython/src/main/res/values-ja/strings.xml b/qpython/src/main/res/values-ja/strings.xml index 18a6d9fb..0d8e709d 100644 --- a/qpython/src/main/res/values-ja/strings.xml +++ b/qpython/src/main/res/values-ja/strings.xml @@ -486,4 +486,8 @@ 現在Python2を使用中です。Python3に切り替えますか? 設定に移動 ネットワークにラグが発生しています。しばらくお待ちください。 + You can run QPython script even it\'s in background if this mode is enabled. + Disable + QPython will restart to apply changes. + "Please grant the creat shortcut permission. " diff --git a/qpython/src/main/res/values-ru/strings.xml b/qpython/src/main/res/values-ru/strings.xml index 33a26d74..f2c0f763 100644 --- a/qpython/src/main/res/values-ru/strings.xml +++ b/qpython/src/main/res/values-ru/strings.xml @@ -430,4 +430,8 @@ w Подтвердить Я Необходимо получить разрешение для доступа к хранилищу. Не могу подключиться к серверу Google. Пожалуйста, проверьте ваше соединение. + You can run QPython script even it\'s in background if this mode is enabled. + Disable + QPython will restart to apply changes. + "Please grant the creat shortcut permission. " \ No newline at end of file diff --git a/qpython/src/main/res/values-zh-rCN/strings.xml b/qpython/src/main/res/values-zh-rCN/strings.xml index aeff98b2..1c877ca9 100644 --- a/qpython/src/main/res/values-zh-rCN/strings.xml +++ b/qpython/src/main/res/values-zh-rCN/strings.xml @@ -450,5 +450,9 @@ 运行 请先从QPYPI中安装kivy库 获得帮助 + You can run QPython script even it\'s in background if this mode is enabled. + Disable + QPython will restart to apply changes. + "Please grant the creat shortcut permission. " diff --git a/qpython/src/main/res/values-zh-rTW/strings.xml b/qpython/src/main/res/values-zh-rTW/strings.xml index 6677a532..be376ec1 100644 --- a/qpython/src/main/res/values-zh-rTW/strings.xml +++ b/qpython/src/main/res/values-zh-rTW/strings.xml @@ -291,5 +291,9 @@ 衆籌 衆籌中 獲得幫助 + You can run QPython script even it\'s in background if this mode is enabled. + Disable + QPython will restart to apply changes. + "Please grant the creat shortcut permission. " \ No newline at end of file diff --git a/qpython/src/main/res/values/strings.xml b/qpython/src/main/res/values/strings.xml index 1c38275b..90b9b9c4 100644 --- a/qpython/src/main/res/values/strings.xml +++ b/qpython/src/main/res/values/strings.xml @@ -720,6 +720,7 @@ Project\'s main.py does not exit root + keep alive sl4a qpypi update_py3 @@ -734,5 +735,10 @@ Do you want to override? + You can run QPython script even it\'s in background if this mode is enabled. + Disable + Keep Alive Mode + QPython will restart to apply changes. + "Please grant the creat shortcut permission. " diff --git a/qpython/src/main/res/xml/qpython_setting.xml b/qpython/src/main/res/xml/qpython_setting.xml index 245731f7..777fdbc4 100644 --- a/qpython/src/main/res/xml/qpython_setting.xml +++ b/qpython/src/main/res/xml/qpython_setting.xml @@ -109,6 +109,12 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/qpython/src/od/java/org/qpython/qpy/codeshare/ShareCodeUtil.java b/qpython/src/od/java/org/qpython/qpy/codeshare/ShareCodeUtil.java new file mode 100644 index 00000000..3841e9a9 --- /dev/null +++ b/qpython/src/od/java/org/qpython/qpy/codeshare/ShareCodeUtil.java @@ -0,0 +1,743 @@ +package org.qpython.qpy.codeshare; + +import android.app.Activity; +import android.content.SharedPreferences; +import android.preference.PreferenceManager; +import android.util.Log; +import android.widget.Toast; + +import com.google.firebase.database.DataSnapshot; +import com.google.firebase.database.DatabaseError; +import com.google.firebase.database.DatabaseReference; +import com.google.firebase.database.FirebaseDatabase; +import com.google.firebase.database.ValueEventListener; +import com.google.gson.reflect.TypeToken; +import com.quseit.util.ACache; +import com.quseit.util.FileHelper; +import com.quseit.util.NetStateUtil; + +import org.json.JSONObject; +import org.qpython.qpy.R; +import org.qpython.qpy.codeshare.pojo.BookmarkerList; +import org.qpython.qpy.codeshare.pojo.CloudFile; +import org.qpython.qpy.codeshare.pojo.Gist; +import org.qpython.qpy.codeshare.pojo.GistBase; +import org.qpython.qpy.main.app.App; +import org.qpython.qpy.main.app.CONF; +import org.qpython.qpy.texteditor.common.TextFileUtils; +import org.qpython.qpysdk.utils.DateTimeHelper; + +import java.io.File; +import java.util.ArrayList; +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import rx.Observable; +import rx.functions.Action1; + +import static org.qpython.qpy.codeshare.CONSTANT.DOT_REPLACE; +import static org.qpython.qpy.codeshare.CONSTANT.SLASH_REPLACE; +import static org.qpython.qpy.main.server.CacheKey.CLOUD_FILE; + +/** + * FireBase database util + * Created by Hmei on 2017-08-10. + */ + +public class ShareCodeUtil { + private static final boolean CLEAR = false; + + private static final String CLOUD = "cloud"; + private static final String GIST = "gist"; + private static final String USER = "user"; + private static final String BASE = "base"; + private static final String GIST_LIST = "gist_list"; + private static final String HISTORY = "history"; + private static final String COMMENT = "comment"; + private static final String BOOKMARK = "bookmark"; + private static final String COMMIT = "commit"; + private static final String INDEX = "index"; + private static final String USAGE = "usage"; + private static final String PROJECT = "project"; + private static final String SCRIPT = "script"; + private static final String OTHER = "other"; + private static final String PROJECT_PATH = "/projects/"; + private static final String SCRIPTS_PATH = "/scripts/"; + + private static final int MAX_FILE = 100; + + private DatabaseReference reference; + private String email; + private String userName; + private String avatarUrl; + + private SharedPreferences sharedPreferences; + private int currentFileCount = -1; + + private ShareCodeUtil() { + reference = FirebaseDatabase.getInstance().getReference(); + if (App.getUser() != null) { + email = App.getUser().getEmail().replace(".", "_"); + userName = App.getUser().getUserName(); + avatarUrl = App.getUser().getAvatarUrl(); + sharedPreferences = PreferenceManager.getDefaultSharedPreferences(App.getContext()); + initUsage(null); + } else { + email = ""; + userName = ""; + avatarUrl = ""; + } + //LogUtil.d("ShareCodeUtil", "ShareCodeUtil:"+App.getUser().getEmail()); + + Log.d("ShareCodeUtil", "ShareCodeUtil:"+email); + } + + public static ShareCodeUtil getInstance() { + return ShareCodeHolder.INSTANCE; + } + + @SuppressWarnings({"DEBUG_ONLY"}) + public void clearAll() { + if (CLEAR) { + reference.child(CLOUD).child(email).removeValue( + (databaseError, databaseReference) -> + Toast.makeText(App.getContext(), "Clear", Toast.LENGTH_SHORT).show()); + } + } + + public void createScriptGist(String title, String desc, String msg, String content, DatabaseReference.CompletionListener listener) { + String date = DateTimeHelper.getDate(); + // Add to all gist repo + Gist gist = new Gist(); + gist.setTitle(title); + gist.setAuthor(userName); + gist.setAvatar(avatarUrl); + gist.setDescribe(desc); + gist.setDate(date); + DatabaseReference path = reference.child(GIST).child(SCRIPT).push();// gist/script/ + String gistId = path.getKey(); + path.setValue(gist, listener); + commitGist(gistId, msg, content, null, null); + + // Add to user repo + GistBase baseGist = new GistBase(); + baseGist.setAuthor(userName); + baseGist.setAvatar(avatarUrl); + baseGist.setDate(date); + baseGist.setTitle(title); + reference.child(USER).child(email).child(GIST_LIST).child(SCRIPT).child(gistId).setValue(baseGist);// user//gist_list/script/ + + // Add to base repo + reference.child(BASE).child(SCRIPT).child(gistId).setValue(baseGist); // base/script/ + } + + public void createProjectGist(String projectName, String desc, String msg, List paths, DatabaseReference.CompletionListener completionListener) { + for (String path : paths) { + File file = new File(path); + String code = TextFileUtils.readTextFile(file); + String fileName = file.getName().replace(".", DOT_REPLACE); + + String date = DateTimeHelper.getDate(); + // Add to all gist repo + Gist gist = new Gist(); + gist.setTitle(fileName); + gist.setAuthor(userName); + gist.setAvatar(avatarUrl); + gist.setDescribe(desc); + gist.setDate(date); + DatabaseReference subReference = reference.child(GIST).child(PROJECT);// gist/script/ + String gistId = subReference.getKey(); + subReference.child(gistId).child(projectName).child(fileName).setValue(gist, completionListener);// gist/project/// + commitGist(gistId, msg, code, projectName, fileName); + + // Add to user repo + GistBase baseGist = new GistBase(); + baseGist.setAuthor(userName); + baseGist.setAuthor(avatarUrl); + baseGist.setDate(date); + baseGist.setTitle(fileName); + // user//gist_list/project/// + reference.child(USER).child(email).child(GIST_LIST).child(PROJECT).child(gistId).child(projectName).child(fileName).setValue(baseGist); + + // Add to base repo + // base/project/// + reference.child(BASE).child(PROJECT).child(gistId).child(projectName).child(fileName).setValue(baseGist); + } + } + + public void commitGist(String gistId, String msg, String content, String projName, String fileName) { + Gist.HistoryBean historyBean = new Gist.HistoryBean(); + historyBean.setData(DateTimeHelper.getDate()); + historyBean.setMassage(msg); + historyBean.setContent(content); + if (projName != null) { + DatabaseReference subRe = reference.child(GIST).child(PROJECT).child(gistId).child(projName).child(fileName).child(HISTORY).push(); + String historyId = subRe.getKey(); + subRe.child(historyId).setValue(historyBean); // gist//history/} + } else { + DatabaseReference subRe = reference.child(GIST).child(SCRIPT).child(gistId).child(HISTORY).push(); + String historyId = subRe.getKey(); + subRe.child(historyId).setValue(historyBean); // gist//history/} + reference.child(GIST).child(SCRIPT).child(gistId).child("lastCommitCode").setValue(content); + } + } + + public void sendComment(String gistId, String comment, boolean isProj, CommentCallback callback) { + sendComment(gistId, "", comment, "", isProj, callback); + } + + /** + * @param comment 评论 + * @param reComment 被回复的评论 + */ + public void sendComment(String gistId, String to, String comment, String reComment, boolean isProj, CommentCallback callback) { + Gist.CommentBean commentBean = new Gist.CommentBean(); + commentBean.setFrom_content(comment); + commentBean.setData(DateTimeHelper.getDate()); + commentBean.setFrom(userName); + commentBean.setAvatar(avatarUrl); + commentBean.setRe(to); + commentBean.setRe_content(reComment); + callback.commentBean(commentBean); + DatabaseReference subRe = reference.child(GIST).child(isProj ? PROJECT : SCRIPT).child(gistId).child(COMMENT).push(); + String commentId = subRe.getKey(); + subRe.child(commentId).setValue(commentBean); + } + + public void bookmark(String gistId) { + reference.child(GIST).child(gistId).child(BOOKMARK).child(email).setValue(userName); + reference.child(USER).child(email).child(BOOKMARK).child(gistId).setValue(true); + } + + public void cancelBookmark(String gistId) { + reference.child(GIST).child(gistId).child(BOOKMARK).child(email).removeValue(); + reference.child(USER).child(email).child(BOOKMARK).child(gistId).removeValue(); + } + + public void getBaseScriptGistList(Action1> callback) { + reference.child(BASE).child(SCRIPT).addListenerForSingleValueEvent(new SimpleValueEventListener() { + @Override + public void onDataGet(HashMap value) { + List dataList = new ArrayList<>(); + for (Object key : value.keySet()) { + GistBase gistBase = App.getGson().fromJson(new JSONObject((Map) value.get(key)).toString(), GistBase.class); + gistBase.setId((String) key); + dataList.add(gistBase); + } + Observable.just(dataList) + .subscribe(callback); + } + }); + } + + public void getBaseProjectGistList(Action1> callback) { + reference.child(BASE).child(PROJECT).addListenerForSingleValueEvent(new SimpleValueEventListener() { + @Override + public void onDataGet(HashMap value) { + List dataList = new ArrayList<>(); + for (Object key : value.keySet()) { + GistBase gistBase = App.getGson().fromJson(new JSONObject((Map) value.get(key)).toString(), GistBase.class); + gistBase.setId((String) key); + dataList.add(gistBase); + } + Observable.just(dataList) + .subscribe(callback); + } + }); + } + + public void getGistDetail(String gistId, boolean isProj, Action1 callback) { + reference.child(GIST).child(isProj ? PROJECT : SCRIPT).child(gistId).addListenerForSingleValueEvent(new SimpleValueEventListener() { + @Override + public void onDataGet(HashMap value) { + JSONObject jsonObject = new JSONObject(value); + Gist gist = App.getGson().fromJson(String.valueOf(jsonObject), Gist.class); + + HashMap historyMap = (HashMap) value.get(HISTORY); + if (historyMap != null) { + List historyList = new ArrayList<>(); + for (Object historyKey : historyMap.keySet()) { + Gist.HistoryBean historyBean = App.getGson().fromJson(String.valueOf(new JSONObject((Map) historyMap.get(historyKey))), Gist.HistoryBean.class); + historyBean.setHistoryId((String) historyKey); + historyList.add(historyBean); + } + gist.setHistory(historyList); + } + + HashMap commentMap = (HashMap) value.get(COMMENT); + if (commentMap != null) { + List commentList = new ArrayList<>(); + for (Object commentKey : commentMap.keySet()) { + Gist.CommentBean commentBean = App.getGson().fromJson(String.valueOf(new JSONObject((Map) commentMap.get(commentKey))), Gist.CommentBean.class); + commentBean.setId((String) commentKey); + commentList.add(commentBean); + } + gist.setComment(commentList); + } + + HashMap bookmarkMap = (HashMap) value.get(BOOKMARK); + if (bookmarkMap != null) { + List bookmarkerList = new ArrayList<>(); + for (Object bookmarkKey : bookmarkMap.keySet()) { + Gist.BookmarkerBean bookmarkerBean = App.getGson().fromJson(String.valueOf(new JSONObject((Map) bookmarkMap.get(bookmarkKey))), Gist.BookmarkerBean.class); + bookmarkerBean.setId((String) bookmarkKey); + bookmarkerList.add(bookmarkerBean); + } + gist.setBookmarker(bookmarkerList); + } + Observable.just(gist) + .subscribe(callback); + } + }); + } + + // TODO: 2017-08-14 Need to fix + public void getMyGistList(Action1> callback) { + reference.child(USER).child(GIST_LIST).addListenerForSingleValueEvent(new ValueEventListener() { + @Override + public void onDataChange(DataSnapshot dataSnapshot) { + HashMap object = (HashMap) dataSnapshot.getValue(); + List userGist = new ArrayList<>(); + if (object == null) { + return; + } + for (Object key : object.keySet()) { + userGist.add(App.getGson().fromJson(new JSONObject((Map) object.get(key)).toString(), GistBase.class)); + } + Observable.just(userGist) + .subscribe(callback); + } + + @Override + public void onCancelled(DatabaseError databaseError) { + + } + }); + } + + public void getMyBookmarkList(Action1> callback) { + reference.child(USER).child(BOOKMARK).addListenerForSingleValueEvent(new ValueEventListener() { + @Override + public void onDataChange(DataSnapshot dataSnapshot) { + HashMap object = (HashMap) dataSnapshot.getValue(); + List userGist = new ArrayList<>(); + if (object == null) { + return; + } + for (Object key : object.keySet()) { + userGist.add(App.getGson().fromJson(new JSONObject((Map) object.get(key)).toString(), BookmarkerList.class)); + } + Observable.just(userGist) + .subscribe(callback); + } + + @Override + public void onCancelled(DatabaseError databaseError) { + + } + }); + } + + public void getGistCommentList(String gistId, Action1> callback) { + reference.child(GIST).child(gistId).child(COMMENT).addListenerForSingleValueEvent(new ValueEventListener() { + @Override + public void onDataChange(DataSnapshot dataSnapshot) { + HashMap object = (HashMap) dataSnapshot.getValue(); + List commentList = new ArrayList<>(); + if (object == null) { + return; + } + for (Object key : object.keySet()) { + commentList.add(App.getGson().fromJson(new JSONObject((Map) object.get(key)).toString(), Gist.CommentBean.class)); + } + Observable.just(commentList) + .subscribe(callback); + } + + @Override + public void onCancelled(DatabaseError databaseError) { + + } + }); + } + + public boolean uploadFolder(String path, DatabaseReference.CompletionListener completionListener, int[] size) { + File folder = new File(path); + // 获取该文件夹下符合后缀文件及非隐藏文件 + + if (!folder.isDirectory()) { + return false; + } + List files = FileHelper.filterExt(folder, App.getContext().getResources().getStringArray(R.array.support_file_ext), size[0]); + if (files.size() == 0) { + Toast.makeText(App.getContext(), R.string.no_file, Toast.LENGTH_SHORT).show(); + return false; + } + + if (!hasSpace(files.size())) { + return false; + } + + for (File file : files) { + uploadFile(file.getAbsolutePath(), completionListener); + } + +// // 如果在project目录下 +// if ("projects".equals(folder.getName())) { +// DatabaseReference projNode = reference.child(CLOUD).child(email).child(PROJECT).child(folder.getName().replace(".", DOT_REPLACE)); +// for (int i = 0; i < files.size(); i++) { +// String date = DateTimeHelper.AGO_FULL_DATE_FORMATTER.format(new Date(files.get(i).lastModified())); +// String subPath = files.get(i).getAbsolutePath(); +// String subKey = subPath.substring(subPath.indexOf(folder.getName()) + folder.getName().length(), subPath.length()).replace(".", DOT_REPLACE).replace("/", SLASH_REPLACE); +// if (i == files.size() - 1) { +// projNode.child(subKey) +// .child(date) +// .setValue(TextFileUtils.readTextFile(files.get(i)), completionListener); +// } else { +// projNode.child(subKey) +// .child(date) +// .setValue(TextFileUtils.readTextFile(files.get(i))); +// } +// reference.child(CLOUD) +// .child(email) +// .child(INDEX) +// .child(PROJECT) +// .child(folder.getName().replace(".", DOT_REPLACE) + subKey) +// .setValue(date); +// } +// } else if ("scripts".equals(folder.getName())) { +// // 上传整个scripts文件夹 +// for (int i = 0; i < files.size(); i++) { +// String abs_path = files.get(i).getAbsolutePath(); +// String key = abs_path +// .substring(abs_path.indexOf(SCRIPTS_PATH) + SCRIPTS_PATH.length()) +// .replace(".", DOT_REPLACE) +// .replace("/", SLASH_REPLACE); +// DatabaseReference content = reference.child(CLOUD) +// .child(email) +// .child(SCRIPT) +// .child(key) +// .child(DateTimeHelper.AGO_FULL_DATE_FORMATTER.format(new Date(files.get(i).lastModified()))); +// if (i == files.size() - 1) { +// content.setValue(TextFileUtils.readTextFile(files.get(i)), completionListener); +// } else { +// content.setValue(TextFileUtils.readTextFile(files.get(i))); +// } +// +// reference.child(CLOUD) +// .child(email) +// .child(INDEX) +// .child(SCRIPT) +// .child(key) +// .setValue(DateTimeHelper.AGO_FULL_DATE_FORMATTER.format(new Date(files.get(i).lastModified()))); +// } +// } else { +// // 普通文件夹 +// int fileListSize = files.size(); +// for (File file : files) { +// fileListSize--; +// final String rootNode = "/qpython/"; +// String node = file.getAbsolutePath().substring(path.indexOf(rootNode) + rootNode.length()); +// DatabaseReference child = +// reference +// .child(CLOUD) +// .child(email)/*.child(folder.getName().replace(".", DOT_REPLACE))*/ +// .child(OTHER) +// .child(node.replace(".", DOT_REPLACE).replace("/", SLASH_REPLACE)) +// .child(DateTimeHelper.getDate()); +// if (fileListSize == 0) { +// child.setValue(TextFileUtils.readTextFile(file), completionListener); +// } else { +// child.setValue(TextFileUtils.readTextFile(file)); +// } +// +// reference.child(CLOUD) +// .child(email) +// .child(INDEX) +// .child(OTHER) +// .child(node.replace(".", DOT_REPLACE).replace("/", SLASH_REPLACE)) +// .setValue(DateTimeHelper.getDate()); +// } +// } + return true; + } + + public boolean uploadFile(String path, DatabaseReference.CompletionListener completionListener) { + File file = new File(path); + + if (!hasSpace(1)) { + return false; + } + + if (path.contains(PROJECT_PATH)) { + String subPath = path.substring(path.indexOf(PROJECT_PATH) + PROJECT_PATH.length()); + String projName = subPath.split("/")[0]; + reference.child(CLOUD) + .child(email) + .child(PROJECT) + .child(projName) + .child(subPath.replace(".", DOT_REPLACE).replace("/", SLASH_REPLACE).replace(projName, "")) + .child(DateTimeHelper.AGO_FULL_DATE_FORMATTER.format(new Date(file.lastModified()))) + .setValue(TextFileUtils.readTextFile(file), completionListener); + + reference.child(CLOUD) + .child(email) + .child(INDEX) + .child(PROJECT) + .child(subPath.replace(".", DOT_REPLACE).replace("/", SLASH_REPLACE)) + .setValue(DateTimeHelper.AGO_FULL_DATE_FORMATTER.format(new Date(file.lastModified()))); + } else if (path.contains(SCRIPTS_PATH)) { + reference.child(CLOUD) + .child(email) + .child(SCRIPT) + .child(file.getName().replace(".", DOT_REPLACE)) + .child(DateTimeHelper.AGO_FULL_DATE_FORMATTER.format(new Date(file.lastModified()))) + .setValue(TextFileUtils.readTextFile(file), completionListener); + + reference.child(CLOUD) + .child(email) + .child(INDEX) + .child(SCRIPT) + .child(file.getName().replace(".", DOT_REPLACE)) + .setValue(DateTimeHelper.AGO_FULL_DATE_FORMATTER.format(new Date(file.lastModified()))); + } else { + final String rootNode = "/qpython/"; + String uploadKey = path.substring(path.indexOf(rootNode) + rootNode.length()); + reference.child(CLOUD) + .child(email) + .child(OTHER) + .child(uploadKey.replace(".", DOT_REPLACE).replace("/", SLASH_REPLACE)) + .child(DateTimeHelper.getDate()) + .setValue(TextFileUtils.readTextFile(file), completionListener); + + reference.child(CLOUD) + .child(email) + .child(INDEX) + .child(OTHER) + .child(uploadKey.replace(".", DOT_REPLACE).replace("/", SLASH_REPLACE)) + .setValue(DateTimeHelper.getDate()); + } + return true; + } + + public void getUploadedScripts(boolean forceRefresh, Activity context, Action1> callback) { + if (CLEAR) { + return; + } + String content = FileHelper.getFileContents(CONF.CLOUD_MAP_CACHE_PATH); + List cloudFiles = content == null ? null : App.getGson().fromJson(content, new TypeToken>() { + }.getType()); + if (cloudFiles != null && !forceRefresh) { + Observable.just(cloudFiles) + .subscribe(callback); + } else { + email = App.getUser()!=null?App.getUser().getEmail().replace(".","_"):""; + if (email.equals("")) { + Toast.makeText(context, "Waiting the firebase to initializ...",Toast.LENGTH_SHORT).show(); + + } else { + reference.child(CLOUD).child(email).child(INDEX).addListenerForSingleValueEvent(new ValueEventListener() { + @Override + public void onDataChange(DataSnapshot dataSnapshot) { + HashMap value = (HashMap) dataSnapshot.getValue(); + if (value == null) { + resetUsage(0); + Observable.just(new ArrayList()) + .subscribe(callback); + return; + } + List cloudFiles = new ArrayList<>(); + for (String node : value.keySet()) { + switch (node) { + case SCRIPT: + HashMap script; + script = (HashMap) value.get(SCRIPT); + for (String o : script.keySet()) { + CloudFile cloudFile = new CloudFile(); + cloudFile.setName(o); + cloudFile.setKey(SCRIPT + "/" + o); + cloudFile.setPath(SCRIPTS_PATH); + cloudFile.setUploadTime((String) script.get(o)); + cloudFiles.add(cloudFile); + } + break; + case PROJECT: + HashMap project = (HashMap) value.get(PROJECT); + for (String s : project.keySet()) { + CloudFile cloudFile = new CloudFile(); + cloudFile.setKey(PROJECT + "/" + s); + String[] nodes = s.split(SLASH_REPLACE); + cloudFile.setName(nodes[nodes.length - 1]); + cloudFile.setProjectName(nodes[0]); + cloudFile.setPath(s.replace(nodes[0], "")); + cloudFile.setUploadTime((String) project.get(s)); + cloudFiles.add(cloudFile); + } + break; + default: + HashMap other = (HashMap) value.get(OTHER); + for (String s : other.keySet()) { + CloudFile cloudFile = new CloudFile(); + cloudFile.setPath(s); + cloudFile.setKey(OTHER + "/" + s); + String[] nodes = s.split(SLASH_REPLACE); + boolean index = false; + for (String node1 : nodes) { + if (index) { + cloudFile.setProjectName(node1); + break; + } else if (node1.equals("projects3")) { + index = true; + } + } + cloudFile.setName(nodes[nodes.length - 1]); + cloudFile.setUploadTime((String) other.get(s)); + cloudFiles.add(cloudFile); + } + break; + } + } + resetUsage(cloudFiles.size()); + Observable.just(cloudFiles) + .subscribe(callback); + ACache.get(context).put(CLOUD_FILE, App.getGson().toJson(cloudFiles)); + + } + + @Override + public void onCancelled(DatabaseError databaseError) { + + } + }); + } + } + } + + public void getFileContent(String path, Action1 callback) { + reference.child(CLOUD) + .child(email) + .child(path) + .addListenerForSingleValueEvent(new ValueEventListener() { + @Override + public void onDataChange(DataSnapshot dataSnapshot) { + HashMap dateContent = (HashMap) dataSnapshot.getValue(); + String latest = ""; + for (String s : dateContent.keySet()) { + latest = DateTimeHelper.isLater(s, latest) ? s : latest; + } + + Observable.just(dateContent.get(latest)) + .subscribe(callback); + } + + @Override + public void onCancelled(DatabaseError databaseError) { + + } + }); + } + + public void deleteUploadScript(CloudFile cloudFile, DatabaseReference.CompletionListener listener) { + reference.child(CLOUD) + .child(email) + .child(cloudFile.getKey()) + .removeValue(listener); + reference.child(CLOUD) + .child(email) + .child(INDEX) + .child(cloudFile.getKey()) + .removeValue(); + changeUsage(-1); + } + + public void initUsage(Action1 callback) { + if (CLEAR) { + return; + } + if (currentFileCount == -1) { + if (NetStateUtil.isConnected(App.getContext())) { + reference.child(CLOUD) + .child(email) + .child(USAGE) + .addListenerForSingleValueEvent(new ValueEventListener() { + @Override + public void onDataChange(DataSnapshot dataSnapshot) { + if (dataSnapshot.getValue() != null) { + currentFileCount = ((Long) dataSnapshot.getValue()).intValue(); + } else { + currentFileCount = 0; + } + if (callback != null) { + Observable.just(currentFileCount) + .subscribe(callback); + } + } + + @Override + public void onCancelled(DatabaseError databaseError) { + + } + }); + } + } else { + callback.call(currentFileCount); + } + } + + public int changeUsage(int change) { + currentFileCount += change; + reference.child(CLOUD) + .child(email) + .child(USAGE) + .setValue(currentFileCount); + return currentFileCount; + } + + public void resetUsage(int value) { + currentFileCount = value; + reference.child(CLOUD) + .child(email) + .child(USAGE) + .setValue(currentFileCount); + } + + private boolean hasSpace(int waiting) { + if (currentFileCount < 0) { + Toast.makeText(App.getContext(), R.string.usage_not_init, Toast.LENGTH_SHORT).show(); + return false; + } else if (currentFileCount + waiting > MAX_FILE) { + Toast.makeText(App.getContext(), R.string.no_space, Toast.LENGTH_SHORT).show(); + return false; + } else { + return true; + } + } + + public interface CommentCallback { + void commentBean(Gist.CommentBean comment); + } + + private static class ShareCodeHolder { + private static final ShareCodeUtil INSTANCE = new ShareCodeUtil(); + } + + abstract class SimpleValueEventListener implements ValueEventListener { + @Override + public void onDataChange(DataSnapshot dataSnapshot) { + HashMap object = (HashMap) dataSnapshot.getValue(); + if (object == null) { + return; + } + onDataGet(object); + } + + @Override + public void onCancelled(DatabaseError databaseError) { + + } + + public abstract void onDataGet(HashMap value); + } +} \ No newline at end of file diff --git a/qpython/src/od/java/org/qpython/qpy/codeshare/pojo/CloudFile.java b/qpython/src/od/java/org/qpython/qpy/codeshare/pojo/CloudFile.java new file mode 100644 index 00000000..09a9471c --- /dev/null +++ b/qpython/src/od/java/org/qpython/qpy/codeshare/pojo/CloudFile.java @@ -0,0 +1,97 @@ +package org.qpython.qpy.codeshare.pojo; + + +import org.qpython.qpy.codeshare.CONSTANT; +import org.qpython.qpysdk.QPyConstants; + +import java.io.Serializable; + +public class CloudFile implements Serializable { + private String projectName; + private String path; + private String uploadTime; + private String name; + private String content; + private String key; + private boolean uploading; + + public String getPath() { + if (projectName == null) { + if (path.contains("scripts3")) { + return "/" + path.replace(CONSTANT.SLASH_REPLACE, "/").replace(CONSTANT.DOT_REPLACE, "."); + } else if (path.contains("scripts")) { + return path.replace(CONSTANT.SLASH_REPLACE, "/") + getName(); + } else { + return "/" + path.replace(CONSTANT.SLASH_REPLACE, "/").replace(CONSTANT.DOT_REPLACE, "."); + } + } else { + String projNode = path.contains("projects3") ? "projects3/" : "projects/"; + return "/" + projNode + getProjectName() + path + .replace(CONSTANT.SLASH_REPLACE, "/") + .replace(CONSTANT.DOT_REPLACE, ".") + .replace(projNode, "") + .replace(getProjectName(), ""); + } + } + + public void setPath(String path) { + this.path = path; + } + + public String getUploadTime() { + return uploadTime; + } + + public void setUploadTime(String uploadTime) { + this.uploadTime = uploadTime; + } + + public String getName() { + return name.replace(CONSTANT.DOT_REPLACE, "."); + } + + public void setName(String name) { + this.name = name; + } + + public String getContent() { + return content; + } + + public void setContent(String content) { + this.content = content; + } + + public long getContentSize() { + return content == null ? 0 : content.length(); + } + + public String getProjectName() { + return projectName == null ? null : projectName. + replace(CONSTANT.SLASH_REPLACE, "/").replace(CONSTANT.DOT_REPLACE, "."); + } + + public void setProjectName(String projectName) { + this.projectName = projectName; + } + + public boolean isUploading() { + return uploading; + } + + public void setUploading(boolean uploading) { + this.uploading = uploading; + } + + public String getKey() { + return key == null ? getName().replace(".", CONSTANT.DOT_REPLACE) : key; + } + + public void setKey(String key) { + this.key = key; + } + + public String getAbsolutePath() { + return QPyConstants.ABSOLUTE_PATH + getPath(); + } +} diff --git a/qpython/src/od/java/org/qpython/qpy/main/PayActivity.java b/qpython/src/od/java/org/qpython/qpy/main/PayActivity.java new file mode 100644 index 00000000..c1a7b8d2 --- /dev/null +++ b/qpython/src/od/java/org/qpython/qpy/main/PayActivity.java @@ -0,0 +1,188 @@ +package org.qpython.qpy.main; + +import android.app.PendingIntent; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.IntentSender; +import android.content.ServiceConnection; +import android.os.Bundle; +import android.os.IBinder; +import android.os.RemoteException; +import android.support.annotation.Nullable; +import android.support.v7.app.AppCompatActivity; +import android.support.v7.widget.GridLayoutManager; +import android.view.MenuItem; +import android.view.View; +import android.widget.Toast; + +import com.android.vending.billing.IInAppBillingService; +import com.quseit.util.ImageUtil; + +import org.json.JSONException; +import org.json.JSONObject; +import org.qpython.qpy.R; +import org.qpython.qpy.main.activity.PurchaseActivity; +import org.qpython.qpy.main.server.MySubscriber; +import org.qpython.qpy.main.widget.GridSpace; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import rx.Observable; +import rx.android.schedulers.AndroidSchedulers; +import rx.schedulers.Schedulers; + +/** + * Created by Hmei + * 1/30/18. + */ + +public class PayActivity extends AppCompatActivity { + public static final int BUY_REQUEST_CODE = 2333; + private IInAppBillingService mService; + private ServiceConnection mServiceConn; + private ArrayList skuList; + private MySubscriber callback; + + @Override + protected void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + } + + void initIAB(ArrayList skuList, MySubscriber callback) { + this.skuList = skuList; + this.callback = callback; + mServiceConn = new ServiceConnection() { + @Override + public void onServiceDisconnected(ComponentName name) { + mService = null; + } + + @Override + public void onServiceConnected(ComponentName name, + IBinder service) { + mService = IInAppBillingService.Stub.asInterface(service); + getPrices(skuList, callback); + } + }; + + Intent serviceIntent = new Intent("com.android.vending.billing.InAppBillingService.BIND"); + serviceIntent.setPackage("com.android.vending"); + bindService(serviceIntent, mServiceConn, Context.BIND_AUTO_CREATE); + } + + /** + * 从Google服务器获取不同国家价格表并显示 + */ + private void getPrices(ArrayList skuList, MySubscriber callback) { + if (mService == null) { + Toast.makeText(this, R.string.lose_google_server, Toast.LENGTH_SHORT).show(); + if (findViewById(R.id.pb) != null) findViewById(R.id.pb).setVisibility(View.GONE); + return; + } +// ArrayList skuList; +// if (!isCrowdFunding) { +// skuList = new ArrayList<>(Arrays.asList(getResources().getStringArray(R.array.sku))); +// } else { +// skuList = new ArrayList<>(Arrays.asList(getResources().getStringArray(R.array.crowdfunding))); +// int index = (int) (getIntent().getIntExtra(PERCENT, 0) / 100.00 * 4); +// String sku = skuList.get(index); +// skuList.clear(); +// skuList.add(sku); +// } + Bundle querySkus = new Bundle(); + querySkus.putStringArrayList("ITEM_ID_LIST", skuList); + try { + Observable.just(mService.getSkuDetails(3, getPackageName(), "inapp", querySkus)) + .map(bundle -> { + ArrayList responseList = bundle.getStringArrayList("DETAILS_LIST"); + if (responseList == null) { + return null; + } + String[] prices = new String[responseList.size()]; + for (int i = 0; i < responseList.size(); i++) { + JSONObject object; + try { + object = new JSONObject(responseList.get(i)); + String price = object.getString("price"); + prices[i] = price; + } catch (JSONException e) { + e.printStackTrace(); + } + } + Arrays.sort(prices, (o1, o2) -> Integer.parseInt(o1.replaceAll("[^0-9]", "")) - Integer.parseInt(o2.replaceAll("[^0-9]", ""))); + return prices; + } + ) + .subscribeOn(Schedulers.newThread()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(callback +// new MySubscriber() { +// @Override +// public void onNext(String[] o) { +// super.onNext(o); +// if (o == null) { +// return; +// } +// invalidateData(); +// GridSpace gridSpace = new GridSpace(2, (int) ImageUtil.dp2px(16), false); +// binding.list.setLayoutManager(new GridLayoutManager(PurchaseActivity.this, 2)); +// binding.list.addItemDecoration(gridSpace); +// binding.list.setAdapter(new PurchaseActivity.ListAdapter(o)); +// } +// } + ); + } catch (RemoteException e) { + e.printStackTrace(); + } + } + + private void purchase(String sku) { +// String[] skus; +// if (isCrowdFunding) { +// skus = getResources().getStringArray(R.array.crowdfunding); +// } else { +// skus = getResources().getStringArray(R.array.sku); +// } + try { + if (mService == null) { + Toast.makeText(this, R.string.lose_google_server, Toast.LENGTH_SHORT).show(); + return; + } + Bundle buyIntentBundle = mService.getBuyIntent(3, getPackageName(), + sku, "inapp", "bGoa+V7g/yqDXvKRqq+JTFn4uQZbPiQJo4pf9RzJ"); + switch (buyIntentBundle.getInt("RESPONSE_CODE")) { + case 0: + //BILLING_RESPONSE_RESULT_OK + PendingIntent pendingIntent = buyIntentBundle.getParcelable("BUY_INTENT"); + startIntentSenderForResult(pendingIntent.getIntentSender(), + BUY_REQUEST_CODE, new Intent(), 0, 0, 0); + break; + + } + } catch (RemoteException | IntentSender.SendIntentException | NullPointerException e) { + e.printStackTrace(); + } + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + if (item.getItemId() == R.id.refresh_menu) { + if (findViewById(R.id.pb) != null) findViewById(R.id.pb).setVisibility(View.VISIBLE); + getPrices(skuList, callback); + return true; + } + return super.onOptionsItemSelected(item); + } + + @Override + public void onDestroy() { + super.onDestroy(); + if (mService != null) { + unbindService(mServiceConn); + } + } + +} diff --git a/qpython/src/od/java/org/qpython/qpy/main/activity/BaseActivity.java b/qpython/src/od/java/org/qpython/qpy/main/activity/BaseActivity.java new file mode 100644 index 00000000..d3e259f8 --- /dev/null +++ b/qpython/src/od/java/org/qpython/qpy/main/activity/BaseActivity.java @@ -0,0 +1,301 @@ +package org.qpython.qpy.main.activity; + +import android.content.ActivityNotFoundException; +import android.content.Context; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; +import android.content.res.Configuration; +import android.net.Uri; +import android.os.Build; +import android.os.Bundle; +import android.os.Environment; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.v4.app.ActivityCompat; +import android.support.v4.content.ContextCompat; +import android.support.v4.util.ArrayMap; +import android.support.v7.app.AlertDialog; +import android.support.v7.app.AppCompatActivity; +import android.widget.ArrayAdapter; +import android.widget.Toast; +import android.text.TextUtils; + +import com.quseit.util.NAction; +import com.quseit.util.NUtil; + +import org.qpython.qpy.R; +import org.qpython.qpy.console.ShellTermSession; +import org.qpython.qpy.console.util.TermSettings; +import org.renpy.android.ResourceManager; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.UnsupportedEncodingException; +import java.net.URLEncoder; +import java.text.MessageFormat; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +import org.qpython.qpy.main.server.gist.TokenManager; + +public class BaseActivity extends AppCompatActivity { + // QPython interfaces + private static final int SCRIPT_CONSOLE_CODE = 1237; + private static final int PID_INIT_VALUE = -1; + private static final int DEFAULT_BUFFER_SIZE = 8192; + private static final int LOG_NOTIFICATION_ID = (int) System.currentTimeMillis(); + + + //private FirebaseAnalytics mFirebaseAnalytics; + private ArrayList mArguments = new ArrayList<>(); + private InputStream mIn; + private OutputStream mOut; + private Map mActionMap = new ArrayMap<>(); + private TermSettings mSettings; + private ShellTermSession session; + + private boolean permissionGrant = true; + + protected static ShellTermSession createTermSession(Context context, TermSettings settings, String initialCommand, String path) { + ShellTermSession session = null; + try { + session = new ShellTermSession(context, settings, initialCommand, path); + session.setProcessExitMessage(context.getString(R.string.process_exit_message)); + + } catch (IOException e) { + e.printStackTrace(); + } + + return session; + } + + protected void toast(String content) { + Toast.makeText(this, content, Toast.LENGTH_SHORT).show(); + } + + @Override + protected void onResume() { + super.onResume(); + } + + @Override + protected void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + //mFirebaseAnalytics = FirebaseAnalytics.getInstance(this); + } + + @Override + protected void onPause() { + super.onPause(); + } + + @Override + public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { + PermissionAction action = mActionMap.get(requestCode); + if (action != null) { + if (grantResults.length > 0 && (grantResults[0] == PackageManager.PERMISSION_GRANTED)) { + action.onGrant(); + } else { + action.onDeny(); + } + + } + mActionMap.remove(action); + } + + public final void checkPermissionDo(String[] permissions, PermissionAction action) { + if (Build.VERSION.SDK_INT >= 23) { + boolean granted = true; + for (String permission : permissions) { + int checkPermission = ContextCompat.checkSelfPermission(this, permission); + granted = checkPermission == PackageManager.PERMISSION_GRANTED; + } + if (!granted) { + int code = permissions.hashCode() & 0xffff; + mActionMap.put(code, action); + ActivityCompat.requestPermissions(this, permissions, code); + } else { + action.onGrant(); + } + } else { + action.onGrant(); + } + } + + + // feedback + public void onFeedback(String feedback) { + String app = getString(R.string.app_name); + int ver = NUtil.getVersinoCode(getApplicationContext()); + String subject = MessageFormat.format(getString(com.quseit.android.R.string.feeback_email_title), app, ver, Build.PRODUCT); + + String lastError = ""; + String code = NAction.getCode(getApplicationContext()); + File log = new File(Environment.getExternalStorageDirectory() + "/" + code + "_last_err.log"); + if (log.exists()) { + lastError = com.quseit.util.FileHelper.getFileContents(log.getAbsolutePath()); + } + + String body = feedback.isEmpty() ? feedback : MessageFormat.format(getString(R.string.feedback_email_body), Build.PRODUCT, + Build.VERSION.RELEASE, Build.VERSION.SDK, lastError, feedback); + + + Intent twitterIntent = getPackageManager().getLaunchIntentForPackage("com.twitter.android"); + if (twitterIntent != null) { + List list = new ArrayList<>(); + list.add("Feedback with Twitter"); + list.add("Feedback with Email"); + new AlertDialog.Builder(this) + .setTitle(R.string.feedback) + .setNegativeButton(R.string.close, (dialog, which) -> dialog.dismiss()) + .setAdapter(new ArrayAdapter<>(this, R.layout.dialog_feedback, list), (dialog, which) -> { + if (which == 0) { + twitter(body); + } else { + email(subject, body); + } + }) + .show(); + } else { + email(subject, body); + } + } + + private void email(String subject, String body) { + Intent intent = new Intent(Intent.ACTION_SENDTO, Uri.parse("mailto:" + getString(R.string.ui_feedback_mail))); + + intent.putExtra(Intent.EXTRA_SUBJECT, subject); + intent.putExtra(Intent.EXTRA_TEXT, session == null ? body : session.getTranscriptText().trim()); + try { + startActivity(Intent.createChooser(intent, + getString(R.string.email_transcript_chooser_title))); + } catch (ActivityNotFoundException e) { + Toast.makeText(this, + R.string.email_transcript_no_email_activity_found, + Toast.LENGTH_LONG) + .show(); + } + } + + private void twitter(String message) { + Intent tweetIntent = new Intent(Intent.ACTION_SEND); + if (message.isEmpty()){ + tweetIntent.putExtra(Intent.EXTRA_TEXT, "@qpython,"); + }else { + tweetIntent.putExtra(Intent.EXTRA_TEXT, "@qpython\n" + message); + } + tweetIntent.setType("text/plain"); + + PackageManager packManager = getPackageManager(); + List resolvedInfoList = packManager.queryIntentActivities(tweetIntent, PackageManager.MATCH_DEFAULT_ONLY); + + boolean resolved = false; + for (ResolveInfo resolveInfo : resolvedInfoList) { + if (resolveInfo.activityInfo.packageName.startsWith("com.twitter.android")) { + tweetIntent.setClassName( + resolveInfo.activityInfo.packageName, + resolveInfo.activityInfo.name); + resolved = true; + break; + } + } + if (resolved) { + startActivity(tweetIntent); + } else { + Intent i = new Intent(); + i.putExtra(Intent.EXTRA_TEXT, message); + i.setAction(Intent.ACTION_VIEW); + i.setData(Uri.parse("https://twitter.com/intent/tweet?text=" + urlEncode(message))); + startActivity(i); + Toast.makeText(this, "Twitter app isn't found", Toast.LENGTH_LONG).show(); + } + } + + private String urlEncode(String s) { + try { + return URLEncoder.encode(s, "UTF-8"); + } catch (UnsupportedEncodingException e) { + e.printStackTrace(); + return ""; + } + } + + protected boolean checkExpired(final String resource, String filesDir, String tag) { + ResourceManager resourceManager = new ResourceManager(this); + + String data_version = resourceManager.getString(resource + "_version"); + String disk_version = "0"; + + // If no version, no unpacking is necessary. + if (data_version == null) { + return false; + } + + // Check the current disk version, if any. + String disk_version_fn = filesDir + "/" + tag + "_" + resource + ".version"; + + try { + byte buf[] = new byte[64]; + InputStream is = new FileInputStream(disk_version_fn); + int len = is.read(buf); + disk_version = new String(buf, 0, len); + is.close(); + } catch (Exception e) { + + disk_version = "0"; + + } + + if (!NUtil.isNumeric(disk_version)) { + disk_version = "0"; + + } + + if ((int) (Double.parseDouble(data_version) - Double.parseDouble(disk_version)) > 0 || disk_version.equals("0")) { + try { + FileOutputStream os = new FileOutputStream(disk_version_fn); + try { + os.write(data_version.getBytes()); + os.close(); + + } catch (IOException e) { + e.printStackTrace(); + //Mint.logException(e); + + } + + } catch (FileNotFoundException e) { + e.printStackTrace(); + //Mint.logException(e); + + } + return true; + } else { + return false; + } + } + + public interface PermissionAction { + void onGrant(); + + void onDeny(); + } + + public void ifLogin(Login afterLogin) { + if (!TextUtils.isEmpty(TokenManager.getToken())) { + afterLogin.process(); + } else { + Toast.makeText(this, R.string.login_first, Toast.LENGTH_SHORT).show(); + } + } + public interface Login { + void process(); + } +} \ No newline at end of file diff --git a/qpython/src/od/java/org/qpython/qpy/main/activity/FundingPurchaseActivity.java b/qpython/src/od/java/org/qpython/qpy/main/activity/FundingPurchaseActivity.java new file mode 100644 index 00000000..6e6cf10a --- /dev/null +++ b/qpython/src/od/java/org/qpython/qpy/main/activity/FundingPurchaseActivity.java @@ -0,0 +1,143 @@ +package org.qpython.qpy.main.activity; + +import android.content.Context; +import android.content.Intent; +import android.databinding.DataBindingUtil; +import android.os.Bundle; +import android.support.annotation.Nullable; +import android.view.Menu; +import android.view.View; +import android.widget.Toast; + +import com.quseit.util.VeDate; + +import org.json.JSONException; +import org.json.JSONObject; +import org.qpython.qpy.R; +import org.qpython.qpy.databinding.ActivityFundingPurchaseBinding; +import org.qpython.qpy.main.app.App; +import org.qpython.qpy.main.server.MySubscriber; +import org.qpython.qpy.main.server.model.GooglePurchaseModel; +import org.qpython.qpy.utils.UpdateHelper; + +import java.util.ArrayList; +import java.util.Arrays; + +/** + * Created by Hmei + * 1/30/18. + */ + +public class FundingPurchaseActivity extends PayActivity { + private static final String ARTICLE_ID = "article_id"; + private static final String FUNDING_COUNT = "fundingCount"; + // private final String[] prices = get + private ActivityFundingPurchaseBinding binding; + private String sku; + + public static void startSupport(Context context, String articleId, int fundingPercent) { + Intent starter = new Intent(context, FundingPurchaseActivity.class); + starter.putExtra(ARTICLE_ID, articleId); + starter.putExtra(FUNDING_COUNT, fundingPercent); + context.startActivity(starter); + } + + @Override + protected void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + binding = DataBindingUtil.setContentView(this, R.layout.activity_funding_purchase); + binding.pb.setVisibility(View.VISIBLE); + initView(); + initPrice(); + initListener(); + } + + private void initView() { + setSupportActionBar(binding.lt.toolbar); + binding.lt.toolbar.setNavigationIcon(R.drawable.ic_back); + binding.lt.toolbar.setNavigationOnClickListener(v -> finish()); + setTitle(R.string.reward); + } + + private void initPrice() { + String[] skus = getResources().getStringArray(R.array.crowdfunding); + int fundingCount = getIntent().getIntExtra(FUNDING_COUNT, 0); + if (fundingCount < 100) { + sku = skus[0]; + } else if (fundingCount < 500) { + sku = skus[1]; + } else if (fundingCount < 2000) { + sku = skus[2]; + } else { + sku = skus[3]; + } + ArrayList skuList = new ArrayList<>(); + skuList.add(sku); + initIAB(skuList, new MySubscriber() { + @Override + public void onNext(String[] o) { + super.onNext(o); + if (o == null) { + return; + } + invalidateData(true); + binding.price.setText(o[0]); + } + }); + } + + private void initListener() { + binding.price.setOnClickListener(v -> payUtil.purchase(sku)); + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + getMenuInflater().inflate(R.menu.refresh_menu, menu); + return true; + } + + private void invalidateData(boolean showData) { + binding.price.setVisibility(showData ? View.VISIBLE : View.GONE); + binding.tvThanks.setVisibility(showData ? View.VISIBLE : View.GONE); + binding.noData.llRoot.setVisibility(showData ? View.GONE : View.VISIBLE); + binding.pb.setVisibility(View.GONE); + } + + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent data) { + super.onActivityResult(requestCode, resultCode, data); + if (requestCode == BUY_REQUEST_CODE) { + if (resultCode == RESULT_OK) { + GooglePurchaseModel model = App.getGson().fromJson(data.getStringExtra("INAPP_PURCHASE_DATA"), GooglePurchaseModel.class); + if (model == null) { + return; + } + switch (model.getProductId()) { + default: + Toast.makeText(this, R.string.thanks_your_support, Toast.LENGTH_SHORT).show(); + break; + } + // 统计赞赏数据 + JSONObject jsonObject = new JSONObject(); + try { + jsonObject.put("type", model.getProductId()); + jsonObject.put("time", VeDate.getStringDateHourAsInt()); + int percent = getIntent().getIntExtra(FUNDING_COUNT, 0); + String[] fundingCount = getResources().getStringArray(R.array.funding_count_divider); + jsonObject.put("crowdfunding", + percent > (Integer.parseInt(fundingCount[1]) / Integer.parseInt(fundingCount[2])) ? 3 : + percent > (Integer.parseInt(fundingCount[0]) / Integer.parseInt(fundingCount[1])) ? 2 : 1);//0 非众筹/ 1: 0-100 /2: 100-500/3 500-2000 + jsonObject.put("articleId", getIntent().getStringExtra(ARTICLE_ID)); + if (App.getUser() != null) { + jsonObject.put("account", App.getUser().getEmail()); + } + } catch (JSONException e) { + e.printStackTrace(); + } + UpdateHelper.submitIAPLog(this, model.getOrderId(), App.getGson().toJson(jsonObject)); + payUtil.digestPurchase(model.getPurchaseToken()); + finish(); + } + } + } +} diff --git a/qpython/src/od/java/org/qpython/qpy/main/activity/PayActivity.java b/qpython/src/od/java/org/qpython/qpy/main/activity/PayActivity.java new file mode 100644 index 00000000..c637254e --- /dev/null +++ b/qpython/src/od/java/org/qpython/qpy/main/activity/PayActivity.java @@ -0,0 +1,54 @@ +package org.qpython.qpy.main.activity; + +import android.os.Bundle; +import android.support.annotation.Nullable; +import android.support.v7.app.AppCompatActivity; +import android.view.MenuItem; +import android.view.View; + +import org.qpython.qpy.R; +import org.qpython.qpy.main.server.MySubscriber; +import org.qpython.qpy.main.service.PayUtil; + +import java.util.ArrayList; + +/** + * Created by Hmei + * 1/30/18. + */ + +public class PayActivity extends AppCompatActivity { + public static final int BUY_REQUEST_CODE = 2333; + private ArrayList skuList; + private MySubscriber callback; + protected PayUtil payUtil; + + @Override + protected void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + payUtil = new PayUtil(this); + } + + protected void initIAB(ArrayList skuList, MySubscriber callback) { + this.skuList = skuList; + this.callback = callback; + payUtil.initIAP(() -> payUtil.getPrices(skuList,callback)); + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + if (item.getItemId() == R.id.refresh_menu) { + if (findViewById(R.id.pb) != null) findViewById(R.id.pb).setVisibility(View.VISIBLE); + payUtil.getPrices(skuList, callback); + return true; + } + return super.onOptionsItemSelected(item); + } + + @Override + public void onDestroy() { + super.onDestroy(); + payUtil.unbindPayService(); + } + +} diff --git a/qpython/src/od/java/org/qpython/qpy/main/activity/PurchaseActivity.java b/qpython/src/od/java/org/qpython/qpy/main/activity/PurchaseActivity.java new file mode 100644 index 00000000..74335cc2 --- /dev/null +++ b/qpython/src/od/java/org/qpython/qpy/main/activity/PurchaseActivity.java @@ -0,0 +1,158 @@ +package org.qpython.qpy.main.activity; + +import android.content.Context; +import android.content.Intent; +import android.databinding.DataBindingUtil; +import android.os.Bundle; +import android.support.annotation.Nullable; +import android.support.v7.widget.GridLayoutManager; +import android.support.v7.widget.RecyclerView; +import android.view.LayoutInflater; +import android.view.Menu; +import android.view.View; +import android.view.ViewGroup; +import android.widget.Toast; + +import com.quseit.util.ImageUtil; +import com.quseit.util.VeDate; + +import org.json.JSONException; +import org.json.JSONObject; +import org.qpython.qpy.R; +import org.qpython.qpy.databinding.ActivityPurchaseBinding; +import org.qpython.qpy.databinding.ItemPriceBinding; +import org.qpython.qpy.main.adapter.MyViewHolder; +import org.qpython.qpy.main.app.App; +import org.qpython.qpy.main.server.MySubscriber; +import org.qpython.qpy.main.server.model.GooglePurchaseModel; +import org.qpython.qpy.main.widget.GridSpace; +import org.qpython.qpy.utils.UpdateHelper; + +import java.util.ArrayList; +import java.util.Arrays; + +/** + * Google purchase activity + * Created by Hmei on 2017-07-19. + */ + +public class PurchaseActivity extends PayActivity { + + private static final String ARTICLE_ID = "article_id"; + + private ActivityPurchaseBinding binding; + private ArrayList skus; + + public static void start(Context context, String articleId) { + Intent starter = new Intent(context, PurchaseActivity.class); + starter.putExtra(ARTICLE_ID, articleId); + context.startActivity(starter); + } + + + @Override + protected void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + binding = DataBindingUtil.setContentView(this, R.layout.activity_purchase); + skus = new ArrayList<>(Arrays.asList(getResources().getStringArray(R.array.sku))); + + initIAB(skus, new MySubscriber() { + @Override + public void onNext(String[] o) { + super.onNext(o); + if (o == null) { + return; + } + invalidateData(); + GridSpace gridSpace = new GridSpace(2, (int) ImageUtil.dp2px(16), false); + binding.list.setLayoutManager(new GridLayoutManager(PurchaseActivity.this, 2)); + binding.list.addItemDecoration(gridSpace); + binding.list.setAdapter(new PurchaseActivity.ListAdapter(o)); + } + }); + + initView(); + } + + private void initView() { + setSupportActionBar(binding.lt.toolbar); + binding.lt.toolbar.setNavigationIcon(R.drawable.ic_back); + binding.lt.toolbar.setNavigationOnClickListener(v -> finish()); + setTitle(R.string.reward); + } + + private void invalidateData() { + binding.tvThanks.setVisibility(View.VISIBLE); + binding.list.setVisibility(View.VISIBLE); + binding.noData.llRoot.setVisibility(View.GONE); + } + + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + getMenuInflater().inflate(R.menu.refresh_menu, menu); + return true; + } + + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent data) { + super.onActivityResult(requestCode, resultCode, data); + if (requestCode == BUY_REQUEST_CODE) { + if (resultCode == RESULT_OK) { + GooglePurchaseModel model = App.getGson().fromJson(data.getStringExtra("INAPP_PURCHASE_DATA"), GooglePurchaseModel.class); + if (model == null) { + return; + } + switch (model.getProductId()) { + default: + Toast.makeText(this, R.string.thanks_your_support, Toast.LENGTH_SHORT).show(); + break; + } + // 统计赞赏数据 + JSONObject jsonObject = new JSONObject(); + try { + jsonObject.put("type", model.getProductId()); + jsonObject.put("time", VeDate.getStringDateHourAsInt()); + jsonObject.put("crowdfunding", 0);//0 非众筹/ 1: 0-100 /2: 100-500/3 500-2000 + jsonObject.put("articleId", getIntent().getStringExtra(ARTICLE_ID)); + if (App.getUser() != null) { + jsonObject.put("account", App.getUser().getEmail()); + } + } catch (JSONException e) { + e.printStackTrace(); + } + UpdateHelper.submitIAPLog(this, model.getOrderId(), App.getGson().toJson(jsonObject)); + payUtil.digestPurchase(model.getPurchaseToken()); + finish(); + } + } + } + + private class ListAdapter extends RecyclerView.Adapter> { + String[] dataList; + + ListAdapter(String[] dataList) { + this.dataList = dataList; + } + + @Override + public MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { + ItemPriceBinding binding = DataBindingUtil.inflate(LayoutInflater.from(PurchaseActivity.this), R.layout.item_price, parent, false); + MyViewHolder holder = new MyViewHolder<>(binding.getRoot()); + holder.setBinding(binding); + return holder; + } + + @Override + public void onBindViewHolder(MyViewHolder holder, int position) { + ItemPriceBinding binding = holder.getBinding(); + binding.tvPrices.setText(dataList[position]); + binding.tvPrices.setOnClickListener(v -> payUtil.purchase(skus.get(position))); + } + + @Override + public int getItemCount() { + return dataList.length; + } + } +} diff --git a/qpython/src/od/java/org/qpython/qpy/main/activity/SignInActivity.java b/qpython/src/od/java/org/qpython/qpy/main/activity/SignInActivity.java new file mode 100644 index 00000000..b5fc5d2c --- /dev/null +++ b/qpython/src/od/java/org/qpython/qpy/main/activity/SignInActivity.java @@ -0,0 +1,184 @@ +package org.qpython.qpy.main.activity; + +import android.content.Intent; +import android.databinding.DataBindingUtil; +import android.net.Uri; +import android.os.Bundle; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.v7.app.AppCompatActivity; +import android.text.Html; +import android.util.Log; +import android.view.View; +import android.widget.Toast; + +import com.google.android.gms.auth.api.Auth; +import com.google.android.gms.auth.api.signin.GoogleSignInAccount; +import com.google.android.gms.auth.api.signin.GoogleSignInOptions; +import com.google.android.gms.auth.api.signin.GoogleSignInResult; +import com.google.android.gms.common.ConnectionResult; +import com.google.android.gms.common.api.GoogleApiClient; +import com.google.firebase.auth.AuthCredential; +import com.google.firebase.auth.FirebaseAuth; +import com.google.firebase.auth.FirebaseUser; +import com.google.firebase.auth.GoogleAuthProvider; + +import org.qpython.qpy.R; +import org.qpython.qpy.databinding.ActivitySignInBinding; +import org.qpython.qpy.main.app.App; +import org.qpython.qpy.main.app.CONF; +import org.qpython.qpy.main.app.User; +import org.qpython.qpy.main.server.gist.loginScreen.LoginControler; +import org.qpython.qpy.main.server.gist.loginScreen.LoginView; + +/** + * SignIn + * Created by Hmei on 2017-08-04. + */ + +public class SignInActivity extends AppCompatActivity implements GoogleApiClient.OnConnectionFailedListener ,LoginView{ + private static final int RC_SIGN_IN = 54503; + + private GoogleApiClient mGoogleApiClient; + private ActivitySignInBinding binding; + private FirebaseAuth mAuth; + + private LoginControler mLoginControler; + + { + mAuth = FirebaseAuth.getInstance(); + FirebaseUser currentUser = mAuth.getCurrentUser(); + initUserInfo(currentUser); + } + + @Override + protected void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + binding = DataBindingUtil.setContentView(this, R.layout.activity_sign_in); + binding.textView3.setText(Html.fromHtml(getString(R.string.by_signing_in_you_agree_to_out_privacy_policy_term_of_service))); + GoogleSignInOptions gso = new GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_SIGN_IN) + .requestIdToken(CONF.GOOGLE_ID_TOKEN) + .requestEmail() + .build(); + + mGoogleApiClient = new GoogleApiClient.Builder(this) + .enableAutoManage(this, this) + .addApi(Auth.GOOGLE_SIGN_IN_API, gso) + .addOnConnectionFailedListener(connectionResult -> showToast(getString(R.string.lost_google_hint))) + .build(); + initListener(); + + mLoginControler = new LoginControler(this); + } + + private void initListener() { + binding.textView3.setOnClickListener(v -> startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(getString(R.string.privacy_html))))); + binding.button2.setOnClickListener(v -> signIn()); + binding.button3.setOnClickListener(v -> finish()); + } + + private void signIn() { + Intent signInIntent = Auth.GoogleSignInApi.getSignInIntent(mGoogleApiClient); + startActivityForResult(signInIntent, RC_SIGN_IN); + } + + @Override + public void onActivityResult(int requestCode, int resultCode, Intent data) { + super.onActivityResult(requestCode, resultCode, data); + // Result returned from launching the Intent from GoogleSignInApi.getSignInIntent(...); + if (requestCode == RC_SIGN_IN) { + GoogleSignInResult result = Auth.GoogleSignInApi.getSignInResultFromIntent(data); + if (result.isSuccess()) { + GoogleSignInAccount account = result.getSignInAccount(); + firebaseAuthWithGoogle(account); + } else { + Log.e("LOGIN", result.getStatus().toString()); + try { + String msg = result.getStatus().getStatusMessage().trim(); + showToast(getString(R.string.login_error) + (msg.equals("") ? "" : ": ") + msg); + initUserInfo(null); + } catch (NullPointerException ignore) { + showToast(getString(R.string.no_google)); + } + } + } + } + + private void firebaseAuthWithGoogle(GoogleSignInAccount acct) { + showLoading(); + AuthCredential credential = GoogleAuthProvider.getCredential(acct.getIdToken(), "45:FD:60:98:01:9A:37:D9:84:03:06:36:02:F6:85:2C:A2:1F:B8:67"); + mAuth.signInWithCredential(credential) + .addOnCompleteListener(this, task -> { + if (task.isSuccessful()) { + FirebaseUser user = mAuth.getCurrentUser(); + initUserInfo(user); + } else { + showToast(getString(R.string.auth_failed)); + initUserInfo(null); + hideLoading(); + } + }); + } + + private void initUserInfo(FirebaseUser currentUser) { + if (currentUser == null) { + App.setUser(null); + return; + } + Log.d("SingInActivity", "NICK:"+currentUser.getDisplayName()+"-UN:"+currentUser.getEmail()); + User user = new User(); + user.setUserId(currentUser.getUid()); + user.setAvatarUrl(currentUser.getPhotoUrl() == null ? "" : currentUser.getPhotoUrl().toString()); + user.setEmail(currentUser.getEmail()); + user.setUserName(currentUser.getEmail()); + user.setNick(currentUser.getDisplayName()); + App.setUser(user); + if (mLoginControler!=null) { + mLoginControler.login(user); + } else { + Toast.makeText(this, R.string.signin_error, Toast.LENGTH_SHORT).show(); + } + } + + @Override + public void onConnectionFailed(@NonNull ConnectionResult connectionResult) { + initUserInfo(null); + } + + @Override + public void showLoading() { + binding.progressBar2.setVisibility(View.VISIBLE); + } + + @Override + public void hideLoading() { + binding.progressBar2.setVisibility(View.GONE); + } + + @Override + public void showToast(String msg) { + Toast.makeText(SignInActivity.this, msg, Toast.LENGTH_SHORT) + .show(); + } + + @Override + public void loginSuccess() { + Log.d("SignInActivity", "loginSuccess"); +// setResult(RESULT_OK); +// this.finish(); + + setResult(RESULT_OK); + binding.progressBar2.setVisibility(View.GONE); + showToast("login successfully"); + finish(); + } + + @Override + protected void onDestroy() { + super.onDestroy(); + if (mLoginControler!=null) { + + mLoginControler.onDestroy(); + } + } +} \ No newline at end of file diff --git a/qpython/src/od/java/org/qpython/qpy/main/activity/UserActivity.java b/qpython/src/od/java/org/qpython/qpy/main/activity/UserActivity.java new file mode 100644 index 00000000..b142018e --- /dev/null +++ b/qpython/src/od/java/org/qpython/qpy/main/activity/UserActivity.java @@ -0,0 +1,76 @@ +package org.qpython.qpy.main.activity; + +import android.content.Context; +import android.content.Intent; +import android.databinding.DataBindingUtil; +import android.os.Bundle; +import android.support.annotation.Nullable; +import android.support.v7.app.AlertDialog; +import android.support.v7.app.AppCompatActivity; +import android.view.View; + +import com.google.firebase.auth.FirebaseAuth; +import com.quseit.util.ImageDownLoader; + +import org.qpython.qpy.R; +import org.qpython.qpy.codeshare.CONSTANT; +import org.qpython.qpy.codeshare.ShareCodeUtil; +import org.qpython.qpy.databinding.ActivityUserBinding; +import org.qpython.qpy.main.app.App; +import org.qpython.qpy.main.app.CONF; + +import java.io.File; + +public class UserActivity extends AppCompatActivity { + ActivityUserBinding binding; + + public static void start(Context context) { + Intent starter = new Intent(context, UserActivity.class); + context.startActivity(starter); + } + + @Override + protected void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + binding = DataBindingUtil.setContentView(this, R.layout.activity_user); + setSupportActionBar(binding.toolbar); + setTitle(getString(R.string.me)); + binding.toolbar.setNavigationIcon(R.drawable.ic_back); + binding.toolbar.setNavigationOnClickListener(v -> finish()); + + ImageDownLoader.setImageFromUrl(this, binding.avatar, App.getUser().getAvatarUrl()); + binding.name.setText(App.getUser().getNick()); + //binding.email.setText(App.getUser().getEmail()); + + binding.usage.setText(R.string.my_space_empty); + binding.logout.setOnClickListener(v -> logout()); + binding.myShareLayout.setOnClickListener(v -> MyGistActivity.startMyShare(UserActivity.this)); + setUsage(); + } + + private void setUsage() { + ShareCodeUtil.getInstance().initUsage(integer -> + binding.usage.setText(getString(R.string.my_space, integer == null ? 0 : integer))); + } + + private void logout() { + new AlertDialog.Builder(this, R.style.MyDialog) + .setTitle(R.string.lout) + .setPositiveButton(R.string.yes, (dialog, which) -> { + FirebaseAuth.getInstance().signOut(); + App.setUser(null); + getPreferences(MODE_PRIVATE).edit().putBoolean(CONSTANT.IS_UPLOAD_INIT, false) + .putString(CONSTANT.CLOUDED_MAP, "") + .apply(); + File cloud_cache = new File(CONF.CLOUD_MAP_CACHE_PATH); + if (cloud_cache.exists()) { + cloud_cache.delete(); + } + finish(); + }) + .setNegativeButton(R.string.no, null) + .create() + .show(); + + } +} \ No newline at end of file diff --git a/qpython/src/od/java/org/qpython/qpy/main/app/AppInit.java b/qpython/src/od/java/org/qpython/qpy/main/app/AppInit.java new file mode 100644 index 00000000..76cc52ab --- /dev/null +++ b/qpython/src/od/java/org/qpython/qpy/main/app/AppInit.java @@ -0,0 +1,37 @@ +package org.qpython.qpy.main.app; + +import android.content.Context; +import android.util.Log; + +import com.google.firebase.FirebaseApp; +import com.google.firebase.iid.FirebaseInstanceId; +import com.quseit.config.BASE_CONF; + +import org.qpython.qpy.codeshare.ShareCodeUtil; + +/** + * 文 件 名: AppInit + * 创 建 人: ZhangRonghua + * 创建日期: 2018/3/8 15:13 + * 修改时间: + * 修改备注: + */ + +public class AppInit { + + public static void init(Context context){ + + FirebaseApp.initializeApp(context); + if (BASE_CONF.DEBUG) { + try { + FirebaseInstanceId xx = FirebaseInstanceId.getInstance(); + if (xx != null) { + Log.e("Firebase", xx.getToken()); + } + } catch (Exception e) { + e.printStackTrace(); + } + } + ShareCodeUtil.getInstance(); + } +} diff --git a/qpython/src/od/java/org/qpython/qpy/main/fragment/ExplorerFragment.java b/qpython/src/od/java/org/qpython/qpy/main/fragment/ExplorerFragment.java new file mode 100644 index 00000000..6705c248 --- /dev/null +++ b/qpython/src/od/java/org/qpython/qpy/main/fragment/ExplorerFragment.java @@ -0,0 +1,503 @@ +package org.qpython.qpy.main.fragment; + +import android.content.Intent; +import android.databinding.DataBindingUtil; +import android.graphics.Color; +import android.net.Uri; +import android.os.Bundle; +import android.os.Environment; +import android.support.annotation.Nullable; +import android.support.v4.app.Fragment; +import android.support.v7.app.AlertDialog; +import android.support.v7.widget.LinearLayoutManager; +import android.text.TextUtils; +import android.util.Log; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.Toast; + +import com.google.firebase.database.DatabaseReference; +import com.quseit.util.FileHelper; +import com.quseit.util.ImageUtil; +import com.yanzhenjie.recyclerview.swipe.SwipeMenuCreator; +import com.yanzhenjie.recyclerview.swipe.SwipeMenuItem; + +import org.qpython.qpy.R; +import org.qpython.qpy.codeshare.ShareCodeUtil; +import org.qpython.qpy.codeshare.pojo.CloudFile; +import org.qpython.qpy.databinding.FragmentExplorerBinding; +import org.qpython.qpy.main.activity.NotebookActivity; +import org.qpython.qpy.main.activity.SettingActivity; +import org.qpython.qpy.main.activity.SignInActivity; +import org.qpython.qpy.main.app.App; +import org.qpython.qpy.main.app.CONF; +import org.qpython.qpy.texteditor.EditorActivity; +import org.qpython.qpy.texteditor.TedLocalActivity; +import org.qpython.qpy.texteditor.common.CommonEnums; +import org.qpython.qpy.texteditor.common.RecentFiles; +import org.qpython.qpy.texteditor.ui.adapter.FolderAdapter; +import org.qpython.qpy.texteditor.ui.adapter.bean.FolderBean; +import org.qpython.qpy.texteditor.ui.view.EnterDialog; +import org.qpython.qpy.texteditor.widget.crouton.Crouton; +import org.qpython.qpy.texteditor.widget.crouton.Style; +import org.qpython.qpy.utils.FileUtils; +import org.qpython.qpy.utils.NotebookUtil; + +import java.io.File; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import static com.quseit.util.FolderUtils.sortTypeByName; + +public class ExplorerFragment extends Fragment { + private static final int REQUEST_SAVE_AS = 107; + private static final int REQUEST_HOME_PAGE = 109; + private static final int REQUEST_RECENT = 111; + private static final int LOGIN_REQUEST = 2741; + + private static final String TYPE = "type"; + + private int WIDTH = (int) ImageUtil.dp2px(60); + + private FragmentExplorerBinding binding; + private List folderList; + private FolderAdapter adapter; + private Map cloudedMap = new HashMap<>(); + + private boolean openable = true; // 是否可打开文件 + private boolean uploadable; + + private int type; + private String curPath; + + public static ExplorerFragment newInstance(int type) { + ExplorerFragment myFragment = new ExplorerFragment(); + + Bundle args = new Bundle(); + args.putInt(TYPE, type); + myFragment.setArguments(args); + + return myFragment; + } + + @Nullable + @Override + public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, Bundle savedInstanceState) { + return inflater.inflate(R.layout.fragment_explorer, null); + } + + @Override + public void onViewCreated(View view, @Nullable Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + binding = DataBindingUtil.bind(view); + type = getArguments().getInt(TYPE); + initView(); + initListener(); + initCloud(); + switch (type) { + case REQUEST_RECENT: + binding.rlPath.setVisibility(View.GONE); + uploadable = false; + break; + case REQUEST_SAVE_AS: + binding.ivNewFolder.setVisibility(View.VISIBLE); + uploadable = false; + break; + case REQUEST_HOME_PAGE: + binding.ivNewFolder.setVisibility(View.VISIBLE); + uploadable = true; + break; + } + } + + private void initView() { + SwipeMenuCreator swipeMenuCreator = (leftMenu, rightMenu, viewType) -> { +// SwipeMenuItem uploadItem = new SwipeMenuItem(getContext()) +// .setBackgroundColor(Color.parseColor("#FF4798F3")) +// .setImage(R.drawable.ic_cloud_upload) +// .setHeight(ViewGroup.LayoutParams.MATCH_PARENT) +// .setWidth(WIDTH); + + SwipeMenuItem renameItem = new SwipeMenuItem(getContext()) + .setBackgroundColor(Color.parseColor("#FF4BAC07")) + .setImage(R.drawable.ic_file_rename) + .setHeight(ViewGroup.LayoutParams.MATCH_PARENT) + .setWidth(WIDTH); + + SwipeMenuItem deleteItem = new SwipeMenuItem(getContext()) + .setBackgroundColor(Color.parseColor("#FFD14136")) + .setImage(R.drawable.ic_editor_filetree_close) + .setHeight(ViewGroup.LayoutParams.MATCH_PARENT) + .setWidth(WIDTH); + + switch (type) { + case REQUEST_RECENT: + rightMenu.addMenuItem(deleteItem); + break; + case REQUEST_SAVE_AS: + rightMenu.addMenuItem(deleteItem); + break; + case REQUEST_HOME_PAGE: + //rightMenu.addMenuItem(uploadItem); + rightMenu.addMenuItem(renameItem); + rightMenu.addMenuItem(deleteItem); + break; + } + }; + + folderList = new ArrayList<>(); + adapter = new FolderAdapter(folderList, getArguments().getInt(TYPE) == REQUEST_RECENT); + adapter.setCloudMap(cloudedMap); + binding.swipeList.setLayoutManager(new LinearLayoutManager(getContext())); + binding.swipeList.setSwipeMenuCreator(swipeMenuCreator); + openDir(CONF.ABSOLUTE_PATH); + } + + private void initListener() { + binding.ivNewFolder.setOnClickListener(v -> doNewDir()); + binding.prevFolder.setOnClickListener(v -> { + try { + //采用Environment来获取sdcard路径 + String parentPath = new File(curPath).getParent(); + + if (parentPath.length()>=Environment.getExternalStorageDirectory().getAbsolutePath().length()) { + openDir(parentPath); + } + } catch (Exception e) { + e.printStackTrace(); + } + }); + binding.swipeList.setSwipeMenuItemClickListener(menuBridge -> { + binding.swipeList.smoothCloseMenu(); + switch (menuBridge.getPosition()) { + case 0: +// if (uploadable) { +// uploadFile(menuBridge.getAdapterPosition()); +// } else { +// deleteFile(menuBridge.getAdapterPosition()); +// } +// break; +// case 1: + renameFile(menuBridge.getAdapterPosition()); + break; + case 1: + deleteFile(menuBridge.getAdapterPosition()); + break; + } + }); + adapter.setClickListener(new FolderAdapter.Click() { + @Override + public void onItemClick(int position) { + FolderBean item = folderList.get(position); + if (item.getType().equals(CommonEnums.FileType.FILE)) { + if (!openable) { + Toast.makeText(getActivity(), R.string.cant_open, Toast.LENGTH_SHORT).show(); + return; + } + //判断文件类型 + int lastDot = item.getName().lastIndexOf("."); + if (lastDot != -1) { + String ext = item.getName().substring(lastDot + 1); + openFile(item.getFile(), ext); + } + } else { + openDir(item.getPath()); + } + } + + @Override + public void onLongClick(int position) { + binding.swipeList.smoothOpenRightMenu(position); + } + }); + + binding.swipeList.setAdapter(adapter); + } + + private void gotoSetting() { + SettingActivity.startActivity(getActivity()); + } + private void openFile(File file, String ext) { + List textExts = Arrays.asList(getContext().getResources().getStringArray(R.array.text_ext)); + if (textExts.contains(ext)) { + EditorActivity.start(getContext(), Uri.fromFile(file)); + } else if (ext.equals("ipynb")) { + boolean notebookenable = NotebookUtil.isNotebookEnable(getActivity()); + if (notebookenable) { + NotebookActivity.start(getActivity(), file.getAbsolutePath(), false); + } else { + + new AlertDialog.Builder(getActivity(),R.style.MyDialog) + .setTitle(R.string.dialog_alert) + .setMessage(getString(R.string.ennable_notebook_first)) + .setNegativeButton(R.string.cancel, (dialog1, which) -> dialog1.dismiss()) + .setPositiveButton(R.string.ok, (dialog1, which) -> gotoSetting()) + .create() + .show(); + + //Toast.makeText(getActivity(), R.string.ennable_notebook_first, Toast.LENGTH_SHORT).show(); + } + } else { + FileUtils.openFile(getContext(), file); + } + } + + private void uploadFile(int adapterPosition) { + File file = folderList.get(adapterPosition).getFile(); + + // only support type in + String ext = ""; + if (file.getName().lastIndexOf(".") > 0) { + ext = file.getName().substring(file.getName().lastIndexOf(".") + 1); + } + boolean isSupport = false; + if (!file.isDirectory()) { + for (String s : getResources().getStringArray(R.array.support_file_ext)) { + if (s.equals(ext)) { + isSupport = true; + break; + } + } + } else { + isSupport = true; + } + + if (!isSupport) { + Toast.makeText(getContext(), R.string.not_support_type_hint, Toast.LENGTH_SHORT).show(); + return; + } + + // only available for already login user + if (App.getUser() == null) { + new AlertDialog.Builder(getActivity(), R.style.MyDialog) + .setTitle(R.string.need_login) + .setMessage(R.string.upload_login_hint) + .setNegativeButton(R.string.no, null) + .setPositiveButton(getString(R.string.login_now), (dialog, which) -> + startActivityForResult(new Intent(getActivity(), SignInActivity.class), LOGIN_REQUEST) + ) + .create() + .show(); + return; + } + + // upload + folderList.get(adapterPosition).setUploading(true); + adapter.notifyItemChanged(adapterPosition); + int[] size = {1}; + DatabaseReference.CompletionListener listener = ((databaseError, databaseReference) -> { + if (databaseError == null) { +// updateClouded(folderList.get(adapterPosition).getFile()); +// adapter.setUploadFile(adapterPosition); +// ((TedLocalActivity) getActivity()).setNewUpload(); +// Toast.makeText(getActivity(), R.string.upload_suc, Toast.LENGTH_SHORT).show(); +// ShareCodeUtil.getInstance().changeUsage(size[0]); + } else { + Toast.makeText(getActivity(), databaseError.getMessage(), Toast.LENGTH_SHORT).show(); + } + folderList.get(adapterPosition).setUploading(false); + adapter.notifyDataSetChanged(); + }); + if (folderList.get(adapterPosition).getFile().isDirectory()) { + // 如果上传整个projects目录,则将该目录下项目分开上传 + if (folderList.get(adapterPosition).getName().contains("projects")) { + File[] files = folderList.get(adapterPosition).getFile().listFiles(); + for (int i = 0; i < files.length; i++) { + if (!ShareCodeUtil.getInstance().uploadFolder(files[i].getPath(), i == 0 ? null : listener, size)) { + folderList.get(adapterPosition).setUploading(false); + adapter.notifyDataSetChanged(); + } + } + } else { + if (!ShareCodeUtil.getInstance().uploadFolder(folderList.get(adapterPosition).getPath(), listener, size)) { + folderList.get(adapterPosition).setUploading(false); + adapter.notifyDataSetChanged(); + } + } + } else { + if (!ShareCodeUtil.getInstance().uploadFile(folderList.get(adapterPosition).getPath(), listener)) { + folderList.get(adapterPosition).setUploading(false); + adapter.notifyDataSetChanged(); + } + } + } + + private void renameFile(int adapterPosition) { + new EnterDialog(getContext()) + .setTitle(getString(R.string.rename)) + .setConfirmListener(name -> { + File oldFile = folderList.get(adapterPosition).getFile(); + File newFile = new File(oldFile.getParent(), name); + boolean renameSuc = oldFile.renameTo(newFile); + if (renameSuc) { + folderList.set(adapterPosition, new FolderBean(newFile)); + adapter.notifyItemChanged(adapterPosition); + return true; + } else { + Toast.makeText(getActivity(), R.string.rename_fail, Toast.LENGTH_SHORT).show(); + return false; + } + }) + .setText(folderList.get(adapterPosition).getName()) + .show(); + } + + private void deleteFile(int adapterPosition) { + switch (type) { + case REQUEST_RECENT: + RecentFiles.removePath(folderList.get(adapterPosition).getPath()); + folderList.remove(adapterPosition); + adapter.notifyDataSetChanged(); + break; + case REQUEST_HOME_PAGE: + case REQUEST_SAVE_AS: + AlertDialog.Builder builder = new AlertDialog.Builder(getActivity(), R.style.MyDialog); + builder.setTitle(R.string.warning) + .setMessage(R.string.delete_file_hint) + .setNegativeButton(R.string.no, null) + .setPositiveButton(R.string.yes, (dialog, which) -> { + String dir = folderList.get(adapterPosition).getFile().getParent(); + FileHelper.clearDir(folderList.get(adapterPosition).getFile().getAbsolutePath(), 0, true); + openDir(dir); + +// folderList.remove(adapterPosition); +// adapter.notifyItemRemoved(adapterPosition); + }) + .show(); + break; + } + } + + private void openDir(String dirPath) { + if (type == REQUEST_RECENT) { + folderList.clear(); + for (String path : RecentFiles.getRecentFiles()) { + folderList.add(new FolderBean(new File(path))); + } + + if (folderList.size() == 0) { + binding.emptyHint.setVisibility(View.VISIBLE); + } else { + binding.emptyHint.setVisibility(View.GONE); + adapter.notifyDataSetChanged(); + } + } else { + binding.tvPath.setText(dirPath); + curPath = dirPath; + File dir = new File(dirPath); + if (dir.exists()) { + File[] files = dir.listFiles(); + if (files != null) { + Arrays.sort(files, sortTypeByName); + folderList.clear(); + adapter.notifyDataSetChanged(); + for (File file : files) { + if (!dir.getName().equals(CONF.BASE_PATH)) { + if (!file.getName().startsWith(".")) { + folderList.add(new FolderBean(file)); + } + + } else { + folderList.add(new FolderBean(file)); + + } + + } + adapter.notifyDataSetChanged(); + } + } else { + Toast.makeText(getContext(), R.string.file_not_exists, Toast.LENGTH_SHORT).show(); + } + } + } + + private void doNewDir() { + new EnterDialog(getContext()) + .setTitle(getString(R.string.new_folder)) + .setHint(getString(R.string.folder_name)) + .setConfirmListener(name -> { + File dirN = new File(curPath, name.equals("") ? getString(R.string.untitled_folder) : name); + if (dirN.exists()) { + Crouton.makeText(getActivity(), getString(R.string.toast_folder_exist), Style.ALERT).show(); + return false; + } else { + if (dirN.mkdirs()) { + openDir(curPath + "/" + name); + } else { + Toast.makeText(getContext(), R.string.mkdir_fail, Toast.LENGTH_SHORT).show(); + } + return true; + } + }) + .show(); + } + + public void backToPrev() { + Log.d("ExplorerFragment", "backToPrev:"+curPath); + String qpyDir = Environment.getExternalStorageDirectory().getAbsolutePath()+"/qpython"; + if (curPath == null || qpyDir.equals(curPath) || Environment.getExternalStorageDirectory().getAbsolutePath().equals(curPath)) { + getActivity().finish(); + } else { + String parentPath = new File(curPath).getParent(); + if (!TextUtils.isEmpty(parentPath)) openDir(parentPath); + } + } + + private void updateClouded(File file) { + if (file.getParent().contains("projects")) { + String parent = file.getParent() + "/"; + String subPath = file.getPath().substring(file.getPath().indexOf(parent) + parent.length()); + String projName = subPath.contains("/") ? subPath.substring(0, subPath.indexOf("/")) : "/" + subPath; + cloudedMap.put(CONF.ABSOLUTE_PATH + projName, true); + } + if (file.isDirectory()) { + for (File file1 : FileHelper.filterExt(file, getResources().getStringArray(R.array.support_file_ext))) { + cloudedMap.put(file1.getPath(), true); + } + } else { + cloudedMap.put(file.getPath(), true); + } + } + + public String getCurPath() { + return curPath; + } + + private void initCloud() { + if (App.getUser() == null) { + return; + } + ShareCodeUtil.getInstance().getUploadedScripts(false, getActivity(), cloudFiles -> { + if (cloudFiles == null || cloudFiles.size() == 0) { + return; + } + for (CloudFile cloudFile : cloudFiles) { + if (cloudFile.getPath().contains("/projects")) { + String projNode = cloudFile.getPath().contains("/projects3/") ? "/projects3/" : "/projects/"; + cloudedMap.put(CONF.ABSOLUTE_PATH + projNode + cloudFile.getProjectName(), true); + } + cloudedMap.put(CONF.ABSOLUTE_PATH + cloudFile.getPath(), true); + } + adapter.setCloudMap(cloudedMap); + adapter.notifyDataSetChanged(); + }); + } + + public void deleteCloudedMap(String absolutePath) { + if (cloudedMap.containsKey(absolutePath)) { + cloudedMap.remove(absolutePath); + adapter.notifyDataSetChanged(); + } + } + + public void updateCloudedFiles(Map map) { + if (map != null) { + cloudedMap.putAll(map); + } + adapter.notifyDataSetChanged(); + } +} diff --git a/qpython/src/od/java/org/qpython/qpy/main/fragment/MyProjectFragment.java b/qpython/src/od/java/org/qpython/qpy/main/fragment/MyProjectFragment.java new file mode 100644 index 00000000..1b52cf26 --- /dev/null +++ b/qpython/src/od/java/org/qpython/qpy/main/fragment/MyProjectFragment.java @@ -0,0 +1,256 @@ +package org.qpython.qpy.main.fragment; + +import android.annotation.SuppressLint; +import android.content.Context; +import android.databinding.DataBindingUtil; +import android.graphics.Color; +import android.os.Bundle; +import android.support.annotation.Nullable; +import android.support.v4.app.Fragment; +import android.support.v7.app.AlertDialog; +import android.support.v7.widget.LinearLayoutManager; +import android.text.Html; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.Toast; + +import com.quseit.util.DateTimeHelper; +import com.quseit.util.ImageUtil; +import com.yanzhenjie.recyclerview.swipe.SwipeMenuCreator; +import com.yanzhenjie.recyclerview.swipe.SwipeMenuItem; + +import org.qpython.qpy.R; +import org.qpython.qpy.codeshare.CONSTANT; +import org.qpython.qpy.codeshare.ShareCodeUtil; +import org.qpython.qpy.codeshare.pojo.CloudFile; +import org.qpython.qpy.databinding.FragmentRefreshRvBinding; +import org.qpython.qpy.main.adapter.CloudScriptAdapter; +import org.qpython.qpy.texteditor.TedLocalActivity; +import org.qpython.qpysdk.QPyConstants; + +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; +import java.util.concurrent.TimeUnit; + +import rx.Observable; +import rx.android.schedulers.AndroidSchedulers; + +public class MyProjectFragment extends Fragment { + private int WIDTH = (int) ImageUtil.dp2px(60); + private FragmentRefreshRvBinding binding; + private CloudScriptAdapter adapter; + private List scriptList = new ArrayList<>(); + private TedLocalActivity activity; + + public boolean isLoading; + + @Nullable + @Override + public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, Bundle savedInstanceState) { + binding = DataBindingUtil.bind(LayoutInflater.from(getContext()).inflate(R.layout.fragment_refresh_rv, null)); + return binding.getRoot(); + } + + @Override + public void onViewCreated(View view, @Nullable Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + scriptList = new ArrayList<>(); + adapter = new CloudScriptAdapter(scriptList); + ShareCodeUtil.getInstance().clearAll(); + initView(); + initListener(); + retry(true); + } + + @Override + public void onAttach(Context context) { + super.onAttach(context); + activity = (TedLocalActivity) context; + } + + @Override + public void onResume() { + super.onResume(); + if (adapter != null) { + adapter.notifyDataSetChanged(); + } + } + + @Override + public void onStop() { + super.onStop(); +// activity.locatedCloud(scriptList); + } + + public void retry(boolean forceRefresh) { + if (scriptList != null && adapter != null) { + scriptList.clear(); + adapter.notifyDataSetChanged(); + } + startProgressBar(); + isLoading = true; + ShareCodeUtil.getInstance().getUploadedScripts(forceRefresh, getActivity(), cloudFiles -> { + isLoading = false; + if (cloudFiles == null || cloudFiles.size() == 0) { + showEmpty(); + return; + } +// ((TedLocalActivity) getActivity()).updateCloudFiles(cloudFiles); + scriptList.clear(); + scriptList.addAll(cloudFiles); + adapter.notifyDataSetChanged(); + binding.progressBar.setVisibility(View.GONE); + binding.netError.setVisibility(View.GONE); + }); + } + + public void notifyDataSetChange() { + if (adapter != null) adapter.notifyDataSetChanged(); + } + + private void startProgressBar() { + binding.progressBar.setVisibility(View.VISIBLE); + binding.netError.setVisibility(View.GONE); + Observable.just(null) + .delay(5, TimeUnit.SECONDS) + .observeOn(AndroidSchedulers.mainThread()) + .doOnNext(o -> showNetErrorText()) + .subscribe(); + } + + private void showEmpty() { + if (binding.progressBar.getVisibility() == View.VISIBLE) { + binding.progressBar.setVisibility(View.GONE); + binding.netError.setText(R.string.cloud_empty_hint); + binding.netError.setVisibility(View.VISIBLE); + } + } + + private void showNetErrorText() { + if (binding.progressBar.getVisibility() == View.VISIBLE) { + binding.progressBar.setVisibility(View.GONE); + binding.netError.setText(R.string.net_lagging); + binding.netError.setVisibility(View.VISIBLE); + } + } + + private void initView() { + SwipeMenuCreator swipeMenuCreator = (leftMenu, rightMenu, viewType) -> { + SwipeMenuItem deleteItem = new SwipeMenuItem(getContext()) + .setBackgroundColor(Color.parseColor("#FFD14136")) + .setImage(R.drawable.ic_editor_filetree_close) + .setHeight(ViewGroup.LayoutParams.MATCH_PARENT) + .setWidth(WIDTH); + + SwipeMenuItem downloadItem = new SwipeMenuItem(getContext()) + .setBackgroundColor(Color.parseColor("#FF4798F3")) + .setImage(R.drawable.ic_cloud_download) + .setHeight(ViewGroup.LayoutParams.MATCH_PARENT) + .setWidth(WIDTH); + rightMenu.addMenuItem(downloadItem); + rightMenu.addMenuItem(deleteItem); + }; + binding.swipeList.setLayoutManager(new LinearLayoutManager(getContext())); + binding.swipeList.setSwipeMenuCreator(swipeMenuCreator); + } + + @SuppressLint("StringFormatMatches") + private void initListener() { + binding.netError.setOnClickListener(v -> retry(true)); + binding.swipeList.setSwipeMenuItemClickListener(menuBridge -> { + menuBridge.closeMenu(); + switch (menuBridge.getPosition()) { + case 0: + // download + CloudFile cloudFile = scriptList.get(menuBridge.getAdapterPosition()); + cloudFile.setUploading(true); + adapter.notifyItemChanged(menuBridge.getAdapterPosition()); + String path; + path = QPyConstants.ABSOLUTE_PATH + cloudFile.getPath(); + File file = new File(path); + if (file.exists()) { + new AlertDialog.Builder(getContext(), R.style.MyDialog) + .setTitle(R.string.override_hint) + .setMessage(Html.fromHtml(getString(R.string.conflict_hint, + cloudFile.getUploadTime(), + DateTimeHelper.AGO_FULL_DATE_FORMATTER.format(new Date(file.lastModified()))))) + .setNegativeButton(R.string.no, (dialog, which) ->{ + cloudFile.setUploading(false); + adapter.notifyItemChanged(menuBridge.getAdapterPosition()); + }) + .setPositiveButton(R.string.yes, + (dialog, which) -> + getRemoteContentNWrite(cloudFile, path, menuBridge.getAdapterPosition())) + .create() + .show(); + } else { + getRemoteContentNWrite(cloudFile, path, menuBridge.getAdapterPosition()); + } + break; + case 1: + // delete + scriptList.get(menuBridge.getAdapterPosition()).setUploading(true); + adapter.notifyItemChanged(menuBridge.getAdapterPosition()); + ShareCodeUtil.getInstance().deleteUploadScript(scriptList.get(menuBridge.getAdapterPosition()), (databaseError, databaseReference) -> { +// if (databaseError == null && getContext() != null) { +// Toast.makeText(getContext(), R.string.delete_remote_suc, Toast.LENGTH_SHORT).show(); +// adapter.notifyItemRemoved(menuBridge.getAdapterPosition()); +// ((TedLocalActivity) getActivity()).deleteCloudFile(scriptList.get(menuBridge.getAdapterPosition()).getAbsolutePath()); +// scriptList.remove(menuBridge.getAdapterPosition()); +// if (scriptList.size() == 0) { +// binding.netError.setText(R.string.cloud_empty_hint); +// binding.netError.setVisibility(View.VISIBLE); +// } else { +// binding.netError.setVisibility(View.INVISIBLE); +// } +// } else { +// Toast.makeText(getContext(), databaseError.getMessage(), Toast.LENGTH_SHORT).show(); +// } + }); + break; + } + }); + adapter.setCallback((position) -> binding.swipeList.smoothOpenRightMenu(position)); + binding.swipeList.setAdapter(adapter); + } + + private void getRemoteContentNWrite(CloudFile cloudFile, String path, int position) { + String key = cloudFile.getKey(); + if (cloudFile.getProjectName() != null && !cloudFile.getKey().contains("projects3")) { + key = key.replace(cloudFile.getProjectName() + CONSTANT.SLASH_REPLACE, cloudFile.getProjectName() + "/" + CONSTANT.SLASH_REPLACE); + } + ShareCodeUtil.getInstance().getFileContent(key, content -> { + writeFile(path, content, position); + scriptList.get(position).setUploading(false); + adapter.notifyItemChanged(position); + }); + } + + private void writeFile(String path, String content, int adapterPosition) { + try { + FileWriter writer = new FileWriter(new File(path), false); + writer.write(content); + writer.close(); + Toast.makeText(getContext(), R.string.file_downloaded, Toast.LENGTH_SHORT).show(); + adapter.notifyItemChanged(adapterPosition); + } catch (IOException e) { + e.printStackTrace(); + Toast.makeText(getContext(), R.string.override_fail_hint, Toast.LENGTH_SHORT).show(); + } + } + + public void needRefresh(boolean isNewUpload) { + if (binding == null) { + // not init yet + return; + } + if (isNewUpload) { + retry(true); + } + } +} diff --git a/qpython/src/od/java/org/qpython/qpy/main/receiver/LeancloudReceiver.java b/qpython/src/od/java/org/qpython/qpy/main/receiver/LeancloudReceiver.java new file mode 100644 index 00000000..6f12375e --- /dev/null +++ b/qpython/src/od/java/org/qpython/qpy/main/receiver/LeancloudReceiver.java @@ -0,0 +1,18 @@ +package org.qpython.qpy.main.receiver; + +import android.annotation.SuppressLint; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; + +/** + * 处理leancloud的消息推送 + */ +public class LeancloudReceiver extends BroadcastReceiver { + + @SuppressLint("UnsafeProtectedBroadcastReceiver") + @Override + public void onReceive(Context context, Intent intent) { + + } +} \ No newline at end of file diff --git a/qpython/src/od/java/org/qpython/qpy/main/service/MyFirebaseInstanceIDService.java b/qpython/src/od/java/org/qpython/qpy/main/service/MyFirebaseInstanceIDService.java new file mode 100644 index 00000000..362c2336 --- /dev/null +++ b/qpython/src/od/java/org/qpython/qpy/main/service/MyFirebaseInstanceIDService.java @@ -0,0 +1,15 @@ +package org.qpython.qpy.main.service; + +import com.google.firebase.iid.FirebaseInstanceId; +import com.google.firebase.iid.FirebaseInstanceIdService; + +/** + * Created by Hmei on 2017-06-29. + */ + +public class MyFirebaseInstanceIDService extends FirebaseInstanceIdService { + @Override + public void onTokenRefresh() { + String refreshedToken = FirebaseInstanceId.getInstance().getToken(); + } +} diff --git a/qpython/src/od/java/org/qpython/qpy/main/service/MyFirebaseMessagingService.java b/qpython/src/od/java/org/qpython/qpy/main/service/MyFirebaseMessagingService.java new file mode 100644 index 00000000..9f333943 --- /dev/null +++ b/qpython/src/od/java/org/qpython/qpy/main/service/MyFirebaseMessagingService.java @@ -0,0 +1,88 @@ +package org.qpython.qpy.main.service; + +import android.app.Notification; +import android.app.NotificationManager; +import android.app.PendingIntent; +import android.content.Intent; +import android.content.SharedPreferences; +import android.net.Uri; +import android.os.Bundle; +import android.preference.PreferenceManager; + +import com.google.firebase.messaging.FirebaseMessagingService; +import com.google.firebase.messaging.RemoteMessage; + +import org.json.JSONException; +import org.json.JSONObject; +import org.qpython.qpy.R; +import org.qpython.qpy.main.activity.QWebViewActivity; +import org.qpython.qpy.main.app.App; +import org.qpython.qpy.main.app.CONF; +import org.qpython.qpy.main.receiver.NotificationBean; + +import java.util.Map; + + +public class MyFirebaseMessagingService extends FirebaseMessagingService { + private static final int LOG_NOTIFICATION_ID = (int) System.currentTimeMillis(); + + private boolean handled = false; + + @Override + public void onMessageReceived(RemoteMessage remoteMessage) { + super.onMessageReceived(remoteMessage); + Map data = remoteMessage.getData(); + String json = App.getGson().toJson(data); + NotificationBean bean = App.getGson().fromJson(json, NotificationBean.class); + if (!bean.isForce()&&!PreferenceManager.getDefaultSharedPreferences(this).getBoolean(getString(R.string.key_hide_push), true)) { + return; + } + Intent intent; + if (bean.getType().equals("ext")) { + intent = new Intent(Intent.ACTION_VIEW, Uri.parse(bean.getLink())); + } else { + intent = new Intent(App.getContext(), QWebViewActivity.class); + intent.putExtra(QWebViewActivity.TITLE, bean.getTitle()); + intent.putExtra("url", bean.getLink()); + } + + PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, intent, PendingIntent.FLAG_ONE_SHOT); + + Notification.Builder builder = new Notification.Builder(this) + .setSmallIcon(R.drawable.img_home_logo) + .setContentTitle(getString(R.string.app_name)) + .setContentText(bean.getMsg()) + .setContentIntent(pendingIntent) + .setAutoCancel(true); + + Notification notification; + if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.JELLY_BEAN) { + notification = builder.build(); + } else { + notification = builder.getNotification(); + } + NotificationManager mNotifyMgr = (NotificationManager) getSystemService(NOTIFICATION_SERVICE); + mNotifyMgr.notify(LOG_NOTIFICATION_ID, notification); + handled = true; + } + + /* @Override + public void handleIntent(Intent intent) { + super.handleIntent(intent); + if (handled) { + return; + } + Bundle bundle = intent.getExtras(); + JSONObject extras = new JSONObject(); + try { + for (String s : bundle.keySet()) { + extras.put(s, bundle.get(s)); + } + } catch (JSONException e) { + e.printStackTrace(); + } + SharedPreferences.Editor editor = getSharedPreferences(CONF.NOTIFICATION_SP_NAME, MODE_PRIVATE).edit(); + editor.putString(CONF.NOTIFICATION_SP_OBJ, extras.toString()); + editor.apply(); + }*/ +} \ No newline at end of file diff --git a/qpython/src/od/java/org/qpython/qpy/main/service/PayUtil.java b/qpython/src/od/java/org/qpython/qpy/main/service/PayUtil.java new file mode 100644 index 00000000..8b88e8cc --- /dev/null +++ b/qpython/src/od/java/org/qpython/qpy/main/service/PayUtil.java @@ -0,0 +1,150 @@ +package org.qpython.qpy.main.service; + +import android.app.Activity; +import android.app.PendingIntent; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.IntentSender; +import android.content.ServiceConnection; +import android.os.Bundle; +import android.os.IBinder; +import android.os.RemoteException; +import android.view.View; +import android.widget.Toast; + +import com.android.vending.billing.IInAppBillingService; + +import org.json.JSONException; +import org.json.JSONObject; +import org.qpython.qpy.R; +import org.qpython.qpy.main.server.MySubscriber; + +import java.util.ArrayList; +import java.util.Arrays; + +import rx.Observable; +import rx.android.schedulers.AndroidSchedulers; +import rx.schedulers.Schedulers; + +/** + * Created by Hmei + * 1/31/18. + */ + +public class PayUtil { + private static final int BUY_REQUEST_CODE = 2333; + private IInAppBillingService mService; + private ServiceConnection mServiceConn; + private Activity context; + + public PayUtil(Activity context) { + this.context = context; + } + + public void initIAP(PayCallback callback) { + mServiceConn = new ServiceConnection() { + @Override + public void onServiceDisconnected(ComponentName name) { + mService = null; + } + + @Override + public void onServiceConnected(ComponentName name, + IBinder service) { + mService = IInAppBillingService.Stub.asInterface(service); + if (callback != null) callback.doAfterConn(); + } + }; + Intent serviceIntent = new Intent("com.android.vending.billing.InAppBillingService.BIND"); + serviceIntent.setPackage("com.android.vending"); + context.bindService(serviceIntent, mServiceConn, Context.BIND_AUTO_CREATE); + } + + /** + * 从Google服务器获取不同国家价格表并显示 + */ + public void getPrices(ArrayList skuList, MySubscriber callback) { + if (mService == null) { + Toast.makeText(context, R.string.lose_google_server, Toast.LENGTH_SHORT).show(); + if (context.findViewById(R.id.pb) != null) + context.findViewById(R.id.pb).setVisibility(View.GONE); + return; + } + Bundle querySkus = new Bundle(); + querySkus.putStringArrayList("ITEM_ID_LIST", skuList); + try { + Observable.just(mService.getSkuDetails(3, context.getPackageName(), "inapp", querySkus)) + .map(bundle -> { + ArrayList responseList = bundle.getStringArrayList("DETAILS_LIST"); + if (responseList == null) { + return null; + } + String[] prices = new String[responseList.size()]; + for (int i = 0; i < responseList.size(); i++) { + JSONObject object; + try { + object = new JSONObject(responseList.get(i)); + String price = object.getString("price"); + prices[i] = price; + } catch (JSONException e) { + e.printStackTrace(); + } + } + Arrays.sort(prices, (o1, o2) -> Integer.parseInt(o1.replaceAll("[^0-9]", "")) - Integer.parseInt(o2.replaceAll("[^0-9]", ""))); + return prices; + } + ) + .subscribeOn(Schedulers.newThread()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(callback); + } catch (RemoteException e) { + e.printStackTrace(); + } + } + + public void digestPurchase(String purchaseToken) { + // 消耗购买,使能重复赞赏同一金额 + try { + Observable.just(mService.consumePurchase(3, context.getPackageName(), purchaseToken)) + .subscribeOn(Schedulers.newThread()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(); + } catch (RemoteException e) { + e.printStackTrace(); + } + } + + public void purchase(String sku) { + try { + if (mService == null) { + Toast.makeText(context, R.string.lose_google_server, Toast.LENGTH_SHORT).show(); + return; + } + Bundle buyIntentBundle = mService.getBuyIntent(3, context.getPackageName(), + sku, "inapp", "bGoa+V7g/yqDXvKRqq+JTFn4uQZbPiQJo4pf9RzJ"); + switch (buyIntentBundle.getInt("RESPONSE_CODE")) { + case 0: + //BILLING_RESPONSE_RESULT_OK + PendingIntent pendingIntent = buyIntentBundle.getParcelable("BUY_INTENT"); + context.startIntentSenderForResult(pendingIntent.getIntentSender(), + BUY_REQUEST_CODE, new Intent(), 0, 0, 0); + break; + + } + } catch (RemoteException | IntentSender.SendIntentException | NullPointerException e) { + e.printStackTrace(); + } + } + + public void unbindPayService() { + if (mService != null) { + context.unbindService(mServiceConn); + } + } + + public interface PayCallback { + void doAfterConn(); + } + +} diff --git a/qpython/src/od/java/org/qpython/qpy/texteditor/TedLocalActivity.java b/qpython/src/od/java/org/qpython/qpy/texteditor/TedLocalActivity.java new file mode 100644 index 00000000..466f0c8a --- /dev/null +++ b/qpython/src/od/java/org/qpython/qpy/texteditor/TedLocalActivity.java @@ -0,0 +1,308 @@ +package org.qpython.qpy.texteditor; + + +import android.app.Activity; +import android.content.Context; +import android.content.Intent; +import android.content.res.Configuration; +import android.databinding.DataBindingUtil; +import android.graphics.drawable.Drawable; +import android.os.Bundle; +import android.support.v4.app.Fragment; +import android.support.v7.app.AppCompatActivity; +import android.view.MotionEvent; +import android.view.View; +import android.widget.EditText; +import android.widget.Toast; + +import com.google.gson.reflect.TypeToken; +import com.quseit.util.FileHelper; +import com.quseit.util.NAction; + +import org.qpython.qpy.R; +import org.qpython.qpy.codeshare.pojo.CloudFile; +import org.qpython.qpy.databinding.ActivityLocalBinding; +import org.qpython.qpy.main.activity.SignInActivity; +import org.qpython.qpy.main.app.App; +import org.qpython.qpy.main.app.CONF; +import org.qpython.qpy.main.fragment.ExplorerFragment; +import org.qpython.qpy.main.fragment.LocalFragment; +import org.qpython.qpy.main.fragment.MyProjectFragment; +import org.qpython.qpy.utils.NotebookUtil; +import org.qpython.qpysdk.QPyConstants; + +import java.io.File; +import java.lang.reflect.Type; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class TedLocalActivity extends AppCompatActivity { + public static final int REQUEST_SAVE_AS = 107; + public static final int REQUEST_OPEN = 108; + public static final int REQUEST_HOME_PAGE = 109; + public static final int REQUEST_RECENT = 111; + private static final int LOGIN_REQUEST_CODE = 4806; + + private static final String EXTRA_REQUEST_CODE = "request_code"; + private static final String EXTRA_REQUEST_FN = "request_fn"; + + private static final String FRAGMENT_EXPLORER = "explorer"; + private static final String FRAGMENT_CLOUD = "cloud"; + + private ActivityLocalBinding binding; + + private Fragment firstPageFragment; + private MyProjectFragment myProjectFragment; + + private boolean isExplorer = true; + private boolean isNewUpload = false; + //private String defaultFileName = ""; + + public static void start(Context context, int type) { + Intent starter = new Intent(context, TedLocalActivity.class); + starter.putExtra(EXTRA_REQUEST_CODE, type); + context.startActivity(starter); + } + + public static void start(Activity context, int type, int requestCode, String filename) { + Intent starter = new Intent(context, TedLocalActivity.class); + starter.putExtra(EXTRA_REQUEST_CODE, type); + starter.putExtra(EXTRA_REQUEST_FN, filename); + + context.startActivityForResult(starter, requestCode); + } + + public void finishForGetPath(String path) { + Intent intent = new Intent(); + intent.putExtra("path", path); + setResult(Activity.RESULT_OK, intent); + finish(); + } + + public void finishForOpen(String path, boolean isProj) { + Intent intent = new Intent(); + intent.putExtra("path", path); + intent.putExtra("isProj", isProj); + setResult(Activity.RESULT_OK, intent); + finish(); + } + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + binding = DataBindingUtil.setContentView(this, R.layout.activity_local); + setSupportActionBar(binding.toolbar); + int type = getIntent().getIntExtra(EXTRA_REQUEST_CODE, REQUEST_HOME_PAGE); + + initView(); + initListener(); + switch (type) { + case REQUEST_RECENT: + setTitle(R.string.recent); + binding.switchBtn.setVisibility(View.GONE); + firstPageFragment = ExplorerFragment.newInstance(type); + break; + case REQUEST_OPEN: + setTitle(R.string.open); + binding.switchBtn.setVisibility(View.GONE); + binding.explore.setVisibility(View.VISIBLE); + firstPageFragment = new LocalFragment(); + break; + case REQUEST_SAVE_AS: + setTitle(R.string.save_as); + binding.vsSave.getRoot().setVisibility(View.VISIBLE); + initSaveListener(); + + String fn = getIntent().getStringExtra(EXTRA_REQUEST_FN); + if (fn!=null) { + binding.vsSave.etName.setText(fn); + } + binding.switchBtn.setVisibility(View.GONE); + firstPageFragment = ExplorerFragment.newInstance(type); + break; + case REQUEST_HOME_PAGE: + setTitle(R.string.explorer); + firstPageFragment = ExplorerFragment.newInstance(type); + break; + } + + getSupportFragmentManager().beginTransaction() + .add(R.id.container, firstPageFragment, FRAGMENT_EXPLORER) + .commit(); + } + + @Override + public void onConfigurationChanged(Configuration newConfig) { + super.onConfigurationChanged(newConfig); + } + + private void initView() { + binding.toolbar.setNavigationIcon(R.drawable.ic_back); + binding.toolbar.setNavigationOnClickListener(v -> finish()); + if (binding.switchBtn.getVisibility() == View.VISIBLE) { + myProjectFragment = new MyProjectFragment(); + } + } + + private void initListener() { + binding.switchBtn.setOnClickListener(v -> { + if (isExplorer) { + if (App.getUser() == null) { + startActivityForResult(new Intent(this, SignInActivity.class), LOGIN_REQUEST_CODE); + return; + } + binding.switchBtn.setImageResource(R.drawable.ic_folder_open); + binding.refresh.setVisibility(View.VISIBLE); + if (getSupportFragmentManager().findFragmentByTag(FRAGMENT_CLOUD) == null) { + getSupportFragmentManager().beginTransaction().add(R.id.container, myProjectFragment, FRAGMENT_CLOUD).commit(); + getSupportFragmentManager().beginTransaction().hide(firstPageFragment).commit(); + } else { + getSupportFragmentManager().beginTransaction() + .hide(firstPageFragment) + .show(myProjectFragment) + .commit(); + } + myProjectFragment.needRefresh(isNewUpload); + isNewUpload = false; + if (!myProjectFragment.isLoading) { + myProjectFragment.notifyDataSetChange(); + } + } else { + binding.switchBtn.setImageResource(R.drawable.ic_cloud_list); + binding.refresh.setVisibility(View.GONE); + getSupportFragmentManager().beginTransaction() + .hide(myProjectFragment) + .show(firstPageFragment) + .commit(); + } + isExplorer = !isExplorer; + }); + + binding.refresh.setOnClickListener(v -> myProjectFragment.retry(true)); + + binding.explore.setOnClickListener(v -> { + start(this, REQUEST_HOME_PAGE); + finish(); + }); + } + + public void doSave(String fn) { + if (fn.length() == 0) { + Toast.makeText(getApplicationContext(), R.string.toast_filename_empty, Toast.LENGTH_SHORT).show(); + } else { + String filename = ((ExplorerFragment) firstPageFragment).getCurPath() + "/" + fn; + final File f = new File(filename); + if (f.exists()) { + Toast.makeText(this, R.string.file_exist_hint, Toast.LENGTH_SHORT).show(); + } else { + setSaveResult(f.getAbsolutePath()); + } + } + } + + protected boolean setSaveResult(String filepath) { + File f = new File(filepath); + if (f.getParentFile().canWrite()) { + finishForGetPath(filepath); + } else { + Toast.makeText(getApplicationContext(), R.string.toast_folder_cant_write, Toast.LENGTH_SHORT).show(); + } + return true; + } + + private void initSaveListener() { + binding.vsSave.btnSave.setOnClickListener(v -> doSave(binding.vsSave.etName.getText().toString())); + binding.vsSave.etName.setOnTouchListener((v, event) -> { + final int DRAWABLE_RIGHT = 2; + switch (event.getAction()) { + case MotionEvent.ACTION_DOWN: + Drawable deleteText = ((EditText) v).getCompoundDrawables()[DRAWABLE_RIGHT]; + if (deleteText != null) { + if (event.getRawX() >= (v.getRight() - deleteText.getBounds().width())) { + ((EditText) v).setText(""); + return true; + } + } + break; + case MotionEvent.ACTION_UP: + v.performClick(); + break; + default: + break; + } + return false; + }); + } + + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent data) { + super.onActivityResult(requestCode, resultCode, data); + if (resultCode == RESULT_OK) { + switch (requestCode) { + case LOGIN_REQUEST_CODE: + binding.switchBtn.setImageResource(R.drawable.ic_folder_open); + if (getSupportFragmentManager().findFragmentByTag(FRAGMENT_CLOUD) == null) { + getSupportFragmentManager().beginTransaction().add(R.id.container, myProjectFragment, FRAGMENT_CLOUD).commit(); + getSupportFragmentManager().beginTransaction().hide(firstPageFragment).commit(); + } else { + getSupportFragmentManager().beginTransaction() + .hide(firstPageFragment) + .show(myProjectFragment) + .commit(); + } + isExplorer = !isExplorer; + break; + } + } + } + + @Override + public void onBackPressed() { + if (firstPageFragment.isVisible() && firstPageFragment instanceof ExplorerFragment) { + ((ExplorerFragment) firstPageFragment).backToPrev(); + } else { + super.onBackPressed(); + } + } + +// public void deleteCloudFile(String path) { +// if (firstPageFragment instanceof ExplorerFragment) { +// ((ExplorerFragment) firstPageFragment).deleteCloudedMap(path); +// } +// } +// +// public void updateCloudFiles(List cloudFiles) { +// Map map = new HashMap<>(); +// boolean isQPy3 = NAction.isQPy3(getBaseContext()); +// String tag = isQPy3?"/projects3/":"/projects/"; +// for (CloudFile cloudFile : cloudFiles) { +// if (cloudFile.getPath().contains(tag)) { +// map.put(QPyConstants.ABSOLUTE_PATH + tag + cloudFile.getProjectName(), true); +// } +// map.put(QPyConstants.ABSOLUTE_PATH + cloudFile.getPath(), true); +// } +// ((ExplorerFragment) firstPageFragment).updateCloudedFiles(map); +// } +// +// public void setNewUpload() { +// isNewUpload = true; +// } + + /** + * 保存云端文件目录到本地 + */ +// public void locatedCloud(List cloudFiles) { +// if (cloudFiles.size() > 0) { +// Type type = new TypeToken>() { +// }.getType(); +// FileHelper.writeToFile(CONF.CLOUD_MAP_CACHE_PATH, App.getGson().toJson(cloudFiles, type)); +// } +// } + + @Override + protected void onDestroy() { + super.onDestroy(); + NotebookUtil.killServer(); + } +} diff --git a/qpython/src/od/res/layout/activity_local.xml b/qpython/src/od/res/layout/activity_local.xml new file mode 100644 index 00000000..abdcb0aa --- /dev/null +++ b/qpython/src/od/res/layout/activity_local.xml @@ -0,0 +1,86 @@ + + + + + + + + + + + + + +