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 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/qpython/src/od/res/values-zh-rCN/strings.xml b/qpython/src/od/res/values-zh-rCN/strings.xml
new file mode 100644
index 00000000..a900de8f
--- /dev/null
+++ b/qpython/src/od/res/values-zh-rCN/strings.xml
@@ -0,0 +1,5 @@
+
+
+ 使用谷歌登录
+ QPython OS
+
diff --git a/qpython/src/od/res/values/strings.xml b/qpython/src/od/res/values/strings.xml
new file mode 100644
index 00000000..49b3ca29
--- /dev/null
+++ b/qpython/src/od/res/values/strings.xml
@@ -0,0 +1,4 @@
+
+ Sign in with Google
+ QPython OS
+