diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..f4422d9 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2022 songdongdong123 + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md index 66c9b05..8c76c48 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,28 @@ -# Github_RN -nothing +# GitHub Popular + +这是一个用来查看GitHub最受欢迎与最热项目的App,它基于React Native支持Android和iOS双平台。 + +## 目录 + +* [下载](#下载) +* [功能](#功能) +* [运行调试](#运行调试) + +## 下载 +![APP QRcode](https://github.com/songdongdong123/Github_RN/blob/master/assets/qrcode.png) + +## 功能 + +* 支持订阅多种编程语言; +* 支持添加/删除编程语言; +* 支持自定义语言/标签,排序; +* 支持收藏喜欢的项目; +* 支持多种颜色主题自由切换; +* 支持搜索,并自持自定义订阅关键字; + +## 运行调试 + +1. 准备React Native环境,可参考: [getting-started](https://reactnative.cn/docs/0.51/getting-started.html)。 +2. Clone [GitHub Popular](https://github.com/songdongdong123/Github_RN.git),然后终端进入项目根目录。 +3. 终端运行 `npm install`||`yarn install`。 +4. 然后运行 `react-native run-android` || `react-native run-ios`。 \ No newline at end of file diff --git a/android/app/build.gradle b/android/app/build.gradle index 8f870bd..c1c8ed6 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -77,6 +77,7 @@ project.ext.react = [ ] apply from: "../../node_modules/react-native/react.gradle" +apply from: "../../node_modules/react-native-code-push/android/codepush.gradle" /** * Set this to true to create two separate APKs instead of one: @@ -102,7 +103,15 @@ android { minSdkVersion rootProject.ext.minSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion versionCode 1 - versionName "1.0" + versionName "1.0.0" + } + signingConfigs { + release { + storeFile file(MYAPP_RELEASE_STORE_FILE) + storePassword MYAPP_RELEASE_STORE_PASSWORD + keyAlias MYAPP_RELEASE_KEY_ALIAS + keyPassword MYAPP_RELEASE_KEY_PASSWORD + } } splits { abi { @@ -113,11 +122,19 @@ android { } } buildTypes { + releaseStaging { + buildConfigField "String", "CODEPUSH_KEY", '""' + } release { minifyEnabled enableProguardInReleaseBuilds proguardFiles getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro" + signingConfig signingConfigs.release + buildConfigField "String", "CODEPUSH_KEY", '""' } } + lintOptions { + checkReleaseBuilds false + } // applicationVariants are e.g. debug, release applicationVariants.all { variant -> variant.outputs.each { output -> @@ -134,11 +151,18 @@ android { } dependencies { + implementation project(':react-native-code-push') + implementation project(':react-native-webview') + implementation project(':react-native-splash-screen') implementation project(':react-native-vector-icons') implementation project(':react-native-gesture-handler') implementation fileTree(dir: "libs", include: ["*.jar"]) implementation "com.android.support:appcompat-v7:${rootProject.ext.supportLibVersion}" implementation "com.facebook.react:react-native:+" // From node_modules + implementation 'com.umeng.umsdk:common:2.0.0' + implementation 'com.umeng.umsdk:utdid:1.1.5.3' + implementation 'com.umeng.umsdk:analytics:8.0.0' + implementation 'com.umeng.umsdk:push:5.0.2' } // Run this once to be able to run the application with BUCK diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 45e4143..be0bb17 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -4,6 +4,13 @@ + + + + + + + + + - + + diff --git a/android/app/src/main/java/com/github_rn/MainActivity.java b/android/app/src/main/java/com/github_rn/MainActivity.java index 3e492ff..4ee01d4 100644 --- a/android/app/src/main/java/com/github_rn/MainActivity.java +++ b/android/app/src/main/java/com/github_rn/MainActivity.java @@ -1,12 +1,43 @@ package com.github_rn; +import android.nfc.Tag; +import android.os.Bundle; // here +import android.util.Log; + + import com.facebook.react.ReactActivity; import com.facebook.react.ReactActivityDelegate; import com.facebook.react.ReactRootView; import com.swmansion.gesturehandler.react.RNGestureHandlerEnabledRootView; +import org.devio.rn.splashscreen.SplashScreen; // here + +import com.umeng.analytics.MobclickAgent; +import com.umeng.commonsdk.UMConfigure; + public class MainActivity extends ReactActivity { + @Override + protected void onCreate(Bundle savedInstanceState) { + SplashScreen.show(this); // 添加这一句 + super.onCreate(savedInstanceState); + // 注意:如果您已经在AndroidManifest.xml中配置过appkey和channel值,可以调用此版本初始化函数。 + UMConfigure.init(this, "5caac8a63fc1955789000d88", "Umeng", UMConfigure.DEVICE_TYPE_PHONE, null); + // interval: 单位是毫秒,默认Session间隔时间是30秒 + UMConfigure.setLogEnabled(true); + UMConfigure.setProcessEvent(true); + MobclickAgent.setSessionContinueMillis(30000); + } + + public void onResume() { + super.onResume(); + MobclickAgent.onResume(this); + } + public void onPause() { + super.onPause(); + MobclickAgent.onPause(this); + } + /** * Returns the name of the main component registered from JavaScript. * This is used to schedule rendering of the component. diff --git a/android/app/src/main/java/com/github_rn/MainApplication.java b/android/app/src/main/java/com/github_rn/MainApplication.java index 644775c..8db71ab 100644 --- a/android/app/src/main/java/com/github_rn/MainApplication.java +++ b/android/app/src/main/java/com/github_rn/MainApplication.java @@ -3,6 +3,9 @@ import android.app.Application; import com.facebook.react.ReactApplication; +import com.microsoft.codepush.react.CodePush; +import com.reactnativecommunity.webview.RNCWebViewPackage; +import org.devio.rn.splashscreen.SplashScreenReactPackage; import com.oblador.vectoricons.VectorIconsPackage; import com.swmansion.gesturehandler.react.RNGestureHandlerPackage; import com.facebook.react.ReactNativeHost; @@ -10,12 +13,23 @@ import com.facebook.react.shell.MainReactPackage; import com.facebook.soloader.SoLoader; +import com.github_rn.invokenative.DplusReactPackage; +import com.github_rn.invokenative.RNUMConfigure; +import com.umeng.commonsdk.UMConfigure; + + import java.util.Arrays; import java.util.List; public class MainApplication extends Application implements ReactApplication { private final ReactNativeHost mReactNativeHost = new ReactNativeHost(this) { + + @Override + protected String getJSBundleFile() { + return CodePush.getJSBundleFile(); + } + @Override public boolean getUseDeveloperSupport() { return BuildConfig.DEBUG; @@ -25,8 +39,12 @@ public boolean getUseDeveloperSupport() { protected List getPackages() { return Arrays.asList( new MainReactPackage(), + new CodePush(getResources().getString(R.string.reactNativeCodePush_androidDeploymentKey), getApplicationContext(), BuildConfig.DEBUG), + new RNCWebViewPackage(), + new SplashScreenReactPackage(), new VectorIconsPackage(), - new RNGestureHandlerPackage() + new RNGestureHandlerPackage(), + new DplusReactPackage() ); } @@ -45,5 +63,8 @@ public ReactNativeHost getReactNativeHost() { public void onCreate() { super.onCreate(); SoLoader.init(this, /* native exopackage */ false); + // UMConfigure.setLogEnabled(true); + // UMConfigure.setProcessEvent(true); + // RNUMConfigure.init(this, "5caac8a63fc1955789000d88", "Umeng", UMConfigure.DEVICE_TYPE_PHONE, null); } } diff --git a/android/app/src/main/java/com/github_rn/invokenative/AnalyticsModule.java b/android/app/src/main/java/com/github_rn/invokenative/AnalyticsModule.java new file mode 100644 index 0000000..3303065 --- /dev/null +++ b/android/app/src/main/java/com/github_rn/invokenative/AnalyticsModule.java @@ -0,0 +1,199 @@ +package com.github_rn.invokenative; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import org.json.JSONObject; +import org.json.JSONException; +import java.util.Iterator; + +import com.facebook.react.bridge.Callback; +import com.facebook.react.bridge.ReactApplicationContext; +import com.facebook.react.bridge.ReactContextBaseJavaModule; +import com.facebook.react.bridge.ReactMethod; +import com.facebook.react.bridge.ReadableArray; +import com.facebook.react.bridge.ReadableMap; +import com.facebook.react.bridge.ReadableMapKeySetIterator; +import com.facebook.react.bridge.ReadableNativeMap; +import com.facebook.react.bridge.ReadableType; +import com.umeng.analytics.MobclickAgent; + +/** + * 示例: SDK 接口桥接封装类,并未封装SDK所有API(仅封装常用API接口),设置配置参数类API应在Android原生代码 + * 调用,例如:SDK初始化函数,Log开关函数,子进程自定义事件埋点使能函数,异常捕获功能使能/关闭函数等等。 + * 如果还需要封装其它SDK API,请参考本例自行封装 + * Created by wangfei on 17/8/28. + * -- 适配海棠版(common 2.0.0 + analytics 8.0.0) modify by yujie on 18/12/28 + */ + +public class AnalyticsModule extends ReactContextBaseJavaModule { + private ReactApplicationContext context; + + public AnalyticsModule(ReactApplicationContext reactContext) { + super(reactContext); + context = reactContext; + } + + @Override + public String getName() { + return "UMAnalyticsModule"; + } + + /********************************U-App统计*********************************/ + @ReactMethod + public void onPageStart(String pageName) { + //android.util.Log.e("xxxxxx","onPageStart="+mPageName); + + MobclickAgent.onPageStart(pageName); + } + + @ReactMethod + public void onPageEnd(String pageName) { + //android.util.Log.e("xxxxxx","onPageEnd="+mPageName); + + MobclickAgent.onPageEnd(pageName); + + } + @ReactMethod + public void onEvent(String eventId) { + MobclickAgent.onEvent(context, eventId); + } + @ReactMethod + public void onEventWithLable(String eventId, String eventLabel) { + MobclickAgent.onEvent(context, eventId, eventLabel); + } + @ReactMethod + public void onEventWithMap(String eventId, ReadableMap map) { + Map rMap = new HashMap(); + ReadableMapKeySetIterator iterator = map.keySetIterator(); + while (iterator.hasNextKey()) { + String key = iterator.nextKey(); + if (ReadableType.Array == map.getType(key)) { + rMap.put(key, map.getArray(key).toString()); + } else if (ReadableType.Boolean == map.getType(key)) { + rMap.put(key, String.valueOf(map.getBoolean(key))); + } else if (ReadableType.Number == map.getType(key)) { + rMap.put(key, String.valueOf(map.getInt(key))); + } else if (ReadableType.String == map.getType(key)) { + rMap.put(key, map.getString(key)); + } else if (ReadableType.Map == map.getType(key)) { + rMap.put(key, map.getMap(key).toString()); + } + } + MobclickAgent.onEvent(context, eventId, rMap); + } + @ReactMethod + public void onEventWithMapAndCount(String eventId,ReadableMap map,int value) { + Map rMap = new HashMap(); + ReadableMapKeySetIterator iterator = map.keySetIterator(); + while (iterator.hasNextKey()) { + String key = iterator.nextKey(); + if (ReadableType.Array == map.getType(key)) { + rMap.put(key, map.getArray(key).toString()); + } else if (ReadableType.Boolean == map.getType(key)) { + rMap.put(key, String.valueOf(map.getBoolean(key))); + } else if (ReadableType.Number == map.getType(key)) { + rMap.put(key, String.valueOf(map.getInt(key))); + } else if (ReadableType.String == map.getType(key)) { + rMap.put(key, map.getString(key)); + } else if (ReadableType.Map == map.getType(key)) { + rMap.put(key, map.getMap(key).toString()); + } + } + MobclickAgent.onEventValue(context, eventId, rMap, value); + } + + @ReactMethod + public void onEventObject(String eventID, ReadableMap property) { + Map map = new HashMap(); + ReadableMapKeySetIterator iterator = property.keySetIterator(); + while (iterator.hasNextKey()) { + String key = iterator.nextKey(); + if (ReadableType.Array == property.getType(key)) { + map.put(key, property.getArray(key).toString()); + } else if (ReadableType.Boolean == property.getType(key)) { + map.put(key, String.valueOf(property.getBoolean(key))); + } else if (ReadableType.Number == property.getType(key)) { + map.put(key, String.valueOf(property.getInt(key))); + } else if (ReadableType.String == property.getType(key)) { + map.put(key, property.getString(key)); + } else if (ReadableType.Map == property.getType(key)) { + map.put(key, property.getMap(key).toString()); + } + } + + MobclickAgent.onEventObject(context, eventID, map); + + } + @ReactMethod + public void registerPreProperties(ReadableMap map) { + ReadableNativeMap map2 = (ReadableNativeMap) map; + Map map3 = map2.toHashMap(); + Iterator entries = map3.entrySet().iterator(); + JSONObject json = new JSONObject(); + while (entries.hasNext()) { + Map.Entry entry = (Map.Entry) entries.next(); + String key = (String)entry.getKey(); + String value = (String)entry.getValue(); + try { + json.put(key,value); + }catch (JSONException e){ + + } + } + MobclickAgent.registerPreProperties(context,json); + } + @ReactMethod + public void unregisterPreProperty(String propertyName) { + MobclickAgent.unregisterPreProperty(context, propertyName); + + } + + @ReactMethod + public void getPreProperties(Callback callback) { + String result = MobclickAgent.getPreProperties(context).toString(); + callback.invoke(result); + } + @ReactMethod + public void clearPreProperties() { + MobclickAgent.clearPreProperties(context); + + } + @ReactMethod + public void setFirstLaunchEvent(ReadableArray array) { + List list = new ArrayList(); + for (int i = 0; i < array.size(); i++) { + if (ReadableType.Array == array.getType(i)) { + list.add(array.getArray(i).toString()); + } else if (ReadableType.Boolean == array.getType(i)) { + list.add(String.valueOf(array.getBoolean(i))); + } else if (ReadableType.Number == array.getType(i)) { + list.add(String.valueOf(array.getInt(i))); + } else if (ReadableType.String == array.getType(i)) { + list.add(array.getString(i)); + } else if (ReadableType.Map == array.getType(i)) { + list.add(array.getMap(i).toString()); + } + } + MobclickAgent.setFirstLaunchEvent(context, list); + } + /********************************U-Dplus*********************************/ + @ReactMethod + public void profileSignInWithPUID(String puid) { + MobclickAgent.onProfileSignIn(puid); + } + + @ReactMethod + @SuppressWarnings("unused") + public void profileSignInWithPUIDWithProvider(String provider, String puid) { + MobclickAgent.onProfileSignIn(provider, puid); + } + + @ReactMethod + @SuppressWarnings("unused") + public void profileSignOff() { + MobclickAgent.onProfileSignOff(); + } + +} diff --git a/android/app/src/main/java/com/github_rn/invokenative/DplusReactPackage.java b/android/app/src/main/java/com/github_rn/invokenative/DplusReactPackage.java new file mode 100644 index 0000000..6a9ff0c --- /dev/null +++ b/android/app/src/main/java/com/github_rn/invokenative/DplusReactPackage.java @@ -0,0 +1,39 @@ +package com.github_rn.invokenative; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +import com.facebook.react.ReactPackage; +import com.facebook.react.bridge.JavaScriptModule; +import com.facebook.react.bridge.NativeModule; +import com.facebook.react.bridge.ReactApplicationContext; +import com.facebook.react.shell.MainReactPackage; +import com.facebook.react.uimanager.ViewManager; + +/** + * Created by wangfei on 17/8/28. + */ + +public class DplusReactPackage implements ReactPackage { + + @Override + public List createViewManagers(ReactApplicationContext reactContext) { + return Collections.emptyList(); + } + + /** + * 如需要添加本地方法,只需在这里add + * + * @param reactContext + * @return + */ + @Override + public List createNativeModules( + ReactApplicationContext reactContext) { + List modules = new ArrayList<>(); + modules.add(new AnalyticsModule(reactContext)); + return modules; + } +} \ No newline at end of file diff --git a/android/app/src/main/java/com/github_rn/invokenative/RNUMConfigure.java b/android/app/src/main/java/com/github_rn/invokenative/RNUMConfigure.java new file mode 100644 index 0000000..af06a40 --- /dev/null +++ b/android/app/src/main/java/com/github_rn/invokenative/RNUMConfigure.java @@ -0,0 +1,32 @@ +package com.github_rn.invokenative; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; + +import android.annotation.TargetApi; +import android.content.Context; +import android.os.Build.VERSION_CODES; +import com.umeng.commonsdk.UMConfigure; + +/** + * Created by wangfei on 17/9/14. + */ + +public class RNUMConfigure { + public static void init(Context context, String appkey, String channel, int type, String secret){ + initRN("react-native","2.0"); + UMConfigure.init(context,appkey,channel,type,secret); + } + @TargetApi(VERSION_CODES.KITKAT) + private static void initRN(String v, String t){ + Method method = null; + try { + Class config = Class.forName("com.umeng.commonsdk.UMConfigure"); + method = config.getDeclaredMethod("setWraperType", String.class, String.class); + method.setAccessible(true); + method.invoke(null, v,t); + } catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException | ClassNotFoundException e) { + e.printStackTrace(); + } + } +} diff --git a/android/app/src/main/res/drawable-xxxhdpi/launch_screen.png b/android/app/src/main/res/drawable-xxxhdpi/launch_screen.png new file mode 100644 index 0000000..01fcbdb Binary files /dev/null and b/android/app/src/main/res/drawable-xxxhdpi/launch_screen.png differ diff --git a/android/app/src/main/res/layout/launch_screen.xml b/android/app/src/main/res/layout/launch_screen.xml new file mode 100644 index 0000000..86d6a72 --- /dev/null +++ b/android/app/src/main/res/layout/launch_screen.xml @@ -0,0 +1,6 @@ + + + \ No newline at end of file diff --git a/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/android/app/src/main/res/mipmap-hdpi/ic_launcher.png index a2f5908..93248f4 100644 Binary files a/android/app/src/main/res/mipmap-hdpi/ic_launcher.png and b/android/app/src/main/res/mipmap-hdpi/ic_launcher.png differ diff --git a/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png b/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png index 1b52399..93248f4 100644 Binary files a/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png and b/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png differ diff --git a/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/android/app/src/main/res/mipmap-mdpi/ic_launcher.png index ff10afd..93248f4 100644 Binary files a/android/app/src/main/res/mipmap-mdpi/ic_launcher.png and b/android/app/src/main/res/mipmap-mdpi/ic_launcher.png differ diff --git a/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png b/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png index 115a4c7..93248f4 100644 Binary files a/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png and b/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png differ diff --git a/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png index dcd3cd8..93248f4 100644 Binary files a/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png and b/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png b/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png index 459ca60..93248f4 100644 Binary files a/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png and b/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png differ diff --git a/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png index 8ca12fe..93248f4 100644 Binary files a/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png and b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png index 8e19b41..93248f4 100644 Binary files a/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png and b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png differ diff --git a/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png index b824ebd..93248f4 100644 Binary files a/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png and b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png index 4c19a13..93248f4 100644 Binary files a/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png and b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png differ diff --git a/android/app/src/main/res/values/colors.xml b/android/app/src/main/res/values/colors.xml new file mode 100644 index 0000000..fd67559 --- /dev/null +++ b/android/app/src/main/res/values/colors.xml @@ -0,0 +1,6 @@ + + + + #000000 + #ffffff + \ No newline at end of file diff --git a/android/app/src/main/res/values/strings.xml b/android/app/src/main/res/values/strings.xml index f8711ef..2010468 100644 --- a/android/app/src/main/res/values/strings.xml +++ b/android/app/src/main/res/values/strings.xml @@ -1,3 +1,5 @@ + Fm8MkfACfLaQ-HnpPtqDGrZPbGt810f04d24-92c2-41a5-84ae-b434a9f20a91 + Github_RN diff --git a/android/app/src/main/res/values/styles.xml b/android/app/src/main/res/values/styles.xml index 319eb0c..e46695f 100644 --- a/android/app/src/main/res/values/styles.xml +++ b/android/app/src/main/res/values/styles.xml @@ -3,6 +3,8 @@ diff --git a/android/build.gradle b/android/build.gradle index 3231b29..58b1cea 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -25,6 +25,7 @@ allprojects { mavenLocal() google() jcenter() + maven { url 'https://dl.bintray.com/umsdk/release' } maven { // All of React Native (JS, Obj-C sources, Android binaries) is installed from npm url "$rootDir/../node_modules/react-native/android" diff --git a/android/gradle.properties b/android/gradle.properties index 89e0d99..ceae311 100644 --- a/android/gradle.properties +++ b/android/gradle.properties @@ -16,3 +16,7 @@ # This option should only be used with decoupled projects. More details, visit # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects # org.gradle.parallel=true +MYAPP_RELEASE_STORE_FILE=my-release-key.keystore +MYAPP_RELEASE_KEY_ALIAS=my-key-alias +MYAPP_RELEASE_STORE_PASSWORD=172529131 +MYAPP_RELEASE_KEY_PASSWORD=172529131 \ No newline at end of file diff --git a/android/settings.gradle b/android/settings.gradle index 0d84105..e7ed515 100644 --- a/android/settings.gradle +++ b/android/settings.gradle @@ -1,7 +1,14 @@ rootProject.name = 'Github_RN' +include ':react-native-code-push' +project(':react-native-code-push').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-code-push/android/app') +include ':react-native-webview' +project(':react-native-webview').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-webview/android') +include ':react-native-splash-screen' +project(':react-native-splash-screen').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-splash-screen/android') include ':react-native-vector-icons' project(':react-native-vector-icons').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-vector-icons/android') include ':react-native-gesture-handler' project(':react-native-gesture-handler').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-gesture-handler/android') include ':app' + diff --git a/assets/qrcode.png b/assets/qrcode.png new file mode 100644 index 0000000..145ba13 Binary files /dev/null and b/assets/qrcode.png differ diff --git a/ios/Github_RN.xcodeproj/project.pbxproj b/ios/Github_RN.xcodeproj/project.pbxproj index 376dd2a..db46149 100644 --- a/ios/Github_RN.xcodeproj/project.pbxproj +++ b/ios/Github_RN.xcodeproj/project.pbxproj @@ -58,6 +58,10 @@ 466AB0B4C8E04AAAAF7BBE4A /* Octicons.ttf in Resources */ = {isa = PBXBuildFile; fileRef = A7AE27456DC64EB49FCC2DC3 /* Octicons.ttf */; }; 51463D0D71484EE184ABBCCD /* SimpleLineIcons.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 5A353461451E4469B286C768 /* SimpleLineIcons.ttf */; }; D4D8A2191CE3401A9B32DD65 /* Zocial.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 7130809C8BA946E783340944 /* Zocial.ttf */; }; + 92EEB262A0B74CB7996BD838 /* libSplashScreen.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 0A60B592A6A742FE9EB23F86 /* libSplashScreen.a */; }; + D1988C364F694AD8BF35CCA9 /* libCodePush.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 514258C52E324F0D848232D4 /* libCodePush.a */; }; + 3BEF98B363F94BBEBD61279F /* libz.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 470262C5B1BF43C79C911498 /* libz.tbd */; }; + 926576E1EBCA480BB45C2B35 /* libRNCWebView.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E996E96A7CD741309BF0E785 /* libRNCWebView.a */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -386,6 +390,13 @@ A7AE27456DC64EB49FCC2DC3 /* Octicons.ttf */ = {isa = PBXFileReference; name = "Octicons.ttf"; path = "../node_modules/react-native-vector-icons/Fonts/Octicons.ttf"; sourceTree = ""; fileEncoding = undefined; lastKnownFileType = unknown; explicitFileType = undefined; includeInIndex = 0; }; 5A353461451E4469B286C768 /* SimpleLineIcons.ttf */ = {isa = PBXFileReference; name = "SimpleLineIcons.ttf"; path = "../node_modules/react-native-vector-icons/Fonts/SimpleLineIcons.ttf"; sourceTree = ""; fileEncoding = undefined; lastKnownFileType = unknown; explicitFileType = undefined; includeInIndex = 0; }; 7130809C8BA946E783340944 /* Zocial.ttf */ = {isa = PBXFileReference; name = "Zocial.ttf"; path = "../node_modules/react-native-vector-icons/Fonts/Zocial.ttf"; sourceTree = ""; fileEncoding = undefined; lastKnownFileType = unknown; explicitFileType = undefined; includeInIndex = 0; }; + 1EF9F2201C9E4A23A7C4DAC7 /* SplashScreen.xcodeproj */ = {isa = PBXFileReference; name = "SplashScreen.xcodeproj"; path = "../node_modules/react-native-splash-screen/ios/SplashScreen.xcodeproj"; sourceTree = ""; fileEncoding = undefined; lastKnownFileType = wrapper.pb-project; explicitFileType = undefined; includeInIndex = 0; }; + 0A60B592A6A742FE9EB23F86 /* libSplashScreen.a */ = {isa = PBXFileReference; name = "libSplashScreen.a"; path = "libSplashScreen.a"; sourceTree = ""; fileEncoding = undefined; lastKnownFileType = archive.ar; explicitFileType = undefined; includeInIndex = 0; }; + 8B7304002B7D42D28C983932 /* CodePush.xcodeproj */ = {isa = PBXFileReference; name = "CodePush.xcodeproj"; path = "../node_modules/react-native-code-push/ios/CodePush.xcodeproj"; sourceTree = ""; fileEncoding = undefined; lastKnownFileType = wrapper.pb-project; explicitFileType = undefined; includeInIndex = 0; }; + 514258C52E324F0D848232D4 /* libCodePush.a */ = {isa = PBXFileReference; name = "libCodePush.a"; path = "libCodePush.a"; sourceTree = ""; fileEncoding = undefined; lastKnownFileType = archive.ar; explicitFileType = undefined; includeInIndex = 0; }; + 470262C5B1BF43C79C911498 /* libz.tbd */ = {isa = PBXFileReference; name = "libz.tbd"; path = "usr/lib/libz.tbd"; sourceTree = SDKROOT; fileEncoding = undefined; lastKnownFileType = sourcecode.text-based-dylib-definition; explicitFileType = undefined; includeInIndex = 0; }; + 930571BCC2BD43D19D3A1A00 /* RNCWebView.xcodeproj */ = {isa = PBXFileReference; name = "RNCWebView.xcodeproj"; path = "../node_modules/react-native-webview/ios/RNCWebView.xcodeproj"; sourceTree = ""; fileEncoding = undefined; lastKnownFileType = wrapper.pb-project; explicitFileType = undefined; includeInIndex = 0; }; + E996E96A7CD741309BF0E785 /* libRNCWebView.a */ = {isa = PBXFileReference; name = "libRNCWebView.a"; path = "libRNCWebView.a"; sourceTree = ""; fileEncoding = undefined; lastKnownFileType = archive.ar; explicitFileType = undefined; includeInIndex = 0; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -416,6 +427,10 @@ 139FDEF61B0652A700C62182 /* libRCTWebSocket.a in Frameworks */, 29A3648EA1C845C9A74C1C28 /* libRNGestureHandler.a in Frameworks */, AFFD6DB7BFCC490CA8CDB584 /* libRNVectorIcons.a in Frameworks */, + 92EEB262A0B74CB7996BD838 /* libSplashScreen.a in Frameworks */, + D1988C364F694AD8BF35CCA9 /* libCodePush.a in Frameworks */, + 3BEF98B363F94BBEBD61279F /* libz.tbd in Frameworks */, + 926576E1EBCA480BB45C2B35 /* libRNCWebView.a in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -570,6 +585,7 @@ ED297162215061F000B7C4FE /* JavaScriptCore.framework */, ED2971642150620600B7C4FE /* JavaScriptCore.framework */, 2D16E6891FA4F8E400B85C8A /* libReact.a */, + 470262C5B1BF43C79C911498 /* libz.tbd */, ); name = Frameworks; sourceTree = ""; @@ -609,6 +625,9 @@ 139FDEE61B06529A00C62182 /* RCTWebSocket.xcodeproj */, 79F9A0ABB9F6485590DB18A2 /* RNGestureHandler.xcodeproj */, 8309BB063EB945DD9FAFD3E4 /* RNVectorIcons.xcodeproj */, + 1EF9F2201C9E4A23A7C4DAC7 /* SplashScreen.xcodeproj */, + 8B7304002B7D42D28C983932 /* CodePush.xcodeproj */, + 930571BCC2BD43D19D3A1A00 /* RNCWebView.xcodeproj */, ); name = Libraries; sourceTree = ""; @@ -1279,11 +1298,17 @@ "\"$(SRCROOT)/$(TARGET_NAME)\"", "\"$(SRCROOT)/$(TARGET_NAME)\"", "\"$(SRCROOT)/$(TARGET_NAME)\"", + "\"$(SRCROOT)/$(TARGET_NAME)\"", + "\"$(SRCROOT)/$(TARGET_NAME)\"", + "\"$(SRCROOT)/$(TARGET_NAME)\"", ); HEADER_SEARCH_PATHS = ( "$(inherited)", "$(SRCROOT)\..\node_modules\react-native-gesture-handler\ios/**", "$(SRCROOT)\..\node_modules\react-native-vector-icons\RNVectorIconsManager", + "$(SRCROOT)\..\node_modules\react-native-splash-screen\ios", + "$(SRCROOT)\..\node_modules\react-native-code-push\ios\CodePush/**", + "$(SRCROOT)\..\node_modules\react-native-webview\ios", ); }; name = Debug; @@ -1309,11 +1334,17 @@ "\"$(SRCROOT)/$(TARGET_NAME)\"", "\"$(SRCROOT)/$(TARGET_NAME)\"", "\"$(SRCROOT)/$(TARGET_NAME)\"", + "\"$(SRCROOT)/$(TARGET_NAME)\"", + "\"$(SRCROOT)/$(TARGET_NAME)\"", + "\"$(SRCROOT)/$(TARGET_NAME)\"", ); HEADER_SEARCH_PATHS = ( "$(inherited)", "$(SRCROOT)\..\node_modules\react-native-gesture-handler\ios/**", "$(SRCROOT)\..\node_modules\react-native-vector-icons\RNVectorIconsManager", + "$(SRCROOT)\..\node_modules\react-native-splash-screen\ios", + "$(SRCROOT)\..\node_modules\react-native-code-push\ios\CodePush/**", + "$(SRCROOT)\..\node_modules\react-native-webview\ios", ); }; name = Release; @@ -1338,6 +1369,9 @@ "$(inherited)", "$(SRCROOT)\..\node_modules\react-native-gesture-handler\ios/**", "$(SRCROOT)\..\node_modules\react-native-vector-icons\RNVectorIconsManager", + "$(SRCROOT)\..\node_modules\react-native-splash-screen\ios", + "$(SRCROOT)\..\node_modules\react-native-code-push\ios\CodePush/**", + "$(SRCROOT)\..\node_modules\react-native-webview\ios", ); }; name = Debug; @@ -1361,6 +1395,9 @@ "$(inherited)", "$(SRCROOT)\..\node_modules\react-native-gesture-handler\ios/**", "$(SRCROOT)\..\node_modules\react-native-vector-icons\RNVectorIconsManager", + "$(SRCROOT)\..\node_modules\react-native-splash-screen\ios", + "$(SRCROOT)\..\node_modules\react-native-code-push\ios\CodePush/**", + "$(SRCROOT)\..\node_modules\react-native-webview\ios", ); }; name = Release; @@ -1394,11 +1431,17 @@ "\"$(SRCROOT)/$(TARGET_NAME)\"", "\"$(SRCROOT)/$(TARGET_NAME)\"", "\"$(SRCROOT)/$(TARGET_NAME)\"", + "\"$(SRCROOT)/$(TARGET_NAME)\"", + "\"$(SRCROOT)/$(TARGET_NAME)\"", + "\"$(SRCROOT)/$(TARGET_NAME)\"", ); HEADER_SEARCH_PATHS = ( "$(inherited)", "$(SRCROOT)\..\node_modules\react-native-gesture-handler\ios/**", "$(SRCROOT)\..\node_modules\react-native-vector-icons\RNVectorIconsManager", + "$(SRCROOT)\..\node_modules\react-native-splash-screen\ios", + "$(SRCROOT)\..\node_modules\react-native-code-push\ios\CodePush/**", + "$(SRCROOT)\..\node_modules\react-native-webview\ios", ); }; name = Debug; @@ -1432,11 +1475,17 @@ "\"$(SRCROOT)/$(TARGET_NAME)\"", "\"$(SRCROOT)/$(TARGET_NAME)\"", "\"$(SRCROOT)/$(TARGET_NAME)\"", + "\"$(SRCROOT)/$(TARGET_NAME)\"", + "\"$(SRCROOT)/$(TARGET_NAME)\"", + "\"$(SRCROOT)/$(TARGET_NAME)\"", ); HEADER_SEARCH_PATHS = ( "$(inherited)", "$(SRCROOT)\..\node_modules\react-native-gesture-handler\ios/**", "$(SRCROOT)\..\node_modules\react-native-vector-icons\RNVectorIconsManager", + "$(SRCROOT)\..\node_modules\react-native-splash-screen\ios", + "$(SRCROOT)\..\node_modules\react-native-code-push\ios\CodePush/**", + "$(SRCROOT)\..\node_modules\react-native-webview\ios", ); }; name = Release; @@ -1469,11 +1518,17 @@ "\"$(SRCROOT)/$(TARGET_NAME)\"", "\"$(SRCROOT)/$(TARGET_NAME)\"", "\"$(SRCROOT)/$(TARGET_NAME)\"", + "\"$(SRCROOT)/$(TARGET_NAME)\"", + "\"$(SRCROOT)/$(TARGET_NAME)\"", + "\"$(SRCROOT)/$(TARGET_NAME)\"", ); HEADER_SEARCH_PATHS = ( "$(inherited)", "$(SRCROOT)\..\node_modules\react-native-gesture-handler\ios/**", "$(SRCROOT)\..\node_modules\react-native-vector-icons\RNVectorIconsManager", + "$(SRCROOT)\..\node_modules\react-native-splash-screen\ios", + "$(SRCROOT)\..\node_modules\react-native-code-push\ios\CodePush/**", + "$(SRCROOT)\..\node_modules\react-native-webview\ios", ); }; name = Debug; @@ -1506,11 +1561,17 @@ "\"$(SRCROOT)/$(TARGET_NAME)\"", "\"$(SRCROOT)/$(TARGET_NAME)\"", "\"$(SRCROOT)/$(TARGET_NAME)\"", + "\"$(SRCROOT)/$(TARGET_NAME)\"", + "\"$(SRCROOT)/$(TARGET_NAME)\"", + "\"$(SRCROOT)/$(TARGET_NAME)\"", ); HEADER_SEARCH_PATHS = ( "$(inherited)", "$(SRCROOT)\..\node_modules\react-native-gesture-handler\ios/**", "$(SRCROOT)\..\node_modules\react-native-vector-icons\RNVectorIconsManager", + "$(SRCROOT)\..\node_modules\react-native-splash-screen\ios", + "$(SRCROOT)\..\node_modules\react-native-code-push\ios\CodePush/**", + "$(SRCROOT)\..\node_modules\react-native-webview\ios", ); }; name = Release; diff --git a/ios/Github_RN/AppDelegate.m b/ios/Github_RN/AppDelegate.m index 8a3c64e..bcdfd45 100644 --- a/ios/Github_RN/AppDelegate.m +++ b/ios/Github_RN/AppDelegate.m @@ -6,6 +6,7 @@ */ #import "AppDelegate.h" +#import #import #import @@ -16,7 +17,12 @@ - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:( { NSURL *jsCodeLocation; - jsCodeLocation = [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:@"index" fallbackResource:nil]; + + #ifdef DEBUG + jsCodeLocation = [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:@"index" fallbackResource:nil]; + #else + jsCodeLocation = [CodePush bundleURL]; + #endif RCTRootView *rootView = [[RCTRootView alloc] initWithBundleURL:jsCodeLocation moduleName:@"Github_RN" diff --git a/ios/Github_RN/Info.plist b/ios/Github_RN/Info.plist index 0fb44b2..aab6ed9 100644 --- a/ios/Github_RN/Info.plist +++ b/ios/Github_RN/Info.plist @@ -71,5 +71,7 @@ SimpleLineIcons.ttf Zocial.ttf + CodePushDeploymentKey + deployment-key-here diff --git a/js/common/BaseItem.js b/js/common/BaseItem.js index 43e1ffc..4da746e 100644 --- a/js/common/BaseItem.js +++ b/js/common/BaseItem.js @@ -2,6 +2,7 @@ import React, {Component} from 'react'; import {StyleSheet,Image, Text, View, TouchableOpacity } from 'react-native'; import FontAwesome from 'react-native-vector-icons/FontAwesome'; import {PropTypes} from 'prop-types'; +import AnalyticsUtil from '../util/AnalyticsUtil'; export default class BaseItem extends Component { static proTypes = { @@ -48,10 +49,12 @@ export default class BaseItem extends Component { }); } onPressFavorite () { + AnalyticsUtil.onEvent('Collection'); this.setFavoriteState(!this.state.isFavorite); this.props.onFavorite(this.props.projectModel.item, !this.state.isFavorite); } _favoriteIcon () { + const {theme} = this.props; return } diff --git a/js/common/MORE_MENU.js b/js/common/MORE_MENU.js new file mode 100644 index 0000000..d02ac5e --- /dev/null +++ b/js/common/MORE_MENU.js @@ -0,0 +1,24 @@ +/** +* @name MORE_MENU +* @param {CLIPBOARD} +* @author Ethan +* @date 2019-03-27 13:12:58 +*/ +import Octicons from 'react-native-vector-icons/Octicons'; +import Ionicons from 'react-native-vector-icons/Ionicons'; +import MaterialCommunityIcons from 'react-native-vector-icons/MaterialCommunityIcons'; +import MaterialIcons from 'react-native-vector-icons/MaterialIcons'; +export const MORE_MENU = { + Custom_Language: {name:'自定义语言', Icons:Ionicons, icon: 'md-checkbox-outline'}, + Sort_Language: {name:'语言排序', Icons:MaterialCommunityIcons, icon: 'sort'}, + Custom_Theme: {name:'自定义主题', Icons:Ionicons, icon: 'ios-color-palette'}, + Custom_key: {name:'自定义标签', Icons:Ionicons, icon: 'md-checkbox-outline'}, + Sort_key: {name:'标签排序', Icons:MaterialCommunityIcons, icon: 'sort'}, + Remove_key: {name:'标签移除', Icons:Ionicons, icon: 'md-remove'}, + About_Author: {name:'关于作者', Icons:Octicons, icon: 'smiley'}, + About: {name:'关于', Icons:Ionicons, icon: 'logo-github'}, + Tutorial: {name:'教程', Icons:Ionicons, icon: 'ios-bookmarks'}, + Feedback: {name:'反馈', Icons:MaterialIcons, icon: 'feedback'}, + Share: {name:'分享', Icons:Ionicons, icon: 'md-share'}, + CodePush: { name: 'CodePush', Icons: Ionicons, icon: 'ios-planet' }, +}; diff --git a/js/common/NavigationBar.js b/js/common/NavigationBar.js index 9757813..58aec31 100644 --- a/js/common/NavigationBar.js +++ b/js/common/NavigationBar.js @@ -1,10 +1,11 @@ import React, { Component } from 'react'; -import { ViewPropTypes, Text, StyleSheet, View, StatusBar, Platform } from 'react-native'; +import { ViewPropTypes, Text, StyleSheet, View, StatusBar, Platform, DeviceInfo } from 'react-native'; import {PropTypes} from 'prop-types'; const NAV_BAR_HEIGHT_IOS = 44; // 导航栏在IOS中的高度 const NAV_BAR_HEIGHT_ANDROID = 50; // 导航栏在Android中的高度 -const STATUS_BAR_HEIGHT = 20; // 状态栏的高度 +// 状态栏的高度 +const STATUS_BAR_HEIGHT = DeviceInfo.isIPhoneX_deprecated ? 0 : 20; // 设置状态栏所接受的属性 const StatusBarShape = { diff --git a/js/common/SafeAreaViewPlus.js b/js/common/SafeAreaViewPlus.js new file mode 100644 index 0000000..2f679fa --- /dev/null +++ b/js/common/SafeAreaViewPlus.js @@ -0,0 +1,67 @@ +import React, { Component, } from 'react' +import { DeviceInfo, SafeAreaView, StyleSheet, View, ViewPropTypes } from 'react-native' +import PropTypes from 'prop-types' + +export default class SafeAreaViewPlus extends Component { + static propTypes = { + ...ViewPropTypes, + topColor: PropTypes.string, + bottomColor: PropTypes.string, + enablePlus: PropTypes.bool, + topInset: PropTypes.bool, + bottomInset: PropTypes.bool, + } + + static defaultProps = { + topColor: 'transparent', + bottomColor: '#f8f8f8', + enablePlus: true, + topInset: true, + bottomInset: false, + } + + // 顶部安全区域 + renderTopArea = (topColor, topInset) => { + return !DeviceInfo.isIPhoneX_deprecated || !topInset ? null + : + } + + // 底部安全区域 + renderBottomArea = (bottomColor, bottomInset) => { + return !DeviceInfo.isIPhoneX_deprecated || !bottomInset ? null + : + } + + // 系统安全区域 + renderSafeAreaView = () => { + return + {this.props.children} + + } + // 自定义安全区域 + renderSafeAreaViewPlus = () => { + const { children, topColor, bottomColor, topInset, bottomInset } = this.props + return + {this.renderTopArea(topColor, topInset)} + {children} + {this.renderBottomArea(bottomColor, bottomInset)} + + } + + render (): React.ReactNode { + const { enablePlus } = this.props + return enablePlus ? this.renderSafeAreaViewPlus() : this.renderSafeAreaView() + } +} + +const styles = StyleSheet.create({ + container: { + flex: 1, + }, + topArea: { + height: 44, + }, + bottomArea: { + height: 34, + } +}) \ No newline at end of file diff --git a/js/expand/dao/FavoriteDao.js b/js/expand/dao/FavoriteDao.js index cd56618..20c28f5 100644 --- a/js/expand/dao/FavoriteDao.js +++ b/js/expand/dao/FavoriteDao.js @@ -5,7 +5,6 @@ const FAVORITE_KEY_PREFIX = 'favorite_'; export default class FavoriteDao { constructor (flag) { this.favoriteKey = FAVORITE_KEY_PREFIX + flag; - console.log(this.favoriteKey) } /** * 收藏项目,保存收藏项目 diff --git a/js/expand/dao/LanguageDao.js b/js/expand/dao/LanguageDao.js new file mode 100644 index 0000000..47ee111 --- /dev/null +++ b/js/expand/dao/LanguageDao.js @@ -0,0 +1,41 @@ +import { AsyncStorage } from 'react-native'; +import keys from '../../res/data/keys.json'; +import langs from '../../res/data/langs.json'; +export const FLAG_LANGUAGE = { + flag_language: 'language_dao_language', + flag_key: 'language_dao_key' +}; +export default class LanguageDao { + constructor (flag) { + this.flag = flag; + } + fetch () { + // 获取语言或者标签 + return new Promise((resolve, reject) => { + AsyncStorage.getItem(this.flag, (error, result) => { + if (error) { + reject(error); + return; + } + if (!result) { + let data = this.flag === FLAG_LANGUAGE.flag_language ? langs : keys; + this.save(data); + resolve(data); + } else { + try { + resolve(JSON.parse(result)); + } catch (error) { + reject(error); + } + } + }); + }); + } + save (objectData) { + // 保存语言或标签 + let stringData = JSON.stringify(objectData); + AsyncStorage.setItem(this.flag, stringData, (error, result) => { + + }) + } +} \ No newline at end of file diff --git a/js/expand/dao/ThemeDao.js b/js/expand/dao/ThemeDao.js new file mode 100644 index 0000000..20ea53c --- /dev/null +++ b/js/expand/dao/ThemeDao.js @@ -0,0 +1,35 @@ +import { AsyncStorage } from 'react-native'; +import ThemeFactory, { ThemeFlags } from '../../res/styles/ThemeFactory'; + +const THEME_KEY = 'theme_key' + +export default class ThemeDao { + + /** + * 获取当前主题 + * @returns {Promise} + */ + getTheme () { + return new Promise((resolve, reject) => { + AsyncStorage.getItem(THEME_KEY, (error, result) => { + if (error) { + reject(error); + return; + } + if (!result) { + this.save(ThemeFlags.Default); + result = ThemeFlags.Default; + } + resolve(ThemeFactory.createTheme(result)); + }) + }) + } + + /** + * 保存主题标识 + * @param themeFlag + */ + save (themeFlag) { + AsyncStorage.setItem(THEME_KEY, themeFlag, (error => {})); + } +} diff --git a/js/expand/dao/dataStore.js b/js/expand/dao/dataStore.js index bd7242a..b3eb102 100644 --- a/js/expand/dao/dataStore.js +++ b/js/expand/dao/dataStore.js @@ -19,14 +19,14 @@ export default class DataStore { resolve(wrapData); } else { this.fetchNetData(url, flag).then((data) => { - resolve(this.__wrapData(data)); + resolve(this._wrapData(data)); }).catch((error) => { reject(error); }) } }).catch((error) => { this.fetchNetData(url, flag).then((data) => { - resolve(this.__wrapData(data)); + resolve(this._wrapData(data)); }).catch((error) => { reject(error); }) diff --git a/js/navigator/AppNavigator.js b/js/navigator/AppNavigator.js index d4cee2c..62ef198 100644 --- a/js/navigator/AppNavigator.js +++ b/js/navigator/AppNavigator.js @@ -18,6 +18,13 @@ import DetailPage from '../page/DetailPage'; import AxiosDemoPage from '../page/AxiosDemo'; // 网络请求测试页面 import AsyncStorageDemo from '../page/AsyncStorageDemo'; //AsyncStorage测试页面 import DataStorePage from '../page/DataStorePage'; +import WebViewPage from '../page/WebViewPage'; //浏览器页面 +import AboutPage from '../page/about/AboutPage'; +import AboutMePage from '../page/about/AboutMePage'; +import CustomKeyPage from '../page/CustomKeyPage'; +import SortKeyPage from '../page/SortKeyPage'; +import SearchPage from '../page/SearchPage'; +import CodePushPage from '../page/CodePushPage' export const rootCom = 'Init'; //设置跟路由 @@ -46,6 +53,48 @@ const MainNavigator = createStackNavigator({ header: null } }, + WebViewPage: { + screen: WebViewPage, + navigationOptions: { + header: null + } + }, + CustomKeyPage: { + screen: CustomKeyPage, + navigationOptions: { + header: null + } + }, + AboutPage: { + screen: AboutPage, + navigationOptions: { + header: null + } + }, + AboutMePage: { + screen: AboutMePage, + navigationOptions: { + header: null + } + }, + SortKeyPage: { + screen: SortKeyPage, + navigationOptions: { + header: null + } + }, + SearchPage: { + screen: SearchPage, + navigationOptions: { + header: null + } + }, + CodePushPage : { + screen: CodePushPage , + navigationOptions: { + header: null + } + }, AxiosDemoPage: { screen: AxiosDemoPage, navigationOptions: { diff --git a/js/navigator/DynamicTabNavigator.js b/js/navigator/DynamicTabNavigator.js index 00eb492..62cc5f4 100644 --- a/js/navigator/DynamicTabNavigator.js +++ b/js/navigator/DynamicTabNavigator.js @@ -23,6 +23,8 @@ import Mypage from '../page/Mypage'; import MaterialIcons from 'react-native-vector-icons/MaterialIcons'; import Ionicons from 'react-native-vector-icons/Ionicons'; import Entypo from 'react-native-vector-icons/Entypo'; +import EventBus from 'react-native-event-bus'; +import EventTypes from '../util/EventTypes'; const TABS = { PopularPage:{ @@ -97,12 +99,19 @@ export default class DynamicTabNavigator extends Component { PopularPage.navigationOptions.tabBarLabel = '最热' //动态配置Tab属性 return this.Tabs = createAppContainer(createBottomTabNavigator(tabs, { // //这里使用tabBarComponent来覆盖用作标签栏的组件. 达到动态修改标签栏的样式等 - tabBarComponent: props => + tabBarComponent: props => })); } render() { const Tab = this._tabNavigator(); - return + return { + EventBus.getInstance().fireEvent(EventTypes.bottom_tab_select, { // 发送底部tab切换事件 + from: prevState.index, + to: newState.index + }) + }} + /> } } diff --git a/js/page/CodePushPage.js b/js/page/CodePushPage.js new file mode 100644 index 0000000..1b44dab --- /dev/null +++ b/js/page/CodePushPage.js @@ -0,0 +1,182 @@ +import React, { Component } from 'react' +import { Dimensions, StyleSheet, Text, TouchableOpacity, } from 'react-native' +import CodePush from 'react-native-code-push' +import ViewUtils from '../util/ViewUtil' +import NavigationBar from '../common/NavigationBar' +import SafeAreaViewPlus from '../common/SafeAreaViewPlus' +import BackPressComponent from '../common/BackPressComponent' +import GlobalStyles from '../res/styles/GobalStyles' +import NavigationUtil from '../navigator/NavigationUtil' + +class CodePushPage extends Component { + constructor (props) { + super(props) + this.state = { restartAllowed: true } + this.backPress = new BackPressComponent({ backPress: this.onBackPress }) + } + + componentDidMount () { + this.backPress.componentDidMount() + } + + componentWillUnmount () { + this.backPress.componentWillUnmount() + } + + onBackPress = () => { + NavigationUtil.goBack({navigation:this.props.navigation}); + return true + } + + codePushStatusDidChange (syncStatus) { + switch (syncStatus) { + case CodePush.SyncStatus.CHECKING_FOR_UPDATE: + this.setState({ syncMessage: 'Checking for update.' }) + break + case CodePush.SyncStatus.DOWNLOADING_PACKAGE: + this.setState({ syncMessage: 'Downloading package.' }) + break + case CodePush.SyncStatus.AWAITING_USER_ACTION: + this.setState({ syncMessage: 'Awaiting user action.' }) + break + case CodePush.SyncStatus.INSTALLING_UPDATE: + this.setState({ syncMessage: 'Installing update.' }) + break + case CodePush.SyncStatus.UP_TO_DATE: + this.setState({ syncMessage: 'App up to date.', progress: false }) + break + case CodePush.SyncStatus.UPDATE_IGNORED: + this.setState({ syncMessage: 'Update cancelled by user.', progress: false }) + break + case CodePush.SyncStatus.UPDATE_INSTALLED: + this.setState({ syncMessage: 'Update installed and will be applied on restart.', progress: false }) + break + case CodePush.SyncStatus.UNKNOWN_ERROR: + this.setState({ syncMessage: 'An unknown error occurred.', progress: false }) + break + } + } + + codePushDownloadDidProgress (progress) { + this.setState({ progress }) + } + + toggleAllowRestart () { + this.state.restartAllowed + ? CodePush.disallowRestart() + : CodePush.allowRestart() + + this.setState({ restartAllowed: !this.state.restartAllowed }) + } + + getUpdateMetadata () { + CodePush.getUpdateMetadata(CodePush.UpdateState.RUNNING) + .then((metadata: LocalPackage) => { + this.setState({ + syncMessage: metadata ? JSON.stringify(metadata) : 'Running binary version', + progress: false + }) + }, (error: any) => { + this.setState({ syncMessage: 'Error: ' + error, progress: false }) + }) + } + + /** Update is downloaded silently, and applied on restart (recommended) */ + sync () { + CodePush.sync( + {}, + this.codePushStatusDidChange.bind(this), + this.codePushDownloadDidProgress.bind(this) + ) + } + + /** Update pops a confirmation dialog, and then immediately reboots the app */ + syncImmediate () { + CodePush.sync( + { installMode: CodePush.InstallMode.IMMEDIATE, updateDialog: true }, + this.codePushStatusDidChange.bind(this), + this.codePushDownloadDidProgress.bind(this) + ) + } + + render () { + let progressView + if (this.state.progress) { + progressView = ( + {this.state.progress.receivedBytes} of {this.state.progress.totalBytes} bytes + received + ) + } + const { theme } = this.props.navigation.state.params + return ( + + NavigationUtil.goBack({navigation: this.props.navigation}))} + title={'CodePush'} + /> + + Welcome to CodePush! + + + Press for background sync + + + Press for dialog-driven sync + + {progressView} + + Restart {this.state.restartAllowed ? 'allowed' : 'forbidden'} + + + Press for Update Metadata + + {this.state.syncMessage || ''} + + ) + } +} + +const styles = StyleSheet.create({ + container: { + flex: 1, + }, + image: { + margin: 30, + width: Dimensions.get('window').width - 100, + height: 365 * (Dimensions.get('window').width - 100) / 651, + }, + messages: { + marginTop: 30, + textAlign: 'center', + }, + restartToggleButton: { + color: 'blue', + fontSize: 17 + }, + syncButton: { + color: 'green', + fontSize: 17 + }, + welcome: { + fontSize: 20, + textAlign: 'center', + margin: 20 + }, +}) + +/** + * Configured with a MANUAL check frequency for easy testing. For production apps, it is recommended to configure a + * different check frequency, such as ON_APP_START, for a 'hands-off' approach where CodePush.sync() does not + * need to be explicitly called. All options of CodePush.sync() are also available in this decorator. + */ +let codePushOptions = { checkFrequency: CodePush.CheckFrequency.MANUAL } + +CodePushPage = CodePush(codePushOptions)(CodePushPage) + +export default CodePushPage \ No newline at end of file diff --git a/js/page/CustomKeyPage.js b/js/page/CustomKeyPage.js new file mode 100644 index 0000000..7f9cd2e --- /dev/null +++ b/js/page/CustomKeyPage.js @@ -0,0 +1,206 @@ + +import React, {Component} from 'react'; +import {StyleSheet, TouchableOpacity, View, ScrollView, Text, Alert} from 'react-native'; +import Toast from 'react-native-easy-toast'; +import {connect} from 'react-redux'; +import actions from '../redux/action'; +import BackPressComponent from '../common/BackPressComponent'; +import CheckBox from 'react-native-check-box'; + +import { + createMaterialTopTabNavigator, + createAppContainer +} from 'react-navigation'; +import NavigationUtil from '../navigator/NavigationUtil'; +import FavoriteUtil from '../util/FavoriteUtil'; + +import NavigationBar from '../common/NavigationBar'; +import ViewUtil from '../util/ViewUtil'; +import Ionicons from 'react-native-vector-icons/Ionicons'; +import ArrayUtil from '../util/ArrayUtil'; + +// +import LanguageDao, {FLAG_LANGUAGE} from '../expand/dao/LanguageDao'; + +type Props = {}; + +@connect( + state=>state, + { + onLoadLanguage: actions.onLoadLanguage + } +) +export default class CustomKeyPage extends Component { + constructor (props) { + super(props); + this.params = this.props.navigation.state.params; + this.backPress = new BackPressComponent({backPress: (e) => this.onBackPress(e)}); + this.changeValues = []; + this.isRemoveKey = !!this.params.isRemoveKey; + this.languageDao = new LanguageDao(this.params.flag); + this.state = { + keys: [] + } + } + componentDidMount() { + // 注册物理返回键的监听 + this.backPress.componentDidMount(); + // 如果props中标签为空,则从本地存储中获取标签 + if (CustomKeyPage._keys(this.props).length === 0) { + let {onLoadLanguage} = this.props; + onLoadLanguage(this.params.flag); + } + this.setState({ + keys: CustomKeyPage._keys(this.props) + }) + } + componentWillUnmount() { + // 注销物理返回键的监听 + this.backPress.componentWillUnmount(); + } + onBackPress = () => { + this.onBack(); + return true; + } + onBack () { + if (this.changeValues.length > 0) { + Alert.alert('提示','要保存修改吗', [ + { + text: '否', onPress: () => { + NavigationUtil.goBack({navigation:this.props.navigation}); + } + }, + { + text: '是', onPress: () => { + this.onSave(); + } + } + ]) + } else { + NavigationUtil.goBack({navigation:this.props.navigation}); + } + } + static _keys (props, original, state) { + const {flag, isRemoveKey} = props.navigation.state.params; + let key = flag === FLAG_LANGUAGE.flag_key?'keys':'languages'; + if (isRemoveKey&&!original) { + // 如果state中的keys为空则从props中获取 + return state && state.keys && state.keys.length !== 0 && state.keys || props.language[key].map(val => { + // 注意不直接修改props,copy一份 + return { + ...val, + checked: false + }; + }); + } else { + return props.language[key]; + } + } + static getDerivedStateFromProps (nextProps, prevState) { + if (prevState.keys !== CustomKeyPage._keys(nextProps, null, prevState)) { + return { + keys: CustomKeyPage._keys(nextProps, null, prevState) + } + } + return null; + } + onSave () { + if (this.changeValues.length === 0) { + NavigationUtil.goBack({navigation:this.props.navigation}); + return; + } + let keys; + // 移除标签特殊处理 + if (this.isRemoveKey) { + for (let i = 0, l = this.changeValues.length; i < l; i++) { + ArrayUtil.remove(keys = CustomKeyPage._keys(this.props, true), this.changeValues[i], 'name'); + } + } + // 更新本地数据 + this.languageDao.save(keys || this.state.keys); + const {onLoadLanguage} = this.props; + // 更新store + onLoadLanguage(this.params.flag); + NavigationUtil.goBack({navigation:this.props.navigation}); + } + renderView () { + let dataArray = this.state.keys; + if (!dataArray || dataArray.length === 0) return; + let len = dataArray.length; + let views = []; + for (let i = 0, l = len; i < l; i += 2) { + views.push( + + + {this.renderCheckBox(dataArray[i], i)} + {i+1 < len && this.renderCheckBox(dataArray[i+1], i+1)} + + + + ) + } + return views; + } + onClick (data, index) { + data.checked = !data.checked; + ArrayUtil.updateArray(this.changeValues, data); + let tempkeys = this.state.keys; + tempkeys[index] = data; + this.setState({ + keys: tempkeys + }) + } + _checkedImage (checked) { + const {theme} = this.params; + return + } + renderCheckBox (data, index) { + return this.onClick(data, index)} + isChecked={data.checked} + leftText={data.name} + checkedImage={this._checkedImage(true)} + unCheckedImage={this._checkedImage(false)} + /> + } + render() { + let title = this.isRemoveKey?'标签移除': '自定义标签'; + title = this.params.flag === FLAG_LANGUAGE.flag_language ? '自定义语言': title; + let rightButtonTitle = this.isRemoveKey?'移除':'保存'; + // 顶部导航栏设置 + const {theme} = this.params; + let navigationBar = this.onBackPress())} + rightButton={ViewUtil.getRightButton(rightButtonTitle, () => this.onSave())} + /> + return + {navigationBar} + + {this.renderView()} + + + } +} +const styles = StyleSheet.create({ + container: { + flex: 1, + backgroundColor: '#F5FCFF', + }, + item: { + flexDirection: 'row', + }, + line: { + flex: 1, + height: 0.3, + backgroundColor: 'darkgray' + } +}); diff --git a/js/page/CustomTheme.js b/js/page/CustomTheme.js new file mode 100644 index 0000000..7f0422d --- /dev/null +++ b/js/page/CustomTheme.js @@ -0,0 +1,120 @@ +import React, { Component } from 'react' +import { + DeviceInfo, + Modal, + TouchableHighlight, + Platform, + ScrollView, + StyleSheet, + Text, + View +} from 'react-native' +import { connect } from 'react-redux' +import ThemeDao from '../expand/dao/ThemeDao'; +import ThemeFactory, { ThemeFlags } from '../res/styles/ThemeFactory'; +import GlobalStyles from '../res/styles/GobalStyles'; +import actions from '../redux/action'; + +@connect( + state=>state, + { + onThemeChange: actions.onThemeChange + } +) +export default class CustomKeyPage extends Component { + constructor (props) { + super(props) + this.themeDao = new ThemeDao() + } + + // 选择主题 + onSelectTheme (themeKey) { + const { onThemeChange } = this.props + this.props.onClose() + this.themeDao.save(ThemeFlags[themeKey]) + onThemeChange(ThemeFactory.createTheme(ThemeFlags[themeKey])) + } + + renderThemeItem = (themeKey) => { + return this.onSelectTheme(themeKey)} + > + + {themeKey} + + + } + + renderThemeItems = () => { + const views = [] + for (let i = 0, keys = Object.keys(ThemeFlags), length = keys.length; i < length; i += 3) { + const key1 = keys[i], key2 = keys[i + 1], key3 = keys[i + 2] + views.push( + + {this.renderThemeItem(key1)} + {this.renderThemeItem(key2)} + {this.renderThemeItem(key3)} + + ) + } + return views + } + + renderContentView = () => { + return ( + { + this.props.onClose() + }} + > + + + {this.renderThemeItems()} + + + + ) + } + + render () { + let view = this.props.visible ? + {this.renderContentView()} + : null + return view + } +} + +const styles = StyleSheet.create({ + themeItem: { + flex: 1, + height: 120, + margin: 3, + padding: 3, + borderRadius: 2, + alignItems: 'center', + justifyContent: 'center', + }, + modalContainer: { + flex: 1, + margin: 10, + marginBottom: 10 + (DeviceInfo.isIPhoneX_deprecated ? 24 : 0), + marginTop: Platform.OS === 'ios' ? 20 + (DeviceInfo.isIPhoneX_deprecated ? 24 : 0) : 10, + backgroundColor: 'white', + borderRadius: 3, + shadowColor: 'gray', + shadowOffset: { width: 2, height: 2 }, + shadowOpacity: 0.5, + shadowRadius: 2, + padding: 3 + }, + themeText: { + color: 'white', + fontWeight: '500', + fontSize: 16 + } +}) diff --git a/js/page/DetailPage.js b/js/page/DetailPage.js index f263098..c1c1d2e 100644 --- a/js/page/DetailPage.js +++ b/js/page/DetailPage.js @@ -1,5 +1,6 @@ import React, {Component} from 'react'; -import {StyleSheet,DeviceInfo, WebView, TouchableOpacity, Text, View} from 'react-native'; +import {StyleSheet,DeviceInfo, TouchableOpacity, Text, View} from 'react-native'; +import { WebView } from 'react-native-webview'; import FontAwesome from 'react-native-vector-icons/FontAwesome'; import NavigationUtil from '../navigator/NavigationUtil'; import BackPressComponent from '../common/BackPressComponent'; @@ -7,10 +8,14 @@ import FavoriteDao from '../expand/dao/FavoriteDao'; // 自定义顶部导航组件 import NavigationBar from '../common/NavigationBar'; import ViewUtil from '../util/ViewUtil'; +import {connect} from 'react-redux'; +import SafeAreaViewPlus from '../common/SafeAreaViewPlus'; -const THEME_COLOR='#f33'; const TRENDING_URL="https://github.com/" type Props = {}; +@connect( + state=>state.theme +) export default class Detail extends Component { constructor (props) { super(props); @@ -95,16 +100,19 @@ export default class Detail extends Component { } render() { // 顶部导航栏设置 + const {theme} = this.props; const titleLayoutStyle = this.state.title.length > 20 ?{paddingRight: 30,} : null; let navigationBar = this.onBack())} titleLayoutStyle={titleLayoutStyle} title={this.state.title} - style={{backgroundColor: THEME_COLOR}} + style={{backgroundColor: theme.themeColor}} rightButton={this.renderRightButton()} /> return ( - + {navigationBar} this.webView = webView} @@ -112,7 +120,7 @@ export default class Detail extends Component { onNavigationStateChange={(e) => {this.onNavigationStateChange(e)}} source={{uri:this.state.url}} /> - + ); } } diff --git a/js/page/FavoritePage.js b/js/page/FavoritePage.js index 3dedba4..2f7ab79 100644 --- a/js/page/FavoritePage.js +++ b/js/page/FavoritePage.js @@ -24,13 +24,17 @@ import PopularItem from '../common/PopularItem'; import TrendingItem from '../common/TrendingItem'; // 自定义顶部导航组件 import NavigationBar from '../common/NavigationBar'; +import EventTypes from '../util/EventTypes'; +import EventBus from 'react-native-event-bus'; // 顶部导航tab标签配置 const TAB_NAMES = ['最热', '趋势']; -const THEME_COLOR='#f33'; type Props = {}; +@connect( + state=>state.theme +) export default class Favorite extends Component { constructor (props) { super(props); @@ -38,16 +42,17 @@ export default class Favorite extends Component { componentDidMount() { } render() { + const {theme} = this.props; // 状态栏设置 let statusBar = { - backgroundColor: THEME_COLOR, + backgroundColor: theme.themeColor, barStyle: 'light-content', }; // 顶部导航栏设置 let navigationBar = const TabNavigator = createAppContainer(createMaterialTopTabNavigator({ @@ -66,8 +71,8 @@ export default class Favorite extends Component { }, { // tabBar配置选项 tabBarOptions: { - inactiveTintColor: '#333', - activeTintColor: '#f33', + inactiveTintColor: theme.themeColor, + activeTintColor: theme.themeColor, tabStyle: styles.tabStyle, //选项卡的样式对象 upperCaseLabel: false, //是否使标签大写,默认为 true。 scrollEnabled: false, // 是否支持 选项卡滚动 默认为 false @@ -75,12 +80,15 @@ export default class Favorite extends Component { backgroundColor: '#fff', height: 35 // fix 开启scrollEnabled后在android上初次渲染的时候会有高度闪烁的问题,所以这里需要固定高度 }, - indicatorStyle: styles.indicatorStyle, //选项卡指示器的样式对象(选项卡底部的行) + indicatorStyle: { + height: 2, + backgroundColor: theme.themeColor + }, //选项卡指示器的样式对象(选项卡底部的行) labelStyle: styles.labelStyle, // 选项卡标签的样式对象(选项卡文字样式,颜色字体大小等) } } )); - return + return {navigationBar} @@ -102,12 +110,20 @@ class FavoriteTab extends Component { this.favoriteDao = new FavoriteDao(flag); } componentDidMount() { - this.loadData(); + this.loadData(true); + EventBus.getInstance().addListener(EventTypes.bottom_tab_select, this.listener = data => { + if (data.to === 2) { + this.loadData(false); + } + }); + } + componentWillMount() { + // 移除监听器 + EventBus.getInstance().removeListener(this.listener); } loadData (isShowLoading) { // 加载数据 const {onLoadFavoriteData} = this.props; - console.log(this.storeName) onLoadFavoriteData(this.storeName, isShowLoading); } _store () { @@ -123,11 +139,21 @@ class FavoriteTab extends Component { } return store; } + onFavorite (item, isFavorite) { + FavoriteUtil.onFavorite(this.favoriteDao, item, isFavorite, this.storeName); + if (this.storeName === FLAG_STORAGE.flag_popular) { + EventBus.getInstance().fireEvent(EventTypes.favorite_changed_popular); + } else { + EventBus.getInstance().fireEvent(EventTypes.favorite_changed_trending); + } + } renderItem (data) { const item = data.item; const Item = this.storeName === FLAG_STORAGE.flag_popular ? PopularItem : TrendingItem; + const {theme} = this.props.theme; return { NavigationUtil.goPage({ projectModel: item, @@ -135,7 +161,7 @@ class FavoriteTab extends Component { callback }, 'DetailPage') }} - onFavorite={(item, isFavorite) => FavoriteUtil.onFavorite(this.favoriteDao, item, isFavorite, this.storeName)} + onFavorite={(item, isFavorite) => this.onFavorite(item, isFavorite)} /> } genIndicator () { @@ -149,6 +175,7 @@ class FavoriteTab extends Component { } render() { let store = this._store(); // 动态获取state + const {theme} = this.props.theme; return ( { refreshControl={ this.loadData(true)} - tintColor={THEME_COLOR} + tintColor={theme.themeColor} /> } /> @@ -184,13 +211,8 @@ const styles = StyleSheet.create({ // width: 100, padding:0 }, - indicatorStyle: { - height: 2, - backgroundColor: '#f33' - }, labelStyle: { fontSize: 13, - // margin: 0, }, dataText: { flex: 1 diff --git a/js/page/Homepage.js b/js/page/Homepage.js index 1a68d6e..8584bc8 100644 --- a/js/page/Homepage.js +++ b/js/page/Homepage.js @@ -1,16 +1,28 @@ import React, {Component} from 'react'; +import {View} from 'react-native'; import NavigationUtil from '../navigator/NavigationUtil'; import DynamicTabNavigator from '../navigator/DynamicTabNavigator'; import BackPressComponent from '../common/BackPressComponent'; +import CustomTheme from '../page/CustomTheme'; +import SafeAreaViewPlus from '../common/SafeAreaViewPlus'; +import SplashScreen from 'react-native-splash-screen' // 处理安卓的物理返回键 import {BackHandler} from 'react-native'; import {NavigationActions} from 'react-navigation'; import {connect} from 'react-redux'; +import actions from '../redux/action'; type Props = {}; @connect( - state=>state + state=>({ + nav: state.nav, + customThemeViewVisible: state.theme.customThemeViewVisible, + theme: state.theme + }), + { + onShowCustomThemeView: actions.onShowCustomThemeView + } ) export default class Home extends Component { constructor (props) { @@ -19,6 +31,7 @@ export default class Home extends Component { } componentDidMount () { // 注册物理返回键的监听 + SplashScreen && SplashScreen.hide() this.backPress.componentDidMount(); } componentWillUnmount() { @@ -39,10 +52,28 @@ export default class Home extends Component { dispatch(NavigationActions.back()); return true; } + renderCustomThemeView () { + const {onShowCustomThemeView, customThemeViewVisible } = this.props; + return ( + onShowCustomThemeView(false)} + /> + ); + } render() { // 内层的navigator往外层的navigator跳转的时候无法跳转, // 这个时候可以在内层保存外层的navigation + const {theme} = this.props; NavigationUtil.navigation = this.props.navigation; // 给导航管理类添加静态属性 - return + return ( + + {this.renderCustomThemeView()} + + + ) } } diff --git a/js/page/Mypage.js b/js/page/Mypage.js index 55822ec..efe8a66 100644 --- a/js/page/Mypage.js +++ b/js/page/Mypage.js @@ -1,72 +1,172 @@ import React, {Component} from 'react'; -import {StyleSheet, View, Button, Text, TouchableOpacity} from 'react-native'; +import {StyleSheet, ScrollView, View, Button, Text, TouchableOpacity} from 'react-native'; import {connect} from 'react-redux'; import actions from '../redux/action'; import Feather from 'react-native-vector-icons/Feather'; import Ionicons from 'react-native-vector-icons/Ionicons'; // 自定义顶部导航组件 import NavigationBar from '../common/NavigationBar'; - - -const THEME_COLOR = '#f33' +import { MORE_MENU } from '../common/MORE_MENU'; +import GobalStyles from '../res/styles/GobalStyles'; +import ViewUtil from '../util/ViewUtil'; +import NavigationUtil from '../navigator/NavigationUtil'; +import { FLAG_LANGUAGE } from '../expand/dao/LanguageDao'; type Props = {}; @connect( - state=>({}), - {onThemeChange: actions.onThemeChange} + state=>state.theme, + { + onShowCustomThemeView: actions.onShowCustomThemeView + } ) export default class My extends Component { - getRightButton () { - return ( - - {}} - > - - - - - - ) + onClick (menu) { + const { theme } = this.props + let RouteName, params = {theme}; + switch (menu) { + case MORE_MENU.Tutorial: + RouteName = 'WebViewPage'; + params.title = '教程'; + params.url = 'https://github.com/songdongdong123' + break; + case MORE_MENU.About: + RouteName = 'AboutPage'; + break; + case MORE_MENU.About_Author: + RouteName = 'AboutMePage'; + break; + case MORE_MENU.Sort_key: + RouteName = 'SortKeyPage'; + params.flag = FLAG_LANGUAGE.flag_key; + break; + case MORE_MENU.Sort_Language: + RouteName = 'SortKeyPage'; + params.flag = FLAG_LANGUAGE.flag_language; + break; + case MORE_MENU.CodePush: + RouteName = 'CodePushPage'; + break; + case MORE_MENU.Custom_Theme: + const {onShowCustomThemeView} = this.props; + onShowCustomThemeView(true); + break; + case MORE_MENU.Custom_key: + case MORE_MENU.Custom_Language: + case MORE_MENU.Remove_key: + RouteName = 'CustomKeyPage'; + params.isRemoveKey = menu === MORE_MENU.Remove_key; + params.flag = menu !== MORE_MENU.Custom_Language ? FLAG_LANGUAGE.flag_key : FLAG_LANGUAGE.flag_language; + break; + default: + break; + } + if (RouteName) { + NavigationUtil.goPage(params, RouteName); + } } - getLeftButton (callback) { - return ( - - - - ) + getItem (menu) { + const {theme} = this.props; + return ViewUtil.getMenuItem(() => this.onClick(menu), menu, theme.themeColor); } render() { + const {theme} = this.props; let statusBar = { - backgroundColor: THEME_COLOR, + backgroundColor: theme.themeColor, barStyle: 'light-content' }; let navigationBar = return ( - + {navigationBar} - MyPage + + this.onClick(MORE_MENU.About)} + > + + + GitHub Populars + + + + + {/* 教程 */} + {this.getItem(MORE_MENU.Tutorial)} + 趋势管理 + {/* 自定义语言 */} + {this.getItem(MORE_MENU.Custom_Language)} + + {/* 语言排序 */} + {this.getItem(MORE_MENU.Sort_Language)} + + + {/* 最热管理 */} + 最热管理 + {/* 自定义标签 */} + {this.getItem(MORE_MENU.Custom_key)} + + {/* 标签排序 */} + {this.getItem(MORE_MENU.Sort_key)} + + {/* 标签移除 */} + {this.getItem(MORE_MENU.Remove_key)} + + + {/* 设置 */} + 设置 + {/* 自定义主题 */} + {this.getItem(MORE_MENU.Custom_Theme)} + + {/* 关于作者 */} + {this.getItem(MORE_MENU.About_Author)} + + {/* 反馈 */} + {this.getItem(MORE_MENU.Feedback)} + {/* codepush */} + + {this.getItem(MORE_MENU.CodePush)} + ); } } const styles = StyleSheet.create({ - container: { - flex: 1, + about_left: { + flexDirection: 'row', + alignItems: 'center', + }, + item:{ + backgroundColor: 'white', + padding: 10, + height: 90, + alignItems: 'center', + justifyContent: 'space-between', + flexDirection: 'row', + }, + groupTitle: { + marginLeft: 10, + marginTop: 5, + marginBottom: 5, + fontSize: 12, + color: 'gray' } }); diff --git a/js/page/PopularPage.js b/js/page/PopularPage.js index 34b2b01..0ca5dda 100644 --- a/js/page/PopularPage.js +++ b/js/page/PopularPage.js @@ -8,7 +8,9 @@ */ import React, {Component} from 'react'; -import {StyleSheet,DeviceInfo, ActivityIndicator, View, Text, FlatList, RefreshControl } from 'react-native'; +import {StyleSheet,DeviceInfo, ActivityIndicator, +TouchableOpacity, +View, Text, FlatList, RefreshControl } from 'react-native'; import Toast from 'react-native-easy-toast'; import {connect} from 'react-redux'; import actions from '../redux/action'; @@ -23,59 +25,110 @@ import FavoriteUtil from '../util/FavoriteUtil'; import PopularItem from '../common/PopularItem'; // 自定义顶部导航组件 import NavigationBar from '../common/NavigationBar'; +import Ionicons from 'react-native-vector-icons/Ionicons'; -import { getAccountList } from '../axios/api/account'; +//用于页面之间通讯 +import EventTypes from '../util/EventTypes'; +import EventBus from 'react-native-event-bus'; +import AnalyticsUtil from '../util/AnalyticsUtil'; + +// +import { FLAG_LANGUAGE } from '../expand/dao/LanguageDao'; // 顶部导航tab标签配置 const TAB_NAMES = ['Java', 'Android', 'Ios', 'React', 'React-Native', 'PHP']; const URL = 'https://api.github.com/search/repositories?q='; const QUERY_STR = '&sort=stars'; -const THEME_COLOR='#f33'; const PAEG_SIZE = 10; const favoriteDao = new FavoriteDao(FLAG_STORAGE.flag_popular); type Props = {}; -export default class Popuilar extends Component { +@connect( + state=>({ + keys: state.language.keys, + theme: state.theme.theme + }), + { + onLoadLanguage: actions.onLoadLanguage + } +) +export default class Popular extends Component { constructor (props) { super(props); + const {onLoadLanguage} = this.props; + // 获取标签数据 + onLoadLanguage(FLAG_LANGUAGE.flag_key); } componentDidMount() { + AnalyticsUtil.onPageStart('Popular'); } _genTabs () { const tabs = {}; - TAB_NAMES.forEach((item, index) => { - tabs[`tab${index}`] = { - // 这里使用的箭头函数,所以直接使用props,而不是使用this.props - screen: props => , - // screen: function () { //如果是普通函数,则使用this.props - // return - // }, - navigationOptions: { - title: item + const {keys} = this.props; + keys.forEach((item, index) => { + if (item.checked) { + tabs[`tab${index}`] = { + // 这里使用的箭头函数,所以直接使用props,而不是使用this.props + screen: props => , + // screen: function () { //如果是普通函数,则使用this.props + // return + // }, + navigationOptions: { + title: item.name + } } } }); return tabs; } + renderRightButton () { + const {theme} = this.props; + return { + AnalyticsUtil.onEvent('event1'); + // AnalyticsUtil.onEvent('SearchClick'); + // AnalyticsUtil.onEvent('01'); + // AnalyticsUtil.onEventWithMap('SearchClick',{ + // name: 'umeng' + // }); + NavigationUtil.goPage({theme}, 'SearchPage') + }} + > + + + + + } render() { + // 获取标签 + const {keys, theme} = this.props; // 状态栏设置 let statusBar = { - backgroundColor: THEME_COLOR, + backgroundColor: theme.themeColor, barStyle: 'light-content', }; // 顶部导航栏设置 let navigationBar = - const TabNavigator = createAppContainer(createMaterialTopTabNavigator( + const TabNavigator = keys.length ? createAppContainer(createMaterialTopTabNavigator( this._genTabs(), { // tabBar配置选项 tabBarOptions: { - inactiveTintColor: '#333', - activeTintColor: '#f33', + inactiveTintColor: theme.themeColor, + activeTintColor: theme.themeColor, tabStyle: styles.tabStyle, //选项卡的样式对象 upperCaseLabel: false, //是否使标签大写,默认为 true。 scrollEnabled: true, // 是否支持 选项卡滚动 默认为 false @@ -83,14 +136,19 @@ export default class Popuilar extends Component { backgroundColor: '#fff', height: 35 // fix 开启scrollEnabled后在android上初次渲染的时候会有高度闪烁的问题,所以这里需要固定高度 }, - indicatorStyle: styles.indicatorStyle, //选项卡指示器的样式对象(选项卡底部的行) + indicatorStyle: { + height: 2, + backgroundColor: theme.themeColor + }, //选项卡指示器的样式对象(选项卡底部的行) labelStyle: styles.labelStyle, // 选项卡标签的样式对象(选项卡文字样式,颜色字体大小等) - } + }, + lazy: true } - )); - return + )):null; + return {navigationBar} - + {/* TabNavigator存在,渲染标签 */} + {TabNavigator&&} } } @@ -100,7 +158,8 @@ export default class Popuilar extends Component { state=>state, { onLoadPopularData: actions.onLoadPopularData, - onLoadMorePopular: actions.onLoadMorePopular + onLoadMorePopular: actions.onLoadMorePopular, + onFlushPopularFavorite: actions.onFlushPopularFavorite } ) class PopuilarTab extends Component { @@ -108,19 +167,39 @@ class PopuilarTab extends Component { super(props); const {tabLabel} = this.props; this.storeName = tabLabel; + this.isFavoriteChanged = false; } componentDidMount() { this.loadData(); + EventBus.getInstance().addListener(EventTypes.favorite_changed_popular, this.favoriteChangeListener = () => { + // 收藏页面状态变化的通知 + this.isFavoriteChanged = true; + }); + EventBus.getInstance().addListener(EventTypes.bottom_tab_select, this.botomTabSelectListener = (data) => { + // 底部tab切换的通知 + if (data.to === 0 && this.isFavoriteChanged) { + this.loadData(null, true); + } + }); } - loadData (loadMore) { + componentWillMount() { + // 移除监听器 + EventBus.getInstance().removeListener(this.favoriteChangeListener); + EventBus.getInstance().removeListener(this.botomTabSelectListener); + } + loadData (loadMore, refreshFavorite) { + // refreshFavorite 是否刷新收藏状态 // 加载数据 - const {onLoadPopularData, onLoadMorePopular} = this.props; + const {onLoadPopularData, onLoadMorePopular, onFlushPopularFavorite} = this.props; const store = this._store(); const url = this.genFetchUrl(this.storeName); if (loadMore) { onLoadMorePopular(this.storeName,++store.pageIndex, PAEG_SIZE, store.items, favoriteDao, callback => { this.refs.toast.show('没有更多了'); }) + } else if (refreshFavorite) { + // 从favorite页面回跳至popular页面时,刷新popular页面的收藏状态 + onFlushPopularFavorite(this.storeName,store.pageIndex, PAEG_SIZE, store.items, favoriteDao); } else { onLoadPopularData(this.storeName, url, PAEG_SIZE, favoriteDao); } @@ -145,8 +224,10 @@ class PopuilarTab extends Component { } renderItem (data) { const item = data.item; + const {theme} = this.props.theme return { NavigationUtil.goPage({ projectModel: item, @@ -168,6 +249,7 @@ class PopuilarTab extends Component { } render() { let store = this._store(); // 动态获取state + const {theme} = this.props.theme return ( { refreshControl={ this.loadData()} - tintColor={THEME_COLOR} + tintColor={theme.themeColor} /> } ListFooterComponent={() => this.genIndicator()} @@ -221,10 +303,6 @@ const styles = StyleSheet.create({ // width: 100, padding:0 }, - indicatorStyle: { - height: 2, - backgroundColor: '#f33' - }, labelStyle: { fontSize: 13, // margin: 0, diff --git a/js/page/SearchPage.js b/js/page/SearchPage.js new file mode 100644 index 0000000..9e3dbb8 --- /dev/null +++ b/js/page/SearchPage.js @@ -0,0 +1,318 @@ +import React, {Component} from 'react'; +import {StyleSheet, + DeviceInfo, + Platform, + TextInput, + TouchableOpacity, + ActivityIndicator, View, Text, FlatList, RefreshControl } from 'react-native'; +import Toast from 'react-native-easy-toast'; +import {connect} from 'react-redux'; +import actions from '../redux/action'; +import FavoriteDao from '../expand/dao/FavoriteDao'; +import {FLAG_STORAGE} from '../expand/dao/dataStore'; +import { + createMaterialTopTabNavigator, + createAppContainer +} from 'react-navigation'; +import NavigationUtil from '../navigator/NavigationUtil'; +import FavoriteUtil from '../util/FavoriteUtil'; +import PopularItem from '../common/PopularItem'; +import ViewUtil from '../util/ViewUtil'; +import BackPressComponent from '../common/BackPressComponent'; +// 自定义顶部导航组件 +import NavigationBar from '../common/NavigationBar'; + +//用于页面之间通讯 +import EventTypes from '../util/EventTypes'; +import EventBus from 'react-native-event-bus'; + +// +import LanguageDao, { FLAG_LANGUAGE } from '../expand/dao/LanguageDao'; +import GlobalStyles from '../res/styles/GobalStyles'; +import Utils from '../util/Utils'; + +// 顶部导航tab标签配置 +const TAB_NAMES = ['Java', 'Android', 'Ios', 'React', 'React-Native', 'PHP']; +const URL = 'https://api.github.com/search/repositories?q='; +const QUERY_STR = '&sort=stars'; +const PAEG_SIZE = 10; +type Props = {}; + +@connect( + state=>({ + keys: state.language.keys, + theme: state.theme.theme, + search: state.search, + }), + { + onSearch: actions.onSearch, + onSearchCancel: actions.onSearchCancel, + onLoadMoreSearch: actions.onLoadMoreSearch, + onLoadLanguage: actions.onLoadLanguage + } +) +export default class Search extends Component { + constructor (props) { + super(props) + this.params = this.props.navigation.state.params + this.backPress = new BackPressComponent({ backPress: (e) => this.onBackPress(e) }) + this.favoriteDao = new FavoriteDao(FLAG_STORAGE.flag_popular) + this.languageDao = new LanguageDao(FLAG_LANGUAGE.flag_key) + this.isKeyChange = false + } + componentDidMount () { + this.backPress.componentDidMount() + } + componentWillUnmount () { + this.backPress.componentWillUnmount() + } + // 加载数据 + loadData = (loadMore) => { + const { onLoadMoreSearch, onSearch, search, keys } = this.props + if (loadMore) { + onLoadMoreSearch(++search.pageIndex, PAEG_SIZE, search.items, this.favoriteDao, keys, callback => { + this.toast.show('没有更多了') + }) + } else { + onSearch(this.inputKey, PAEG_SIZE, this.searchToken = new Date().getTime(), this.favoriteDao, keys, message => { + this.toast.show(message) + }) + } + } + onBackPress = () => { + const { onSearchCancel, onLoadLanguage } = this.props + onSearchCancel()//退出时取消搜索 + this.refs.input.blur() + NavigationUtil.goBack({navigation: this.props.navigation}) + if (this.isKeyChange) { + onLoadLanguage(FLAG_LANGUAGE.flag_key) + } + return true + } + //保存标签 + saveKey = () => { + const { keys } = this.props + let key = this.inputKey + if (Utils.checkKeyIsExist(keys, key)) { + this.toast.show(key + '已经存在') + } else { + key = { + path: key, + name: key, + checked: true + } + keys.unshift(key)//将key添加到数组的开头 + this.languageDao.save(keys) + this.toast.show(`${key.name}保存成功`) + this.isKeyChange = true + } + } + + onRightButtonClick = () => { + const { onSearchCancel, search } = this.props + if (search.showText === '搜索') { + this.loadData() + } else { + onSearchCancel(this.searchToken) + } + } + + renderItem = (data) => { + const item = data.item + const { theme } = this.params + return { + NavigationUtil.goPage({ + theme, + projectModel: item, + flag: FLAG_STORAGE.flag_popular, + callback, + }, 'DetailPage') + }} + onFavorite={(item, isFavorite) => FavoriteUtil.onFavorite(favoriteDao, item, isFavorite, FLAG_STORAGE.flag_popular)} + /> + } + // 加载条 + renderIndicator = () => { + const { search } = this.props + return search.hideLoadingMore ? null : + + + 正在加载更多 + + } + // 顶部导航 + renderNavBar = () => { + const { theme } = this.params + const { showText, inputKey } = this.props.search + const placeholder = inputKey || '请输入' + let backButton = ViewUtil.getLeftBackButton(() => this.onBackPress()) + let inputView = this.inputKey = text} + style={styles.textInput} + /> + let rightButton = { + this.refs.input.blur()//收起键盘 + this.onRightButtonClick() + }} + > + + {showText} + + + return + {backButton} + {inputView} + {rightButton} + + } + + render() { + const { theme } = this.params; + const { isLoading, projectModels, showBottomButton, hideLoadingMore} = this.props.search; + let statusBar = null + if (Platform.OS === 'ios') { + statusBar = + } + let listView = !isLoading ? this.renderItem(data)} + keyExtractor={item => '' + item.item.id} + contentInset={ //底部安全距离 + { + bottom: 45 + } + } + refreshControl={ + this.loadData()} + tintColor={theme.themeColor} + /> + } + ListFooterComponent={() => this.renderIndicator()} + onEndReached={() => { + console.log('---onEndReached----') + setTimeout(() => { + if (this.canLoadMore) {//fix 滚动时两次调用onEndReached https://github.com/facebook/react-native/issues/14015 + this.loadData(true) + this.canLoadMore = false + } + }, 100) + }} + onEndReachedThreshold={0.5} + onMomentumScrollBegin={() => { + this.canLoadMore = true //fix 初始化时页调用onEndReached的问题 + console.log('---onMomentumScrollBegin-----') + }} + /> : null + let bottomButton = showBottomButton ? + { + this.saveKey() + }} + > + + 朕收下了 + + : null + let indicatorView = isLoading ? + : null + let resultView = + {indicatorView} + {listView} + + return + {statusBar} + {this.renderNavBar()} + {resultView} + {bottomButton} + this.toast = toast}/> + + } +} + +const styles = StyleSheet.create({ + container: { + flex: 1, + }, + tabStyle: { + // minWidth: 50 //fix minWidth会导致tabStyle初次加载时闪烁 + padding: 0 + }, + indicatorStyle: { + height: 2, + backgroundColor: 'white' + }, + labelStyle: { + fontSize: 13, + margin: 0, + }, + indicatorContainer: { + alignItems: 'center' + }, + indicator: { + color: 'red', + margin: 10 + }, + statusBar: { + height: 20 + }, + bottomButton: { + alignItems: 'center', + justifyContent: 'center', + opacity: 0.9, + height: 40, + position: 'absolute', + left: 10, + top: GlobalStyles.window_height - 45 - (DeviceInfo.isIPhoneX_deprecated ? 34 : 0) - (Platform.OS === 'ios' ? 0 : 25), + right: 10, + borderRadius: 3 + }, + centering: { + alignItems: 'center', + justifyContent: 'center', + flex: 1, + }, + textInput: { + flex: 1, + height: (Platform.OS === 'ios') ? 26 : 36, + borderWidth: (Platform.OS === 'ios') ? 1 : 0, + borderColor: 'white', + alignSelf: 'center', + paddingLeft: 5, + marginRight: 10, + marginLeft: 5, + borderRadius: 3, + opacity: 0.7, + color: 'white' + }, + title: { + fontSize: 18, + color: 'white', + fontWeight: '500' + }, +}) diff --git a/js/page/SortKeyPage.js b/js/page/SortKeyPage.js new file mode 100644 index 0000000..9bca917 --- /dev/null +++ b/js/page/SortKeyPage.js @@ -0,0 +1,242 @@ + +import React, {Component} from 'react'; +import {StyleSheet, TouchableOpacity, View, Text, TouchableHighlight, Alert} from 'react-native'; +import {connect} from 'react-redux'; +import actions from '../redux/action'; +import BackPressComponent from '../common/BackPressComponent'; +import SortableListView from 'react-native-sortable-listview'; +import MaterialCommunityIcons from 'react-native-vector-icons/MaterialCommunityIcons'; + +import { + createMaterialTopTabNavigator, + createAppContainer +} from 'react-navigation'; +import NavigationUtil from '../navigator/NavigationUtil'; + +import NavigationBar from '../common/NavigationBar'; +import ViewUtil from '../util/ViewUtil'; +import Ionicons from 'react-native-vector-icons/Ionicons'; +import ArrayUtil from '../util/ArrayUtil'; + +// +import LanguageDao, {FLAG_LANGUAGE} from '../expand/dao/LanguageDao'; +const THEME_COLOR='#f33'; + +type Props = {}; + +@connect( + state=>state, + { + onLoadLanguage: actions.onLoadLanguage + } +) +export default class SortKeyPage extends Component { + constructor (props) { + super(props) + this.params = this.props.navigation.state.params; + console.log(this.params) + this.backPress = new BackPressComponent({ backPress: (e) => this.onBackPress(e) }); + this.languageDao = new LanguageDao(this.params.flag); + + this.state = { + checkedArray: SortKeyPage._keys(this.props) + } + } + onBackPress = () => { + this.onBack(); + return true; + } + onBack () { + if (!ArrayUtil.isEqual(SortKeyPage._keys(this.props), this.state.checkedArray)) { + Alert.alert('提示', '要保存修改吗?', + [ + { + text: '否', onPress: () => { + NavigationUtil.goBack({navigation: this.props.navigation}) + } + }, { + text: '是', onPress: () => { + this.onSave(true) + } + } + ]) + } else { + NavigationUtil.goBack({navigation: this.props.navigation}) + } + } + static _keys (props, state) { + //如果state中有checkedArray则使用state中的checkedArray + if (state && state.checkedArray && state.checkedArray.length) { + return state.checkedArray + } + //否则从原始数据中获取checkedArray + const flag = SortKeyPage._flag(props) + let dataArray = props.language[flag] || [] + let keys = [] + for (let i = 0, j = dataArray.length; i < j; i++) { + let data = dataArray[i] + if (data.checked) keys.push(data) + } + return keys + } + static _flag (props) { + const { flag } = props.navigation.state.params; + return flag === FLAG_LANGUAGE.flag_key ? 'keys' : 'languages'; + } + static getDerivedStateFromProps (nextProps, prevState) { + const checkedArray = SortKeyPage._keys(nextProps, null, prevState) + if (prevState.checkedArray.length !== checkedArray.length) { + return { + checkedArray, + } + } + return null + } + componentDidMount() { + this.backPress.componentDidMount() + //如果props中标签为空则从本地存储中获取标签 + if (SortKeyPage._keys(this.props).length === 0) { + const { onLoadLanguage } = this.props + onLoadLanguage(this.params.flag) + } + } + componentWillUnmount() { + // 注销物理返回键的监听 + this.backPress.componentWillUnmount(); + } + onSave (hasChecked) { + if (!hasChecked) { + //如果没有排序则直接返回 + if (ArrayUtil.isEqual(SortKeyPage._keys(this.props), this.state.checkedArray)) { + NavigationUtil.goBack({navigation:this.props.navigation}) + return + } + } + //保存排序后的数据 + //获取排序后的数据 + //更新本地数据 + this.languageDao.save(this.getSortResult()) + //重新加载排序后的标签,以便其他页面能够及时更新 + const { onLoadLanguage } = this.props + //更新store + onLoadLanguage(this.params.flag) + NavigationUtil.goBack({navigation:this.props.navigation}) + } + getSortResult = () => { + const flag = SortKeyPage._flag(this.props) + //从原始数据中复制一份数据出来,以便对这份数据进行进行排序 + let sortResultArray = ArrayUtil.clone(this.props.language[flag]) + //获取排序之前的排列顺序 + const originalCheckedArray = SortKeyPage._keys(this.props) + //遍历排序之前的数据,用排序后的数据checkedArray进行替换 + for (let i = 0, length = originalCheckedArray.length; i < length; i++) { + let item = originalCheckedArray[i] + //找到要替换的元素所在位置 + let index = this.props.language[flag].indexOf(item) + //进行替换 + sortResultArray.splice(index, 1, this.state.checkedArray[i]) + } + return sortResultArray + } + + renderView () { + let dataArray = this.state.keys; + if (!dataArray || dataArray.length === 0) return; + let len = dataArray.length; + let views = []; + for (let i = 0, l = len; i < l; i += 2) { + views.push( + + + + + + ) + } + return views; + } + onClick (data, index) { + data.checked = !data.checked; + ArrayUtil.updateArray(this.changeValues, data); + this.state.keys[index] = data; + this.setState({ + keys: this.state.keys + }) + } + _checkedImage (checked) { + const {theme} = this.props; + return + } + render() { + let title = this.params.flag === FLAG_LANGUAGE.flag_language ? '语言排序' : '标签排序' + // 顶部导航栏设置 + const { theme } = this.props.theme; + let navigationBar = this.onBackPress())} + rightButton={ViewUtil.getRightButton('保存', () => this.onSave())} + /> + return + {navigationBar} + { + this.state.checkedArray.splice(e.to, 0, this.state.checkedArray.splice(e.from, 1)[0]) + this.forceUpdate() + }} + renderRow={row => } + > + {this.renderView()} + + + } +} + +class SortCell extends Component { + render () { + const { theme } = this.props + return + + + {this.props.data.name} + + + } +} + +const styles = StyleSheet.create({ + container: { + flex: 1, + }, + line: { + flex: 1, + height: 0.3, + backgroundColor: 'darkgray', + }, + hidden: { + height: 0 + }, + item: { + backgroundColor: '#F8F8F8', + borderBottomWidth: 1, + borderColor: '#eee', + height: 50, + justifyContent: 'center' + }, +}); diff --git a/js/page/TrendingPage.js b/js/page/TrendingPage.js index 98b76ea..b4d6855 100644 --- a/js/page/TrendingPage.js +++ b/js/page/TrendingPage.js @@ -37,39 +37,57 @@ import NavigationBar from '../common/NavigationBar'; // 自定义弹窗 import TrendingDialog, {TimeSpans} from '../common/TrendingDialog'; -import { getAccountList } from '../axios/api/account'; +//用于页面之间通讯 +import EventTypes from '../util/EventTypes'; +import EventBus from 'react-native-event-bus'; + +import { FLAG_LANGUAGE } from '../expand/dao/LanguageDao'; +import ArrayUtil from '../util/ArrayUtil'; -// 顶部导航tab标签配置 -const TAB_NAMES = ['All', 'C', 'C#', 'PHP', 'Javascript']; const URL = 'https://github.com/trending/'; const QUERY_STR = '&sort=stars'; -const THEME_COLOR='#f33'; const PAEG_SIZE = 10; const EVENT_TYPE_TIME_SPAN_CHANGE = 'EVENT_TYPE_TIME_SPAN_CHANGE'; const favoriteDao = new FavoriteDao(FLAG_STORAGE.flag_trending); type Props = {}; +@connect( + state=>({ + languages: state.language.languages, + theme: state.theme.theme + }), + { + onLoadLanguage: actions.onLoadLanguage + } +) export default class Trending extends Component { constructor (props) { super(props); this.state = { timeSpan: TimeSpans[0] } + const {onLoadLanguage} = this.props; + onLoadLanguage(FLAG_LANGUAGE.flag_language); + this.preKeys = []; } componentDidMount() { - // console.log(this.props) + } _genTabs () { const tabs = {}; - TAB_NAMES.forEach((item, index) => { - tabs[`tab${index}`] = { - // 这里使用的箭头函数,所以直接使用props,而不是使用this.props - screen: props => , - // screen: function () { //如果是普通函数,则使用this.props - // return - // }, - navigationOptions: { - title: item + const {languages} = this.props; + this.preKeys = languages; + languages.forEach((item, index) => { + if (item.checked) { + tabs[`tab${index}`] = { + // 这里使用的箭头函数,所以直接使用props,而不是使用this.props + screen: props => , + // screen: function () { //如果是普通函数,则使用this.props + // return + // }, + navigationOptions: { + title: item.name + } } } }); @@ -113,17 +131,19 @@ export default class Trending extends Component { onSelect={(tab, index)=>this.onSelectTimeSpan(tab, index)} /> } - _tabNav () { + _tabNav = () => { // 优化效率:根据需要选择是否重新创建TabNavigator // 通常tab发生变化,才需要重新创建 // TabNavigator - if (!this.tabNav) { + const {theme} = this.props; + if (theme !== this.theme || !this.tabNav || !ArrayUtil.isEqual(this.preKeys, this.props.key)) { + this.theme = theme this.tabNav = createAppContainer(createMaterialTopTabNavigator( this._genTabs(), { // tabBar配置选项 tabBarOptions: { - inactiveTintColor: '#333', - activeTintColor: '#f33', + inactiveTintColor: theme.themeColor, + activeTintColor: theme.themeColor, tabStyle: styles.tabStyle, //选项卡的样式对象 upperCaseLabel: false, //是否使标签大写,默认为 true。 scrollEnabled: true, // 是否支持 选项卡滚动 默认为 false @@ -131,31 +151,36 @@ export default class Trending extends Component { backgroundColor: '#fff', //fix 开启scrollEnabled后在android上初次渲染的时候会有高度闪烁的问题,所以这里需要固定高度 height:35 }, - indicatorStyle: styles.indicatorStyle, //选项卡指示器的样式对象(选项卡底部的行) + indicatorStyle: { + height: 2, + backgroundColor: theme.themeColor + }, //选项卡指示器的样式对象(选项卡底部的行) labelStyle: styles.labelStyle, // 选项卡标签的样式对象(选项卡文字样式,颜色字体大小等) - } + }, + lazy: true } )); } return this.tabNav; } render() { + const {languages, theme} = this.props; // 状态栏设置 let statusBar = { - backgroundColor: THEME_COLOR, + backgroundColor: theme.themeColor, barStyle: 'light-content' }; // 顶部导航栏设置 let navigationBar = // 顶部标签组件 - const TabNavigator = this._tabNav(); - return + const TabNavigator = languages.length?this._tabNav():null; + return {navigationBar} - + {TabNavigator&&} {this.renderTrendingDialog()} } @@ -166,7 +191,8 @@ export default class Trending extends Component { state=>state, { onLoadTrendingData: actions.onLoadTrendingData, - onLoadMoreTrending: actions.onLoadMoreTrending + onLoadMoreTrending: actions.onLoadMoreTrending, + onFlushTrendingFavorite: actions.onFlushTrendingFavorite } ) class TrendingTab extends Component { @@ -175,6 +201,8 @@ class TrendingTab extends Component { const {tabLabel, timeSpan} = this.props; this.storeName = tabLabel; this.timeSpan = timeSpan; + // 是否刷新页面的收藏状态 + this.isFavoriteChanged = false; } componentDidMount() { this.loadData(); @@ -182,21 +210,39 @@ class TrendingTab extends Component { this.timeSpan = timeSpan; this.loadData(); }); + + EventBus.getInstance().addListener(EventTypes.favorite_changed_trending, this.favoriteChangeListener = () => { + // 收藏页面状态变化的通知 + this.isFavoriteChanged = true; + }); + EventBus.getInstance().addListener(EventTypes.bottom_tab_select, this.botomTabSelectListener = (data) => { + // 底部tab切换的通知 + if (data.to === 1 && this.isFavoriteChanged) { + this.loadData(null, true); + } + }); } componentWillUnmount () { if (this.timeSpanChangeListener) { this.timeSpanChangeListener.remove(); } + // 移除监听器 + EventBus.getInstance().removeListener(this.favoriteChangeListener); + EventBus.getInstance().removeListener(this.botomTabSelectListener); + // debugger + // this.loadData = null; } - loadData (loadMore) { + loadData (loadMore, refreshFavorite) { // 加载数据 - const {onLoadTrendingData, onLoadMoreTrending} = this.props; + const {onLoadTrendingData, onLoadMoreTrending, onFlushTrendingFavorite} = this.props; const store = this._store(); const url = this.genFetchUrl(this.storeName); if (loadMore) { onLoadMoreTrending(this.storeName,++store.pageIndex, PAEG_SIZE, store.items, favoriteDao, callback => { this.refs.toast.show('没有更多了'); }) + } else if (refreshFavorite) { + onFlushTrendingFavorite(this.storeName, store.pageIndex, PAEG_SIZE, store.items, favoriteDao) } else { onLoadTrendingData(this.storeName, url, PAEG_SIZE, favoriteDao); } @@ -221,8 +267,10 @@ class TrendingTab extends Component { } renderItem (data) { const item = data.item; + const {theme} = this.props.theme; return { NavigationUtil.goPage({ projectModel: item, @@ -244,6 +292,7 @@ class TrendingTab extends Component { } render() { let store = this._store(); // 动态获取state + const {theme} = this.props.theme return ( { refreshControl={ this.loadData()} - tintColor={THEME_COLOR} + tintColor={theme.themeColor} /> } ListFooterComponent={() => this.genIndicator()} diff --git a/js/page/WebViewPage.js b/js/page/WebViewPage.js new file mode 100644 index 0000000..78c2ce2 --- /dev/null +++ b/js/page/WebViewPage.js @@ -0,0 +1,98 @@ +import React, {Component} from 'react'; +import {StyleSheet,DeviceInfo, TouchableOpacity, Text, View} from 'react-native'; +import { WebView } from 'react-native-webview'; +import FontAwesome from 'react-native-vector-icons/FontAwesome'; +import NavigationUtil from '../navigator/NavigationUtil'; +import BackPressComponent from '../common/BackPressComponent'; +import FavoriteDao from '../expand/dao/FavoriteDao'; +// 自定义顶部导航组件 +import NavigationBar from '../common/NavigationBar'; +import ViewUtil from '../util/ViewUtil'; +import {connect} from 'react-redux'; + +const TRENDING_URL="https://github.com/" +type Props = {}; +@connect( + state=>state.theme +) +export default class WebViewPage extends Component { + constructor (props) { + super(props); + this.params = this.props.navigation.state.params; + const {title, url} = this.params; + this.state = { + title: title, + url: url, + canGoBack: false + } + this.backPress = new BackPressComponent({backPress: () => this.onBackPress()}); + } + componentDidMount () { + // 注册物理返回键的监听 + this.backPress.componentDidMount(); + } + componentWillUnmount() { + // 注销物理返回键的监听 + this.backPress.componentWillUnmount(); + } + onBackPress = () => { + /** + *处理android中的物理返回键 + *@returns{boolean} + * + */ + this.onBack(); + return true; + } + onBack () { + if(this.state.canGoBack) { + // 使用webView的goback返回webview中上一个路由 + this.webView.goBack() + } else { + // 否则返回我们native的上一页 + NavigationUtil.goBack({navigation:this.props.navigation}); + } + } + onNavigationStateChange (navState) { + // webview路由变化时调用 + this.setState({ + canGoBack: navState.canGoBack, + url: navState.url + }) + } + render() { + // 顶部导航栏设置 + // 状态栏设置 + // let statusBar = { + // backgroundColor: '#36c', + // barStyle: 'light-content', + // hidden: false + // }; + const {theme} = this.props; + let navigationBar = this.onBackPress())} + /> + return ( + + {navigationBar} + this.webView = webView} + // 显示加载进度条 + startInLoadingState={true} + onNavigationStateChange={(e) => {this.onNavigationStateChange(e)}} + source={{uri:this.state.url}} + /> + + ); + } +} + +const styles = StyleSheet.create({ + container: { + flex: 1, + marginTop: DeviceInfo.isIphoneX_deprecated?30:0, + } +}); diff --git a/js/page/WelcomePage.js b/js/page/WelcomePage.js index e79add9..4ed2778 100644 --- a/js/page/WelcomePage.js +++ b/js/page/WelcomePage.js @@ -1,11 +1,14 @@ import React, {Component} from 'react'; import {StyleSheet, Text, View} from 'react-native'; import NavigationUtil from '../navigator/NavigationUtil'; - +import SplashScreen from 'react-native-splash-screen' +import AnalyticsUtil from '../util/AnalyticsUtil'; type Props = {}; export default class Welcome extends Component { componentDidMount() { this.timer = setTimeout(() => { + SplashScreen.hide(); + AnalyticsUtil.onPageStart('Welcome'); NavigationUtil.ResetToHomePage(this.props) }, 200); } @@ -13,11 +16,7 @@ export default class Welcome extends Component { this.timer&&clearTimeout(this.timer) } render() { - return ( - - WelcomePage - - ); + return null; } } diff --git a/js/page/about/AboutCommon.js b/js/page/about/AboutCommon.js new file mode 100644 index 0000000..bf35923 --- /dev/null +++ b/js/page/about/AboutCommon.js @@ -0,0 +1,191 @@ +import React from 'react'; +import {View, Image, Text, Dimensions, Platform, StyleSheet, DeviceInfo} from 'react-native'; +import BackPressComponent from '../../common/BackPressComponent'; +import NavigationUtil from '../../navigator/NavigationUtil'; +import config from '../../res/data/github_app_config.json'; +import ParallaxScrollView from 'react-native-parallax-scroll-view'; +import GobalStyles from '../../res/styles/GobalStyles'; +import ViewUtil from '../../util/ViewUtil'; + +export const FLAG_ABOUT = { + flag_about: 'about', + flag_about_me: 'about_me' +} + +export default class AboutCommon { + constructor(props, updateState) { + this.props = props; + this.updateState = updateState; + this.backPress = new BackPressComponent({backPress: () => this.onBackPress()}); + } + componentDidMount () { + // 注册物理返回键的监听 + this.backPress.componentDidMount(); + // 获取配置文件 + fetch('https://m.ethansblogs.com/github_app_config.json').then(response => { + if (response.ok) { + return response.json(); + } + throw new Error('NetWork Error') + }).then(config => { + if (config) { + this.updateState({ + data: config + }) + } + }).catch(e => { + console(e); + }) + } + componentWillUnmount() { + // 注销物理返回键的监听 + this.backPress.componentWillUnmount(); + } + onBackPress = () => { + /** + *处理android中的物理返回键 + */ + NavigationUtil.goBack({navigation:this.props.navigation}); + return true; + } + onShare () { + + } + getParallaxRenderConfig (params) { + let config = {}; + let avatar = typeof(params.avatar) === 'string' ? {uri: params.avatar} : params.avatar; + // debugger + config.renderBackground=() => ( + // 背景设置 + + + + + ); + config.renderForeground=() => ( + // 前景 + + + + {params.name} + + + {params.description} + + + ); + + config.renderStickyHeader=() => ( + // 悬停的顶部展示内容 + + {params.name} + + ); + + config.renderFixedHeader=() => ( + // 顶部固定位置的左右按钮 + + {ViewUtil.getLeftBackButton(() => NavigationUtil.goBack({navigation: this.props.navigation}))} + {ViewUtil.getShareButton(() =>this.onShare())} + + ); + + return config; + } + render (contentView, params) { + const renderConfig = this.getParallaxRenderConfig(params); + const {theme} = this.props; + return ( + + {/* 内容区域 */} + {contentView} + + ) + } +} + +const window = Dimensions.get('window'); +const AVATAR_SIZE = 90; +const PARALLAX_HEADER_HEIGHT = 270; +const TOP = (Platform.OS === 'ios') ? 20 + (DeviceInfo.isIPhoneX_deprecated?24:0):0; +const STICKY_HEADER_HEIGHT = (Platform.OS === 'ios') ? GobalStyles.nav_bar_height_ios + TOP : GobalStyles.nav_bar_height_android; + +const styles = StyleSheet.create({ + container: { + // flex: 1, + backgroundColor: 'black' + }, + background: { + position: 'absolute', + top: 0, + left: 0, + width: window.width, + height: PARALLAX_HEADER_HEIGHT + }, + stickySection: { + height: STICKY_HEADER_HEIGHT, + // width: 300, + justifyContent: 'center', + paddingTop: TOP, + }, + stickySectionText: { + color: 'white', + fontSize: 20, + margin: 10, + textAlign: 'center' + }, + fixedSection: { + position: 'absolute', + left:0, + right:0, + bottom:0, + paddingRight: 8, + paddingTop: TOP, + flexDirection: 'row', + alignItems: 'center', + justifyContent:'space-between' + }, + fixedSectionText: { + color: '#999', + fontSize: 20 + }, + parallaxHeader: { + alignItems: 'center', + flex: 1, + flexDirection: 'column', + paddingTop: 100 + }, + avatar: { + marginBottom: 10, + borderRadius: AVATAR_SIZE / 2 + }, + sectionSpeakerText: { + color: 'white', + fontSize: 24, + marginBottom: 10, + }, + sectionTitleText: { + color: 'white', + fontSize: 18, + marginRight: 10, + marginLeft: 10, + } +}); diff --git a/js/page/about/AboutMePage.js b/js/page/about/AboutMePage.js new file mode 100644 index 0000000..f31fe8b --- /dev/null +++ b/js/page/about/AboutMePage.js @@ -0,0 +1,115 @@ +import React, {Component} from 'react'; +import { View, StatusBar, Linking, Clipboard } from 'react-native'; +import { MORE_MENU } from '../../common/MORE_MENU'; +import GobalStyles from '../../res/styles/GobalStyles'; +import ViewUtil from '../../util/ViewUtil'; +import NavigationUtil from '../../navigator/NavigationUtil'; +import AboutCommon, {FLAG_ABOUT} from './AboutCommon'; +import config from '../../res/data/github_app_config.json'; +import Ionicons from 'react-native-vector-icons/Ionicons'; +import Toast from 'react-native-easy-toast'; +type Props = {}; + +export default class AboutMePage extends Component { + constructor (props) { + super(props); + this.params = this.props.navigation.state.params; + this.aboutCommon = new AboutCommon({ + ...this.params, + navigation: this.props.navigation, + flagAbout: FLAG_ABOUT.flag_about_me, + }, data => this.setState({...data})) + this.state = { + data: config, + showTutorial: true, + showBlog: false, + showQQ: false, + showContact: false + } + } + onClick (tab) { + if (!tab) return; + if (tab.url) { + NavigationUtil.goPage({ + title: tab.title, + url: tab.url + }, 'WebViewPage') + return; + } + if (tab.account&&tab.account.indexOf('@')>-1) { + let url = 'qqmail://' + tab.account; + Linking.canOpenURL(url).then(support => { + if (!support) { + console.log('Can\'t handle url:' + url); + } else { + Linking.openURL(url); + } + }).catch(e => { + console.error('An eooro occurred' + e); + }); + return; + } + if (tab.account) { + Clipboard.setString(tab.account); + this.toast.show(tab.title + tab.account + '已复制到剪贴板。'); + } + } + getItem (menu) { + const {theme} = this.params; + return ViewUtil.getMenuItem(() => this.onClick(menu), menu, theme.themeColor); + } + _item (data, isShow, key) { + // console.log(this.state.data) + // debugger + const {theme} = this.params; + return ViewUtil.getSettingItem(() => { + // 该箭头函数为,传递给getSettingItem中TouchableOpacity组件的onPress回调函数, + this.setState({ + [key]: !this.state[key] + }) + }, data.name, theme.themeColor, Ionicons, data.icon, isShow?'ios-arrow-up': 'ios-arrow-down') + } + renderItems (dic, isShowAccount) { + const {theme} = this.params; + if (!dic) return null; + let views = []; + for (let i in dic) { + let title = isShowAccount ? dic[i].title + ':' + dic[i].account : dic[i].title; + views.push( + + { + ViewUtil.getSettingItem(()=> this.onClick(dic[i]), title, theme.themeColor) + } + + + ) + } + return views; + } + render() { + const content = + {this._item(this.state.data.aboutMe.Tutorial, this.state.showTutorial, 'showTutorial')} + + {this.state.showTutorial?this.renderItems(this.state.data.aboutMe.Tutorial.items):null} + + {this._item(this.state.data.aboutMe.Blog, this.state.showBlog, 'showBlog')} + + {this.state.showBlog?this.renderItems(this.state.data.aboutMe.Blog.items):null} + + {this._item(this.state.data.aboutMe.QQ, this.state.showQQ, 'showQQ')} + + {this.state.showQQ?this.renderItems(this.state.data.aboutMe.QQ.items):null} + + {this._item(this.state.data.aboutMe.Contact, this.state.showContact, 'showContact')} + + {this.state.showContact?this.renderItems(this.state.data.aboutMe.Contact.items):null} + + + return + {this.aboutCommon.render(content, this.state.data.author)} + this.toast=toast} + position={'center'} + /> + + } +} diff --git a/js/page/about/AboutPage.js b/js/page/about/AboutPage.js new file mode 100644 index 0000000..96c7b91 --- /dev/null +++ b/js/page/about/AboutPage.js @@ -0,0 +1,74 @@ +import React, {Component} from 'react'; +import { View, StatusBar, Linking } from 'react-native'; +import { MORE_MENU } from '../../common/MORE_MENU'; +import GobalStyles from '../../res/styles/GobalStyles'; +import ViewUtil from '../../util/ViewUtil'; +import NavigationUtil from '../../navigator/NavigationUtil'; +import AboutCommon, {FLAG_ABOUT} from './AboutCommon'; +import config from '../../res/data/github_app_config.json'; + +type Props = {}; + +export default class AboutPage extends Component { + constructor (props) { + super(props); + this.params = this.props.navigation.state.params; + // debugger + this.aboutCommon = new AboutCommon({ + ...this.params, + navigation: this.props.navigation, + flagAbout: FLAG_ABOUT.flag_about, + }, data => this.setState({...data})) + this.state = { + data: config + } + } + onClick (menu) { + const {theme} = this.params; + let RouteName, params = {theme}; + switch (menu) { + case MORE_MENU.Tutorial: + RouteName = 'WebViewPage'; + params.title = '教程'; + params.url = 'https://ml.66jingcai.cn/' + break; + case MORE_MENU.About_Author: + RouteName = 'AboutMePage'; + break; + case MORE_MENU.Feedback: + const url = "qqmail://172529131@qq.com" + Linking.canOpenURL(url).then(support => { + if (!support) { + console.log('Can\'t handle url:' + url); + } else { + Linking.openURL(url); + } + }).catch(e => { + console.error('An eooro occurred' + e); + }); + break; + default: + break; + } + if (RouteName) { + NavigationUtil.goPage(params, RouteName); + } + } + getItem (menu) { + const {theme} = this.params; + return ViewUtil.getMenuItem(() => this.onClick(menu), menu, theme.themeColor); + } + render() { + const content = + {/* 教程 */} + {this.getItem(MORE_MENU.Tutorial)} + + {/* 关于作者 */} + {this.getItem(MORE_MENU.About_Author)} + + {/* 反馈 */} + {this.getItem(MORE_MENU.Feedback)} + + return this.aboutCommon.render(content, this.state.data.app); + } +} diff --git a/js/redux/action/ActionUtil.js b/js/redux/action/ActionUtil.js index e8a953f..0a7fc57 100644 --- a/js/redux/action/ActionUtil.js +++ b/js/redux/action/ActionUtil.js @@ -8,7 +8,7 @@ import ProjectModel from '../../model/ProjectModel'; * @param {*} data * @param {*} pageSize */ -export default function handleData (actionType, dispatch, storeName, data, pageSize, favoriteDao) { +export default function handleData (actionType, dispatch, storeName, data, pageSize, favoriteDao, params) { let fixItems = []; if (data && data.data) { if (Array.isArray(data.data)) { @@ -27,7 +27,8 @@ export default function handleData (actionType, dispatch, storeName, data, pageS items: fixItems, projectModels: projectModels, storeName, - pageIndex: 1 // 初始页 + pageIndex: 1, // 初始页 + ...params }) }) } @@ -46,4 +47,10 @@ export async function _projectModels (showItems, favoriteDao, callback) { if (typeof callback === 'function') { callback(projectModels); } +} + +export const doCallBack = (callBack, object) => { + if (typeof callBack === 'function') { + callBack(object) + } } \ No newline at end of file diff --git a/js/redux/action/action_types.js b/js/redux/action/action_types.js index 43b0b52..1e92628 100644 --- a/js/redux/action/action_types.js +++ b/js/redux/action/action_types.js @@ -1,17 +1,32 @@ export default { THEME_CHANGE: 'THEME_CHANGE', THEME_INIT: 'THEME_INIT', + SHOW_THEME_VIEW: 'SHOW_THEME_VIEW', + POPULAR_REFRESH: 'POPULAR_REFRESH', //popular数据刷新 POPULAR_REFRESH_FAIL: 'POPULAR_REFRESH_FAIL', //popular数据加载失败 POPULAR_REFRESH_SUCCESS: 'POPULAR_REFRESH_SUCCESS', //popular数据加载成功 POPULAR_LOAD_MORE_SUCCESS: 'POPULAR_LOAD_MORE_SUCCESS', //加载更多 POPULAR_LOAD_MORE_FAIL: 'POPULAR_LOAD_MORE_FAIL', // + FLUSH_POPULAR_FAVORITE: 'FLUSH_POPULAR_FAVORITE', // 刷新popular页面的收藏状态 + TRENDING_REFRESH: 'TRENDING_REFRESH', //TRENDING数据刷新 TRENDING_REFRESH_FAIL: 'TRENDING_REFRESH_FAIL', //TRENDING数据加载失败 TRENDING_REFRESH_SUCCESS: 'TRENDING_REFRESH_SUCCESS', //TRENDING数据加载成功 TRENDING_LOAD_MORE_SUCCESS: 'TRENDING_LOAD_MORE_SUCCESS', //TRENDING加载更多 TRENDING_LOAD_MORE_FAIL: 'TRENDING_LOAD_MORE_FAIL', //TRENDING加载更多失败 - FAVORITE_LOAD_DATA: 'FAVORITE_LOAD_DATA', - FAVORITE_LOAD_SUCCESS: 'FAVORITE_LOAD_SUCCESS', + FLUSH_TRENDING_FAVORITE: 'FLUSH_TRENDING_FAVORITE', // 刷新trending页面的收藏状态 + + FAVORITE_LOAD_DATA: 'FAVORITE_LOAD_DATA', // 加载favorite页面数据 + FAVORITE_LOAD_SUCCESS: 'FAVORITE_LOAD_SUCCESS', // favorite数据加载成功 FAVORITE_LOAD_FAIL: 'FAVORITE_LOAD_FAIL', + + LANGUAGE_LOAD_SUCCESS: 'LANGUAGE_LOAD_SUCCESS', + + SEARCH_REFRESH: 'SEARCH_REFRESH',//搜索数据加载 + SEARCH_CANCEL: 'SEARCH_CANCEL',//搜索取消 + SEARCH_REFRESH_SUCCESS: 'SEARCH_REFRESH_SUCCESS',//搜索数据加载成功 + SEARCH_REFRESH_FAIL: 'SEARCH_REFRESH_FAIL',//搜索失败 + SEARCH_LOAD_MORE_SUCCESS: 'SEARCH_LOAD_MORE_SUCCESS',//趋势更多数据加载成功 + SEARCH_LOAD_MORE_FAIL: 'SEARCH_LOAD_MORE_FAIL',//趋势更多数据加载失败 } \ No newline at end of file diff --git a/js/redux/action/favorite/index.js b/js/redux/action/favorite/index.js index 4b85e91..868df66 100644 --- a/js/redux/action/favorite/index.js +++ b/js/redux/action/favorite/index.js @@ -11,13 +11,15 @@ import ProjectModel from '../../../model/ProjectModel'; export function onLoadFavoriteData(flag, isShowLoading) { return dispatch => { // 派发刷新状态 - dispatch({type:Types.FAVORITE_LOAD_DATA, storeName: flag}); + if (isShowLoading) { + // 根据isShowLoading决定是否显示loading动画 + dispatch({type:Types.FAVORITE_LOAD_DATA, storeName: flag}); + } new FavoriteDao(flag).getAllItems().then(items => { let resultData = []; for (let i = 0, len = items.length; i < len; i++) { resultData.push(new ProjectModel(items[i], true)); } - console.log(resultData) dispatch({ type: Types.FAVORITE_LOAD_SUCCESS, projectModels: resultData, diff --git a/js/redux/action/index.js b/js/redux/action/index.js index 7981ef3..54a0f49 100644 --- a/js/redux/action/index.js +++ b/js/redux/action/index.js @@ -1,7 +1,9 @@ -import {onThemeChange} from './theme'; -import {onLoadPopularData, onLoadMorePopular} from './popular'; -import {onLoadTrendingData, onLoadMoreTrending} from './trending'; +import {onThemeChange, onThemeInit, onShowCustomThemeView} from './theme'; +import {onLoadPopularData, onLoadMorePopular, onFlushPopularFavorite} from './popular'; +import {onLoadTrendingData, onLoadMoreTrending, onFlushTrendingFavorite} from './trending'; import {onLoadFavoriteData} from './favorite'; +import {onLoadLanguage} from './language'; +import {onSearch, onSearchCancel, onLoadMoreSearch} from './search'; export default { onThemeChange, @@ -9,5 +11,13 @@ export default { onLoadMorePopular, onLoadTrendingData, onLoadMoreTrending, - onLoadFavoriteData + onLoadFavoriteData, + onFlushPopularFavorite, + onFlushTrendingFavorite, + onLoadLanguage, + onThemeInit, + onShowCustomThemeView, + onSearch, + onSearchCancel, + onLoadMoreSearch } \ No newline at end of file diff --git a/js/redux/action/language/index.js b/js/redux/action/language/index.js new file mode 100644 index 0000000..69be032 --- /dev/null +++ b/js/redux/action/language/index.js @@ -0,0 +1,23 @@ +import Types from '../action_types'; +import LanguageDao from '../../../expand/dao/LanguageDao'; +/* +* @name index +* @param {CLIPBOARD} +* @author Ethan +* @date 2019-03-26 19:30:08 +*/ +export function onLoadLanguage(flagkey) { + return async dispatch => { + // 派发刷新状态 + try { + let languages = await new LanguageDao(flagkey).fetch(); + dispatch({ + type: Types.LANGUAGE_LOAD_SUCCESS, + languages: languages, + flag: flagkey + }) + } catch (error) { + console.log(error) + } + } +} diff --git a/js/redux/action/popular/index.js b/js/redux/action/popular/index.js index 90b634c..0af6be4 100644 --- a/js/redux/action/popular/index.js +++ b/js/redux/action/popular/index.js @@ -64,4 +64,28 @@ export function onLoadMorePopular (storeName, pageIndex, pageSize, dataArray=[], } }, 500); } +} + +/** + * 刷新页面收藏状态 + * @param {*} storeName + * @param {*} pageIndex + * @param {*} pageSize + * @param {*} [dataArray=[]] + * @param {*} favoriteDao + * @returns + */ +export function onFlushPopularFavorite (storeName, pageIndex, pageSize, dataArray = [], favoriteDao) { + return dispatch => { + // 本次和载入的最大数量 + let max = pageSize * pageIndex > dataArray.length ? dataArray.length : pageIndex * pageSize; + _projectModels(dataArray.slice(0, max), favoriteDao, data => { + dispatch({ + type: Types.FLUSH_POPULAR_FAVORITE, + storeName: storeName, + pageIndex: pageIndex, + projectModels: data + }) + }) + } } \ No newline at end of file diff --git a/js/redux/action/search/index.js b/js/redux/action/search/index.js new file mode 100644 index 0000000..9b9d835 --- /dev/null +++ b/js/redux/action/search/index.js @@ -0,0 +1,126 @@ +import Types from '../action_types'; +import handleData, { _projectModels, doCallBack } from '../ActionUtil'; +import ArrayUtil from '../../../util/ArrayUtil'; +import Utils from '../../../util/Utils'; + +const API_URL = 'https://api.github.com/search/repositories?q=' +const QUERY_STR = '&sort=stars' +const CANCEL_TOKENS = [] + +/** + * 发起搜索 + * @param inputKey + * @param pageSize + * @param token + * @param favoriteDao + * @param popularKeys + * @param callBack + * @returns {Function} + */ +export function onSearch (inputKey, pageSize, token, favoriteDao, popularKeys, callBack) { + return dispatch => { + dispatch({ type: Types.SEARCH_REFRESH }) + fetch(getFetchUrl(inputKey)).then(response => { + //如果任务取消,则不做任何处理 + return hasCancel(token) ? null : response.json() + }).then(res => { + if (hasCancel(token, true)) { + //如果任务取消,则不做任何处理 + console.log('user cancel') + return + } + if (!res || !res.items || res.items.length === 0) { + dispatch({ type: Types.SEARCH_REFRESH_FAIL, message: `没找到关于${inputKey}的项目` }) + doCallBack(callBack, `没找到关于${inputKey}的项目`) + return + } + let items = res.items; + handleData(Types.SEARCH_REFRESH_SUCCESS, dispatch, '', { data: items }, pageSize, favoriteDao, { + showBottomButton: !Utils.checkKeyIsExist(popularKeys, inputKey), + inputKey + }) + }).catch(err => { + console.log(err) + dispatch({ type: Types.SEARCH_REFRESH_FAIL, error: err }) + }) + } + +} + +/** + * 取消一个异步任务 + * @param token + * @returns {Function} + */ +export function onSearchCancel (token) { + return dispatch => { + CANCEL_TOKENS.push(token) + dispatch({ type: Types.SEARCH_CANCEL }) + } +} + +export function onLoadMoreSearch (pageIndex, pageSize, dataArray = [], favoriteDao, callBack) { + return dispatch => { + setTimeout(() => { + if ((pageIndex - 1) * pageSize >= dataArray.length) { + if (typeof callBack === 'function') { + callBack('no more') + } + dispatch({ + type: Types.SEARCH_LOAD_MORE_FAIL, + error: 'no more', + pageIndex: --pageIndex + }) + } else { + //本次和载入的最大数量 + let max = pageSize * pageIndex > dataArray.length ? dataArray.length : pageSize * pageIndex + _projectModels(dataArray.slice(0, max), favoriteDao, data => { + dispatch({ + type: Types.SEARCH_REFRESH_SUCCESS, + pageIndex, + projectModels: data + }) + }) + } + }, 500) + } +} + +/** + * 拼接url + * @param key + * @returns {string} + */ +function getFetchUrl (key) { + return `${API_URL}${key}${QUERY_STR}` +} + +/** + * 检查指定token是否已取消 + * @param token + * @param isRemove + * @returns {boolean} + */ +function hasCancel (token, isRemove) { + if (CANCEL_TOKENS.includes(token)) { + isRemove && ArrayUtil.remove(CANCEL_TOKENS, token) + return true + } + return false +} + + + +/** + * 检查该Item是否被收藏 + * **/ +function checkFavorite (item, keys = []) { + if (!keys) return false + for (let i = 0, len = keys.length; i < len; i++) { + let id = item.id ? item.id : item.fullName + if (id.toString() === keys[i]) { + return true + } + } + return false +} \ No newline at end of file diff --git a/js/redux/action/theme/index.js b/js/redux/action/theme/index.js index 9ff1c09..6bfd5ef 100644 --- a/js/redux/action/theme/index.js +++ b/js/redux/action/theme/index.js @@ -1,8 +1,31 @@ import Types from '../action_types'; +import ThemeDao from '../../../expand/dao/ThemeDao'; +/** + * 主题更改 + * @param theme + * @returns {{theme: *, type: string}} + */ +export function onThemeChange (theme) { + return { type: Types.THEME_CHANGE, theme } +} -export function onThemeChange(theme) { - return { - type: Types.THEME_CHANGE, - theme: theme +/** + * 初始化主题 + * @returns {Function} + */ +export function onThemeInit () { + return dispatch => { + new ThemeDao().getTheme().then(data => { + dispatch(onThemeChange(data)) + }) } -} \ No newline at end of file +} + +/** + * 显示自定义主题弹窗 + * @param show + * @returns {{type: string, customThemeVisible: *}} + */ +export function onShowCustomThemeView (show) { + return { type: Types.SHOW_THEME_VIEW, customThemeViewVisible: show } +} diff --git a/js/redux/action/trending/index.js b/js/redux/action/trending/index.js index 216c4eb..97069bf 100644 --- a/js/redux/action/trending/index.js +++ b/js/redux/action/trending/index.js @@ -67,3 +67,27 @@ export function onLoadMoreTrending (storeName, pageIndex, pageSize, dataArray=[] }, 500); } } + +/** + * 刷新页面收藏状态 + * @param {*} storeName + * @param {*} pageIndex + * @param {*} pageSize + * @param {*} [dataArray=[]] + * @param {*} favoriteDao + * @returns + */ +export function onFlushTrendingFavorite (storeName, pageIndex, pageSize, dataArray = [], favoriteDao) { + return dispatch => { + // 本次和载入的最大数量 + let max = pageSize * pageIndex > dataArray.length ? dataArray.length : pageIndex * pageSize; + _projectModels(dataArray.slice(0, max), favoriteDao, data => { + dispatch({ + type: Types.FLUSH_TRENDING_FAVORITE, + storeName: storeName, + pageIndex: pageIndex, + projectModels: data + }) + }) + } +} \ No newline at end of file diff --git a/js/redux/reducer/favorite/index.js b/js/redux/reducer/favorite/index.js index 7573311..fd3898a 100644 --- a/js/redux/reducer/favorite/index.js +++ b/js/redux/reducer/favorite/index.js @@ -21,7 +21,6 @@ const defaultState = {} * @returns */ export default function onAction (state=defaultState, action) { - console.log(action) switch (action.type) { // 获取数据 case Types.FAVORITE_LOAD_DATA: diff --git a/js/redux/reducer/index.js b/js/redux/reducer/index.js index 6278721..8f712d0 100644 --- a/js/redux/reducer/index.js +++ b/js/redux/reducer/index.js @@ -3,6 +3,8 @@ import theme from './theme'; import popular from './pouplar'; import trending from './trending'; import favorite from './favorite'; +import language from './language'; +import search from './search'; import { rootCom, RootNavigator } from '../../navigator/AppNavigator'; // 1.指定默认的state @@ -21,6 +23,8 @@ const index = combineReducers({ theme: theme, popular: popular, trending: trending, - favorite: favorite + favorite: favorite, + language: language, + search: search }); export default index; \ No newline at end of file diff --git a/js/redux/reducer/language/index.js b/js/redux/reducer/language/index.js new file mode 100644 index 0000000..15e805f --- /dev/null +++ b/js/redux/reducer/language/index.js @@ -0,0 +1,35 @@ +import Types from '../../action/action_types'; +import {FLAG_LANGUAGE} from '../../../expand/dao/LanguageDao'; + +const defaultState = { + languages: [], + keys: [] +} + +/** + * + * + * @export + * @param {*} [state=defaultState] + * @param {*} action + * @returns + */ +export default function onAction (state=defaultState, action) { + switch (action.type) { + // 获取数据 + case Types.LANGUAGE_LOAD_SUCCESS: + if (FLAG_LANGUAGE.flag_key === action.flag) { + return { + ...state, + keys: action.languages + } + } else { + return { + ...state, + languages: action.languages + } + } + default: + return state; + } +} \ No newline at end of file diff --git a/js/redux/reducer/pouplar/index.js b/js/redux/reducer/pouplar/index.js index 8bf3331..95a2590 100644 --- a/js/redux/reducer/pouplar/index.js +++ b/js/redux/reducer/pouplar/index.js @@ -60,6 +60,14 @@ export default function onAction (state=defaultState, action) { hideLoadingMore: true, pageIndex: action.pageIndex }}; + case Types.FLUSH_POPULAR_FAVORITE: + return { + ...state, + [action.storeName]: { + ...state[action.storeName], + projectModels: action.projectModels + } + }; default: return state; } diff --git a/js/redux/reducer/search/index.js b/js/redux/reducer/search/index.js new file mode 100644 index 0000000..6000a3b --- /dev/null +++ b/js/redux/reducer/search/index.js @@ -0,0 +1,63 @@ +import Types from '../../action/action_types'; + +const defaultState = { + showText: '搜索', + items: [], + isLoading: false, + projectModels: [],//要显示的数据 + hideLoadingMore: true,//默认隐藏加载更多 + showBottomButton: false +} + +export default function onAction (state = defaultState, action) { + switch (action.type) { + case Types.SEARCH_REFRESH://搜索数据 + return { + ...state, + isLoading: true, + hideLoadingMore: true, + showBottomButton: false, + showText: '取消' + } + case Types.SEARCH_REFRESH_SUCCESS://获取数据成功 + console.log(action) + return { + ...state,//原始数据 + isLoading: false, + hideLoadingMore: false, + showBottomButton: action.showBottomButton, + items: action.items, + projectModels: action.projectModels, + pageIndex: action.pageIndex, + showText: '搜索', + inputKey: action.inputKey + } + case Types.SEARCH_REFRESH_FAIL://下拉刷新失败 + return { + ...state, + isLoading: false, + showText: '搜索' + } + case Types.SEARCH_CANCEL://搜索取消 + return { + ...state, + isLoading: false, + showText: '搜索' + } + case Types.SEARCH_LOAD_MORE_SUCCESS://上拉加载更多成功 + return { + ...state, + projectModels: action.projectModels, + hideLoadingMore: false, + pageIndex: action.pageIndex, + } + case Types.SEARCH_LOAD_MORE_FAIL://上拉加载更多失败 + return { + ...state, + hideLoadingMore: true, + pageIndex: action.pageIndex, + } + default: + return state + } +} diff --git a/js/redux/reducer/theme/index.js b/js/redux/reducer/theme/index.js index bdcbd36..d66a3e6 100644 --- a/js/redux/reducer/theme/index.js +++ b/js/redux/reducer/theme/index.js @@ -1,13 +1,22 @@ import Types from '../../action/action_types'; - +import ThemeFactory, {ThemeFlags} from '../../../res/styles/ThemeFactory'; const defaultState = { - theme: '#f33' + theme: ThemeFactory.createTheme(ThemeFlags.Default), + customThemeViewVisible: false } export default function onAction (state=defaultState, action) { switch (action.type) { case Types.THEME_CHANGE: - return {...state, theme:action.theme}; + return { + ...state, + theme: action.theme + } + case Types.SHOW_THEME_VIEW: + return { + ...state, + customThemeViewVisible: action.customThemeViewVisible + } default: - return state; + return state } } \ No newline at end of file diff --git a/js/redux/reducer/trending/index.js b/js/redux/reducer/trending/index.js index 5c9b761..6c2cc0d 100644 --- a/js/redux/reducer/trending/index.js +++ b/js/redux/reducer/trending/index.js @@ -60,6 +60,15 @@ export default function onAction (state=defaultState, action) { hideLoadingMore: true, pageIndex: action.pageIndex }}; + // 刷新趋势页面的收藏状态 + case Types.FLUSH_TRENDING_FAVORITE: + return { + ...state, + [action.storeName]: { + ...state[action.storeName], + projectModels: action.projectModels + } + }; default: return state; } diff --git a/js/res/data/github_app_config.json b/js/res/data/github_app_config.json new file mode 100644 index 0000000..000dcb0 --- /dev/null +++ b/js/res/data/github_app_config.json @@ -0,0 +1,90 @@ +{ + "aboutMe": { + "Tutorial": { + "name": "教程", + "icon": "ios-bookmarks", + "items": [ + { + "title": "React Native基础教程", + "url": "https://reactnative.cn/docs/sectionlist.html#docsNav" + }, + { + "title": "React Native高级实战教程", + "url": "https://coding.m.imooc.com/classindex.html?cid=89" + } + ] + }, + "Blog": { + "name": "技术博客", + "icon": "ios-laptop", + "items": [ + { + "title": "个人博客", + "url": "http://www.cnblogs.com/songdongdong/" + }, + { + "title": "CSDN", + "url": "http://www.cnblogs.com/songdongdong/" + }, + { + "title": "简书", + "url": "http://www.cnblogs.com/songdongdong/" + }, + { + "title": "GitHub", + "url": "https://github.com/songdongdong123" + }, + { + "title": "慕课网", + "url": "https://github.com/songdongdong123" + } + ] + }, + "Contact": { + "name": "联系方式", + "icon": "ios-contacts", + "items": { + "QQ": { + "title": "QQ", + "account": "172529131" + }, + "Email": { + "title": "Email", + "account": "ethanSongs@163.com" + } + } + }, + "QQ": { + "name": "技术交流群", + "icon": "ios-chatbubbles", + "items": [ + { + "title": "移动开发者技术分享群", + "account": "172529131" + }, + { + "title": "React Native学习交流群", + "account": "172529131" + } + ] + } + }, + "info": { + "html_url": "https://github.com/crazycodeboy/", + "url": "https://api.github.com/repos/crazycodeboy/", + "currentRepoUrl": "https://api.github.com/repos/crazycodeboy/GitHubPopular" + }, + "author": { + "name": "Ethan", + "description": "专注于前端开发,分享知识,共享快乐。", + "avatar": "https://avatars0.githubusercontent.com/u/20263883?s=400&u=221df9487b574ebfb83d3402d61a7839316b2c3d&v=4", + "backgroundImg": "http://www.devio.org/io/GitHubPopular/img/for_githubpopular_about_me.jpg", + "url": "https://github.com/songdongdong123" + }, + "app": { + "name": "GitHub Popular", + "description": "这是一个用来查看GitHub最受欢迎与最热项目的App,它基于React Native支持Android和iOS双平台。", + "avatar": "http://www.devio.org/io/GitHubPopular/img/ic_app.png", + "backgroundImg": "http://www.devio.org/io/GitHubPopular/img/for_githubpopular_about_me.jpg" + } +} \ No newline at end of file diff --git a/js/res/data/keys.json b/js/res/data/keys.json new file mode 100644 index 0000000..bdf48c7 --- /dev/null +++ b/js/res/data/keys.json @@ -0,0 +1,38 @@ +[ + { + "path": "start:>1", + "name": "ALL", + "short_name": "ALL", + "checked": true + }, + { + "path": "ios", + "name": "ios", + "checked": true + }, + { + "path": "react-native", + "name": "React Native", + "checked": true + }, + { + "path": "MySQL", + "name": "MySQL", + "checked": false + }, + { + "path": "AngularJS", + "name": "AngularJS", + "checked": false + }, + { + "path": "jQuery", + "name": "jQuery", + "checked": false + }, + { + "path": "react", + "name": "React", + "checked": true + } +] \ No newline at end of file diff --git a/js/res/data/langs.json b/js/res/data/langs.json new file mode 100644 index 0000000..0bbe606 --- /dev/null +++ b/js/res/data/langs.json @@ -0,0 +1,319 @@ +[ + { + "path": "", + "name": "All Language", + "short_name": "All", + "checked": true + }, + { + "path": "unknown", + "name": "Unknown", + "checked": true + }, + { + "path": "as3", + "name": "ActionScript", + "short_name": "AS", + "checked": false + }, + { + "path": "apacheconf", + "name": "ApacheConf", + "checked": false + }, + { + "path": "nasm", + "name": "Assembly", + "short_name": "NASM", + "checked": false + }, + { + "path": "bat", + "name": "Batchfile", + "short_name": "BAT", + "checked": false + }, + { + "path": "c", + "name": "C", + "checked": false + }, + { + "path": "csharp", + "name": "C#", + "checked": false + }, + { + "path": "cpp", + "name": "C++", + "checked": false + }, + { + "path": "cmake", + "name": "CMake", + "checked": false + }, + { + "path": "css", + "name": "CSS", + "checked": false + }, + { + "path": "clojure", + "name": "Clojure", + "checked": false + }, + { + "path": "coffeescript", + "name": "CoffeeScript", + "checked": false + }, + { + "path": "common-lisp", + "short_name": "Lisp", + "name": "Common Lisp", + "checked": false + }, + { + "path": "crystal", + "name": "Crystal", + "checked": false + }, + { + "path": "d", + "name": "D", + "checked": false + }, + { + "path": "dart", + "name": "Dart", + "checked": false + }, + { + "path": "elixir", + "name": "Elixir", + "checked": false + }, + { + "path": "emacs-lisp", + "short_name": "Lisp", + "name": "Emacs Lisp", + "checked": false + }, + { + "path": "erlang", + "name": "Erlang", + "checked": false + }, + { + "path": "fsharp", + "name": "F#", + "checked": false + }, + { + "path": "game-maker-language", + "short_name": "GML", + "name": "Game Maker Language", + "checked": false + }, + { + "path": "go", + "name": "Go", + "checked": false + }, + { + "path": "groovy", + "name": "Groovy", + "checked": false + }, + { + "path": "html", + "name": "HTML", + "checked": false + }, + { + "path": "haskell", + "name": "Haskell", + "checked": false + }, + { + "path": "haxe", + "name": "Haxe", + "checked": false + }, + { + "path": "inno-setup", + "short_name": "Inno", + "name": "Inno Setup", + "checked": false + }, + { + "path": "java", + "name": "Java", + "checked": true + }, + { + "path": "javascript", + "short_name": "JS", + "name": "JavaScript", + "checked": true + }, + { + "path": "julia", + "name": "Julia", + "checked": false + }, + { + "path": "kotlin", + "name": "Kotlin", + "checked": true + }, + { + "path": "livescript", + "short_name": "LS", + "name": "LiveScript", + "checked": false + }, + { + "path": "lua", + "name": "Lua", + "checked": false + }, + { + "path": "makefile", + "name": "Makefile", + "checked": false + }, + { + "path": "matlab", + "name": "Matlab", + "checked": false + }, + { + "path": "nsis", + "name": "NSIS", + "checked": false + }, + { + "path": "nimrod", + "name": "Nimrod", + "checked": false + }, + { + "path": "ocaml", + "name": "OCaml", + "checked": false + }, + { + "short_name": "Obj-C", + "path": "objective-c", + "name": "Objective-C", + "checked": true + }, + { + "short_name": "Obj-C++", + "path": "objective-c%2B%2B", + "name": "Objective-C++", + "checked": false + }, + { + "path": "php", + "name": "PHP", + "checked": true + }, + { + "path": "plsql", + "name": "PLSQL", + "checked": false + }, + { + "path": "pascal", + "name": "Pascal", + "checked": false + }, + { + "path": "perl", + "name": "Perl", + "checked": false + }, + { + "path": "postscript", + "name": "PostScript", + "checked": false + }, + { + "path": "powershell", + "name": "PowerShell", + "checked": false + }, + { + "path": "python", + "name": "Python", + "checked": false + }, + { + "path": "qml", + "name": "QML", + "checked": false + }, + { + "path": "r", + "name": "R", + "checked": false + }, + { + "path": "ruby", + "name": "Ruby", + "checked": false + }, + { + "path": "rust", + "name": "Rust", + "checked": false + }, + { + "path": "scala", + "name": "Scala", + "checked": false + }, + { + "path": "scheme", + "name": "Scheme", + "checked": false + }, + { + "path": "bash", + "name": "Shell", + "checked": false + }, + { + "path": "supercollider", + "name": "SuperCollider", + "checked": false + }, + { + "path": "swift", + "name": "Swift", + "checked": true + }, + { + "path": "tex", + "name": "TeX", + "checked": false + }, + { + "path": "typescript", + "name": "TypeScript", + "checked": true + }, + { + "path": "verilog", + "name": "Verilog", + "checked": true + }, + { + "path": "xslt", + "name": "XSLT", + "checked": false + } +] \ No newline at end of file diff --git a/js/res/styles/GobalStyles.js b/js/res/styles/GobalStyles.js new file mode 100644 index 0000000..3685743 --- /dev/null +++ b/js/res/styles/GobalStyles.js @@ -0,0 +1,24 @@ +/** +* @name GobalStyles +* @param {CLIPBOARD} +* @author Ethan +* @date 2019-03-27 14:15:32 +*/ +import { Dimensions } from 'react-native' +const BACKGROUND_COLOR = '#F3F3F4'; +const { height, width } = Dimensions.get('window') +export default { + line: { + height: 0.5, + opacity: 0.5, + backgroundColor: 'darkgray' + }, + root_container: { + flex: 1, + backgroundColor: BACKGROUND_COLOR + }, + nav_bar_height_ios: 44, + nav_bar_height_android: 50, + backgroundColor: BACKGROUND_COLOR, + window_height: height, +}; \ No newline at end of file diff --git a/js/res/styles/ThemeFactory.js b/js/res/styles/ThemeFactory.js new file mode 100644 index 0000000..0166f9c --- /dev/null +++ b/js/res/styles/ThemeFactory.js @@ -0,0 +1,54 @@ +/** + * 主题 + */ +import React from 'react' +import { StyleSheet } from 'react-native' + +export const ThemeFlags = { + Default: '#f33', + Red: '#F44336', + Pink: '#E91E63', + Purple: '#9C27B0', + DeepPurple: '#673AB7', + Indigo: '#3F51B5', + Blue: '#2196F3', + LightBlue: '#03A9F4', + Cyan: '#00BCD4', + Teal: '#009688', + Green: '#4CAF50', + LightGreen: '#8BC34A', + Lime: '#CDDC39', + Yellow: '#FFEB3B', + Amber: '#FFC107', + Orange: '#FF9800', + DeepOrange: '#FF5722', + Brown: '#795548', + Grey: '#9E9E9E', + BlueGrey: '#607D8B', + Black: '#000000' +} + +export default class ThemeFactory { + /** + * 创建一个主题样式 + * @param themeFlag 主题标识 + * @returns {{themeColor: *, styles: *}} + */ + static createTheme (themeFlag) { + return { + themeColor: themeFlag, + styles: StyleSheet.create({ + selectedTitleStyle: { + color: themeFlag, + }, + tabBarSelectedIcon: { + tintColor: themeFlag, + }, + navBar: { + backgroundColor: themeFlag, + } + }), + } + + } +} diff --git a/js/util/AnalyticsUtil.js b/js/util/AnalyticsUtil.js new file mode 100644 index 0000000..0f2a9f5 --- /dev/null +++ b/js/util/AnalyticsUtil.js @@ -0,0 +1,5 @@ +/** + * Created by wangfei on 17/8/30. + */ +var { NativeModules } = require('react-native'); +module.exports = NativeModules.UMAnalyticsModule; \ No newline at end of file diff --git a/js/util/ArrayUtil.js b/js/util/ArrayUtil.js new file mode 100644 index 0000000..e1a19b4 --- /dev/null +++ b/js/util/ArrayUtil.js @@ -0,0 +1,47 @@ +export default class ArrayUtil { + static isEqual (arr1, arr2) { + if (!(arr1&&arr2)) return false; // 如果有一个数组不存在或者为空,返回false + if (arr1.length !== arr2.length) return false; + for (let i = 0, l = arr1.length; i < l; i++) { + if (arr1[i] !== arr2[i]) return false; + } + return true; + } + static updateArray (array, item) { + // 更新数组 + for (let i = 0, len = array.length; i < len; i++) { + let temp = array[i]; + if (item === temp) { + array.splice(i, 1); + return; + } + } + array.push(item); + } + + static remove (array, item, id) { + // 移除元素 + if (!array) return; + for (let i = 0, l = array.length; i < l; i++) { + const val = array[i]; + if (item === val || val && val[id] && val[id] === item[id]) { + array.splice(i, 1); + } + } + return array; + } + + /** + * 数组克隆 + * @param from + * @returns {Array} + */ + static clone (from) { + if (!from) return [] + let newArray = [] + for (let i = 0, length = from.length; i < length; i++) { + newArray[i] = from[i] + } + return newArray + } +} \ No newline at end of file diff --git a/js/util/EventTypes.js b/js/util/EventTypes.js new file mode 100644 index 0000000..e75fdf7 --- /dev/null +++ b/js/util/EventTypes.js @@ -0,0 +1,5 @@ +export default { + bottom_tab_select: 'bottom_tab_select', + favorite_changed_popular: 'favorite_changed_popular', + favorite_changed_trending: 'favorite_changed_trending' +} \ No newline at end of file diff --git a/js/util/Utils.js b/js/util/Utils.js index 9fd41ac..a01af68 100644 --- a/js/util/Utils.js +++ b/js/util/Utils.js @@ -15,4 +15,16 @@ export default class Utils { } return false; } + + /** + * 检查key是否存在于keys中 + * @param keys + * @param key + */ + static checkKeyIsExist (keys, key) { + for (let i = 0, l = keys.length; i < l; i++) { + if (key.toLowerCase() === keys[i].name.toLowerCase()) return true + } + return false + } } \ No newline at end of file diff --git a/js/util/ViewUtil.js b/js/util/ViewUtil.js index 9c54130..4ad8e2a 100644 --- a/js/util/ViewUtil.js +++ b/js/util/ViewUtil.js @@ -1,7 +1,6 @@ import React from 'react'; -import {TouchableOpacity} from 'react-native'; +import {TouchableOpacity, StyleSheet, View, Text} from 'react-native'; import Ionicons from 'react-native-vector-icons/Ionicons'; - export default class ViewUtil { /** * @@ -42,5 +41,76 @@ export default class ViewUtil { /> } - + /** + * + * + * @static + * @param {*} callback 点击item回调函数 + * @param {*} text 显示文办 + * @param {*} color 图标颜色 + * @param {*} Icons react-native-vector-icons组件 + * @param {*} icon 左侧图标 + * @param {*} expandableIcon 右侧图标 + * @memberof ViewUtil + */ + static getSettingItem (callback, text, color, Icons, icon, expandableIcon) { + return ( + + + { + Icons&&icon? + : + } + {text} + + + + ) + } + static getMenuItem (callback, menu, color, expandableIcon) { + return ViewUtil.getSettingItem(callback, menu.name, color, menu.Icons, menu.icon, expandableIcon) + } + + /** + * 获取右侧文字按钮 + * @param title + * @param callBack + * @returns {*} + */ + static getRightButton (title, callBack) { + return + + {title} + + + } } + +const styles = StyleSheet.create({ + setting_item_container: { + backgroundColor: 'white', + padding: 10, + height: 60, + alignItems: 'center', + justifyContent: 'space-between', + flexDirection: 'row', + } +}) \ No newline at end of file diff --git a/package.json b/package.json index b9ee4f5..b0a552a 100644 --- a/package.json +++ b/package.json @@ -12,10 +12,17 @@ "axios": "^0.18.0", "react": "16.6.3", "react-native": "0.58.6", + "react-native-check-box": "^2.1.7", + "react-native-code-push": "^5.6.0", "react-native-easy-toast": "^1.2.0", + "react-native-event-bus": "^1.0.0", "react-native-gesture-handler": "^1.1.0", "react-native-htmlview": "^0.13.0", + "react-native-parallax-scroll-view": "^0.21.3", + "react-native-sortable-listview": "^0.2.8", + "react-native-splash-screen": "^3.2.0", "react-native-vector-icons": "^6.3.0", + "react-native-webview": "^5.6.2", "react-navigation": "^3.3.2", "react-navigation-redux-helpers": "^3.0.0", "react-redux": "^6.0.1", diff --git a/yarn.lock b/yarn.lock index 141774a..b372d71 100644 --- a/yarn.lock +++ b/yarn.lock @@ -874,6 +874,11 @@ resolved "http://registry.npm.taobao.org/@types/node/download/@types/node-11.10.5.tgz#fbaca34086bdc118011e1f05c47688d432f2d571" integrity sha1-+6yjQIa9wRgBHh8FxHaI1DLy1XE= +"@types/node@^8.0.7": + version "8.10.45" + resolved "http://registry.npm.taobao.org/@types/node/download/@types/node-8.10.45.tgz#4c49ba34106bc7dced77ff6bae8eb6543cde8351" + integrity sha1-TEm6NBBrx9ztd/9rro62VDzeg1E= + "@types/stack-utils@^1.0.1": version "1.0.1" resolved "http://registry.npm.taobao.org/@types/stack-utils/download/@types/stack-utils-1.0.1.tgz#0a851d3bd96498fa25c33ab7278ed3bd65f06c3e" @@ -926,15 +931,22 @@ acorn-walk@^6.0.1: integrity sha1-02O2b1+sXwGP+cOh57b44xDMORM= acorn@^5.5.3: - version "5.7.3" - resolved "http://registry.npm.taobao.org/acorn/download/acorn-5.7.3.tgz#67aa231bf8812974b85235a96771eb6bd07ea279" - integrity sha1-Z6ojG/iBKXS4UjWpZ3Hra9B+onk= + version "5.7.4" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-5.7.4.tgz#3e8d8a9947d0599a1796d10225d7432f4a4acf5e" + integrity sha512-1D++VG7BhrtvQpNbBzovKNc1FLGGEE/oGe7b9xJm/RFHMBeUaUGpluV9RLjZa47YFdPcDAenEYuq9pQPcMdLJg== acorn@^6.0.1: version "6.1.1" resolved "http://registry.npm.taobao.org/acorn/download/acorn-6.1.1.tgz#7d25ae05bb8ad1f9b699108e1094ecd7884adc1f" integrity sha1-fSWuBbuK0fm2mRCOEJTs14hK3B8= +agent-base@4, agent-base@^4.1.0, agent-base@^4.2.0: + version "4.2.1" + resolved "http://registry.npm.taobao.org/agent-base/download/agent-base-4.2.1.tgz#d89e5999f797875674c07d87f260fc41e83e8ca9" + integrity sha1-2J5ZmfeXh1Z0wH2H8mD8Qeg+jKk= + dependencies: + es6-promisify "^5.0.0" + ajv@^6.5.5: version "6.10.0" resolved "http://registry.npm.taobao.org/ajv/download/ajv-6.10.0.tgz#90d0d54439da587cd7e843bfb7045f50bd22bdf1" @@ -959,6 +971,11 @@ ansi-cyan@^0.1.1: dependencies: ansi-wrap "0.1.0" +ansi-escapes@^1.1.0: + version "1.4.0" + resolved "http://registry.npm.taobao.org/ansi-escapes/download/ansi-escapes-1.4.0.tgz#d3a8a83b319aa67793662b13e761c7911422306e" + integrity sha1-06ioOzGapneTZisT52HHkRQiMG4= + ansi-escapes@^3.0.0: version "3.2.0" resolved "http://registry.npm.taobao.org/ansi-escapes/download/ansi-escapes-3.2.0.tgz#8780b98ff9dbf5638152d1f1fe5c1d7b4442976b" @@ -1152,6 +1169,11 @@ assign-symbols@^1.0.0: resolved "http://registry.npm.taobao.org/assign-symbols/download/assign-symbols-1.0.0.tgz#59667f41fadd4f20ccbc2bb96b8d4f7f78ec0367" integrity sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c= +ast-types@0.x.x: + version "0.12.3" + resolved "http://registry.npm.taobao.org/ast-types/download/ast-types-0.12.3.tgz#2299c6201d34b2a749a2dd9f2de7ef5f0e84f423" + integrity sha1-IpnGIB00sqdJot2fLefvXw6E9CM= + astral-regex@^1.0.0: version "1.0.0" resolved "http://registry.npm.taobao.org/astral-regex/download/astral-regex-1.0.0.tgz#6c8c3fb827dd43ee3918f27b82782ab7658a6fd9" @@ -1390,7 +1412,7 @@ bser@^2.0.0: dependencies: node-int64 "^0.4.0" -buffer-crc32@^0.2.13: +buffer-crc32@^0.2.13, buffer-crc32@~0.2.3: version "0.2.13" resolved "http://registry.npm.taobao.org/buffer-crc32/download/buffer-crc32-0.2.13.tgz#0d333e3f00eac50aa1454abd30ef8c2a5d9a7242" integrity sha1-DTM+PwDqxQqhRUq9MO+MKl2ackI= @@ -1466,7 +1488,7 @@ caseless@~0.12.0: resolved "http://registry.npm.taobao.org/caseless/download/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc" integrity sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw= -chalk@^1.1.1: +chalk@^1.0.0, chalk@^1.1.1: version "1.1.3" resolved "http://registry.npm.taobao.org/chalk/download/chalk-1.1.3.tgz#a8115c55e4a702fe4d150abd3872822a7e09fc98" integrity sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg= @@ -1511,6 +1533,13 @@ class-utils@^0.3.5: isobject "^3.0.0" static-extend "^0.1.1" +cli-cursor@^1.0.1: + version "1.0.2" + resolved "http://registry.npm.taobao.org/cli-cursor/download/cli-cursor-1.0.2.tgz#64da3f7d56a54412e59794bd62dc35295e8f2987" + integrity sha1-ZNo/fValRBLll5S9Ytw1KV6PKYc= + dependencies: + restore-cursor "^1.0.1" + cli-cursor@^2.1.0: version "2.1.0" resolved "http://registry.npm.taobao.org/cli-cursor/download/cli-cursor-2.1.0.tgz#b35dac376479facc3e94747d41d0d0f5238ffcb5" @@ -1551,6 +1580,18 @@ code-point-at@^1.0.0: resolved "http://registry.npm.taobao.org/code-point-at/download/code-point-at-1.1.0.tgz#0d070b4d043a5bea33a2f1a40e2edb3d9a4ccf77" integrity sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c= +code-push@2.0.6: + version "2.0.6" + resolved "http://registry.npm.taobao.org/code-push/download/code-push-2.0.6.tgz#fb4e140c90fdb7fa047f3be6f53caacc8915d0b8" + integrity sha1-+04UDJD9t/oEfzvm9TyqzIkV0Lg= + dependencies: + q "^1.4.1" + recursive-fs "0.1.4" + slash "1.0.0" + superagent "^3.8.0" + superagent-proxy "^1.0.3" + yazl "^2.4.1" + collection-visit@^1.0.0: version "1.0.0" resolved "http://registry.npm.taobao.org/collection-visit/download/collection-visit-1.0.0.tgz#4bc0373c164bc3291b4d368c829cf1a80a59dca0" @@ -1608,7 +1649,7 @@ compare-versions@^3.2.1: resolved "http://registry.npm.taobao.org/compare-versions/download/compare-versions-3.4.0.tgz#e0747df5c9cb7f054d6d3dc3e1dbc444f9e92b26" integrity sha1-4HR99cnLfwVNbT3D4dvERPnpKyY= -component-emitter@^1.2.1: +component-emitter@^1.2.0, component-emitter@^1.2.1: version "1.2.1" resolved "http://registry.npm.taobao.org/component-emitter/download/component-emitter-1.2.1.tgz#137918d6d78283f7df7a6b7c5a63e140e69425e6" integrity sha1-E3kY1teCg/ffemt8WmPhQOaUJeY= @@ -1638,7 +1679,7 @@ concat-map@0.0.1: resolved "http://registry.npm.taobao.org/concat-map/download/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s= -concat-stream@^1.6.0: +concat-stream@^1.4.7, concat-stream@^1.6.0: version "1.6.2" resolved "http://registry.npm.taobao.org/concat-stream/download/concat-stream-1.6.2.tgz#904bdf194cd3122fc675c77fc4ac3d4ff0fd1a34" integrity sha1-kEvfGUzTEi/Gdcd/xKw9T/D9GjQ= @@ -1670,6 +1711,11 @@ convert-source-map@^1.1.0, convert-source-map@^1.4.0: dependencies: safe-buffer "~5.1.1" +cookiejar@^2.1.0: + version "2.1.2" + resolved "http://registry.npm.taobao.org/cookiejar/download/cookiejar-2.1.2.tgz#dd8a235530752f988f9a0844f3fc589e3111125c" + integrity sha1-3YojVTB1L5iPmghE8/xYnjERElw= + copy-descriptor@^0.1.0: version "0.1.1" resolved "http://registry.npm.taobao.org/copy-descriptor/download/copy-descriptor-0.1.1.tgz#676f6eb3c39997c2ee1ac3a924fd6124748f578d" @@ -1757,6 +1803,13 @@ dashdash@^1.12.0: dependencies: assert-plus "^1.0.0" +data-uri-to-buffer@2: + version "2.0.1" + resolved "http://registry.npm.taobao.org/data-uri-to-buffer/download/data-uri-to-buffer-2.0.1.tgz#ca8f56fe38b1fd329473e9d1b4a9afcd8ce1c045" + integrity sha1-yo9W/jix/TKUc+nRtKmvzYzhwEU= + dependencies: + "@types/node" "^8.0.7" + data-urls@^1.0.0: version "1.1.0" resolved "http://registry.npm.taobao.org/data-urls/download/data-urls-1.1.0.tgz#15ee0582baa5e22bb59c77140da8f9c76963bbfe" @@ -1773,20 +1826,27 @@ debug@2.6.9, debug@^2.1.2, debug@^2.2.0, debug@^2.3.3: dependencies: ms "2.0.0" -debug@^3.2.6: - version "3.2.6" - resolved "http://registry.npm.taobao.org/debug/download/debug-3.2.6.tgz#e83d17de16d8a7efb7717edbe5fb10135eee629b" - integrity sha1-6D0X3hbYp++3cX7b5fsQE17uYps= +debug@3.1.0: + version "3.1.0" + resolved "http://registry.npm.taobao.org/debug/download/debug-3.1.0.tgz#5bb5a0672628b64149566ba16819e61518c67261" + integrity sha1-W7WgZyYotkFJVmuhaBnmFRjGcmE= dependencies: - ms "^2.1.1" + ms "2.0.0" -debug@^4.1.0, debug@^4.1.1: +debug@4, debug@^4.1.0, debug@^4.1.1: version "4.1.1" resolved "http://registry.npm.taobao.org/debug/download/debug-4.1.1.tgz#3b72260255109c6b589cee050f1d516139664791" integrity sha1-O3ImAlUQnGtYnO4FDx1RYTlmR5E= dependencies: ms "^2.1.1" +debug@^3.1.0, debug@^3.2.6: + version "3.2.6" + resolved "http://registry.npm.taobao.org/debug/download/debug-3.2.6.tgz#e83d17de16d8a7efb7717edbe5fb10135eee629b" + integrity sha1-6D0X3hbYp++3cX7b5fsQE17uYps= + dependencies: + ms "^2.1.1" + decamelize@^1.1.1, decamelize@^1.2.0: version "1.2.0" resolved "http://registry.npm.taobao.org/decamelize/download/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290" @@ -1843,6 +1903,15 @@ define-property@^2.0.2: is-descriptor "^1.0.2" isobject "^3.0.1" +degenerator@^1.0.4: + version "1.0.4" + resolved "http://registry.npm.taobao.org/degenerator/download/degenerator-1.0.4.tgz#fcf490a37ece266464d9cc431ab98c5819ced095" + integrity sha1-/PSQo37OJmRk2cxDGrmMWBnO0JU= + dependencies: + ast-types "0.x.x" + escodegen "1.x.x" + esprima "3.x.x" + delayed-stream@~1.0.0: version "1.0.0" resolved "http://registry.npm.taobao.org/delayed-stream/download/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" @@ -2001,17 +2070,29 @@ es-to-primitive@^1.2.0: is-date-object "^1.0.1" is-symbol "^1.0.2" +es6-promise@^4.0.3: + version "4.2.6" + resolved "http://registry.npm.taobao.org/es6-promise/download/es6-promise-4.2.6.tgz#b685edd8258886365ea62b57d30de28fadcd974f" + integrity sha1-toXt2CWIhjZepitX0w3ij63Nl08= + +es6-promisify@^5.0.0: + version "5.0.0" + resolved "http://registry.npm.taobao.org/es6-promisify/download/es6-promisify-5.0.0.tgz#5109d62f3e56ea967c4b63505aef08291c8a5203" + integrity sha1-UQnWLz5W6pZ8S2NQWu8IKRyKUgM= + dependencies: + es6-promise "^4.0.3" + escape-html@~1.0.3: version "1.0.3" resolved "http://registry.npm.taobao.org/escape-html/download/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988" integrity sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg= -escape-string-regexp@^1.0.2, escape-string-regexp@^1.0.5: +escape-string-regexp@1.0.5, escape-string-regexp@^1.0.2, escape-string-regexp@^1.0.5: version "1.0.5" resolved "http://registry.npm.taobao.org/escape-string-regexp/download/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ= -escodegen@^1.9.1: +escodegen@1.x.x, escodegen@^1.9.1: version "1.11.1" resolved "http://registry.npm.taobao.org/escodegen/download/escodegen-1.11.1.tgz#c485ff8d6b4cdb89e27f4a856e91f118401ca510" integrity sha1-xIX/jWtM24nif0qFbpHxGEAcpRA= @@ -2023,7 +2104,7 @@ escodegen@^1.9.1: optionalDependencies: source-map "~0.6.1" -esprima@^3.1.3: +esprima@3.x.x, esprima@^3.1.3: version "3.1.3" resolved "http://registry.npm.taobao.org/esprima/download/esprima-3.1.3.tgz#fdca51cee6133895e3c88d535ce49dbff62a4633" integrity sha1-/cpRzuYTOJXjyI1TXOSdv/YqRjM= @@ -2101,6 +2182,11 @@ execa@^1.0.0: signal-exit "^3.0.0" strip-eof "^1.0.0" +exit-hook@^1.0.0: + version "1.1.1" + resolved "http://registry.npm.taobao.org/exit-hook/download/exit-hook-1.1.1.tgz#f05ca233b48c05d54fff07765df8507e95c02ff8" + integrity sha1-8FyiM7SMBdVP/wd2XfhQfpXAL/g= + exit@^0.1.2: version "0.1.2" resolved "http://registry.npm.taobao.org/exit/download/exit-0.1.2.tgz#0632638f8d877cc82107d30a0fff1a17cba1cd0c" @@ -2167,11 +2253,20 @@ extend-shallow@^3.0.0, extend-shallow@^3.0.2: assign-symbols "^1.0.0" is-extendable "^1.0.1" -extend@~3.0.2: +extend@^3.0.0, extend@~3.0.2: version "3.0.2" resolved "http://registry.npm.taobao.org/extend/download/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa" integrity sha1-+LETa0Bx+9jrFAr/hYsQGewpFfo= +external-editor@^1.0.1: + version "1.1.1" + resolved "http://registry.npm.taobao.org/external-editor/download/external-editor-1.1.1.tgz#12d7b0db850f7ff7e7081baf4005700060c4600b" + integrity sha1-Etew24UPf/fnCBuvQAVwAGDEYAs= + dependencies: + extend "^3.0.0" + spawn-sync "^1.0.15" + tmp "^0.0.29" + external-editor@^2.0.4: version "2.2.0" resolved "http://registry.npm.taobao.org/external-editor/download/external-editor-2.2.0.tgz#045511cfd8d133f3846673d1047c154e214ad3d5" @@ -2292,6 +2387,14 @@ fbjs@^1.0.0: setimmediate "^1.0.5" ua-parser-js "^0.7.18" +figures@^1.3.5: + version "1.7.0" + resolved "http://registry.npm.taobao.org/figures/download/figures-1.7.0.tgz#cbe1e3affcf1cd44b80cadfed28dc793a9701d2e" + integrity sha1-y+Hjr/zxzUS4DK3+0o3Hk6lwHS4= + dependencies: + escape-string-regexp "^1.0.5" + object-assign "^4.1.0" + figures@^2.0.0: version "2.0.0" resolved "http://registry.npm.taobao.org/figures/download/figures-2.0.0.tgz#3ab1a2d2a62c8bfb431a0c94cb797a2fce27c962" @@ -2299,6 +2402,11 @@ figures@^2.0.0: dependencies: escape-string-regexp "^1.0.5" +file-uri-to-path@1: + version "1.0.0" + resolved "http://registry.npm.taobao.org/file-uri-to-path/download/file-uri-to-path-1.0.0.tgz#553a7b8446ff6f684359c445f1e37a05dacc33dd" + integrity sha1-VTp7hEb/b2hDWcRF8eN6BdrMM90= + filename-regex@^2.0.0: version "2.0.1" resolved "http://registry.npm.taobao.org/filename-regex/download/filename-regex-2.0.1.tgz#c1c4b9bee3e09725ddb106b75c1e301fe2f18b26" @@ -2393,7 +2501,7 @@ forever-agent@~0.6.1: resolved "http://registry.npm.taobao.org/forever-agent/download/forever-agent-0.6.1.tgz#fbc71f0c41adeb37f96c577ad1ed42d8fdacca91" integrity sha1-+8cfDEGt6zf5bFd60e1C2P2sypE= -form-data@~2.3.2: +form-data@^2.3.1, form-data@~2.3.2: version "2.3.3" resolved "http://registry.npm.taobao.org/form-data/download/form-data-2.3.3.tgz#dcce52c05f644f298c6a7ab936bd724ceffbf3a6" integrity sha1-3M5SwF9kTymManq5Nr1yTO/786Y= @@ -2402,6 +2510,11 @@ form-data@~2.3.2: combined-stream "^1.0.6" mime-types "^2.1.12" +formidable@^1.2.0: + version "1.2.1" + resolved "http://registry.npm.taobao.org/formidable/download/formidable-1.2.1.tgz#70fb7ca0290ee6ff961090415f4b3df3d2082659" + integrity sha1-cPt8oCkO5v+WEJBBX0s989IIJlk= + fragment-cache@^0.2.1: version "0.2.1" resolved "http://registry.npm.taobao.org/fragment-cache/download/fragment-cache-0.2.1.tgz#4290fad27f13e89be7f33799c6bc5a0abfff0d19" @@ -2443,6 +2556,14 @@ fsevents@^1.2.3: nan "^2.9.2" node-pre-gyp "^0.10.0" +ftp@~0.3.10: + version "0.3.10" + resolved "http://registry.npm.taobao.org/ftp/download/ftp-0.3.10.tgz#9197d861ad8142f3e63d5a83bfe4c59f7330885d" + integrity sha1-kZfYYa2BQvPmPVqDv+TFn3MwiF0= + dependencies: + readable-stream "1.1.x" + xregexp "2.0.0" + function-bind@^1.1.1: version "1.1.1" resolved "http://registry.npm.taobao.org/function-bind/download/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d" @@ -2490,6 +2611,18 @@ get-stream@^4.0.0: dependencies: pump "^3.0.0" +get-uri@^2.0.0: + version "2.0.3" + resolved "http://registry.npm.taobao.org/get-uri/download/get-uri-2.0.3.tgz#fa13352269781d75162c6fc813c9e905323fbab5" + integrity sha1-+hM1Iml4HXUWLG/IE8npBTI/urU= + dependencies: + data-uri-to-buffer "2" + debug "4" + extend "~3.0.2" + file-uri-to-path "1" + ftp "~0.3.10" + readable-stream "3" + get-value@^2.0.3, get-value@^2.0.6: version "2.0.6" resolved "http://registry.npm.taobao.org/get-value/download/get-value-2.0.6.tgz#dc15ca1c672387ca76bd37ac0a395ba2042a2c28" @@ -2517,6 +2650,17 @@ glob-parent@^2.0.0: dependencies: is-glob "^2.0.0" +glob@^5.0.15: + version "5.0.15" + resolved "http://registry.npm.taobao.org/glob/download/glob-5.0.15.tgz#1bc936b9e02f4a603fcc222ecf7633d30b8b93b1" + integrity sha1-G8k2ueAvSmA/zCIuz3Yz0wuLk7E= + dependencies: + inflight "^1.0.4" + inherits "2" + minimatch "2 || 3" + once "^1.3.0" + path-is-absolute "^1.0.0" + glob@^7.0.3, glob@^7.1.1, glob@^7.1.2, glob@^7.1.3: version "7.1.3" resolved "http://registry.npm.taobao.org/glob/download/glob-7.1.3.tgz#3960832d3f1574108342dafd3a67b332c0969df1" @@ -2683,7 +2827,7 @@ htmlparser2-without-node-native@^3.9.2: inherits "^2.0.1" readable-stream "^2.0.2" -http-errors@~1.6.2: +http-errors@1.6.3, http-errors@~1.6.2: version "1.6.3" resolved "http://registry.npm.taobao.org/http-errors/download/http-errors-1.6.3.tgz#8b55680bb4be283a0b5bf4ea2e38580be1d9320d" integrity sha1-i1VoC7S+KDoLW/TqLjhYC+HZMg0= @@ -2693,6 +2837,14 @@ http-errors@~1.6.2: setprototypeof "1.1.0" statuses ">= 1.4.0 < 2" +http-proxy-agent@^2.1.0: + version "2.1.0" + resolved "http://registry.npm.taobao.org/http-proxy-agent/download/http-proxy-agent-2.1.0.tgz#e4821beef5b2142a2026bd73926fe537631c5405" + integrity sha1-5IIb7vWyFCogJr1zkm/lN2McVAU= + dependencies: + agent-base "4" + debug "3.1.0" + http-signature@~1.2.0: version "1.2.0" resolved "http://registry.npm.taobao.org/http-signature/download/http-signature-1.2.0.tgz#9aecd925114772f3d95b65a60abb8f7c18fbace1" @@ -2702,6 +2854,21 @@ http-signature@~1.2.0: jsprim "^1.2.2" sshpk "^1.7.0" +https-proxy-agent@^2.2.1: + version "2.2.1" + resolved "http://registry.npm.taobao.org/https-proxy-agent/download/https-proxy-agent-2.2.1.tgz#51552970fa04d723e04c56d04178c3f92592bbc0" + integrity sha1-UVUpcPoE1yPgTFbQQXjD+SWSu8A= + dependencies: + agent-base "^4.1.0" + debug "^3.1.0" + +iconv-lite@0.4.23: + version "0.4.23" + resolved "http://registry.npm.taobao.org/iconv-lite/download/iconv-lite-0.4.23.tgz#297871f63be507adcfbfca715d0cd0eed84e9a63" + integrity sha1-KXhx9jvlB63Pv8pxXQzQ7thOmmM= + dependencies: + safer-buffer ">= 2.1.2 < 3" + iconv-lite@0.4.24, iconv-lite@^0.4.17, iconv-lite@^0.4.4, iconv-lite@~0.4.13: version "0.4.24" resolved "http://registry.npm.taobao.org/iconv-lite/download/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" @@ -2750,7 +2917,7 @@ inflight@^1.0.4: once "^1.3.0" wrappy "1" -inherits@2, inherits@2.0.3, inherits@^2.0.1, inherits@^2.0.3, inherits@~2.0.3: +inherits@2, inherits@2.0.3, inherits@^2.0.1, inherits@^2.0.3, inherits@~2.0.1, inherits@~2.0.3: version "2.0.3" resolved "http://registry.npm.taobao.org/inherits/download/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" integrity sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4= @@ -2760,6 +2927,26 @@ ini@~1.3.0: resolved "http://registry.npm.taobao.org/ini/download/ini-1.3.5.tgz#eee25f56db1c9ec6085e0c22778083f596abf927" integrity sha1-7uJfVtscnsYIXgwid4CD9Zar+Sc= +inquirer@1.1.2: + version "1.1.2" + resolved "http://registry.npm.taobao.org/inquirer/download/inquirer-1.1.2.tgz#ac3ba5f06b8e7291abd9f22912c03f09cfe2dd1f" + integrity sha1-rDul8GuOcpGr2fIpEsA/Cc/i3R8= + dependencies: + ansi-escapes "^1.1.0" + chalk "^1.0.0" + cli-cursor "^1.0.1" + cli-width "^2.0.0" + external-editor "^1.0.1" + figures "^1.3.5" + lodash "^4.3.0" + mute-stream "0.0.6" + pinkie-promise "^2.0.0" + run-async "^2.2.0" + rx "^4.1.0" + string-width "^1.0.1" + strip-ansi "^3.0.0" + through "^2.3.6" + inquirer@^3.0.6: version "3.3.0" resolved "http://registry.npm.taobao.org/inquirer/download/inquirer-3.3.0.tgz#9dd2f2ad765dcab1ff0443b491442a20ba227dc9" @@ -2780,7 +2967,7 @@ inquirer@^3.0.6: strip-ansi "^4.0.0" through "^2.3.6" -invariant@^2.2.2, invariant@^2.2.4: +invariant@2.2.4, invariant@^2.2.2, invariant@^2.2.4: version "2.2.4" resolved "http://registry.npm.taobao.org/invariant/download/invariant-2.2.4.tgz#610f3c92c9359ce1db616e538008d23ff35158e6" integrity sha1-YQ88ksk1nOHbYW5TgAjSP/NRWOY= @@ -2797,6 +2984,11 @@ invert-kv@^2.0.0: resolved "http://registry.npm.taobao.org/invert-kv/download/invert-kv-2.0.0.tgz#7393f5afa59ec9ff5f67a27620d11c226e3eec02" integrity sha1-c5P1r6Weyf9fZ6J2INEcIm4+7AI= +ip@^1.1.4, ip@^1.1.5: + version "1.1.5" + resolved "http://registry.npm.taobao.org/ip/download/ip-1.1.5.tgz#bdded70114290828c0a039e72ef25f5aaec4354a" + integrity sha1-vd7XARQpCCjAoDnnLvJfWq7ENUo= + is-accessor-descriptor@^0.1.6: version "0.1.6" resolved "http://registry.npm.taobao.org/is-accessor-descriptor/download/is-accessor-descriptor-0.1.6.tgz#a9e12cb3ae8d876727eeef3843f8a0897b5c98d6" @@ -3750,7 +3942,7 @@ loose-envify@^1.0.0, loose-envify@^1.1.0, loose-envify@^1.3.1, loose-envify@^1.4 dependencies: js-tokens "^3.0.0 || ^4.0.0" -lru-cache@^4.0.1: +lru-cache@^4.0.1, lru-cache@^4.1.2: version "4.1.5" resolved "http://registry.npm.taobao.org/lru-cache/download/lru-cache-4.1.5.tgz#8bbe50ea85bed59bc9e33dcab8235ee9bcf443cd" integrity sha1-i75Q6oW+1ZvJ4z3KuCNe6bz0Q80= @@ -3824,6 +4016,11 @@ merge@^1.2.0: resolved "http://registry.npm.taobao.org/merge/download/merge-1.2.1.tgz#38bebf80c3220a8a487b6fcfb3941bb11720c145" integrity sha1-OL6/gMMiCopIe2/Ps5QbsRcgwUU= +methods@^1.1.1: + version "1.1.2" + resolved "http://registry.npm.taobao.org/methods/download/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee" + integrity sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4= + metro-babel-register@^0.49.1: version "0.49.2" resolved "http://registry.npm.taobao.org/metro-babel-register/download/metro-babel-register-0.49.2.tgz#746c73311135bd6c2af4d83c2cc6c5cbcf0e8a65" @@ -4118,7 +4315,7 @@ mime@1.4.1: resolved "http://registry.npm.taobao.org/mime/download/mime-1.4.1.tgz#121f9ebc49e3766f311a76e1fa1c8003c4b03aa6" integrity sha1-Eh+evEnjdm8xGnbh+hyAA8SwOqY= -mime@^1.3.4: +mime@^1.3.4, mime@^1.4.1: version "1.6.0" resolved "http://registry.npm.taobao.org/mime/download/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1" integrity sha1-Ms2eXGRVO9WNGaVor0Uqz/BJgbE= @@ -4135,7 +4332,7 @@ min-document@^2.19.0: dependencies: dom-walk "^0.1.0" -minimatch@^3.0.3, minimatch@^3.0.4: +"minimatch@2 || 3", minimatch@^3.0.3, minimatch@^3.0.4: version "3.0.4" resolved "http://registry.npm.taobao.org/minimatch/download/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" integrity sha1-UWbihkV/AzBgZL5Ul+jbsMPTIIM= @@ -4208,6 +4405,11 @@ ms@^2.1.1: resolved "http://registry.npm.taobao.org/ms/download/ms-2.1.1.tgz#30a5864eb3ebb0a66f2ebe6d727af06a09d86e0a" integrity sha1-MKWGTrPrsKZvLr5tcnrwagnYbgo= +mute-stream@0.0.6: + version "0.0.6" + resolved "http://registry.npm.taobao.org/mute-stream/download/mute-stream-0.0.6.tgz#48962b19e169fd1dfc240b3f1e7317627bbc47db" + integrity sha1-SJYrGeFp/R38JAs/HnMXYnu8R9s= + mute-stream@0.0.7: version "0.0.7" resolved "http://registry.npm.taobao.org/mute-stream/download/mute-stream-0.0.7.tgz#3075ce93bc21b8fab43e1bc4da7e8115ed1e7bab" @@ -4254,6 +4456,11 @@ negotiator@0.6.1: resolved "http://registry.npm.taobao.org/negotiator/download/negotiator-0.6.1.tgz#2b327184e8992101177b28563fb5e7102acd0ca9" integrity sha1-KzJxhOiZIQEXeyhWP7XnECrNDKk= +netmask@^1.0.6: + version "1.0.6" + resolved "http://registry.npm.taobao.org/netmask/download/netmask-1.0.6.tgz#20297e89d86f6f6400f250d9f4f6b4c1945fcd35" + integrity sha1-ICl+idhvb2QA8lDZ9Pa0wZRfzTU= + nice-try@^1.0.4: version "1.0.5" resolved "http://registry.npm.taobao.org/nice-try/download/nice-try-1.0.5.tgz#a3378a7696ce7d223e88fc9b764bd7ef1089e366" @@ -4461,6 +4668,11 @@ once@^1.3.0, once@^1.3.1, once@^1.4.0: dependencies: wrappy "1" +onetime@^1.0.0: + version "1.1.0" + resolved "http://registry.npm.taobao.org/onetime/download/onetime-1.1.0.tgz#a1f7838f8314c516f05ecefcbc4ccfe04b4ed789" + integrity sha1-ofeDj4MUxRbwXs78vEzP4EtO14k= + onetime@^2.0.0: version "2.0.1" resolved "http://registry.npm.taobao.org/onetime/download/onetime-2.0.1.tgz#067428230fd67443b2794b22bba528b6867962d4" @@ -4523,7 +4735,12 @@ os-locale@^3.0.0: lcid "^2.0.0" mem "^4.0.0" -os-tmpdir@^1.0.0, os-tmpdir@~1.0.2: +os-shim@^0.1.2: + version "0.1.3" + resolved "http://registry.npm.taobao.org/os-shim/download/os-shim-0.1.3.tgz#6b62c3791cf7909ea35ed46e17658bb417cb3917" + integrity sha1-a2LDeRz3kJ6jXtRuF2WLtBfLORc= + +os-tmpdir@^1.0.0, os-tmpdir@~1.0.1, os-tmpdir@~1.0.2: version "1.0.2" resolved "http://registry.npm.taobao.org/os-tmpdir/download/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274" integrity sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ= @@ -4601,6 +4818,31 @@ p-try@^2.0.0: resolved "http://registry.npm.taobao.org/p-try/download/p-try-2.0.0.tgz#85080bb87c64688fa47996fe8f7dfbe8211760b1" integrity sha1-hQgLuHxkaI+keZb+j3376CEXYLE= +pac-proxy-agent@^2.0.1: + version "2.0.2" + resolved "http://registry.npm.taobao.org/pac-proxy-agent/download/pac-proxy-agent-2.0.2.tgz#90d9f6730ab0f4d2607dcdcd4d3d641aa26c3896" + integrity sha1-kNn2cwqw9NJgfc3NTT1kGqJsOJY= + dependencies: + agent-base "^4.2.0" + debug "^3.1.0" + get-uri "^2.0.0" + http-proxy-agent "^2.1.0" + https-proxy-agent "^2.2.1" + pac-resolver "^3.0.0" + raw-body "^2.2.0" + socks-proxy-agent "^3.0.0" + +pac-resolver@^3.0.0: + version "3.0.0" + resolved "http://registry.npm.taobao.org/pac-resolver/download/pac-resolver-3.0.0.tgz#6aea30787db0a891704deb7800a722a7615a6f26" + integrity sha1-auoweH2wqJFwTet4AKcip2FabyY= + dependencies: + co "^4.6.0" + degenerator "^1.0.4" + ip "^1.1.5" + netmask "^1.0.6" + thunkify "^2.1.2" + parse-glob@^3.0.4: version "3.0.4" resolved "http://registry.npm.taobao.org/parse-glob/download/parse-glob-3.0.4.tgz#b2c376cfb11f35513badd173ef0bb6e3a388391c" @@ -4687,6 +4929,11 @@ path-type@^3.0.0: dependencies: pify "^3.0.0" +pegjs@^0.10.0: + version "0.10.0" + resolved "http://registry.npm.taobao.org/pegjs/download/pegjs-0.10.0.tgz#cf8bafae6eddff4b5a7efb185269eaaf4610ddbd" + integrity sha1-z4uvrm7d/0tafvsYUmnqr0YQ3b0= + performance-now@^2.1.0: version "2.1.0" resolved "http://registry.npm.taobao.org/performance-now/download/performance-now-2.1.0.tgz#6309f4e0e5fa913ec1c69307ae364b4b377c9e7b" @@ -4702,6 +4949,18 @@ pify@^3.0.0: resolved "http://registry.npm.taobao.org/pify/download/pify-3.0.0.tgz#e5a4acd2c101fdf3d9a4d07f0dbc4db49dd28176" integrity sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY= +pinkie-promise@^2.0.0: + version "2.0.1" + resolved "http://registry.npm.taobao.org/pinkie-promise/download/pinkie-promise-2.0.1.tgz#2135d6dfa7a358c069ac9b178776288228450ffa" + integrity sha1-ITXW36ejWMBprJsXh3YogihFD/o= + dependencies: + pinkie "^2.0.0" + +pinkie@^2.0.0: + version "2.0.4" + resolved "http://registry.npm.taobao.org/pinkie/download/pinkie-2.0.4.tgz#72556b80cfa0d48a974e80e77248e80ed4f7f870" + integrity sha1-clVrgM+g1IqXToDnckjoDtT3+HA= + pirates@^4.0.0, pirates@^4.0.1: version "4.0.1" resolved "http://registry.npm.taobao.org/pirates/download/pirates-4.0.1.tgz#643a92caf894566f91b2b986d2c66950a8e2fb87" @@ -4732,7 +4991,7 @@ plist@2.0.1: xmlbuilder "8.2.2" xmldom "0.1.x" -plist@^3.0.0: +plist@3.0.1, plist@^3.0.0: version "3.0.1" resolved "http://registry.npm.taobao.org/plist/download/plist-3.0.1.tgz#a9b931d17c304e8912ef0ba3bdd6182baf2e1f8c" integrity sha1-qbkx0XwwTokS7wujvdYYK68uH4w= @@ -4819,7 +5078,7 @@ prompts@^2.0.1: kleur "^3.0.2" sisteransi "^1.0.0" -prop-types@^15.5.10, prop-types@^15.5.8, prop-types@^15.6.1, prop-types@^15.6.2, prop-types@^15.7.2: +prop-types@^15.5.10, prop-types@^15.5.7, prop-types@^15.5.8, prop-types@^15.6.0, prop-types@^15.6.1, prop-types@^15.6.2, prop-types@^15.7.2: version "15.7.2" resolved "http://registry.npm.taobao.org/prop-types/download/prop-types-15.7.2.tgz#52c41e75b8c87e72b9d9360e0206b99dcbffa6c5" integrity sha1-UsQedbjIfnK52TYOAga5ncv/psU= @@ -4828,6 +5087,25 @@ prop-types@^15.5.10, prop-types@^15.5.8, prop-types@^15.6.1, prop-types@^15.6.2, object-assign "^4.1.1" react-is "^16.8.1" +proxy-agent@2: + version "2.3.1" + resolved "http://registry.npm.taobao.org/proxy-agent/download/proxy-agent-2.3.1.tgz#3d49d863d46cf5f37ca8394848346ea02373eac6" + integrity sha1-PUnYY9Rs9fN8qDlISDRuoCNz6sY= + dependencies: + agent-base "^4.2.0" + debug "^3.1.0" + http-proxy-agent "^2.1.0" + https-proxy-agent "^2.2.1" + lru-cache "^4.1.2" + pac-proxy-agent "^2.0.1" + proxy-from-env "^1.0.0" + socks-proxy-agent "^3.0.0" + +proxy-from-env@^1.0.0: + version "1.0.0" + resolved "http://registry.npm.taobao.org/proxy-from-env/download/proxy-from-env-1.0.0.tgz#33c50398f70ea7eb96d21f7b817630a55791c7ee" + integrity sha1-M8UDmPcOp+uW0h97gXYwpVeRx+4= + pseudomap@^1.0.2: version "1.0.2" resolved "http://registry.npm.taobao.org/pseudomap/download/pseudomap-1.0.2.tgz#f052a28da70e618917ef0a8ac34c1ae5a68286b3" @@ -4856,6 +5134,16 @@ punycode@^2.1.0, punycode@^2.1.1: resolved "http://registry.npm.taobao.org/punycode/download/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec" integrity sha1-tYsBCsQMIsVldhbI0sLALHv0eew= +q@^1.4.1: + version "1.5.1" + resolved "http://registry.npm.taobao.org/q/download/q-1.5.1.tgz#7e32f75b41381291d04611f1bf14109ac00651d7" + integrity sha1-fjL3W0E4EpHQRhHxvxQQmsAGUdc= + +qs@^6.5.1: + version "6.7.0" + resolved "http://registry.npm.taobao.org/qs/download/qs-6.7.0.tgz#41dc1a015e3d581f1621776be31afb2876a9b1bc" + integrity sha1-QdwaAV49WB8WIXdr4xr7KHapsbw= + qs@~6.5.2: version "6.5.2" resolved "http://registry.npm.taobao.org/qs/download/qs-6.5.2.tgz#cb3ae806e8740444584ef154ce8ee98d403f3e36" @@ -4883,6 +5171,16 @@ range-parser@~1.2.0: resolved "http://registry.npm.taobao.org/range-parser/download/range-parser-1.2.0.tgz#f49be6b487894ddc40dcc94a322f611092e00d5e" integrity sha1-9JvmtIeJTdxA3MlKMi9hEJLgDV4= +raw-body@^2.2.0: + version "2.3.3" + resolved "http://registry.npm.taobao.org/raw-body/download/raw-body-2.3.3.tgz#1b324ece6b5706e153855bc1148c65bb7f6ea0c3" + integrity sha1-GzJOzmtXBuFThVvBFIxlu39uoMM= + dependencies: + bytes "3.0.0" + http-errors "1.6.3" + iconv-lite "0.4.23" + unpipe "1.0.0" + rc@^1.2.7: version "1.2.8" resolved "http://registry.npm.taobao.org/rc/download/rc-1.2.8.tgz#cd924bf5200a075b83c188cd6b9e211b7fc0d3ed" @@ -4921,6 +5219,26 @@ react-lifecycles-compat@^3.0.4: resolved "http://registry.npm.taobao.org/react-lifecycles-compat/download/react-lifecycles-compat-3.0.4.tgz#4f1a273afdfc8f3488a8c516bfda78f872352362" integrity sha1-TxonOv38jzSIqMUWv9p4+HI1I2I= +react-native-check-box@^2.1.7: + version "2.1.7" + resolved "http://registry.npm.taobao.org/react-native-check-box/download/react-native-check-box-2.1.7.tgz#f349d3505a13c15d19949ec4cec40c7adc4f3626" + integrity sha1-80nTUFoTwV0ZlJ7EzsQMetxPNiY= + dependencies: + prop-types "^15.5.7" + +react-native-code-push@^5.6.0: + version "5.6.0" + resolved "http://registry.npm.taobao.org/react-native-code-push/download/react-native-code-push-5.6.0.tgz#e905d32d188c5f7bcff446d813271d03a69543fd" + integrity sha1-6QXTLRiMX3vP9EbYEycdA6aVQ/0= + dependencies: + code-push "2.0.6" + glob "^5.0.15" + hoist-non-react-statics "^2.3.1" + inquirer "1.1.2" + plist "3.0.1" + semver "^5.6.0" + xcode "1.0.0" + react-native-easy-toast@^1.2.0: version "1.2.0" resolved "http://registry.npm.taobao.org/react-native-easy-toast/download/react-native-easy-toast-1.2.0.tgz#0f70bcb99e3306cda4800c244bfb4a67d42276ed" @@ -4928,6 +5246,13 @@ react-native-easy-toast@^1.2.0: dependencies: prop-types "^15.5.10" +react-native-event-bus@^1.0.0: + version "1.0.0" + resolved "http://registry.npm.taobao.org/react-native-event-bus/download/react-native-event-bus-1.0.0.tgz#e193b17bb8639b623f1d72e8596e0cd8635b59d1" + integrity sha1-4ZOxe7hjm2I/HXLoWW4M2GNbWdE= + dependencies: + prop-types "^15.5.7" + react-native-gesture-handler@^1.1.0: version "1.1.0" resolved "http://registry.npm.taobao.org/react-native-gesture-handler/download/react-native-gesture-handler-1.1.0.tgz#2a7d545ad2e0ca23adce22b2af441ad360ecccee" @@ -4954,6 +5279,13 @@ react-native-htmlview@^0.13.0: entities "^1.1.1" htmlparser2-without-node-native "^3.9.2" +react-native-parallax-scroll-view@^0.21.3: + version "0.21.3" + resolved "http://registry.npm.taobao.org/react-native-parallax-scroll-view/download/react-native-parallax-scroll-view-0.21.3.tgz#d956ad224ce3b0a2725b80574bcbf688bbeda14e" + integrity sha1-2VatIkzjsKJyW4BXS8v2iLvtoU4= + dependencies: + prop-types "^15.6.0" + react-native-safe-area-view@^0.13.0: version "0.13.1" resolved "http://registry.npm.taobao.org/react-native-safe-area-view/download/react-native-safe-area-view-0.13.1.tgz#834bbb6d22f76a7ff07de56725ee5667ba1386b0" @@ -4966,6 +5298,16 @@ react-native-safe-area-view@^0.13.0: resolved "http://registry.npm.taobao.org/react-native-screens/download/react-native-screens-1.0.0-alpha.22.tgz#7a120377b52aa9bbb94d0b8541a014026be9289b" integrity sha1-ehIDd7Uqqbu5TQuFQaAUAmvpKJs= +react-native-sortable-listview@^0.2.8: + version "0.2.8" + resolved "http://registry.npm.taobao.org/react-native-sortable-listview/download/react-native-sortable-listview-0.2.8.tgz#0d514fa73eb406a46baea96aa38a71e40257af90" + integrity sha1-DVFPpz60BqRrrqlqo4px5AJXr5A= + +react-native-splash-screen@^3.2.0: + version "3.2.0" + resolved "http://registry.npm.taobao.org/react-native-splash-screen/download/react-native-splash-screen-3.2.0.tgz#d47ec8557b1ba988ee3ea98d01463081b60fff45" + integrity sha1-1H7IVXsbqYjuPqmNAUYwgbYP/0U= + react-native-tab-view@^1.0.0, react-native-tab-view@^1.2.0: version "1.3.2" resolved "http://registry.npm.taobao.org/react-native-tab-view/download/react-native-tab-view-1.3.2.tgz#c4e43a538dcacce151938745cea09176beeccbc3" @@ -4982,6 +5324,14 @@ react-native-vector-icons@^6.3.0: prop-types "^15.6.2" yargs "^8.0.2" +react-native-webview@^5.6.2: + version "5.6.2" + resolved "http://registry.npm.taobao.org/react-native-webview/download/react-native-webview-5.6.2.tgz#aa257f01d0038c514874d1c41d99528f83274d8c" + integrity sha1-qiV/AdADjFFIdNHEHZlSj4MnTYw= + dependencies: + escape-string-regexp "1.0.5" + invariant "2.2.4" + react-native@0.58.6: version "0.58.6" resolved "http://registry.npm.taobao.org/react-native/download/react-native-0.58.6.tgz#a6d273cd2f51b0e3b141dad5f19622e0000497b3" @@ -5163,7 +5513,26 @@ read-pkg@^3.0.0: normalize-package-data "^2.3.2" path-type "^3.0.0" -readable-stream@^2.0.1, readable-stream@^2.0.2, readable-stream@^2.0.6, readable-stream@^2.2.2, readable-stream@~2.3.6: +readable-stream@1.1.x: + version "1.1.14" + resolved "http://registry.npm.taobao.org/readable-stream/download/readable-stream-1.1.14.tgz#7cf4c54ef648e3813084c636dd2079e166c081d9" + integrity sha1-fPTFTvZI44EwhMY23SB54WbAgdk= + dependencies: + core-util-is "~1.0.0" + inherits "~2.0.1" + isarray "0.0.1" + string_decoder "~0.10.x" + +readable-stream@3: + version "3.3.0" + resolved "http://registry.npm.taobao.org/readable-stream/download/readable-stream-3.3.0.tgz#cb8011aad002eb717bf040291feba8569c986fb9" + integrity sha1-y4ARqtAC63F78EApH+uoVpyYb7k= + dependencies: + inherits "^2.0.3" + string_decoder "^1.1.1" + util-deprecate "^1.0.1" + +readable-stream@^2.0.1, readable-stream@^2.0.2, readable-stream@^2.0.6, readable-stream@^2.2.2, readable-stream@^2.3.5, readable-stream@~2.3.6: version "2.3.6" resolved "http://registry.npm.taobao.org/readable-stream/download/readable-stream-2.3.6.tgz#b11c27d88b8ff1fbe070643cf94b0c79ae1b0aaf" integrity sha1-sRwn2IuP8fvgcGQ8+UsMea4bCq8= @@ -5183,6 +5552,11 @@ realpath-native@^1.1.0: dependencies: util.promisify "^1.0.0" +recursive-fs@0.1.4: + version "0.1.4" + resolved "http://registry.npm.taobao.org/recursive-fs/download/recursive-fs-0.1.4.tgz#47e08b1ddab8d7d9a960aa0d0daea76f875b63fa" + integrity sha1-R+CLHdq419mpYKoNDa6nb4dbY/o= + redux-thunk@^2.3.0: version "2.3.0" resolved "http://registry.npm.taobao.org/redux-thunk/download/redux-thunk-2.3.0.tgz#51c2c19a185ed5187aaa9a2d08b666d0d6467622" @@ -5360,6 +5734,14 @@ resolve@^1.10.0, resolve@^1.3.2, resolve@^1.5.0, resolve@^1.8.1: dependencies: path-parse "^1.0.6" +restore-cursor@^1.0.1: + version "1.0.1" + resolved "http://registry.npm.taobao.org/restore-cursor/download/restore-cursor-1.0.1.tgz#34661f46886327fed2991479152252df92daa541" + integrity sha1-NGYfRohjJ/7SmRR5FSJS35LapUE= + dependencies: + exit-hook "^1.0.0" + onetime "^1.0.0" + restore-cursor@^2.0.0: version "2.0.0" resolved "http://registry.npm.taobao.org/restore-cursor/download/restore-cursor-2.0.0.tgz#9f7ee287f82fd326d4fd162923d62129eee0dfaf" @@ -5409,6 +5791,11 @@ rx-lite@*, rx-lite@^4.0.8: resolved "http://registry.npm.taobao.org/rx-lite/download/rx-lite-4.0.8.tgz#0b1e11af8bc44836f04a6407e92da42467b79444" integrity sha1-Cx4Rr4vESDbwSmQH6S2kJGe3lEQ= +rx@^4.1.0: + version "4.1.0" + resolved "http://registry.npm.taobao.org/rx/download/rx-4.1.0.tgz#a5f13ff79ef3b740fe30aa803fb09f98805d4782" + integrity sha1-pfE/957zt0D+MKqAP7CfmIBdR4I= + safe-buffer@5.1.2, safe-buffer@^5.0.1, safe-buffer@^5.1.2, safe-buffer@~5.1.0, safe-buffer@~5.1.1: version "5.1.2" resolved "http://registry.npm.taobao.org/safe-buffer/download/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" @@ -5481,6 +5868,11 @@ scheduler@^0.11.2: resolved "http://registry.npm.taobao.org/semver/download/semver-5.6.0.tgz#7e74256fbaa49c75aa7c7a205cc22799cac80004" integrity sha1-fnQlb7qknHWqfHogXMInmcrIAAQ= +semver@^5.6.0: + version "5.7.0" + resolved "http://registry.npm.taobao.org/semver/download/semver-5.7.0.tgz?cache=0&other_urls=http%3A%2F%2Fregistry.npm.taobao.org%2Fsemver%2Fdownload%2Fsemver-5.7.0.tgz#790a7cf6fea5459bac96110b29b60412dc8ff96b" + integrity sha1-eQp89v6lRZuslhELKbYEEtyP+Ws= + send@0.16.2: version "0.16.2" resolved "http://registry.npm.taobao.org/send/download/send-0.16.2.tgz#6ecca1e0f8c156d141597559848df64730a6bbc1" @@ -5596,6 +5988,11 @@ sisteransi@^1.0.0: resolved "http://registry.npm.taobao.org/sisteransi/download/sisteransi-1.0.0.tgz#77d9622ff909080f1c19e5f4a1df0c1b0a27b88c" integrity sha1-d9liL/kJCA8cGeX0od8MGwonuIw= +slash@1.0.0: + version "1.0.0" + resolved "http://registry.npm.taobao.org/slash/download/slash-1.0.0.tgz#c41f2f6c39fc16d1cd17ad4b5d896114ae470d55" + integrity sha1-xB8vbDn8FtHNF61LXYlhFK5HDVU= + slash@^2.0.0: version "2.0.0" resolved "http://registry.npm.taobao.org/slash/download/slash-2.0.0.tgz#de552851a1759df3a8f206535442f5ec4ddeab44" @@ -5606,6 +6003,11 @@ slide@^1.1.5: resolved "http://registry.npm.taobao.org/slide/download/slide-1.1.6.tgz#56eb027d65b4d2dce6cb2e2d32c4d4afc9e1d707" integrity sha1-VusCfWW00tzmyy4tMsTUr8nh1wc= +smart-buffer@^1.0.13: + version "1.1.15" + resolved "http://registry.npm.taobao.org/smart-buffer/download/smart-buffer-1.1.15.tgz#7f114b5b65fab3e2a35aa775bb12f0d1c649bf16" + integrity sha1-fxFLW2X6s+KjWqd1uxLw0cZJvxY= + snapdragon-node@^2.0.1: version "2.1.1" resolved "http://registry.npm.taobao.org/snapdragon-node/download/snapdragon-node-2.1.1.tgz#6c175f86ff14bdb0724563e8f3c1b021a286853b" @@ -5636,6 +6038,22 @@ snapdragon@^0.8.1: source-map-resolve "^0.5.0" use "^3.1.0" +socks-proxy-agent@^3.0.0: + version "3.0.1" + resolved "http://registry.npm.taobao.org/socks-proxy-agent/download/socks-proxy-agent-3.0.1.tgz#2eae7cf8e2a82d34565761539a7f9718c5617659" + integrity sha1-Lq58+OKoLTRWV2FTmn+XGMVhdlk= + dependencies: + agent-base "^4.1.0" + socks "^1.1.10" + +socks@^1.1.10: + version "1.1.10" + resolved "http://registry.npm.taobao.org/socks/download/socks-1.1.10.tgz#5b8b7fc7c8f341c53ed056e929b7bf4de8ba7b5a" + integrity sha1-W4t/x8jzQcU+0FbpKbe/Tei6e1o= + dependencies: + ip "^1.1.4" + smart-buffer "^1.0.13" + source-map-resolve@^0.5.0: version "0.5.2" resolved "http://registry.npm.taobao.org/source-map-resolve/download/source-map-resolve-0.5.2.tgz#72e2cc34095543e43b2c62b2c4c10d4a9054f259" @@ -5670,6 +6088,14 @@ source-map@^0.6.0, source-map@^0.6.1, source-map@~0.6.1: resolved "http://registry.npm.taobao.org/source-map/download/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" integrity sha1-dHIq8y6WFOnCh6jQu95IteLxomM= +spawn-sync@^1.0.15: + version "1.0.15" + resolved "http://registry.npm.taobao.org/spawn-sync/download/spawn-sync-1.0.15.tgz#b00799557eb7fb0c8376c29d44e8a1ea67e57476" + integrity sha1-sAeZVX63+wyDdsKdROih6mfldHY= + dependencies: + concat-stream "^1.4.7" + os-shim "^0.1.2" + spdx-correct@^3.0.0: version "3.1.0" resolved "http://registry.npm.taobao.org/spdx-correct/download/spdx-correct-3.1.0.tgz#fb83e504445268f154b074e218c87c003cd31df4" @@ -5796,6 +6222,18 @@ string-width@^1.0.1: is-fullwidth-code-point "^2.0.0" strip-ansi "^4.0.0" +string_decoder@^1.1.1: + version "1.2.0" + resolved "http://registry.npm.taobao.org/string_decoder/download/string_decoder-1.2.0.tgz#fe86e738b19544afe70469243b2a1ee9240eae8d" + integrity sha1-/obnOLGVRK/nBGkkOyoe6SQOro0= + dependencies: + safe-buffer "~5.1.0" + +string_decoder@~0.10.x: + version "0.10.31" + resolved "http://registry.npm.taobao.org/string_decoder/download/string_decoder-0.10.31.tgz#62e203bc41766c6c28c9fc84301dab1c5310fa94" + integrity sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ= + string_decoder@~1.1.1: version "1.1.1" resolved "http://registry.npm.taobao.org/string_decoder/download/string_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8" @@ -5839,6 +6277,30 @@ strip-json-comments@~2.0.1: resolved "http://registry.npm.taobao.org/strip-json-comments/download/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a" integrity sha1-PFMZQukIwml8DsNEhYwobHygpgo= +superagent-proxy@^1.0.3: + version "1.0.3" + resolved "http://registry.npm.taobao.org/superagent-proxy/download/superagent-proxy-1.0.3.tgz#acfa776672f11c24a90ad575e855def8be44f741" + integrity sha1-rPp3ZnLxHCSpCtV16FXe+L5E90E= + dependencies: + debug "^3.1.0" + proxy-agent "2" + +superagent@^3.8.0: + version "3.8.3" + resolved "http://registry.npm.taobao.org/superagent/download/superagent-3.8.3.tgz#460ea0dbdb7d5b11bc4f78deba565f86a178e128" + integrity sha1-Rg6g29t9WxG8T3jeulZfhqF44Sg= + dependencies: + component-emitter "^1.2.0" + cookiejar "^2.1.0" + debug "^3.1.0" + extend "^3.0.0" + form-data "^2.3.1" + formidable "^1.2.0" + methods "^1.1.1" + mime "^1.4.1" + qs "^6.5.1" + readable-stream "^2.3.5" + supports-color@^2.0.0: version "2.0.0" resolved "http://registry.npm.taobao.org/supports-color/download/supports-color-2.0.0.tgz#535d045ce6b6363fa40117084629995e9df324c7" @@ -5917,11 +6379,23 @@ through@^2.3.6: resolved "http://registry.npm.taobao.org/through/download/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5" integrity sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU= +thunkify@^2.1.2: + version "2.1.2" + resolved "http://registry.npm.taobao.org/thunkify/download/thunkify-2.1.2.tgz#faa0e9d230c51acc95ca13a361ac05ca7e04553d" + integrity sha1-+qDp0jDFGsyVyhOjYawFyn4EVT0= + time-stamp@^1.0.0: version "1.1.0" resolved "http://registry.npm.taobao.org/time-stamp/download/time-stamp-1.1.0.tgz#764a5a11af50561921b133f3b44e618687e0f5c3" integrity sha1-dkpaEa9QVhkhsTPztE5hhofg9cM= +tmp@^0.0.29: + version "0.0.29" + resolved "http://registry.npm.taobao.org/tmp/download/tmp-0.0.29.tgz?cache=0&other_urls=http%3A%2F%2Fregistry.npm.taobao.org%2Ftmp%2Fdownload%2Ftmp-0.0.29.tgz#f25125ff0dd9da3ccb0c2dd371ee1288bb9128c0" + integrity sha1-8lEl/w3Z2jzLDC3Tce4SiLuRKMA= + dependencies: + os-tmpdir "~1.0.1" + tmp@^0.0.33: version "0.0.33" resolved "http://registry.npm.taobao.org/tmp/download/tmp-0.0.33.tgz#6d34335889768d21b2bcda0aa277ced3b1bfadf9" @@ -6080,7 +6554,7 @@ union-value@^1.0.0: is-extendable "^0.1.1" set-value "^0.4.3" -unpipe@~1.0.0: +unpipe@1.0.0, unpipe@~1.0.0: version "1.0.0" resolved "http://registry.npm.taobao.org/unpipe/download/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec" integrity sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw= @@ -6110,7 +6584,7 @@ use@^3.1.0: resolved "http://registry.npm.taobao.org/use/download/use-3.1.1.tgz#d50c8cac79a19fbc20f2911f56eb973f4e10070f" integrity sha1-1QyMrHmhn7wg8pEfVuuXP04QBw8= -util-deprecate@~1.0.1: +util-deprecate@^1.0.1, util-deprecate@~1.0.1: version "1.0.2" resolved "http://registry.npm.taobao.org/util-deprecate/download/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" integrity sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8= @@ -6128,6 +6602,11 @@ utils-merge@1.0.1: resolved "http://registry.npm.taobao.org/utils-merge/download/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713" integrity sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM= +uuid@3.0.1: + version "3.0.1" + resolved "http://registry.npm.taobao.org/uuid/download/uuid-3.0.1.tgz?cache=0&other_urls=http%3A%2F%2Fregistry.npm.taobao.org%2Fuuid%2Fdownload%2Fuuid-3.0.1.tgz#6544bba2dfda8c1cf17e629a3a305e2bb1fee6c1" + integrity sha1-ZUS7ot/ajBzxfmKaOjBeK7H+5sE= + uuid@^3.3.2: version "3.3.2" resolved "http://registry.npm.taobao.org/uuid/download/uuid-3.3.2.tgz#1b4af4955eb3077c501c23872fc6513811587131" @@ -6301,6 +6780,15 @@ ws@^5.2.0: dependencies: async-limiter "~1.0.0" +xcode@1.0.0: + version "1.0.0" + resolved "http://registry.npm.taobao.org/xcode/download/xcode-1.0.0.tgz#e1f5b1443245ded38c180796df1a10fdeda084ec" + integrity sha1-4fWxRDJF3tOMGAeW3xoQ/e2ghOw= + dependencies: + pegjs "^0.10.0" + simple-plist "^0.2.1" + uuid "3.0.1" + xcode@^1.0.0: version "1.1.0" resolved "http://registry.npm.taobao.org/xcode/download/xcode-1.1.0.tgz#9fcb63f417a9af377bfb743a5c22afce4e1da964" @@ -6341,6 +6829,11 @@ xpipe@^1.0.5: resolved "http://registry.npm.taobao.org/xpipe/download/xpipe-1.0.5.tgz#8dd8bf45fc3f7f55f0e054b878f43a62614dafdf" integrity sha1-jdi/Rfw/f1Xw4FS4ePQ6YmFNr98= +xregexp@2.0.0: + version "2.0.0" + resolved "http://registry.npm.taobao.org/xregexp/download/xregexp-2.0.0.tgz#52a63e56ca0b84a7f3a5f3d61872f126ad7a5943" + integrity sha1-UqY+VsoLhKfzpfPWGHLxJq16WUM= + xtend@~4.0.1: version "4.0.1" resolved "http://registry.npm.taobao.org/xtend/download/xtend-4.0.1.tgz#a5c6d532be656e23db820efb943a1f04998d63af" @@ -6436,3 +6929,10 @@ yargs@^9.0.0: which-module "^2.0.0" y18n "^3.2.1" yargs-parser "^7.0.0" + +yazl@^2.4.1: + version "2.5.1" + resolved "http://registry.npm.taobao.org/yazl/download/yazl-2.5.1.tgz#a3d65d3dd659a5b0937850e8609f22fffa2b5c35" + integrity sha1-o9ZdPdZZpbCTeFDoYJ8i//orXDU= + dependencies: + buffer-crc32 "~0.2.3"