├── .buckconfig ├── .flowconfig ├── .gitattributes ├── .gitignore ├── .watchmanconfig ├── README.md ├── __tests__ └── App-test.js ├── android ├── app │ ├── BUCK │ ├── build.gradle │ ├── build_defs.bzl │ ├── proguard-rules.pro │ ├── src │ │ ├── debug │ │ │ └── AndroidManifest.xml │ │ └── main │ │ │ ├── AndroidManifest.xml │ │ │ ├── java │ │ │ └── com │ │ │ │ └── wanandroid_rn │ │ │ │ ├── MainActivity.java │ │ │ │ └── MainApplication.java │ │ │ └── res │ │ │ ├── mipmap-hdpi │ │ │ └── ic_wan_rn.png │ │ │ ├── mipmap-mdpi │ │ │ └── ic_wan_rn.png │ │ │ ├── mipmap-xhdpi │ │ │ └── ic_wan_rn.png │ │ │ ├── mipmap-xxhdpi │ │ │ └── ic_wan_rn.png │ │ │ ├── mipmap-xxxhdpi │ │ │ └── ic_wan_rn.png │ │ │ └── values │ │ │ ├── strings.xml │ │ │ └── styles.xml │ └── wanandroid-mvvm.jks ├── build.gradle ├── gradle.properties ├── gradle │ └── wrapper │ │ ├── gradle-wrapper.jar │ │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── keystores │ ├── BUCK │ └── debug.keystore.properties └── settings.gradle ├── app-release.apk ├── app.json ├── babel.config.js ├── index.js ├── ios ├── WanAndroid_RN-tvOS │ └── Info.plist ├── WanAndroid_RN-tvOSTests │ └── Info.plist ├── WanAndroid_RN.xcodeproj │ ├── project.pbxproj │ └── xcshareddata │ │ └── xcschemes │ │ ├── WanAndroid_RN-tvOS.xcscheme │ │ └── WanAndroid_RN.xcscheme ├── WanAndroid_RN │ ├── AppDelegate.h │ ├── AppDelegate.m │ ├── Base.lproj │ │ └── LaunchScreen.xib │ ├── Images.xcassets │ │ ├── AppIcon.appiconset │ │ │ └── Contents.json │ │ └── Contents.json │ ├── Info.plist │ └── main.m └── WanAndroid_RNTests │ ├── Info.plist │ └── WanAndroid_RNTests.m ├── metro.config.js ├── package.json ├── res ├── ic_about.png ├── ic_add.png ├── ic_avatar.png ├── ic_back.png ├── ic_dashboard.png ├── ic_delete_item.png ├── ic_drawer.png ├── ic_favorite.png ├── ic_favorite_not.png ├── ic_home.png ├── ic_logout.png ├── ic_navigation.png ├── ic_project.png ├── ic_right_arrow.png ├── ic_search.png ├── ic_todo.png └── ic_wechat.png ├── screenshot ├── android.png └── ios.png ├── src ├── component │ ├── AboutPage.js │ ├── ArticleListPage.js │ ├── ArticleTabPage.js │ ├── DrawerPage.js │ ├── KnowledgePage.js │ ├── SearchPage.js │ ├── SearchResultPage.js │ ├── WebPage.js │ ├── login │ │ ├── EditTodoPage.js │ │ ├── FavoritePage.js │ │ ├── LoginPage.js │ │ ├── RegisterPage.js │ │ └── TodoPage.js │ └── main │ │ ├── HomePage.js │ │ ├── KnowledgeTreePage.js │ │ ├── MainPage.js │ │ ├── NavigationPage.js │ │ ├── ProjectPage.js │ │ └── WeChatPage.js ├── config │ ├── colors.js │ ├── index.js │ ├── strings.js │ └── styles.js ├── http │ └── HttpUtils.js ├── index.js ├── utils │ ├── AccountUtils.js │ ├── HintUtils.js │ └── IOSUtils.js └── widget │ ├── ArticleItemView.js │ ├── EmptyView.js │ ├── EndView.js │ ├── ErrorView.js │ ├── HeaderBar.js │ ├── LineDivider.js │ ├── LoadingView.js │ └── MainHeaderBar.js └── yarn.lock /.buckconfig: -------------------------------------------------------------------------------- 1 | 2 | [android] 3 | target = Google Inc.:Google APIs:23 4 | 5 | [maven_repositories] 6 | central = https://repo1.maven.org/maven2 7 | -------------------------------------------------------------------------------- /.flowconfig: -------------------------------------------------------------------------------- 1 | [ignore] 2 | ; We fork some components by platform 3 | .*/*[.]android.js 4 | 5 | ; Ignore "BUCK" generated dirs 6 | /\.buckd/ 7 | 8 | ; Ignore unexpected extra "@providesModule" 9 | .*/node_modules/.*/node_modules/fbjs/.* 10 | 11 | ; Ignore duplicate module providers 12 | ; For RN Apps installed via npm, "Libraries" folder is inside 13 | ; "node_modules/react-native" but in the source repo it is in the root 14 | .*/Libraries/react-native/React.js 15 | 16 | ; Ignore polyfills 17 | .*/Libraries/polyfills/.* 18 | 19 | ; Ignore metro 20 | .*/node_modules/metro/.* 21 | 22 | [include] 23 | 24 | [libs] 25 | node_modules/react-native/Libraries/react-native/react-native-interface.js 26 | node_modules/react-native/flow/ 27 | 28 | [options] 29 | emoji=true 30 | 31 | esproposal.optional_chaining=enable 32 | esproposal.nullish_coalescing=enable 33 | 34 | module.system=haste 35 | module.system.haste.use_name_reducers=true 36 | # get basename 37 | module.system.haste.name_reducers='^.*/\([a-zA-Z0-9$_.-]+\.js\(\.flow\)?\)$' -> '\1' 38 | # strip .js or .js.flow suffix 39 | module.system.haste.name_reducers='^\(.*\)\.js\(\.flow\)?$' -> '\1' 40 | # strip .ios suffix 41 | module.system.haste.name_reducers='^\(.*\)\.ios$' -> '\1' 42 | module.system.haste.name_reducers='^\(.*\)\.android$' -> '\1' 43 | module.system.haste.name_reducers='^\(.*\)\.native$' -> '\1' 44 | module.system.haste.paths.blacklist=.*/__tests__/.* 45 | module.system.haste.paths.blacklist=.*/__mocks__/.* 46 | module.system.haste.paths.blacklist=/node_modules/react-native/Libraries/Animated/src/polyfills/.* 47 | module.system.haste.paths.whitelist=/node_modules/react-native/Libraries/.* 48 | 49 | munge_underscores=true 50 | 51 | module.name_mapper='^[./a-zA-Z0-9$_-]+\.\(bmp\|gif\|jpg\|jpeg\|png\|psd\|svg\|webp\|m4v\|mov\|mp4\|mpeg\|mpg\|webm\|aac\|aiff\|caf\|m4a\|mp3\|wav\|html\|pdf\)$' -> 'RelativeImageStub' 52 | 53 | module.file_ext=.js 54 | module.file_ext=.jsx 55 | module.file_ext=.json 56 | module.file_ext=.native.js 57 | 58 | suppress_type=$FlowIssue 59 | suppress_type=$FlowFixMe 60 | suppress_type=$FlowFixMeProps 61 | suppress_type=$FlowFixMeState 62 | 63 | suppress_comment=\\(.\\|\n\\)*\\$FlowFixMe\\($\\|[^(]\\|(\\(\\)? *\\(site=[a-z,_]*react_native[a-z,_]*\\)?)\\) 64 | suppress_comment=\\(.\\|\n\\)*\\$FlowIssue\\((\\(\\)? *\\(site=[a-z,_]*react_native[a-z,_]*\\)?)\\)?:? #[0-9]+ 65 | suppress_comment=\\(.\\|\n\\)*\\$FlowFixedInNextDeploy 66 | suppress_comment=\\(.\\|\n\\)*\\$FlowExpectedError 67 | 68 | [version] 69 | ^0.92.0 70 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | *.pbxproj -text 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # OSX 2 | # 3 | .DS_Store 4 | 5 | # Xcode 6 | # 7 | build/ 8 | *.pbxuser 9 | !default.pbxuser 10 | *.mode1v3 11 | !default.mode1v3 12 | *.mode2v3 13 | !default.mode2v3 14 | *.perspectivev3 15 | !default.perspectivev3 16 | xcuserdata 17 | *.xccheckout 18 | *.moved-aside 19 | DerivedData 20 | *.hmap 21 | *.ipa 22 | *.xcuserstate 23 | project.xcworkspace 24 | 25 | # Android/IntelliJ 26 | # 27 | build/ 28 | .idea 29 | .gradle 30 | local.properties 31 | *.iml 32 | 33 | # node.js 34 | # 35 | node_modules/ 36 | npm-debug.log 37 | yarn-error.log 38 | 39 | # BUCK 40 | buck-out/ 41 | \.buckd/ 42 | *.keystore 43 | 44 | # fastlane 45 | # 46 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the 47 | # screenshots whenever they are needed. 48 | # For more information about the recommended setup visit: 49 | # https://docs.fastlane.tools/best-practices/source-control/ 50 | 51 | */fastlane/report.xml 52 | */fastlane/Preview.html 53 | */fastlane/screenshots 54 | 55 | # Bundle artifact 56 | *.jsbundle 57 | -------------------------------------------------------------------------------- /.watchmanconfig: -------------------------------------------------------------------------------- 1 | {} -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # WanAndroid_RN 2 | 玩安卓客户端 React-Native 版,可以查看各种开发相关的知识,适配了 Android 和 iOS,内容比较完整 3 | 4 | 封装了加载中、空数据、错误、到达最底部等不同状态的视图,在错误时可以点击重新加载,具有较好的用户体验 5 | 6 | 如果觉得项目还不错,点个 star 鼓励下作者吧 o(╥﹏╥)o 7 | 8 | 也欢迎大家发起 issue 或提交 PR 9 | 10 | ### 业务内容 11 | 几乎对接了玩安卓的所有 API,主要包括以下内容: 12 | - 注册、登录 13 | - 收藏、取消收藏 14 | - 新增、编辑待办任务 15 | - 查看、搜索各类项目和文章 16 | - 网站导航、知识体系、公众号 17 | 18 | ### 安装包 19 | [Android](https://github.com/binaryshao/WanAndroid_RN/raw/master/app-release.apk) 20 | 21 | 22 | ### 效果图 23 | #### Android 24 | 25 | 26 | #### iOS 27 | 28 | 29 | ### 用到的开源库 30 | - react-navigation:界面跳转、创建抽屉布局、创建 Tab 页,创建 HeaderBar(返回按钮、标题、右侧视图) 31 | - async-storage:持久化键值对 32 | - react-native-modal-datetime-picker:时间选择器 33 | - react-native-root-toast:吐司 34 | - react-native-swiper:banner 35 | - react-native-ultimate-listview:列表,从某些角度来看并不好用 36 | 37 | ### 感谢 38 | [鸿神提供的数据源](https://www.wanandroid.com/blog/show/2) 39 | 40 | ### 关于作者 41 | 个人网站  : [binaryshao](http://sbingo666.com/) 42 | 43 | Github      : [binaryshao](https://github.com/binaryshao) 44 | 45 | CSDN       : [binaryshao](https://blog.csdn.net/recordgrowth) 46 | 47 | Email        : shaojianbin@sbingo666.com 48 | -------------------------------------------------------------------------------- /__tests__/App-test.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @format 3 | */ 4 | 5 | import 'react-native'; 6 | import React from 'react'; 7 | import App from '../App'; 8 | 9 | // Note: test renderer must be required after react-native. 10 | import renderer from 'react-test-renderer'; 11 | 12 | it('renders correctly', () => { 13 | renderer.create(); 14 | }); 15 | -------------------------------------------------------------------------------- /android/app/BUCK: -------------------------------------------------------------------------------- 1 | # To learn about Buck see [Docs](https://buckbuild.com/). 2 | # To run your application with Buck: 3 | # - install Buck 4 | # - `npm start` - to start the packager 5 | # - `cd android` 6 | # - `keytool -genkey -v -keystore keystores/debug.keystore -storepass android -alias androiddebugkey -keypass android -dname "CN=Android Debug,O=Android,C=US"` 7 | # - `./gradlew :app:copyDownloadableDepsToLibs` - make all Gradle compile dependencies available to Buck 8 | # - `buck install -r android/app` - compile, install and run application 9 | # 10 | 11 | load(":build_defs.bzl", "create_aar_targets", "create_jar_targets") 12 | 13 | lib_deps = [] 14 | 15 | create_aar_targets(glob(["libs/*.aar"])) 16 | 17 | create_jar_targets(glob(["libs/*.jar"])) 18 | 19 | android_library( 20 | name = "all-libs", 21 | exported_deps = lib_deps, 22 | ) 23 | 24 | android_library( 25 | name = "app-code", 26 | srcs = glob([ 27 | "src/main/java/**/*.java", 28 | ]), 29 | deps = [ 30 | ":all-libs", 31 | ":build_config", 32 | ":res", 33 | ], 34 | ) 35 | 36 | android_build_config( 37 | name = "build_config", 38 | package = "com.wanandroid_rn", 39 | ) 40 | 41 | android_resource( 42 | name = "res", 43 | package = "com.wanandroid_rn", 44 | res = "src/main/res", 45 | ) 46 | 47 | android_binary( 48 | name = "app", 49 | keystore = "//android/keystores:debug", 50 | manifest = "src/main/AndroidManifest.xml", 51 | package_type = "debug", 52 | deps = [ 53 | ":app-code", 54 | ], 55 | ) 56 | -------------------------------------------------------------------------------- /android/app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: "com.android.application" 2 | 3 | import com.android.build.OutputFile 4 | 5 | /** 6 | * The react.gradle file registers a task for each build variant (e.g. bundleDebugJsAndAssets 7 | * and bundleReleaseJsAndAssets). 8 | * These basically call `react-native bundle` with the correct arguments during the Android build 9 | * cycle. By default, bundleDebugJsAndAssets is skipped, as in debug/dev mode we prefer to load the 10 | * bundle directly from the development server. Below you can see all the possible configurations 11 | * and their defaults. If you decide to add a configuration block, make sure to add it before the 12 | * `apply from: "../../node_modules/react-native/react.gradle"` line. 13 | * 14 | * project.ext.react = [ 15 | * // the name of the generated asset file containing your JS bundle 16 | * bundleAssetName: "index.android.bundle", 17 | * 18 | * // the entry file for bundle generation 19 | * entryFile: "index.android.js", 20 | * 21 | * // whether to bundle JS and assets in debug mode 22 | * bundleInDebug: false, 23 | * 24 | * // whether to bundle JS and assets in release mode 25 | * bundleInRelease: true, 26 | * 27 | * // whether to bundle JS and assets in another build variant (if configured). 28 | * // See http://tools.android.com/tech-docs/new-build-system/user-guide#TOC-Build-Variants 29 | * // The configuration property can be in the following formats 30 | * // 'bundleIn${productFlavor}${buildType}' 31 | * // 'bundleIn${buildType}' 32 | * // bundleInFreeDebug: true, 33 | * // bundleInPaidRelease: true, 34 | * // bundleInBeta: true, 35 | * 36 | * // whether to disable dev mode in custom build variants (by default only disabled in release) 37 | * // for example: to disable dev mode in the staging build type (if configured) 38 | * devDisabledInStaging: true, 39 | * // The configuration property can be in the following formats 40 | * // 'devDisabledIn${productFlavor}${buildType}' 41 | * // 'devDisabledIn${buildType}' 42 | * 43 | * // the root of your project, i.e. where "package.json" lives 44 | * root: "../../", 45 | * 46 | * // where to put the JS bundle asset in debug mode 47 | * jsBundleDirDebug: "$buildDir/intermediates/assets/debug", 48 | * 49 | * // where to put the JS bundle asset in release mode 50 | * jsBundleDirRelease: "$buildDir/intermediates/assets/release", 51 | * 52 | * // where to put drawable resources / React Native assets, e.g. the ones you use via 53 | * // require('./image.png')), in debug mode 54 | * resourcesDirDebug: "$buildDir/intermediates/res/merged/debug", 55 | * 56 | * // where to put drawable resources / React Native assets, e.g. the ones you use via 57 | * // require('./image.png')), in release mode 58 | * resourcesDirRelease: "$buildDir/intermediates/res/merged/release", 59 | * 60 | * // by default the gradle tasks are skipped if none of the JS files or assets change; this means 61 | * // that we don't look at files in android/ or ios/ to determine whether the tasks are up to 62 | * // date; if you have any other folders that you want to ignore for performance reasons (gradle 63 | * // indexes the entire tree), add them here. Alternatively, if you have JS files in android/ 64 | * // for example, you might want to remove it from here. 65 | * inputExcludes: ["android/**", "ios/**"], 66 | * 67 | * // override which node gets called and with what additional arguments 68 | * nodeExecutableAndArgs: ["node"], 69 | * 70 | * // supply additional arguments to the packager 71 | * extraPackagerArgs: [] 72 | * ] 73 | */ 74 | 75 | project.ext.react = [ 76 | entryFile: "index.js" 77 | ] 78 | 79 | apply from: "../../node_modules/react-native/react.gradle" 80 | 81 | /** 82 | * Set this to true to create two separate APKs instead of one: 83 | * - An APK that only works on ARM devices 84 | * - An APK that only works on x86 devices 85 | * The advantage is the size of the APK is reduced by about 4MB. 86 | * Upload all the APKs to the Play Store and people will download 87 | * the correct one based on the CPU architecture of their device. 88 | */ 89 | def enableSeparateBuildPerCPUArchitecture = false 90 | 91 | /** 92 | * Run Proguard to shrink the Java bytecode in release builds. 93 | */ 94 | def enableProguardInReleaseBuilds = false 95 | 96 | android { 97 | compileSdkVersion rootProject.ext.compileSdkVersion 98 | 99 | compileOptions { 100 | sourceCompatibility JavaVersion.VERSION_1_8 101 | targetCompatibility JavaVersion.VERSION_1_8 102 | } 103 | 104 | defaultConfig { 105 | applicationId "com.wanandroid_rn" 106 | minSdkVersion rootProject.ext.minSdkVersion 107 | targetSdkVersion rootProject.ext.targetSdkVersion 108 | versionCode 1 109 | versionName "1.0" 110 | } 111 | splits { 112 | abi { 113 | reset() 114 | enable enableSeparateBuildPerCPUArchitecture 115 | universalApk false // If true, also generate a universal APK 116 | include "armeabi-v7a", "x86", "arm64-v8a", "x86_64" 117 | } 118 | } 119 | signingConfigs { 120 | release { 121 | storeFile file(RELEASE_STORE_FILE) 122 | storePassword RELEASE_STORE_PASSWORD 123 | keyAlias RELEASE_KEY_ALIAS 124 | keyPassword RELEASE_KEY_PASSWORD 125 | v1SigningEnabled true 126 | v2SigningEnabled true 127 | } 128 | } 129 | buildTypes { 130 | debug { 131 | signingConfig signingConfigs.release 132 | } 133 | release { 134 | signingConfig signingConfigs.release 135 | minifyEnabled enableProguardInReleaseBuilds 136 | proguardFiles getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro" 137 | } 138 | } 139 | // applicationVariants are e.g. debug, release 140 | applicationVariants.all { variant -> 141 | variant.outputs.each { output -> 142 | // For each separate APK per architecture, set a unique version code as described here: 143 | // http://tools.android.com/tech-docs/new-build-system/user-guide/apk-splits 144 | def versionCodes = ["armeabi-v7a":1, "x86":2, "arm64-v8a": 3, "x86_64": 4] 145 | def abi = output.getFilter(OutputFile.ABI) 146 | if (abi != null) { // null for the universal-debug, universal-release variants 147 | output.versionCodeOverride = 148 | versionCodes.get(abi) * 1048576 + defaultConfig.versionCode 149 | } 150 | } 151 | } 152 | } 153 | 154 | dependencies { 155 | implementation project(':@react-native-community_async-storage') 156 | implementation project(':react-native-webview') 157 | implementation project(':react-native-gesture-handler') 158 | implementation fileTree(dir: "libs", include: ["*.jar"]) 159 | implementation "com.android.support:appcompat-v7:${rootProject.ext.supportLibVersion}" 160 | implementation "com.facebook.react:react-native:+" // From node_modules 161 | } 162 | 163 | // Run this once to be able to run the application with BUCK 164 | // puts all compile dependencies into folder libs for BUCK to use 165 | task copyDownloadableDepsToLibs(type: Copy) { 166 | from configurations.compile 167 | into 'libs' 168 | } 169 | -------------------------------------------------------------------------------- /android/app/build_defs.bzl: -------------------------------------------------------------------------------- 1 | """Helper definitions to glob .aar and .jar targets""" 2 | 3 | def create_aar_targets(aarfiles): 4 | for aarfile in aarfiles: 5 | name = "aars__" + aarfile[aarfile.rindex("/") + 1:aarfile.rindex(".aar")] 6 | lib_deps.append(":" + name) 7 | android_prebuilt_aar( 8 | name = name, 9 | aar = aarfile, 10 | ) 11 | 12 | def create_jar_targets(jarfiles): 13 | for jarfile in jarfiles: 14 | name = "jars__" + jarfile[jarfile.rindex("/") + 1:jarfile.rindex(".jar")] 15 | lib_deps.append(":" + name) 16 | prebuilt_jar( 17 | name = name, 18 | binary_jar = jarfile, 19 | ) 20 | -------------------------------------------------------------------------------- /android/app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in /usr/local/Cellar/android-sdk/24.3.3/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | -------------------------------------------------------------------------------- /android/app/src/debug/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /android/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 12 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /android/app/src/main/java/com/wanandroid_rn/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.wanandroid_rn; 2 | 3 | import com.facebook.react.ReactActivity; 4 | import com.facebook.react.ReactActivityDelegate; 5 | import com.facebook.react.ReactRootView; 6 | import com.swmansion.gesturehandler.react.RNGestureHandlerEnabledRootView; 7 | 8 | public class MainActivity extends ReactActivity { 9 | 10 | /** 11 | * Returns the name of the main component registered from JavaScript. 12 | * This is used to schedule rendering of the component. 13 | */ 14 | @Override 15 | protected String getMainComponentName() { 16 | return "WanAndroid_RN"; 17 | } 18 | 19 | @Override 20 | protected ReactActivityDelegate createReactActivityDelegate() { 21 | return new ReactActivityDelegate(this, getMainComponentName()) { 22 | @Override 23 | protected ReactRootView createRootView() { 24 | return new RNGestureHandlerEnabledRootView(MainActivity.this); 25 | } 26 | }; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /android/app/src/main/java/com/wanandroid_rn/MainApplication.java: -------------------------------------------------------------------------------- 1 | package com.wanandroid_rn; 2 | 3 | import android.app.Application; 4 | 5 | import com.facebook.react.ReactApplication; 6 | import com.reactnativecommunity.asyncstorage.AsyncStoragePackage; 7 | import com.reactnativecommunity.webview.RNCWebViewPackage; 8 | import com.swmansion.gesturehandler.react.RNGestureHandlerPackage; 9 | import com.facebook.react.ReactNativeHost; 10 | import com.facebook.react.ReactPackage; 11 | import com.facebook.react.shell.MainReactPackage; 12 | import com.facebook.soloader.SoLoader; 13 | 14 | import java.util.Arrays; 15 | import java.util.List; 16 | 17 | public class MainApplication extends Application implements ReactApplication { 18 | 19 | private final ReactNativeHost mReactNativeHost = new ReactNativeHost(this) { 20 | @Override 21 | public boolean getUseDeveloperSupport() { 22 | return BuildConfig.DEBUG; 23 | } 24 | 25 | @Override 26 | protected List getPackages() { 27 | return Arrays.asList( 28 | new MainReactPackage(), 29 | new AsyncStoragePackage(), 30 | new RNCWebViewPackage(), 31 | new RNGestureHandlerPackage() 32 | ); 33 | } 34 | 35 | @Override 36 | protected String getJSMainModuleName() { 37 | return "index"; 38 | } 39 | }; 40 | 41 | @Override 42 | public ReactNativeHost getReactNativeHost() { 43 | return mReactNativeHost; 44 | } 45 | 46 | @Override 47 | public void onCreate() { 48 | super.onCreate(); 49 | SoLoader.init(this, /* native exopackage */ false); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-hdpi/ic_wan_rn.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/binaryshao/WanAndroid_RN/e9bdd0b8caf6a0578f6eeefb0649e3d420dec531/android/app/src/main/res/mipmap-hdpi/ic_wan_rn.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-mdpi/ic_wan_rn.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/binaryshao/WanAndroid_RN/e9bdd0b8caf6a0578f6eeefb0649e3d420dec531/android/app/src/main/res/mipmap-mdpi/ic_wan_rn.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xhdpi/ic_wan_rn.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/binaryshao/WanAndroid_RN/e9bdd0b8caf6a0578f6eeefb0649e3d420dec531/android/app/src/main/res/mipmap-xhdpi/ic_wan_rn.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxhdpi/ic_wan_rn.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/binaryshao/WanAndroid_RN/e9bdd0b8caf6a0578f6eeefb0649e3d420dec531/android/app/src/main/res/mipmap-xxhdpi/ic_wan_rn.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxxhdpi/ic_wan_rn.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/binaryshao/WanAndroid_RN/e9bdd0b8caf6a0578f6eeefb0649e3d420dec531/android/app/src/main/res/mipmap-xxxhdpi/ic_wan_rn.png -------------------------------------------------------------------------------- /android/app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | WanAndroid_RN 3 | 4 | -------------------------------------------------------------------------------- /android/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /android/app/wanandroid-mvvm.jks: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/binaryshao/WanAndroid_RN/e9bdd0b8caf6a0578f6eeefb0649e3d420dec531/android/app/wanandroid-mvvm.jks -------------------------------------------------------------------------------- /android/build.gradle: -------------------------------------------------------------------------------- 1 | // Top-level build file where you can add configuration options common to all sub-projects/modules. 2 | 3 | buildscript { 4 | ext { 5 | buildToolsVersion = "28.0.3" 6 | minSdkVersion = 16 7 | compileSdkVersion = 28 8 | targetSdkVersion = 28 9 | supportLibVersion = "28.0.0" 10 | } 11 | repositories { 12 | google() 13 | jcenter() 14 | } 15 | dependencies { 16 | classpath("com.android.tools.build:gradle:3.4.0") 17 | 18 | // NOTE: Do not place your application dependencies here; they belong 19 | // in the individual module build.gradle files 20 | } 21 | } 22 | 23 | allprojects { 24 | repositories { 25 | mavenLocal() 26 | google() 27 | jcenter() 28 | maven { 29 | // All of React Native (JS, Obj-C sources, Android binaries) is installed from npm 30 | url "$rootDir/../node_modules/react-native/android" 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /android/gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | 3 | # IDE (e.g. Android Studio) users: 4 | # Gradle settings configured through the IDE *will override* 5 | # any settings specified in this file. 6 | 7 | # For more details on how to configure your build environment visit 8 | # http://www.gradle.org/docs/current/userguide/build_environment.html 9 | 10 | # Specifies the JVM arguments used for the daemon process. 11 | # The setting is particularly useful for tweaking memory settings. 12 | # Default value: -Xmx10248m -XX:MaxPermSize=256m 13 | # org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 14 | 15 | # When configured, Gradle will run in incubating parallel mode. 16 | # This option should only be used with decoupled projects. More details, visit 17 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 18 | # org.gradle.parallel=true 19 | RELEASE_STORE_FILE=wanandroid-mvvm.jks 20 | RELEASE_STORE_PASSWORD=sbingo666 21 | RELEASE_KEY_ALIAS=wanandroid-mvvm 22 | RELEASE_KEY_PASSWORD=sbingo666 -------------------------------------------------------------------------------- /android/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/binaryshao/WanAndroid_RN/e9bdd0b8caf6a0578f6eeefb0649e3d420dec531/android/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /android/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-5.5.1-all.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /android/gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | # 4 | # Copyright 2015 the original author or authors. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # http://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | 19 | ############################################################################## 20 | ## 21 | ## Gradle start up script for UN*X 22 | ## 23 | ############################################################################## 24 | 25 | # Attempt to set APP_HOME 26 | # Resolve links: $0 may be a link 27 | PRG="$0" 28 | # Need this for relative symlinks. 29 | while [ -h "$PRG" ] ; do 30 | ls=`ls -ld "$PRG"` 31 | link=`expr "$ls" : '.*-> \(.*\)$'` 32 | if expr "$link" : '/.*' > /dev/null; then 33 | PRG="$link" 34 | else 35 | PRG=`dirname "$PRG"`"/$link" 36 | fi 37 | done 38 | SAVED="`pwd`" 39 | cd "`dirname \"$PRG\"`/" >/dev/null 40 | APP_HOME="`pwd -P`" 41 | cd "$SAVED" >/dev/null 42 | 43 | APP_NAME="Gradle" 44 | APP_BASE_NAME=`basename "$0"` 45 | 46 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 47 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 48 | 49 | # Use the maximum available, or set MAX_FD != -1 to use that value. 50 | MAX_FD="maximum" 51 | 52 | warn () { 53 | echo "$*" 54 | } 55 | 56 | die () { 57 | echo 58 | echo "$*" 59 | echo 60 | exit 1 61 | } 62 | 63 | # OS specific support (must be 'true' or 'false'). 64 | cygwin=false 65 | msys=false 66 | darwin=false 67 | nonstop=false 68 | case "`uname`" in 69 | CYGWIN* ) 70 | cygwin=true 71 | ;; 72 | Darwin* ) 73 | darwin=true 74 | ;; 75 | MINGW* ) 76 | msys=true 77 | ;; 78 | NONSTOP* ) 79 | nonstop=true 80 | ;; 81 | esac 82 | 83 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 84 | 85 | # Determine the Java command to use to start the JVM. 86 | if [ -n "$JAVA_HOME" ] ; then 87 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 88 | # IBM's JDK on AIX uses strange locations for the executables 89 | JAVACMD="$JAVA_HOME/jre/sh/java" 90 | else 91 | JAVACMD="$JAVA_HOME/bin/java" 92 | fi 93 | if [ ! -x "$JAVACMD" ] ; then 94 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 95 | 96 | Please set the JAVA_HOME variable in your environment to match the 97 | location of your Java installation." 98 | fi 99 | else 100 | JAVACMD="java" 101 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 102 | 103 | Please set the JAVA_HOME variable in your environment to match the 104 | location of your Java installation." 105 | fi 106 | 107 | # Increase the maximum file descriptors if we can. 108 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 109 | MAX_FD_LIMIT=`ulimit -H -n` 110 | if [ $? -eq 0 ] ; then 111 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 112 | MAX_FD="$MAX_FD_LIMIT" 113 | fi 114 | ulimit -n $MAX_FD 115 | if [ $? -ne 0 ] ; then 116 | warn "Could not set maximum file descriptor limit: $MAX_FD" 117 | fi 118 | else 119 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 120 | fi 121 | fi 122 | 123 | # For Darwin, add options to specify how the application appears in the dock 124 | if $darwin; then 125 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 126 | fi 127 | 128 | # For Cygwin, switch paths to Windows format before running java 129 | if $cygwin ; then 130 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 131 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 132 | JAVACMD=`cygpath --unix "$JAVACMD"` 133 | 134 | # We build the pattern for arguments to be converted via cygpath 135 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 136 | SEP="" 137 | for dir in $ROOTDIRSRAW ; do 138 | ROOTDIRS="$ROOTDIRS$SEP$dir" 139 | SEP="|" 140 | done 141 | OURCYGPATTERN="(^($ROOTDIRS))" 142 | # Add a user-defined pattern to the cygpath arguments 143 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 144 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 145 | fi 146 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 147 | i=0 148 | for arg in "$@" ; do 149 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 150 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 151 | 152 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 153 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 154 | else 155 | eval `echo args$i`="\"$arg\"" 156 | fi 157 | i=$((i+1)) 158 | done 159 | case $i in 160 | (0) set -- ;; 161 | (1) set -- "$args0" ;; 162 | (2) set -- "$args0" "$args1" ;; 163 | (3) set -- "$args0" "$args1" "$args2" ;; 164 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 165 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 166 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 167 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 168 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 169 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 170 | esac 171 | fi 172 | 173 | # Escape application args 174 | save () { 175 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 176 | echo " " 177 | } 178 | APP_ARGS=$(save "$@") 179 | 180 | # Collect all arguments for the java command, following the shell quoting and substitution rules 181 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 182 | 183 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong 184 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then 185 | cd "$(dirname "$0")" 186 | fi 187 | 188 | exec "$JAVACMD" "$@" 189 | -------------------------------------------------------------------------------- /android/gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem http://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%" == "" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%" == "" set DIRNAME=. 29 | set APP_BASE_NAME=%~n0 30 | set APP_HOME=%DIRNAME% 31 | 32 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 33 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 34 | 35 | @rem Find java.exe 36 | if defined JAVA_HOME goto findJavaFromJavaHome 37 | 38 | set JAVA_EXE=java.exe 39 | %JAVA_EXE% -version >NUL 2>&1 40 | if "%ERRORLEVEL%" == "0" goto init 41 | 42 | echo. 43 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 44 | echo. 45 | echo Please set the JAVA_HOME variable in your environment to match the 46 | echo location of your Java installation. 47 | 48 | goto fail 49 | 50 | :findJavaFromJavaHome 51 | set JAVA_HOME=%JAVA_HOME:"=% 52 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 53 | 54 | if exist "%JAVA_EXE%" goto init 55 | 56 | echo. 57 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 58 | echo. 59 | echo Please set the JAVA_HOME variable in your environment to match the 60 | echo location of your Java installation. 61 | 62 | goto fail 63 | 64 | :init 65 | @rem Get command-line arguments, handling Windows variants 66 | 67 | if not "%OS%" == "Windows_NT" goto win9xME_args 68 | 69 | :win9xME_args 70 | @rem Slurp the command line arguments. 71 | set CMD_LINE_ARGS= 72 | set _SKIP=2 73 | 74 | :win9xME_args_slurp 75 | if "x%~1" == "x" goto execute 76 | 77 | set CMD_LINE_ARGS=%* 78 | 79 | :execute 80 | @rem Setup the command line 81 | 82 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 83 | 84 | @rem Execute Gradle 85 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 86 | 87 | :end 88 | @rem End local scope for the variables with windows NT shell 89 | if "%ERRORLEVEL%"=="0" goto mainEnd 90 | 91 | :fail 92 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 93 | rem the _cmd.exe /c_ return code! 94 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 95 | exit /b 1 96 | 97 | :mainEnd 98 | if "%OS%"=="Windows_NT" endlocal 99 | 100 | :omega 101 | -------------------------------------------------------------------------------- /android/keystores/BUCK: -------------------------------------------------------------------------------- 1 | keystore( 2 | name = "debug", 3 | properties = "debug.keystore.properties", 4 | store = "debug.keystore", 5 | visibility = [ 6 | "PUBLIC", 7 | ], 8 | ) 9 | -------------------------------------------------------------------------------- /android/keystores/debug.keystore.properties: -------------------------------------------------------------------------------- 1 | key.store=debug.keystore 2 | key.alias=androiddebugkey 3 | key.store.password=android 4 | key.alias.password=android 5 | -------------------------------------------------------------------------------- /android/settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'WanAndroid_RN' 2 | include ':@react-native-community_async-storage' 3 | project(':@react-native-community_async-storage').projectDir = new File(rootProject.projectDir, '../node_modules/@react-native-community/async-storage/android') 4 | include ':react-native-webview' 5 | project(':react-native-webview').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-webview/android') 6 | include ':react-native-gesture-handler' 7 | project(':react-native-gesture-handler').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-gesture-handler/android') 8 | 9 | include ':app' 10 | -------------------------------------------------------------------------------- /app-release.apk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/binaryshao/WanAndroid_RN/e9bdd0b8caf6a0578f6eeefb0649e3d420dec531/app-release.apk -------------------------------------------------------------------------------- /app.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "WanAndroid_RN", 3 | "displayName": "WanAndroid_RN" 4 | } -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: ['module:metro-react-native-babel-preset'], 3 | }; 4 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | import {AppRegistry} from 'react-native'; 2 | import App from "./src"; 3 | import {name as appName} from "./app.json"; 4 | 5 | AppRegistry.registerComponent(appName, () => App); 6 | -------------------------------------------------------------------------------- /ios/WanAndroid_RN-tvOS/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | org.reactjs.native.example.$(PRODUCT_NAME:rfc1034identifier) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | LSRequiresIPhoneOS 24 | 25 | UILaunchStoryboardName 26 | LaunchScreen 27 | UIRequiredDeviceCapabilities 28 | 29 | armv7 30 | 31 | UISupportedInterfaceOrientations 32 | 33 | UIInterfaceOrientationPortrait 34 | UIInterfaceOrientationLandscapeLeft 35 | UIInterfaceOrientationLandscapeRight 36 | 37 | UIViewControllerBasedStatusBarAppearance 38 | 39 | NSLocationWhenInUseUsageDescription 40 | 41 | NSAppTransportSecurity 42 | 43 | 44 | NSExceptionDomains 45 | 46 | localhost 47 | 48 | NSExceptionAllowsInsecureHTTPLoads 49 | 50 | 51 | 52 | 53 | 54 | 55 | -------------------------------------------------------------------------------- /ios/WanAndroid_RN-tvOSTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | org.reactjs.native.example.$(PRODUCT_NAME:rfc1034identifier) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | 24 | 25 | -------------------------------------------------------------------------------- /ios/WanAndroid_RN.xcodeproj/xcshareddata/xcschemes/WanAndroid_RN-tvOS.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 29 | 35 | 36 | 37 | 43 | 49 | 50 | 51 | 52 | 53 | 58 | 59 | 61 | 67 | 68 | 69 | 70 | 71 | 77 | 78 | 79 | 80 | 81 | 82 | 92 | 94 | 100 | 101 | 102 | 103 | 104 | 105 | 111 | 113 | 119 | 120 | 121 | 122 | 124 | 125 | 128 | 129 | 130 | -------------------------------------------------------------------------------- /ios/WanAndroid_RN.xcodeproj/xcshareddata/xcschemes/WanAndroid_RN.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 29 | 35 | 36 | 37 | 43 | 49 | 50 | 51 | 52 | 53 | 58 | 59 | 61 | 67 | 68 | 69 | 70 | 71 | 77 | 78 | 79 | 80 | 81 | 82 | 92 | 94 | 100 | 101 | 102 | 103 | 104 | 105 | 111 | 113 | 119 | 120 | 121 | 122 | 124 | 125 | 128 | 129 | 130 | -------------------------------------------------------------------------------- /ios/WanAndroid_RN/AppDelegate.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Facebook, Inc. and its affiliates. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | */ 7 | 8 | #import 9 | #import 10 | 11 | @interface AppDelegate : UIResponder 12 | 13 | @property (nonatomic, strong) UIWindow *window; 14 | 15 | @end 16 | -------------------------------------------------------------------------------- /ios/WanAndroid_RN/AppDelegate.m: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Facebook, Inc. and its affiliates. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | */ 7 | 8 | #import "AppDelegate.h" 9 | 10 | #import 11 | #import 12 | #import 13 | 14 | @implementation AppDelegate 15 | 16 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions 17 | { 18 | RCTBridge *bridge = [[RCTBridge alloc] initWithDelegate:self launchOptions:launchOptions]; 19 | RCTRootView *rootView = [[RCTRootView alloc] initWithBridge:bridge 20 | moduleName:@"WanAndroid_RN" 21 | initialProperties:nil]; 22 | 23 | rootView.backgroundColor = [[UIColor alloc] initWithRed:1.0f green:1.0f blue:1.0f alpha:1]; 24 | 25 | self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds]; 26 | UIViewController *rootViewController = [UIViewController new]; 27 | rootViewController.view = rootView; 28 | self.window.rootViewController = rootViewController; 29 | [self.window makeKeyAndVisible]; 30 | return YES; 31 | } 32 | 33 | - (NSURL *)sourceURLForBridge:(RCTBridge *)bridge 34 | { 35 | #if DEBUG 36 | return [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:@"index" fallbackResource:nil]; 37 | #else 38 | return [[NSBundle mainBundle] URLForResource:@"main" withExtension:@"jsbundle"]; 39 | #endif 40 | } 41 | 42 | @end 43 | -------------------------------------------------------------------------------- /ios/WanAndroid_RN/Base.lproj/LaunchScreen.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 21 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /ios/WanAndroid_RN/Images.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "20x20", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "20x20", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "29x29", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "29x29", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "40x40", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "40x40", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "size" : "60x60", 36 | "scale" : "2x" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "size" : "60x60", 41 | "scale" : "3x" 42 | }, 43 | { 44 | "idiom" : "ios-marketing", 45 | "size" : "1024x1024", 46 | "scale" : "1x" 47 | } 48 | ], 49 | "info" : { 50 | "version" : 1, 51 | "author" : "xcode" 52 | } 53 | } -------------------------------------------------------------------------------- /ios/WanAndroid_RN/Images.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /ios/WanAndroid_RN/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleDisplayName 8 | WanAndroid_RN 9 | CFBundleExecutable 10 | $(EXECUTABLE_NAME) 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | $(PRODUCT_NAME) 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | 1.0 21 | CFBundleSignature 22 | ???? 23 | CFBundleVersion 24 | 1 25 | LSRequiresIPhoneOS 26 | 27 | NSLocationWhenInUseUsageDescription 28 | 29 | UILaunchStoryboardName 30 | LaunchScreen 31 | UIRequiredDeviceCapabilities 32 | 33 | armv7 34 | 35 | UISupportedInterfaceOrientations 36 | 37 | UIInterfaceOrientationPortrait 38 | UIInterfaceOrientationLandscapeLeft 39 | UIInterfaceOrientationLandscapeRight 40 | 41 | UIViewControllerBasedStatusBarAppearance 42 | 43 | NSLocationWhenInUseUsageDescription 44 | 45 | NSAppTransportSecurity 46 | 47 | 48 | NSAllowsArbitraryLoads 49 | 50 | NSExceptionDomains 51 | 52 | localhost 53 | 54 | NSExceptionAllowsInsecureHTTPLoads 55 | 56 | 57 | 58 | 59 | 60 | 61 | -------------------------------------------------------------------------------- /ios/WanAndroid_RN/main.m: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Facebook, Inc. and its affiliates. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | */ 7 | 8 | #import 9 | 10 | #import "AppDelegate.h" 11 | 12 | int main(int argc, char * argv[]) { 13 | @autoreleasepool { 14 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /ios/WanAndroid_RNTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | 24 | 25 | -------------------------------------------------------------------------------- /ios/WanAndroid_RNTests/WanAndroid_RNTests.m: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Facebook, Inc. and its affiliates. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | */ 7 | 8 | #import 9 | #import 10 | 11 | #import 12 | #import 13 | 14 | #define TIMEOUT_SECONDS 600 15 | #define TEXT_TO_LOOK_FOR @"Welcome to React Native!" 16 | 17 | @interface WanAndroid_RNTests : XCTestCase 18 | 19 | @end 20 | 21 | @implementation WanAndroid_RNTests 22 | 23 | - (BOOL)findSubviewInView:(UIView *)view matching:(BOOL(^)(UIView *view))test 24 | { 25 | if (test(view)) { 26 | return YES; 27 | } 28 | for (UIView *subview in [view subviews]) { 29 | if ([self findSubviewInView:subview matching:test]) { 30 | return YES; 31 | } 32 | } 33 | return NO; 34 | } 35 | 36 | - (void)testRendersWelcomeScreen 37 | { 38 | UIViewController *vc = [[[RCTSharedApplication() delegate] window] rootViewController]; 39 | NSDate *date = [NSDate dateWithTimeIntervalSinceNow:TIMEOUT_SECONDS]; 40 | BOOL foundElement = NO; 41 | 42 | __block NSString *redboxError = nil; 43 | RCTSetLogFunction(^(RCTLogLevel level, RCTLogSource source, NSString *fileName, NSNumber *lineNumber, NSString *message) { 44 | if (level >= RCTLogLevelError) { 45 | redboxError = message; 46 | } 47 | }); 48 | 49 | while ([date timeIntervalSinceNow] > 0 && !foundElement && !redboxError) { 50 | [[NSRunLoop mainRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.1]]; 51 | [[NSRunLoop mainRunLoop] runMode:NSRunLoopCommonModes beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.1]]; 52 | 53 | foundElement = [self findSubviewInView:vc.view matching:^BOOL(UIView *view) { 54 | if ([view.accessibilityLabel isEqualToString:TEXT_TO_LOOK_FOR]) { 55 | return YES; 56 | } 57 | return NO; 58 | }]; 59 | } 60 | 61 | RCTSetLogFunction(RCTDefaultLogFunction); 62 | 63 | XCTAssertNil(redboxError, @"RedBox error: %@", redboxError); 64 | XCTAssertTrue(foundElement, @"Couldn't find element with text '%@' in %d seconds", TEXT_TO_LOOK_FOR, TIMEOUT_SECONDS); 65 | } 66 | 67 | 68 | @end 69 | -------------------------------------------------------------------------------- /metro.config.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Metro configuration for React Native 3 | * https://github.com/facebook/react-native 4 | * 5 | * @format 6 | */ 7 | 8 | module.exports = { 9 | transformer: { 10 | getTransformOptions: async () => ({ 11 | transform: { 12 | experimentalImportSupport: false, 13 | inlineRequires: false, 14 | }, 15 | }), 16 | }, 17 | }; 18 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "WanAndroid_RN", 3 | "version": "0.0.1", 4 | "private": true, 5 | "scripts": { 6 | "start": "node node_modules/react-native/local-cli/cli.js start", 7 | "test": "jest" 8 | }, 9 | "dependencies": { 10 | "@react-native-community/async-storage": "^1.5.0", 11 | "react": "16.8.3", 12 | "react-native": "0.59.9", 13 | "react-native-gesture-handler": "^1.3.0", 14 | "react-native-modal-datetime-picker": "^7.5.0", 15 | "react-native-root-toast": "^3.1.1", 16 | "react-native-swiper": "^1.5.14", 17 | "react-native-ultimate-listview": "^3.3.0", 18 | "react-native-webview": "^5.12.1", 19 | "react-navigation": "3.11.0", 20 | "prop-types": "latest" 21 | }, 22 | "devDependencies": { 23 | "@babel/core": "^7.4.5", 24 | "@babel/runtime": "^7.4.5", 25 | "babel-jest": "^24.8.0", 26 | "jest": "^24.8.0", 27 | "metro-react-native-babel-preset": "^0.54.1", 28 | "react-test-renderer": "16.8.3" 29 | }, 30 | "jest": { 31 | "preset": "react-native" 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /res/ic_about.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/binaryshao/WanAndroid_RN/e9bdd0b8caf6a0578f6eeefb0649e3d420dec531/res/ic_about.png -------------------------------------------------------------------------------- /res/ic_add.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/binaryshao/WanAndroid_RN/e9bdd0b8caf6a0578f6eeefb0649e3d420dec531/res/ic_add.png -------------------------------------------------------------------------------- /res/ic_avatar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/binaryshao/WanAndroid_RN/e9bdd0b8caf6a0578f6eeefb0649e3d420dec531/res/ic_avatar.png -------------------------------------------------------------------------------- /res/ic_back.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/binaryshao/WanAndroid_RN/e9bdd0b8caf6a0578f6eeefb0649e3d420dec531/res/ic_back.png -------------------------------------------------------------------------------- /res/ic_dashboard.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/binaryshao/WanAndroid_RN/e9bdd0b8caf6a0578f6eeefb0649e3d420dec531/res/ic_dashboard.png -------------------------------------------------------------------------------- /res/ic_delete_item.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/binaryshao/WanAndroid_RN/e9bdd0b8caf6a0578f6eeefb0649e3d420dec531/res/ic_delete_item.png -------------------------------------------------------------------------------- /res/ic_drawer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/binaryshao/WanAndroid_RN/e9bdd0b8caf6a0578f6eeefb0649e3d420dec531/res/ic_drawer.png -------------------------------------------------------------------------------- /res/ic_favorite.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/binaryshao/WanAndroid_RN/e9bdd0b8caf6a0578f6eeefb0649e3d420dec531/res/ic_favorite.png -------------------------------------------------------------------------------- /res/ic_favorite_not.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/binaryshao/WanAndroid_RN/e9bdd0b8caf6a0578f6eeefb0649e3d420dec531/res/ic_favorite_not.png -------------------------------------------------------------------------------- /res/ic_home.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/binaryshao/WanAndroid_RN/e9bdd0b8caf6a0578f6eeefb0649e3d420dec531/res/ic_home.png -------------------------------------------------------------------------------- /res/ic_logout.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/binaryshao/WanAndroid_RN/e9bdd0b8caf6a0578f6eeefb0649e3d420dec531/res/ic_logout.png -------------------------------------------------------------------------------- /res/ic_navigation.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/binaryshao/WanAndroid_RN/e9bdd0b8caf6a0578f6eeefb0649e3d420dec531/res/ic_navigation.png -------------------------------------------------------------------------------- /res/ic_project.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/binaryshao/WanAndroid_RN/e9bdd0b8caf6a0578f6eeefb0649e3d420dec531/res/ic_project.png -------------------------------------------------------------------------------- /res/ic_right_arrow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/binaryshao/WanAndroid_RN/e9bdd0b8caf6a0578f6eeefb0649e3d420dec531/res/ic_right_arrow.png -------------------------------------------------------------------------------- /res/ic_search.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/binaryshao/WanAndroid_RN/e9bdd0b8caf6a0578f6eeefb0649e3d420dec531/res/ic_search.png -------------------------------------------------------------------------------- /res/ic_todo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/binaryshao/WanAndroid_RN/e9bdd0b8caf6a0578f6eeefb0649e3d420dec531/res/ic_todo.png -------------------------------------------------------------------------------- /res/ic_wechat.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/binaryshao/WanAndroid_RN/e9bdd0b8caf6a0578f6eeefb0649e3d420dec531/res/ic_wechat.png -------------------------------------------------------------------------------- /screenshot/android.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/binaryshao/WanAndroid_RN/e9bdd0b8caf6a0578f6eeefb0649e3d420dec531/screenshot/android.png -------------------------------------------------------------------------------- /screenshot/ios.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/binaryshao/WanAndroid_RN/e9bdd0b8caf6a0578f6eeefb0649e3d420dec531/screenshot/ios.png -------------------------------------------------------------------------------- /src/component/AboutPage.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import {ScrollView, Text, View, StyleSheet, Linking, TouchableWithoutFeedback} from 'react-native'; 3 | import * as config from "../config"; 4 | import HintUtils from "../utils/HintUtils"; 5 | 6 | export default class App extends React.Component { 7 | 8 | static navigationOptions = { 9 | title: "关于我们", 10 | }; 11 | 12 | render() { 13 | return 14 | 15 | 玩安卓-RN 16 | {this.getContent("玩 Android 客户端,可以查看各种开发相关的知识,采用 React-Native 开发,内容已经比较完整。")} 17 | {this.getContent("封装了加载中、空数据、错误、到达最底部等不同状态的视图,在错误时可以点击重新加载,具有较好的用户体验。")} 18 | 19 | 业务内容 20 | {this.getContent("几乎对接了玩安卓的所有 API,主要包括以下内容:")} 21 | {this.getContent("- 注册、登录")} 22 | {this.getContent("- 收藏、取消收藏")} 23 | {this.getContent("- 新增、编辑待办任务")} 24 | {this.getContent("- 查看、搜索各类项目和文章")} 25 | {this.getContent("- 网站导航、知识体系、公众号")} 26 | 27 | 用到的开源库 28 | {this.getContent("react-navigation:界面跳转、创建抽屉布局、创建 Tab 页,创建 HeaderBar(返回按钮、标题、右侧视图)")} 29 | {this.getContent("async-storage:持久化键值对")} 30 | {this.getContent("react-native-modal-datetime-picker:时间选择器")} 31 | {this.getContent("react-native-root-toast:吐司")} 32 | {this.getContent("react-native-swiper:banner")} 33 | {this.getContent("react-native-ultimate-listview:列表,从某些角度来看并不好用")} 34 | 35 | 36 | 感谢 37 | {this.getLink("鸿神提供的数据源", 'https://www.wanandroid.com/blog/show/2')} 38 | 39 | 项目地址 40 | {this.getLink("玩安卓-RN", 'https://github.com/binaryshao/WanAndroid_RN')} 41 | {this.getContent("如果觉得项目还不错,点个 star 鼓励下作者吧 o(╥﹏╥)o")} 42 | {this.getContent("也欢迎大家发起 issue 或提交 PR")} 43 | 44 | 45 | } 46 | 47 | getContent(content) { 48 | return {content} 49 | } 50 | 51 | getLink(title, url) { 52 | return { 53 | if (Linking.canOpenURL(url)) { 54 | Linking.openURL(url).catch(error => { 55 | HintUtils.toast(error) 56 | }); 57 | } else { 58 | navigation.navigate('Web', { 59 | url: url, 60 | title: title 61 | }) 62 | } 63 | }}> 64 | 65 | {title} 66 | 67 | 68 | } 69 | } 70 | 71 | const styles = StyleSheet.create({ 72 | title: { 73 | color: config.textColorPrimary, 74 | fontSize: 18, 75 | marginTop: 30, 76 | }, 77 | content: { 78 | color: config.textColorSecondary, 79 | fontSize: 14, 80 | marginTop: 10, 81 | }, 82 | link: { 83 | color: config.colorPrimary, 84 | fontSize: 16, 85 | marginTop: 10, 86 | }, 87 | }); 88 | -------------------------------------------------------------------------------- /src/component/ArticleListPage.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import {DeviceEventEmitter, FlatList, RefreshControl} from 'react-native'; 3 | import HttpUtils from "../http/HttpUtils"; 4 | import LoadingView from "../widget/LoadingView"; 5 | import ErrorView from "../widget/ErrorView"; 6 | import EmptyView from "../widget/EmptyView"; 7 | import ArticleItemView from "../widget/ArticleItemView"; 8 | import LineDivider from "../widget/LineDivider"; 9 | import EndView from "../widget/EndView"; 10 | import * as config from "../config"; 11 | 12 | let loginSubscription, favoriteSubscription; 13 | 14 | export default class App extends React.Component { 15 | 16 | constructor(props) { 17 | super(props); 18 | this.state = { 19 | isLoading: true, 20 | isError: false, 21 | errorInfo: "", 22 | isAllLoaded: false, 23 | isLoadMoreFailed: false, 24 | pageNo: 1, 25 | data: [] 26 | }; 27 | } 28 | 29 | componentDidMount() { 30 | loginSubscription = DeviceEventEmitter.addListener('login', this.retry.bind(this)); 31 | favoriteSubscription = DeviceEventEmitter.addListener('switchFavorite', this.retry.bind(this)); 32 | this.getData(); 33 | } 34 | 35 | componentWillUnmount() { 36 | DeviceEventEmitter.removeSubscription(loginSubscription); 37 | DeviceEventEmitter.removeSubscription(favoriteSubscription); 38 | } 39 | 40 | getData(isLoadingMore) { 41 | HttpUtils.get("wxarticle/list/" + this.props.chapterId + "/" + this.state.pageNo + "/json", null) 42 | .then(result => { 43 | this.setState({ 44 | isLoading: false, 45 | isError: false, 46 | isLoadMoreFailed: false, 47 | pageNo: this.state.pageNo + 1, 48 | isAllLoaded: this.state.pageNo > result.pageCount, 49 | data: isLoadingMore ? [...this.state.data, ...result.datas] : result.datas 50 | }); 51 | }) 52 | .catch(error => { 53 | this.setState({ 54 | isLoading: false, 55 | isError: !isLoadingMore, 56 | errorInfo: error, 57 | isLoadMoreFailed: isLoadingMore, 58 | }) 59 | }); 60 | } 61 | 62 | retry() { 63 | this.setState({ 64 | isLoading: true, 65 | pageNo: 1, 66 | }); 67 | setTimeout(() => { 68 | this.getData(); 69 | }, 500); 70 | } 71 | 72 | render() { 73 | if (this.state.isLoading) { 74 | return ; 75 | } else if (this.state.isError) { 76 | return ; 77 | } else if (this.state.data.length === 0) { 78 | return ; 79 | } 80 | return } 83 | refreshControl={ { 85 | this.state.pageNo = 1; 86 | this.getData(); 87 | }} 88 | refreshing={this.state.pageNo === 1} 89 | colors={config.refreshColors} 90 | />} 91 | onEndReached={() => { 92 | if (!this.state.isAllLoaded) { 93 | this.loadMore(); 94 | } 95 | }} 96 | ListFooterComponent={() => { 97 | if (this.state.isAllLoaded) { 98 | return ; 99 | } else if (this.state.isLoadMoreFailed) { 100 | return ; 101 | } else { 102 | return ; 103 | } 104 | }} 105 | keyExtractor={(item, index) => index + ""} 106 | ItemSeparatorComponent={LineDivider} 107 | /> 108 | } 109 | 110 | loadMore() { 111 | this.setState({isLoadMoreFailed: false}); 112 | this.getData(true); 113 | } 114 | } -------------------------------------------------------------------------------- /src/component/ArticleTabPage.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import {createMaterialTopTabNavigator, createAppContainer} from 'react-navigation'; 3 | import HttpUtils from "../http/HttpUtils"; 4 | import LoadingView from "../widget/LoadingView"; 5 | import ErrorView from "../widget/ErrorView"; 6 | import EmptyView from "../widget/EmptyView"; 7 | import * as config from "../config"; 8 | import ArticleListPage from "./ArticleListPage"; 9 | 10 | export default class App extends React.Component { 11 | 12 | constructor(props) { 13 | super(props); 14 | this.state = { 15 | isLoading: true, 16 | isEmpty: false, 17 | isError: false, 18 | errorInfo: "", 19 | data: [] 20 | }; 21 | } 22 | 23 | componentDidMount() { 24 | if (this.props.path && this.props.path.length > 0) { 25 | this.getData(); 26 | } else if (this.props.data) { 27 | this.setState({ 28 | isLoading: false, 29 | data: this.props.data 30 | }) 31 | } else { 32 | this.setState({ 33 | isLoading: false, 34 | isError: true, 35 | errorInfo: "未传入请求路径!", 36 | }); 37 | } 38 | } 39 | 40 | getData() { 41 | HttpUtils.get(this.props.path, null) 42 | .then(result => { 43 | this.setState({ 44 | isLoading: false, 45 | isEmpty: result.length <= 0, 46 | data: result 47 | }) 48 | }) 49 | .catch(error => { 50 | this.setState({ 51 | isLoading: false, 52 | isError: true, 53 | errorInfo: error 54 | }) 55 | }); 56 | } 57 | 58 | retry() { 59 | this.setState({ 60 | isLoading: true, 61 | }); 62 | setTimeout(() => { 63 | this.getData(); 64 | }, 500); 65 | } 66 | 67 | render() { 68 | if (this.state.isLoading) { 69 | return ; 70 | } else if (this.state.isError) { 71 | return ; 72 | } else if (this.state.isEmpty) { 73 | return 74 | } 75 | let A = createAppContainer(createMaterialTopTabNavigator(this.createTabs(), { 76 | lazy: true, 77 | swipeEnabled: true, 78 | animationEnabled: true, 79 | backBehavior: "none", 80 | tabBarOptions: { 81 | activeTintColor: 'white', 82 | inactiveTintColor: config.colorPrimaryLight, 83 | scrollEnabled: true, 84 | tabStyle: { 85 | minWidth: 50 86 | }, 87 | labelStyle: { 88 | fontSize: 14, 89 | }, 90 | indicatorStyle: { 91 | height: 2, 92 | backgroundColor: 'white' 93 | }, 94 | style: { 95 | backgroundColor: config.colorPrimary, 96 | height: 45, 97 | justifyContent: 'center', 98 | alignItems: 'center' 99 | } 100 | } 101 | })); 102 | return ; 103 | } 104 | 105 | createTabs() { 106 | let tabPages = {}; 107 | this.state.data.map((value, i) => { 108 | tabPages[value.name] = { 109 | screen: () => 110 | } 111 | }); 112 | return tabPages; 113 | } 114 | 115 | } -------------------------------------------------------------------------------- /src/component/DrawerPage.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { 3 | View, 4 | ScrollView, 5 | Image, 6 | Text, 7 | TouchableHighlight, 8 | StyleSheet, 9 | DeviceEventEmitter, 10 | Platform 11 | } from 'react-native'; 12 | import * as config from "../config" 13 | import HintUtils from "../utils/HintUtils"; 14 | import AccountUtils from "../utils/AccountUtils"; 15 | import HttpUtils from "../http/HttpUtils"; 16 | 17 | let loginSubscription; 18 | 19 | export default class App extends React.Component { 20 | 21 | constructor(props) { 22 | super(props); 23 | this.state = { 24 | userName: '', 25 | }; 26 | } 27 | 28 | componentDidMount() { 29 | loginSubscription = DeviceEventEmitter.addListener('login', this.refreshUser.bind(this)); 30 | this.refreshUser(); 31 | } 32 | 33 | refreshUser() { 34 | AccountUtils.getUserName() 35 | .then(name => { 36 | this.setState({ 37 | userName: name, 38 | }) 39 | }); 40 | } 41 | 42 | componentWillUnmount(): void { 43 | DeviceEventEmitter.removeSubscription(loginSubscription); 44 | } 45 | 46 | render() { 47 | return 48 | { 49 | if (this.state.userName) { 50 | HintUtils.toast('你好...' + this.state.userName) 51 | } else { 52 | this.props.navigation.navigate('Login'); 53 | } 54 | }}> 55 | 56 | 57 | 58 | {this.state.userName ? this.state.userName : "还没有登录..."} 59 | 60 | 61 | 62 | {this.getItemView("收藏夹", require('../../res/ic_favorite_not.png'), () => { 63 | if (this.state.userName) { 64 | this.props.navigation.navigate('Favorite'); 65 | } else { 66 | this.props.navigation.navigate('Login'); 67 | HintUtils.toast("请先登录"); 68 | } 69 | })} 70 | {this.getItemView("任务清单", require('../../res/ic_todo.png'), () => { 71 | if (this.state.userName) { 72 | this.props.navigation.navigate('Todo'); 73 | } else { 74 | this.props.navigation.navigate('Login'); 75 | HintUtils.toast("请先登录"); 76 | } 77 | })} 78 | {this.getItemView("关于", require('../../res/ic_about.png'), () => { 79 | this.props.navigation.navigate('About'); 80 | })} 81 | {this.state.userName ? this.getItemView("退出登录", require('../../res/ic_logout.png'), () => { 82 | HintUtils.alert("退出登录", "确定要退出吗?", () => { 83 | HttpUtils.get('user/logout/json', null) 84 | .then(() => { 85 | this.logout(); 86 | }) 87 | }); 88 | }) : null} 89 | 90 | } 91 | 92 | getItemView(action, image, onPress) { 93 | return 94 | 95 | 96 | 97 | {action} 98 | 99 | 100 | 101 | } 102 | 103 | async logout() { 104 | await AccountUtils.removeUser(); 105 | HintUtils.toast("已退出登录"); 106 | this.setState({ 107 | userName: '', 108 | }); 109 | DeviceEventEmitter.emit('logout'); 110 | } 111 | } 112 | 113 | const styles = StyleSheet.create({ 114 | header: { 115 | backgroundColor: config.colorPrimary, 116 | height: Platform.OS === 'ios' ? 150 + config.iosPaddingTop : 150, 117 | justifyContent: 'center', 118 | alignItems: 'center', 119 | ...Platform.select({ 120 | ios: {paddingTop: config.iosPaddingTop}, 121 | android: {} 122 | }) 123 | }, 124 | avatar: { 125 | width: 90, 126 | height: 90, 127 | borderRadius: 45, 128 | borderWidth: 2, 129 | borderColor: 'white', 130 | }, 131 | userName: { 132 | color: 'white', 133 | fontSize: 16, 134 | marginTop: 20 135 | }, 136 | itemView: { 137 | height: 50, 138 | flexDirection: 'row', 139 | alignItems: 'center', 140 | padding: 10 141 | }, 142 | actionImage: { 143 | width: 20, 144 | height: 20, 145 | }, 146 | action: { 147 | fontSize: 14, 148 | color: config.textColorPrimary, 149 | marginLeft: 15, 150 | }, 151 | }); -------------------------------------------------------------------------------- /src/component/KnowledgePage.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ArticleTabPage from "./ArticleTabPage"; 3 | 4 | export default class App extends React.Component { 5 | 6 | static navigationOptions = ({navigation}) => { 7 | return { 8 | title: navigation.getParam('title', ''), 9 | }; 10 | }; 11 | 12 | render() { 13 | const nav = this.props.navigation; 14 | return 15 | } 16 | } -------------------------------------------------------------------------------- /src/component/SearchPage.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { 3 | View, 4 | Text, 5 | FlatList, 6 | TextInput, 7 | TouchableHighlight, 8 | TouchableWithoutFeedback, 9 | Image, StyleSheet 10 | } from 'react-native'; 11 | import HttpUtils from "../http/HttpUtils"; 12 | import LoadingView from "../widget/LoadingView"; 13 | import ErrorView from "../widget/ErrorView"; 14 | import EmptyView from "../widget/EmptyView"; 15 | import HeaderBar from "../widget/HeaderBar"; 16 | import * as config from "../config"; 17 | import HintUtils from "../utils/HintUtils"; 18 | 19 | const colors = ['orange', 'red', 'green', 'black', 'fuchsia', 'chocolate', 'hotpink', 'tomato']; 20 | 21 | export default class App extends React.Component { 22 | 23 | constructor(props) { 24 | super(props); 25 | this.state = { 26 | isLoading: true, 27 | isError: false, 28 | errorInfo: "", 29 | data: [], 30 | keywords: "", 31 | }; 32 | } 33 | 34 | componentDidMount() { 35 | this.getData(); 36 | } 37 | 38 | getData() { 39 | HttpUtils.get("hotkey/json", null) 40 | .then(result => { 41 | this.setState({ 42 | isLoading: false, 43 | isError: false, 44 | data: [result], 45 | }); 46 | }) 47 | .catch(error => { 48 | this.setState({ 49 | isLoading: false, 50 | isError: true, 51 | errorInfo: error, 52 | }) 53 | }); 54 | } 55 | 56 | retry() { 57 | this.setState({ 58 | isLoading: true, 59 | }); 60 | setTimeout(() => { 61 | this.getData(); 62 | }, 500); 63 | } 64 | 65 | render() { 66 | return 67 | {this.renderHeader()} 68 | {this.renderContent()} 69 | ; 70 | } 71 | 72 | renderContent() { 73 | if (this.state.isLoading) { 74 | return ; 75 | } else if (this.state.isError) { 76 | return ; 77 | } else if (this.state.data.length === 0) { 78 | return ; 79 | } 80 | return index + ""} 84 | /> 85 | } 86 | 87 | renderHeader() { 88 | return 91 | { 97 | this.search(this.state.keywords); 98 | }} 99 | onChangeText={(text) => { 100 | this.setState({keywords: text}) 101 | }}/> 102 | { 104 | this.search(this.state.keywords); 105 | }}> 106 | 107 | 111 | 112 | 113 | 114 | } 115 | /> 116 | } 117 | 118 | renderItem = ({item}) => { 119 | return 120 | 121 | 热搜 122 | 123 | 124 | {item.map((value) => { 125 | let color = colors[Math.ceil(Math.random() * (colors.length - 1))]; 126 | return { 127 | this.search(value.name); 128 | }}> 129 | 130 | {value.name} 131 | 132 | 133 | })} 134 | 135 | 136 | } 137 | 138 | search = (keywords) => { 139 | if (keywords) { 140 | this.props.navigation.navigate( 141 | 'SearchResult', { 142 | keywords: keywords, 143 | }); 144 | } else { 145 | HintUtils.toast("请输入搜索内容..."); 146 | } 147 | } 148 | } 149 | 150 | const styles = StyleSheet.create({ 151 | itemContainer: { 152 | margin: 5, 153 | paddingTop: 5, 154 | paddingBottom: 5, 155 | borderRadius: 10, 156 | }, 157 | title: { 158 | fontSize: 18, 159 | color: config.colorPrimaryDark, 160 | marginLeft: 15 161 | }, 162 | contentContainer: { 163 | flex: 1, 164 | flexDirection: 'row', 165 | flexWrap: 'wrap', 166 | marginTop: 10 167 | }, 168 | content: { 169 | fontSize: 15, 170 | padding: 10, 171 | margin: 5 172 | }, 173 | }); -------------------------------------------------------------------------------- /src/component/SearchResultPage.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import {FlatList, RefreshControl} from 'react-native'; 3 | import HttpUtils from "../http/HttpUtils"; 4 | import LoadingView from "../widget/LoadingView"; 5 | import ErrorView from "../widget/ErrorView"; 6 | import EmptyView from "../widget/EmptyView"; 7 | import ArticleItemView from "../widget/ArticleItemView"; 8 | import LineDivider from "../widget/LineDivider"; 9 | import EndView from "../widget/EndView"; 10 | import * as config from "../config"; 11 | 12 | export default class App extends React.Component { 13 | 14 | static navigationOptions = ({navigation}) => { 15 | return { 16 | title: navigation.getParam('keywords', ''), 17 | }; 18 | }; 19 | 20 | constructor(props) { 21 | super(props); 22 | this.state = { 23 | isLoading: true, 24 | isError: false, 25 | errorInfo: "", 26 | isAllLoaded: false, 27 | isLoadMoreFailed: false, 28 | pageNo: 0, 29 | data: [] 30 | }; 31 | } 32 | 33 | componentDidMount() { 34 | this.getData(); 35 | } 36 | 37 | getData(isLoadingMore) { 38 | HttpUtils.post("article/query/" + this.state.pageNo + "/json", {k: this.props.navigation.getParam('keywords')}) 39 | .then(result => { 40 | this.setState({ 41 | isLoading: false, 42 | isError: false, 43 | isLoadMoreFailed: false, 44 | pageNo: this.state.pageNo + 1, 45 | isAllLoaded: this.state.pageNo + 1 > result.pageCount, 46 | data: isLoadingMore ? [...this.state.data, ...result.datas] : result.datas 47 | }); 48 | }) 49 | .catch(error => { 50 | this.setState({ 51 | isLoading: false, 52 | isError: !isLoadingMore, 53 | errorInfo: error, 54 | isLoadMoreFailed: isLoadingMore, 55 | }) 56 | }); 57 | } 58 | 59 | retry() { 60 | this.setState({ 61 | isLoading: true, 62 | pageNo: 1, 63 | }); 64 | setTimeout(() => { 65 | this.getData(); 66 | }, 500); 67 | } 68 | 69 | render() { 70 | if (this.state.isLoading) { 71 | return ; 72 | } else if (this.state.isError) { 73 | return ; 74 | } else if (this.state.data.length === 0) { 75 | return ; 76 | } 77 | return } 80 | refreshControl={ { 82 | this.state.pageNo = 1; 83 | this.getData(); 84 | }} 85 | refreshing={this.state.pageNo === 1} 86 | colors={config.refreshColors} 87 | />} 88 | onEndReached={() => { 89 | if (!this.state.isAllLoaded) { 90 | this.loadMore(); 91 | } 92 | }} 93 | ListFooterComponent={() => { 94 | if (this.state.isAllLoaded) { 95 | return ; 96 | } else if (this.state.isLoadMoreFailed) { 97 | return ; 98 | } else { 99 | return ; 100 | } 101 | }} 102 | keyExtractor={(item, index) => index + ""} 103 | ItemSeparatorComponent={LineDivider} 104 | /> 105 | } 106 | 107 | loadMore() { 108 | this.setState({isLoadMoreFailed: false}); 109 | this.getData(true); 110 | } 111 | } -------------------------------------------------------------------------------- /src/component/WebPage.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import {WebView} from 'react-native-webview'; 3 | import ErrorView from "../widget/ErrorView"; 4 | import LoadingView from "../widget/LoadingView"; 5 | 6 | export default class App extends React.Component { 7 | 8 | static navigationOptions = ({navigation}) => { 9 | return { 10 | title: navigation.getParam('title', ''), 11 | }; 12 | }; 13 | 14 | render() { 15 | const {navigation} = this.props; 16 | const url = navigation.getParam('url', 'https://www.wanandroid.com/'); 17 | return } 20 | renderError={(domain, code, desc) => } 21 | javaScriptEnabled={true} 22 | domStorageEnabled={true} 23 | scalesPageToFit={false} 24 | />; 25 | } 26 | } -------------------------------------------------------------------------------- /src/component/login/EditTodoPage.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { 3 | View, Text, Image, TextInput, TouchableOpacity, 4 | StyleSheet, DeviceEventEmitter, ScrollView, Switch, Platform 5 | } from 'react-native'; 6 | import DateTimePicker from "react-native-modal-datetime-picker"; 7 | import HintUtils from "../../utils/HintUtils"; 8 | import HttpUtils from "../../http/HttpUtils"; 9 | import * as config from "../../config"; 10 | 11 | let item, isAdd; 12 | 13 | export default class App extends React.Component { 14 | 15 | static navigationOptions = ({navigation}) => { 16 | return { 17 | title: navigation.getParam('isAdd') ? "新建任务" : "编辑任务" 18 | }; 19 | }; 20 | 21 | constructor(props) { 22 | super(props); 23 | this.state = { 24 | title: '', 25 | content: '', 26 | dateStr: this.getToday(), 27 | isLoading: false, 28 | showPicker: false, 29 | date: new Date(), 30 | isCompleted: false 31 | }; 32 | item = this.props.navigation.getParam('item'); 33 | isAdd = this.props.navigation.getParam('isAdd'); 34 | } 35 | 36 | componentDidMount() { 37 | if (item) { 38 | this.setState({ 39 | title: item.title, 40 | content: item.content, 41 | dateStr: item.dateStr, 42 | date: new Date(item.dateStr), 43 | isCompleted: item.status === 1, 44 | }) 45 | } 46 | } 47 | 48 | render() { 49 | return 50 | 51 | 52 | 53 | 标题 : 54 | 55 | { 57 | this.setState({title: text}) 58 | }} 59 | placeholder={"必填"} 60 | defaultValue={this.state.title} 61 | clearButtonMode={'always'} 62 | numberOfLines={1} 63 | style={styles.input} 64 | /> 65 | 66 | 67 | 68 | 内容 : 69 | 70 | { 72 | this.setState({content: text}) 73 | }} 74 | placeholder={"必填"} 75 | defaultValue={this.state.content} 76 | clearButtonMode={'always'} 77 | multiline={true} 78 | style={[styles.input, { 79 | textAlign: 'left', 80 | textAlignVertical: 'center', 81 | ...Platform.select({ 82 | ios: {}, 83 | android: {height: 100} 84 | }), 85 | }]} 86 | /> 87 | 88 | { 89 | this.showPicker(); 90 | }}> 91 | 93 | 94 | 完成时间 : 95 | 96 | 98 | {this.state.dateStr} 99 | 100 | 104 | 105 | 106 | {isAdd ? null : 107 | 108 | 109 | 是否已完成 : 110 | 111 | { 114 | this.setState({isCompleted: value}) 115 | }} 116 | value={this.state.isCompleted} 117 | /> 118 | 119 | } 120 | 121 | 122 | 123 | {this.state.isLoading ? "提交中..." : "提交"} 124 | 125 | 126 | 127 | 133 | 134 | ; 135 | } 136 | 137 | getToday() { 138 | return this.getDateStr(new Date()); 139 | } 140 | 141 | getDateStr(date) { 142 | return date.getFullYear() + "-" + (date.getMonth() + 1) + "-" + date.getDate(); 143 | } 144 | 145 | setDate = date => { 146 | this.setState({ 147 | date: date, 148 | dateStr: this.getDateStr(date), 149 | }); 150 | this.hidePicker(); 151 | }; 152 | 153 | showPicker = () => { 154 | this.setState({showPicker: true}); 155 | }; 156 | 157 | hidePicker = () => { 158 | this.setState({showPicker: false}); 159 | }; 160 | 161 | submit() { 162 | if (this.state.title === '') { 163 | HintUtils.toast("请输入标题"); 164 | } else if (this.state.content === '') { 165 | HintUtils.toast("请输入内容"); 166 | } else if (this.state.dateStr === '') { 167 | HintUtils.toast("请选择完成时间"); 168 | } else { 169 | this.setState({ 170 | isLoading: true 171 | }); 172 | let path, params; 173 | params = { 174 | title: this.state.title, 175 | content: this.state.content, 176 | date: this.state.dateStr, 177 | }; 178 | if (isAdd) { 179 | path = 'lg/todo/add/json'; 180 | } else { 181 | path = 'lg/todo/update/' + item.id + '/json'; 182 | params.status = this.state.isCompleted ? 1 : 0; 183 | } 184 | HttpUtils.post(path, params) 185 | .then(result => { 186 | HintUtils.toast(isAdd ? "新增成功" : "编辑成功"); 187 | DeviceEventEmitter.emit('Todo'); 188 | this.props.navigation.pop(); 189 | }) 190 | .finally(() => { 191 | this.setState({ 192 | isLoading: false 193 | }); 194 | }); 195 | } 196 | } 197 | } 198 | 199 | const styles = StyleSheet.create({ 200 | row: { 201 | flexDirection: 'row', 202 | alignItems: 'center', 203 | marginTop: 20 204 | }, 205 | desc: { 206 | color: config.textColorPrimary, 207 | fontSize: 16 208 | }, 209 | input: { 210 | flex: 1, 211 | backgroundColor: 'white', 212 | marginLeft: 10, 213 | borderColor: 'lightgrey', 214 | borderBottomWidth: 1, 215 | }, 216 | submit: { 217 | width: config.SCREEN_WIDTH * 0.7, 218 | padding: 15, 219 | backgroundColor: config.colorPrimary, 220 | borderRadius: 10, 221 | color: 'white', 222 | textAlign: 'center', 223 | fontSize: 16, 224 | }, 225 | dateStr: { 226 | flex: 1, 227 | marginLeft: 10, 228 | color: config.textColorSecondary, 229 | fontSize: 14, 230 | }, 231 | }); -------------------------------------------------------------------------------- /src/component/login/FavoritePage.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import {DeviceEventEmitter, FlatList, RefreshControl} from 'react-native'; 3 | import HttpUtils from "../../http/HttpUtils"; 4 | import LoadingView from "../../widget/LoadingView"; 5 | import ErrorView from "../../widget/ErrorView"; 6 | import EmptyView from "../../widget/EmptyView"; 7 | import ArticleItemView from "../../widget/ArticleItemView"; 8 | import LineDivider from "../../widget/LineDivider"; 9 | import EndView from "../../widget/EndView"; 10 | import * as config from "../../config"; 11 | 12 | let favoriteSubscription; 13 | 14 | export default class App extends React.Component { 15 | 16 | static navigationOptions = { 17 | title: "我的收藏", 18 | }; 19 | 20 | constructor(props) { 21 | super(props); 22 | this.state = { 23 | isLoading: true, 24 | isError: false, 25 | errorInfo: "", 26 | isAllLoaded: false, 27 | isLoadMoreFailed: false, 28 | pageNo: 0, 29 | data: [] 30 | }; 31 | } 32 | 33 | componentDidMount() { 34 | favoriteSubscription = DeviceEventEmitter.addListener('switchFavorite', ()=>{ 35 | this.setState({ 36 | pageNo: 0, 37 | }); 38 | setTimeout(() => { 39 | this.getData(); 40 | }, 500); 41 | }); 42 | this.getData(); 43 | } 44 | 45 | componentWillUnmount() { 46 | DeviceEventEmitter.removeSubscription(favoriteSubscription); 47 | } 48 | 49 | getData(isLoadingMore) { 50 | HttpUtils.get("lg/collect/list/" + this.state.pageNo + "/json", null) 51 | .then(result => { 52 | this.setState({ 53 | isLoading: false, 54 | isError: false, 55 | isLoadMoreFailed: false, 56 | pageNo: this.state.pageNo + 1, 57 | isAllLoaded: this.state.pageNo + 1 > result.pageCount, 58 | data: isLoadingMore ? [...this.state.data, ...result.datas] : result.datas 59 | }); 60 | }) 61 | .catch(error => { 62 | this.setState({ 63 | isLoading: false, 64 | isError: !isLoadingMore, 65 | errorInfo: error, 66 | isLoadMoreFailed: isLoadingMore, 67 | }) 68 | }); 69 | } 70 | 71 | retry() { 72 | this.setState({ 73 | isLoading: true, 74 | pageNo: 0, 75 | }); 76 | setTimeout(() => { 77 | this.getData(); 78 | }, 500); 79 | } 80 | 81 | render() { 82 | if (this.state.isLoading) { 83 | return ; 84 | } else if (this.state.isError) { 85 | return ; 86 | } else if (this.state.data.length === 0) { 87 | return ; 88 | } 89 | return { 92 | info.item.collect = true; 93 | return 94 | }} 95 | refreshControl={ { 97 | this.state.pageNo = 0; 98 | this.getData(); 99 | }} 100 | refreshing={this.state.pageNo === 0} 101 | colors={config.refreshColors} 102 | />} 103 | onEndReached={() => { 104 | if (!this.state.isAllLoaded) { 105 | this.loadMore(); 106 | } 107 | }} 108 | ListFooterComponent={() => { 109 | if (this.state.isAllLoaded) { 110 | return ; 111 | } else if (this.state.isLoadMoreFailed) { 112 | return ; 113 | } else { 114 | return ; 115 | } 116 | }} 117 | keyExtractor={(item, index) => index + ""} 118 | ItemSeparatorComponent={LineDivider} 119 | />; 120 | } 121 | 122 | loadMore() { 123 | this.setState({isLoadMoreFailed: false}); 124 | this.getData(true); 125 | } 126 | } -------------------------------------------------------------------------------- /src/component/login/LoginPage.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import {View, ScrollView, Text, TextInput, TouchableOpacity, StyleSheet, DeviceEventEmitter} from 'react-native'; 3 | import HintUtils from "../../utils/HintUtils"; 4 | import HttpUtils from "../../http/HttpUtils"; 5 | import * as config from "../../config"; 6 | import AccountUtils from "../../utils/AccountUtils"; 7 | 8 | export default class App extends React.Component { 9 | 10 | static navigationOptions = { 11 | title: "登录", 12 | }; 13 | 14 | constructor(props) { 15 | super(props); 16 | this.state = { 17 | userName: '', 18 | password: '', 19 | isLoading: false, 20 | }; 21 | } 22 | 23 | render() { 24 | return 25 | 26 | { 30 | this.setState({userName: text}) 31 | }} 32 | /> 33 | { 37 | this.setState({password: text}) 38 | }} 39 | /> 40 | 41 | 42 | {this.state.isLoading ? "登录中..." : "登录"} 43 | 44 | 45 | { 46 | this.props.navigation.navigate("Register"); 47 | }}> 48 | 49 | 还没有账号?点击去注册 50 | 51 | 52 | 53 | ; 54 | } 55 | 56 | login() { 57 | if (this.state.userName === '') { 58 | HintUtils.toast("请输入用户名"); 59 | } else if (this.state.password === '') { 60 | HintUtils.toast("请输入密码"); 61 | } else { 62 | this.setState({ 63 | isLoading: true 64 | }); 65 | HttpUtils.post("user/login", { 66 | username: this.state.userName, 67 | password: this.state.password 68 | }) 69 | .then(result => { 70 | this.loginSuccess(result.nickname); 71 | }) 72 | .finally(() => { 73 | this.setState({ 74 | isLoading: false 75 | }); 76 | }); 77 | } 78 | } 79 | 80 | async loginSuccess(name) { 81 | await AccountUtils.saveUserName(name); 82 | HintUtils.toast("登录成功"); 83 | DeviceEventEmitter.emit('login'); 84 | this.props.navigation.pop(); 85 | } 86 | } 87 | 88 | const styles = StyleSheet.create({ 89 | input: { 90 | width: config.SCREEN_WIDTH * 0.8, 91 | height: 50, 92 | backgroundColor: 'white', 93 | margin: 10, 94 | borderColor: 'lightgrey', 95 | borderWidth: 1, 96 | borderRadius: 4, 97 | paddingLeft: 10 98 | }, 99 | login: { 100 | width: config.SCREEN_WIDTH * 0.7, 101 | marginTop: 30, 102 | padding: 15, 103 | backgroundColor: config.colorPrimary, 104 | borderRadius: 10, 105 | color: 'white', 106 | textAlign: 'center', 107 | fontSize: 16, 108 | }, 109 | register: { 110 | color: config.colorPrimaryDark, 111 | marginTop: 20, 112 | fontSize: 14, 113 | }, 114 | }); -------------------------------------------------------------------------------- /src/component/login/RegisterPage.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import {View, ScrollView, Text, TextInput, TouchableOpacity, StyleSheet} from 'react-native'; 3 | import HintUtils from "../../utils/HintUtils"; 4 | import HttpUtils from "../../http/HttpUtils"; 5 | import * as config from "../../config"; 6 | 7 | export default class App extends React.Component { 8 | 9 | static navigationOptions = { 10 | title: "注册", 11 | }; 12 | 13 | constructor(props) { 14 | super(props); 15 | this.state = { 16 | userName: '', 17 | password: '', 18 | passwordAgain: '', 19 | isLoading: false, 20 | }; 21 | } 22 | 23 | render() { 24 | return 25 | 26 | { 30 | this.setState({userName: text}) 31 | }} 32 | /> 33 | { 37 | this.setState({password: text}) 38 | }} 39 | /> 40 | { 44 | this.setState({passwordAgain: text}) 45 | }} 46 | /> 47 | 48 | 49 | {this.state.isLoading ? "注册中..." : "注册"} 50 | 51 | 52 | { 53 | this.props.navigation.navigate("Login"); 54 | }}> 55 | 56 | 已经有账号?点击去登录 57 | 58 | 59 | 60 | ; 61 | } 62 | 63 | register() { 64 | if (this.state.userName === '') { 65 | HintUtils.toast("请输入用户名"); 66 | } else if (this.state.password === '') { 67 | HintUtils.toast("请输入密码"); 68 | } else if (this.state.passwordAgain === '') { 69 | HintUtils.toast("请再次输入密码"); 70 | } else if (this.state.password !== this.state.passwordAgain) { 71 | HintUtils.toast("两次输入的密码不一致"); 72 | } else { 73 | this.setState({ 74 | isLoading: true 75 | }); 76 | HttpUtils.post("user/register", { 77 | username: this.state.userName, 78 | password: this.state.password, 79 | repassword: this.state.passwordAgain, 80 | }) 81 | .then(() => { 82 | HintUtils.toast("注册成功"); 83 | this.props.navigation.pop(); 84 | }) 85 | .finally(() => { 86 | this.setState({ 87 | isLoading: false 88 | }); 89 | }); 90 | } 91 | } 92 | } 93 | 94 | const styles = StyleSheet.create({ 95 | input: { 96 | width: config.SCREEN_WIDTH * 0.8, 97 | height: 50, 98 | backgroundColor: 'white', 99 | margin: 10, 100 | borderColor: 'lightgrey', 101 | borderWidth: 1, 102 | borderRadius: 4, 103 | paddingLeft: 10 104 | }, 105 | register: { 106 | width: config.SCREEN_WIDTH * 0.7, 107 | marginTop: 30, 108 | padding: 15, 109 | backgroundColor: config.colorPrimary, 110 | borderRadius: 10, 111 | color: 'white', 112 | textAlign: 'center', 113 | fontSize: 16, 114 | }, 115 | login: { 116 | color: config.colorPrimaryDark, 117 | marginTop: 20, 118 | fontSize: 14, 119 | }, 120 | }); -------------------------------------------------------------------------------- /src/component/login/TodoPage.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { 3 | View, 4 | Text, 5 | FlatList, 6 | TouchableHighlight, 7 | TouchableWithoutFeedback, 8 | Image, StyleSheet, RefreshControl, DeviceEventEmitter 9 | } from 'react-native'; 10 | import HttpUtils from "../../http/HttpUtils"; 11 | import LoadingView from "../../widget/LoadingView"; 12 | import ErrorView from "../../widget/ErrorView"; 13 | import EmptyView from "../../widget/EmptyView"; 14 | import HeaderBar from "../../widget/HeaderBar"; 15 | import * as config from "../../config"; 16 | import HintUtils from "../../utils/HintUtils"; 17 | import EndView from "../../widget/EndView"; 18 | import AccountUtils from "../../utils/AccountUtils"; 19 | 20 | let todoSubscription; 21 | 22 | export default class App extends React.Component { 23 | 24 | constructor(props) { 25 | super(props); 26 | this.state = { 27 | isLoading: true, 28 | isError: false, 29 | errorInfo: "", 30 | isAllLoaded: false, 31 | isLoadMoreFailed: false, 32 | pageNo: 1, 33 | data: [] 34 | }; 35 | } 36 | 37 | componentDidMount() { 38 | todoSubscription = DeviceEventEmitter.addListener('Todo', this.refresh); 39 | this.getData(); 40 | } 41 | 42 | componentWillUnmount() { 43 | DeviceEventEmitter.removeSubscription(todoSubscription); 44 | } 45 | 46 | refresh = () => { 47 | this.setState({ 48 | pageNo: 1, 49 | }); 50 | setTimeout(() => { 51 | this.getData(); 52 | }, 500); 53 | }; 54 | 55 | getData(isLoadingMore) { 56 | HttpUtils.get("lg/todo/v2/list/" + this.state.pageNo + '/json', null) 57 | .then(result => { 58 | this.setState({ 59 | isLoading: false, 60 | isError: false, 61 | isLoadMoreFailed: false, 62 | pageNo: this.state.pageNo + 1, 63 | isAllLoaded: this.state.pageNo > result.pageCount, 64 | data: isLoadingMore ? [...this.state.data, ...result.datas] : result.datas 65 | }); 66 | }) 67 | .catch(error => { 68 | this.setState({ 69 | isLoading: false, 70 | isError: !isLoadingMore, 71 | errorInfo: error, 72 | isLoadMoreFailed: isLoadingMore, 73 | }) 74 | }); 75 | } 76 | 77 | retry() { 78 | this.setState({ 79 | isLoading: true, 80 | pageNo: 1, 81 | }); 82 | setTimeout(() => { 83 | this.getData(); 84 | }, 500); 85 | } 86 | 87 | render() { 88 | return 89 | {this.renderHeader()} 90 | {this.renderContent()} 91 | ; 92 | } 93 | 94 | renderContent() { 95 | if (this.state.isLoading) { 96 | return ; 97 | } else if (this.state.isError) { 98 | return ; 99 | } else if (this.state.data.length === 0) { 100 | return ; 101 | } 102 | return index + ""} 107 | refreshControl={ { 109 | this.state.pageNo = 1; 110 | this.getData(); 111 | }} 112 | refreshing={this.state.pageNo === 1} 113 | colors={config.refreshColors} 114 | />} 115 | onEndReached={() => { 116 | if (!this.state.isAllLoaded) { 117 | this.loadMore(); 118 | } 119 | }} 120 | ListFooterComponent={() => { 121 | if (this.state.isAllLoaded) { 122 | return ; 123 | } else if (this.state.isLoadMoreFailed) { 124 | return ; 125 | } else { 126 | return ; 127 | } 128 | }} 129 | /> 130 | } 131 | 132 | renderHeader() { 133 | return 137 | { 139 | this.editTodo(); 140 | }}> 141 | 142 | 146 | 147 | 148 | 149 | } 150 | /> 151 | } 152 | 153 | renderItem = ({item}) => { 154 | return { 155 | this.editTodo(false, item); 156 | }}> 157 | 158 | 159 | 160 | {item.title} 161 | 162 | 163 | {item.dateStr} 164 | 165 | 169 | {item.status === 0 ? "未完成" : "已完成"} 170 | 171 | 172 | 173 | 174 | {item.content} 175 | 176 | { 178 | HintUtils.alert("删除任务", `确定要删除任务【${item.title}】吗?`, () => { 179 | this.deleteTodo(item.id); 180 | }) 181 | }}> 182 | 186 | 187 | 188 | 189 | 190 | }; 191 | 192 | loadMore() { 193 | this.setState({isLoadMoreFailed: false}); 194 | this.getData(true); 195 | } 196 | 197 | async deleteTodo(id) { 198 | const cookie = await AccountUtils.getCookie(); 199 | let params = { 200 | 'Cookie': cookie, 201 | }; 202 | HttpUtils.post('lg/todo/delete/' + id + '/json', params) 203 | .then(() => { 204 | HintUtils.toast("已删除"); 205 | this.setState({ 206 | pageNo: 1 207 | }); 208 | setTimeout(() => { 209 | this.getData(); 210 | }) 211 | }, 500) 212 | .catch(error => { 213 | }) 214 | } 215 | 216 | editTodo(isAdd = true, item) { 217 | this.props.navigation.navigate('EditTodo', { 218 | isAdd: isAdd, 219 | item: item 220 | }) 221 | } 222 | } 223 | 224 | const styles = StyleSheet.create({ 225 | itemContainer: { 226 | backgroundColor: 'white', 227 | margin: 5, 228 | padding: 10, 229 | borderRadius: 10, 230 | }, 231 | title: { 232 | flex: 1, 233 | fontSize: 18, 234 | color: config.textColorPrimary, 235 | }, 236 | normalText: { 237 | fontSize: 14, 238 | color: config.textColorSecondary, 239 | }, 240 | }); 241 | -------------------------------------------------------------------------------- /src/component/main/HomePage.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { 3 | Text, View, Image, Platform, StyleSheet, 4 | TouchableWithoutFeedback, SafeAreaView, StatusBar, DeviceEventEmitter, 5 | } from 'react-native'; 6 | import {UltimateListView} from "react-native-ultimate-listview"; 7 | import Swiper from 'react-native-swiper'; 8 | import * as config from "../../config"; 9 | import LineDivider from "../../widget/LineDivider"; 10 | import EmptyView from "../../widget/EmptyView"; 11 | import ErrorView from "../../widget/ErrorView"; 12 | import LoadingView from "../../widget/LoadingView"; 13 | import HttpUtils from "../../http/HttpUtils"; 14 | import ArticleItemView from "../../widget/ArticleItemView" 15 | import MainHeaderBar from "../../widget/MainHeaderBar"; 16 | 17 | let loginSubscription, favoriteSubscription; 18 | 19 | export default class App extends React.Component { 20 | 21 | constructor(props) { 22 | super(props); 23 | this.state = { 24 | isLoading: true, 25 | isError: false, 26 | errorInfo: "", 27 | pageNo: 0, 28 | bannerData: [], 29 | bannerIndex: 0 30 | }; 31 | this.getData = this.getData.bind(this); 32 | } 33 | 34 | componentDidMount() { 35 | loginSubscription = DeviceEventEmitter.addListener('login', this.retry.bind(this)); 36 | favoriteSubscription = DeviceEventEmitter.addListener('switchFavorite', this.retry.bind(this)); 37 | this.getData(); 38 | } 39 | 40 | componentWillUnmount() { 41 | DeviceEventEmitter.removeSubscription(loginSubscription); 42 | DeviceEventEmitter.removeSubscription(favoriteSubscription); 43 | } 44 | 45 | render() { 46 | if (this.state.isLoading) { 47 | return ; 48 | } else if (this.state.isError) { 49 | return ; 50 | } 51 | return this.renderData(); 52 | } 53 | 54 | renderData() { 55 | return 56 | 60 | 61 | this.flatList = ref} 63 | refreshable={true} 64 | refreshableColors={config.refreshColors} 65 | onFetch={this.getData.bind(this)} 66 | header={this.renderHeader.bind(this)} 67 | item={this.renderItem.bind(this)} 68 | emptyView={this.emptyView.bind(this)} 69 | separator={this.separator.bind(this)} 70 | firstLoader={false} 71 | keyExtractor={(item, index) => `${index} - ${item}`} 72 | waitingSpinnerText={config.LOADING} 73 | allLoadedText={config.NO_MORE_DATA} 74 | refreshableTitlePull={config.PULL_TO_REFRESH} 75 | refreshableTitleRelease={config.RELEASE_TO_REFRESH} 76 | refreshableTitleRefreshing={config.REFRESHING} 77 | refreshableMode={Platform.OS === "ios" ? "advanced" : "basic"} 78 | /> 79 | 80 | } 81 | 82 | retry() { 83 | this.setState({ 84 | isLoading: true, 85 | pageNo: 0, 86 | }); 87 | setTimeout(() => { 88 | this.getData(); 89 | }, 500); 90 | } 91 | 92 | getData(page, postRefresh, endFetch) { 93 | if (page === 1 || this.state.pageNo === 0) { 94 | this.state.pageNo = 0; 95 | HttpUtils.get("banner/json", null) 96 | .then(result => { 97 | this.setState({ 98 | bannerData: result 99 | }) 100 | }) 101 | .catch(error => { 102 | this.onError(error); 103 | }); 104 | Promise.all([HttpUtils.get("article/top/json", null), 105 | HttpUtils.get("article/list/0/json", null)]) 106 | .then(result => { 107 | result[0].map((value, i) => { 108 | value.isTop = true; 109 | }); 110 | this.showArticles([...result[0], ...result[1].datas], postRefresh); 111 | }) 112 | .catch(error => { 113 | this.onError(error); 114 | }); 115 | } else { 116 | HttpUtils.get("article/list/" + this.state.pageNo + "/json", null) 117 | .then(result => { 118 | this.showArticles(result.datas, postRefresh); 119 | }) 120 | .catch(error => { 121 | this.onError(error); 122 | }); 123 | } 124 | } 125 | 126 | showArticles(result, postRefresh) { 127 | this.setState({ 128 | isLoading: false, 129 | pageNo: this.state.pageNo + 1 130 | }); 131 | if (postRefresh) { 132 | postRefresh(result, config.PAGE_COUNT); 133 | } else { 134 | this.flatList.postRefresh(result, config.PAGE_COUNT); 135 | } 136 | } 137 | 138 | onError(error) { 139 | this.setState({ 140 | isLoading: false, 141 | isError: true, 142 | errorInfo: error 143 | }) 144 | } 145 | 146 | renderHeader() { 147 | return 148 | {this.state.bannerData != null && this.state.bannerData.length > 0 ? 149 | 150 | this.swiper = r} 152 | autoplay={true} 153 | autoplayTimeout={2} 154 | showsPagination={false} 155 | onMomentumScrollEnd={(e, state, context) => { 156 | this.setState({ 157 | bannerIndex: state.index 158 | } 159 | ) 160 | }} 161 | onTouchStart={() => clearTimeout(this.swiper.autoplayTimer)} 162 | onTouchEnd={() => this.swiper.autoplay()} 163 | > 164 | { 165 | this.state.bannerData.map((value, i) => { 168 | this.props.navigation.navigate('Web', { 169 | url: value.url, 170 | title: value.title 171 | }) 172 | }}> 173 | 174 | ) 175 | } 176 | 177 | 178 | {this.state.bannerData[this.state.bannerIndex].title} 180 | {this.state.bannerIndex + 1}/{this.state.bannerData.length} 184 | 185 | : null} 186 | ; 187 | } 188 | 189 | renderItem(item, index, separators) { 190 | return ; 191 | } 192 | 193 | emptyView() { 194 | return ; 195 | } 196 | 197 | separator() { 198 | return ; 199 | } 200 | } 201 | 202 | const styles = StyleSheet.create({ 203 | bannerHint: { 204 | flex: 1, 205 | width: config.SCREEN_WIDTH, 206 | flexDirection: 'row', 207 | alignItems: 'center', 208 | position: 'absolute', 209 | backgroundColor: 'grey', 210 | opacity: 0.75, 211 | height: 30, 212 | bottom: 0, 213 | paddingStart: 10, 214 | paddingEnd: 10, 215 | }, 216 | bannerText: { 217 | color: 'white', 218 | fontSize: 16 219 | } 220 | }); 221 | -------------------------------------------------------------------------------- /src/component/main/KnowledgeTreePage.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import {View, Text, FlatList, TouchableHighlight, StyleSheet} from 'react-native'; 3 | import HttpUtils from "../../http/HttpUtils"; 4 | import LoadingView from "../../widget/LoadingView"; 5 | import ErrorView from "../../widget/ErrorView"; 6 | import EmptyView from "../../widget/EmptyView"; 7 | import * as config from "../../config"; 8 | import MainHeaderBar from "../../widget/MainHeaderBar"; 9 | 10 | const colors = ['orange', 'red', 'green', 'black', 'fuchsia', 'chocolate', 'hotpink', 'tomato']; 11 | let navigation; 12 | 13 | export default class App extends React.Component { 14 | 15 | constructor(props) { 16 | super(props); 17 | navigation = this.props.navigation; 18 | this.state = { 19 | isLoading: true, 20 | isError: false, 21 | errorInfo: "", 22 | data: [] 23 | }; 24 | } 25 | 26 | componentDidMount() { 27 | this.getData(); 28 | } 29 | 30 | getData() { 31 | HttpUtils.get("tree/json", null) 32 | .then(result => { 33 | this.setState({ 34 | isLoading: false, 35 | isError: false, 36 | data: result, 37 | }); 38 | }) 39 | .catch(error => { 40 | this.setState({ 41 | isLoading: false, 42 | isError: true, 43 | errorInfo: error, 44 | }) 45 | }); 46 | } 47 | 48 | retry() { 49 | this.setState({ 50 | isLoading: true, 51 | }); 52 | setTimeout(() => { 53 | this.getData(); 54 | }, 500); 55 | } 56 | 57 | render() { 58 | if (this.state.isLoading) { 59 | return ; 60 | } else if (this.state.isError) { 61 | return ; 62 | } else if (this.state.data.length === 0) { 63 | return ; 64 | } 65 | return 66 | 67 | index + ""} 71 | /> 72 | 73 | } 74 | 75 | renderItem({item}) { 76 | return { 77 | navigation.navigate('Knowledge', { 78 | title: item.name, 79 | data: item.children, 80 | navigation: navigation 81 | }); 82 | }}> 83 | 84 | 85 | {item.name} 86 | 87 | 88 | {item.children.map((value, index) => { 89 | let color = colors[index % (colors.length)]; 90 | return 91 | 92 | {value.name} 93 | 94 | 95 | })} 96 | 97 | 98 | 99 | } 100 | } 101 | 102 | const styles = StyleSheet.create({ 103 | itemContainer: { 104 | backgroundColor: '#B5E3FF', 105 | margin: 5, 106 | paddingTop: 5, 107 | paddingBottom: 5, 108 | borderRadius: 10, 109 | }, 110 | title: { 111 | fontSize: 18, 112 | color: config.textColorPrimary, 113 | marginLeft: 10 114 | }, 115 | contentContainer: { 116 | flex: 1, 117 | flexDirection: 'row', 118 | flexWrap: 'wrap', 119 | marginTop: 10 120 | }, 121 | textContainer: { 122 | borderRadius: 5, 123 | padding: 6, 124 | margin: 5 125 | }, 126 | content: { 127 | fontSize: 14, 128 | color: 'white', 129 | }, 130 | }); -------------------------------------------------------------------------------- /src/component/main/MainPage.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import {StyleSheet, Image, TouchableWithoutFeedback} from 'react-native'; 3 | import {createBottomTabNavigator} from "react-navigation"; 4 | import HomePage from "./HomePage"; 5 | import WeChatPage from "./WeChatPage"; 6 | import ProjectPage from "./ProjectPage"; 7 | import NavigationPage from "./NavigationPage"; 8 | import KnowledgeTreePage from "./KnowledgeTreePage"; 9 | import * as config from "../../config"; 10 | 11 | export default createBottomTabNavigator({ 12 | Home: { 13 | screen: HomePage, 14 | navigationOptions: { 15 | tabBarLabel: "首页", 16 | tabBarIcon: ({focused, tintColor}) => ( 17 | 21 | ), 22 | } 23 | }, 24 | WeChat: { 25 | screen: WeChatPage, 26 | navigationOptions: { 27 | tabBarLabel: "公众号", 28 | tabBarIcon: ({focused, tintColor}) => ( 29 | 33 | ), 34 | } 35 | }, 36 | Project: { 37 | screen: ProjectPage, 38 | navigationOptions: { 39 | tabBarLabel: "项目", 40 | tabBarIcon: ({focused, tintColor}) => ( 41 | 45 | ), 46 | } 47 | }, 48 | Navigation: { 49 | screen: NavigationPage, 50 | navigationOptions: { 51 | tabBarLabel: "网站导航", 52 | tabBarIcon: ({focused, tintColor}) => ( 53 | 57 | ), 58 | } 59 | }, 60 | KnowledgeTree: { 61 | screen: KnowledgeTreePage, 62 | navigationOptions: { 63 | tabBarLabel: "知识体系", 64 | tabBarIcon: ({focused, tintColor}) => ( 65 | 69 | ), 70 | } 71 | } 72 | }, 73 | { 74 | lazy: true, 75 | backBehavior: "none", 76 | tabBarOptions: { 77 | inactiveTintColor: "grey", 78 | activeTintColor: config.colorPrimary, 79 | labelStyle: { 80 | fontSize: 14, 81 | }, 82 | style: { 83 | height: 50, 84 | paddingBottom: 4, 85 | paddingTop: 4 86 | } 87 | }, 88 | }); 89 | 90 | 91 | const styles = StyleSheet.create({ 92 | tabBar: { 93 | width: 25, 94 | height: 25, 95 | } 96 | }); -------------------------------------------------------------------------------- /src/component/main/NavigationPage.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import {View, Text, FlatList, TouchableHighlight, TouchableWithoutFeedback, StyleSheet} from 'react-native'; 3 | import HttpUtils from "../../http/HttpUtils"; 4 | import LoadingView from "../../widget/LoadingView"; 5 | import ErrorView from "../../widget/ErrorView"; 6 | import EmptyView from "../../widget/EmptyView"; 7 | import * as config from "../../config"; 8 | import LineDivider from "../../widget/LineDivider"; 9 | import MainHeaderBar from "../../widget/MainHeaderBar"; 10 | 11 | const colors = ['orange', 'red', 'green', 'black', 'fuchsia', 'chocolate', 'hotpink', 'tomato']; 12 | let navigation; 13 | 14 | export default class App extends React.Component { 15 | 16 | constructor(props) { 17 | super(props); 18 | navigation = this.props.navigation; 19 | this.state = { 20 | isLoading: true, 21 | isError: false, 22 | errorInfo: "", 23 | data: [] 24 | }; 25 | } 26 | 27 | componentDidMount() { 28 | this.getData(); 29 | } 30 | 31 | getData() { 32 | HttpUtils.get("navi/json", null) 33 | .then(result => { 34 | this.setState({ 35 | isLoading: false, 36 | isError: false, 37 | data: result, 38 | }); 39 | }) 40 | .catch(error => { 41 | this.setState({ 42 | isLoading: false, 43 | isError: true, 44 | errorInfo: error, 45 | }) 46 | }); 47 | } 48 | 49 | retry() { 50 | this.setState({ 51 | isLoading: true, 52 | }); 53 | setTimeout(() => { 54 | this.getData(); 55 | }, 500); 56 | } 57 | 58 | render() { 59 | if (this.state.isLoading) { 60 | return ; 61 | } else if (this.state.isError) { 62 | return ; 63 | } else if (this.state.data.length === 0) { 64 | return ; 65 | } 66 | return 67 | 68 | 69 | index + ""} 73 | ItemSeparatorComponent={LineDivider} 74 | /> 75 | index + ""} 80 | /> 81 | 82 | ; 83 | } 84 | 85 | renderLeftItem({item, index}) { 86 | return { 87 | this.refs.rightFlatList.scrollToIndex({animated: true, index: index, viewOffset: 0, viewPosition: 0}); 88 | }}> 89 | 90 | {item.name} 91 | 92 | 93 | } 94 | 95 | renderRightItem({item}) { 96 | return 97 | 98 | {item.name} 99 | 100 | 101 | {item.articles.map((value, index) => { 102 | let color = colors[index % (colors.length)]; 103 | return { 104 | navigation.navigate('Web', { 105 | title: value.title, 106 | url: value.link, 107 | }); 108 | }}> 109 | 110 | 111 | {value.title} 112 | 113 | 114 | 115 | })} 116 | 117 | 118 | } 119 | } 120 | 121 | const styles = StyleSheet.create({ 122 | leftTitle: { 123 | fontSize: 18, 124 | color: config.textColorPrimary, 125 | padding: 15, 126 | width: 120, 127 | flex: 1, 128 | textAlign: 'left' 129 | }, 130 | itemContainer: { 131 | margin: 5, 132 | paddingTop: 5, 133 | paddingBottom: 5, 134 | borderRadius: 10, 135 | }, 136 | title: { 137 | fontSize: 18, 138 | color: config.textColorPrimary, 139 | marginLeft: 10 140 | }, 141 | contentContainer: { 142 | flex: 1, 143 | flexDirection: 'row', 144 | flexWrap: 'wrap', 145 | marginTop: 10 146 | }, 147 | textContainer: { 148 | borderRadius: 5, 149 | padding: 6, 150 | margin: 5 151 | }, 152 | content: { 153 | fontSize: 14, 154 | color: 'white', 155 | }, 156 | }); -------------------------------------------------------------------------------- /src/component/main/ProjectPage.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ArticleTabPage from "../ArticleTabPage"; 3 | import MainHeaderBar from "../../widget/MainHeaderBar"; 4 | import {View} from "react-native"; 5 | 6 | export default class App extends React.Component { 7 | 8 | render() { 9 | return 10 | 11 | 13 | 14 | } 15 | } -------------------------------------------------------------------------------- /src/component/main/WeChatPage.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ArticleTabPage from "../ArticleTabPage"; 3 | import MainHeaderBar from "../../widget/MainHeaderBar"; 4 | import {View} from "react-native"; 5 | 6 | export default class App extends React.Component { 7 | 8 | render() { 9 | return 10 | 11 | 13 | 14 | } 15 | } -------------------------------------------------------------------------------- /src/config/colors.js: -------------------------------------------------------------------------------- 1 | export const colorPrimary = "#2196F3"; 2 | export const colorPrimaryDark = "#1976D2"; 3 | export const colorPrimaryLight = "#BBDEFB"; 4 | export const colorAccent = "#448AFF"; 5 | export const textColorPrimary = "#212121"; 6 | export const textColorSecondary = "#757575"; 7 | export const divider = "#BDBDBD"; 8 | export const transparent = "#00000000"; 9 | export const refreshColors = ['red', 'green', 'blue']; -------------------------------------------------------------------------------- /src/config/index.js: -------------------------------------------------------------------------------- 1 | export * from "./colors"; 2 | export * from "./strings"; 3 | export * from "./styles"; -------------------------------------------------------------------------------- /src/config/strings.js: -------------------------------------------------------------------------------- 1 | import {Dimensions} from "react-native"; 2 | import * as IOSUtils from "../utils/IOSUtils"; 3 | 4 | export const LOADING = "加载中..."; 5 | export const NO_MORE_DATA = "我是有底线的 o(╥﹏╥)o"; 6 | export const PULL_TO_REFRESH = "下拉刷新..."; 7 | export const RELEASE_TO_REFRESH = "释放刷新..."; 8 | export const REFRESHING = "正在刷新..."; 9 | export const LOAD_FAILED = "加载失败 o(╥﹏╥)o"; 10 | export const NO_DATA = "暂无数据 o(╥﹏╥)o"; 11 | export const CLICK4RETRY = "点击重试"; 12 | export const PAGE_COUNT = 20; 13 | export const SCREEN_WIDTH = Dimensions.get('window').width; 14 | export const iosPaddingTop = IOSUtils.isiPhoneX() ? 34 : 20; 15 | -------------------------------------------------------------------------------- /src/config/styles.js: -------------------------------------------------------------------------------- 1 | export const container = { 2 | flex: 1, 3 | flexDirection: 'column', 4 | justifyContent: 'center', 5 | alignItems: 'center', 6 | }; -------------------------------------------------------------------------------- /src/http/HttpUtils.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import HintUtils from "../utils/HintUtils"; 3 | import AccountUtils from "../utils/AccountUtils"; 4 | 5 | const BASE_URL = "https://www.wanandroid.com/"; 6 | 7 | const getRequestUrl = (path) => { 8 | return BASE_URL + path; 9 | }; 10 | 11 | function getFormData(params) { 12 | let formData = new FormData(); 13 | for (let key in params) { 14 | formData.append(key, params[key]); 15 | } 16 | return formData; 17 | } 18 | 19 | async function saveCookie(cookie) { 20 | return await AccountUtils.saveCookie(cookie); 21 | } 22 | 23 | function request(path, config) { 24 | let url = getRequestUrl(path); 25 | HintUtils.logOrAlert(`${config.method}请求:${url}\n参数:\n${JSON.stringify(config, null, "\t")}`); 26 | let request = new Promise((resolve, reject) => { 27 | fetch(url, config) 28 | .then(response => { 29 | HintUtils.logOrAlert(`${url} 原始返回如下:\n${JSON.stringify(response, null, "\t")}`); 30 | if (response.ok) { 31 | if (response.headers.map.hasOwnProperty('set-cookie')) { 32 | const cookie = response.headers.map['set-cookie']; 33 | HintUtils.logOrAlert(`cookie:${cookie}`); 34 | saveCookie(cookie); 35 | } 36 | return response.json(); 37 | } else { 38 | let msg = "请求失败"; 39 | HintUtils.logOrAlert(msg); 40 | HintUtils.toast(msg); 41 | reject(msg); 42 | } 43 | }) 44 | .then(json => { 45 | HintUtils.logOrAlert(`${url} json返回如下:\n${JSON.stringify(json, null, "\t")}`); 46 | if (json) { 47 | if (json.errorCode.toString() === '0') { 48 | resolve(json.data); 49 | } else { 50 | let msg = `业务失败:${json.errorMsg}`; 51 | HintUtils.logOrAlert(msg); 52 | HintUtils.toast(msg); 53 | reject(msg); 54 | } 55 | } 56 | }) 57 | .catch(error => { 58 | let msg = `请求出错:${error}`; 59 | HintUtils.logOrAlert(msg); 60 | HintUtils.toast(msg); 61 | reject(msg); 62 | }) 63 | .done(); 64 | }); 65 | return timeoutRequest(request); 66 | } 67 | 68 | function timeoutRequest(originalRequest, timeout = 12000) { 69 | let timeoutPromise = new Promise((resolve, reject) => { 70 | setTimeout(() => { 71 | reject("请求超时,请检查网络!"); 72 | }, timeout); 73 | }); 74 | return Promise.race([originalRequest, timeoutPromise]); 75 | } 76 | 77 | export default class NetUtils { 78 | 79 | static get(path, params) { 80 | let config = { 81 | method: 'GET', 82 | body: params, 83 | credentials: 'include' 84 | }; 85 | return request(path, config); 86 | }; 87 | 88 | static post(path, params) { 89 | let config = { 90 | method: 'POST', 91 | body: getFormData(params), 92 | credentials: 'include', 93 | }; 94 | return request(path, config); 95 | }; 96 | } 97 | 98 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import {createStackNavigator, createDrawerNavigator, createAppContainer} from 'react-navigation'; 3 | import MainPage from "./component/main/MainPage" 4 | import DrawerPage from "./component/DrawerPage" 5 | import WebPage from "./component/WebPage"; 6 | import KnowledgePage from "./component/KnowledgePage"; 7 | import LoginPage from "./component/login/LoginPage"; 8 | import RegisterPage from "./component/login/RegisterPage"; 9 | import FavoritePage from "./component/login/FavoritePage"; 10 | import * as config from "./config" 11 | import TodoPage from "./component/login/TodoPage"; 12 | import AboutPage from "./component/AboutPage"; 13 | import EditTodoPage from "./component/login/EditTodoPage"; 14 | import SearchPage from "./component/SearchPage"; 15 | import SearchResultPage from "./component/SearchResultPage"; 16 | 17 | const Main = createDrawerNavigator( 18 | { 19 | home: { 20 | screen: MainPage, 21 | navigationOptions: { 22 | drawerLockMode: "unlocked", 23 | } 24 | } 25 | }, 26 | { 27 | drawerWidth: 250, 28 | drawerPosition: "left", 29 | drawerType: 'slide', 30 | contentComponent: DrawerPage, 31 | drawerLockMode: "locked-open", 32 | initialRouteName: 'home', 33 | contentOptions: {}, 34 | }); 35 | 36 | const pages = { 37 | Home: {screen: Main, navigationOptions: () => ({header: null})}, 38 | Web: {screen: WebPage, navigationOptions: () => ({})}, 39 | Knowledge: {screen: KnowledgePage, navigationOptions: () => ({})}, 40 | Login: {screen: LoginPage, navigationOptions: () => ({})}, 41 | Register: {screen: RegisterPage, navigationOptions: () => ({})}, 42 | Favorite: {screen: FavoritePage, navigationOptions: () => ({})}, 43 | Todo: {screen: TodoPage, navigationOptions: () => ({header: null})}, 44 | About: {screen: AboutPage, navigationOptions: () => ({})}, 45 | Search: {screen: SearchPage, navigationOptions: () => ({header: null})}, 46 | SearchResult: {screen: SearchResultPage, navigationOptions: () => ({})}, 47 | EditTodo: {screen: EditTodoPage, navigationOptions: () => ({})}, 48 | }; 49 | 50 | export default createAppContainer(createStackNavigator(pages, { 51 | initialRouteName: "Home", 52 | navigationOptions: ({navigation, screenProps}) => ({ 53 | gesturesEnabled: true, 54 | }), 55 | defaultNavigationOptions: { 56 | headerStyle: { 57 | backgroundColor: config.colorPrimary, 58 | }, 59 | headerTintColor: 'white', 60 | headerTitleStyle: { 61 | fontWeight: 'bold', 62 | fontSize: 16 63 | }, 64 | }, 65 | })); -------------------------------------------------------------------------------- /src/utils/AccountUtils.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import AsyncStorage from '@react-native-community/async-storage'; 3 | 4 | const userNameKey = 'userName'; 5 | const cookieKey = 'cookie'; 6 | 7 | export default class AccountUtils { 8 | static saveUserName = (name) => { 9 | return AsyncStorage.setItem(userNameKey, name); 10 | }; 11 | 12 | static getUserName = () => { 13 | return AsyncStorage.getItem(userNameKey); 14 | }; 15 | 16 | static removeUser = () => { 17 | return AsyncStorage.removeItem(userNameKey); 18 | }; 19 | 20 | static saveCookie(cookie) { 21 | return AsyncStorage.setItem(cookieKey, cookie); 22 | } 23 | 24 | static getCookie = () => { 25 | return AsyncStorage.getItem(cookieKey); 26 | }; 27 | } 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /src/utils/HintUtils.js: -------------------------------------------------------------------------------- 1 | import {NativeModules, Alert} from 'react-native'; 2 | import Toast from 'react-native-root-toast'; 3 | 4 | const alertShow = false; 5 | 6 | const isDev = () => { 7 | const {scriptURL} = NativeModules.SourceCode; 8 | return scriptURL.split('&')[1] === 'dev=true'; 9 | }; 10 | 11 | export default class HintUtils { 12 | static logOrAlert(msg) { 13 | if (__DEV__) { 14 | if (alertShow) { 15 | alert(msg); 16 | } else { 17 | console.log(msg) 18 | } 19 | } 20 | } 21 | 22 | static toast(msg) { 23 | Toast.show(msg, { 24 | position: Toast.positions.CENTER, 25 | duration: Toast.durations.SHORT, 26 | backgroundColor: 'black', 27 | opacity: 0.7, 28 | animation: true, 29 | textStyle: { 30 | fontSize: 14 31 | }, 32 | }) 33 | } 34 | 35 | static alert(title, msg, onConfirm, onCancel) { 36 | Alert.alert(title, msg, [ 37 | {text: "确定", onPress: onConfirm,}, 38 | {text: "取消", onPress: onCancel} 39 | ], {cancelable: false}); 40 | } 41 | 42 | } -------------------------------------------------------------------------------- /src/utils/IOSUtils.js: -------------------------------------------------------------------------------- 1 | import {Platform, Dimensions} from 'react-native'; 2 | 3 | // iPhone X、iPhone XS 4 | const X_WIDTH = 375; 5 | const X_HEIGHT = 812; 6 | 7 | // iPhone XR、iPhone XS Max 8 | const XSMAX_WIDTH = 414; 9 | const XSMAX_HEIGHT = 896; 10 | 11 | const DEVICE_SIZE = Dimensions.get('window'); 12 | const {height, width} = DEVICE_SIZE; 13 | 14 | export {DEVICE_SIZE}; 15 | 16 | export const isiOS = () => Platform.OS === 'ios'; 17 | 18 | export const isiPhoneX = () => { 19 | return ( 20 | isiOS() && 21 | ((height === X_HEIGHT && width === X_WIDTH) || 22 | (height === X_WIDTH && width === X_HEIGHT)) || 23 | ((height === XSMAX_HEIGHT && width === XSMAX_WIDTH) || 24 | (height === XSMAX_WIDTH && width === XSMAX_HEIGHT)) 25 | ); 26 | }; -------------------------------------------------------------------------------- /src/widget/ArticleItemView.js: -------------------------------------------------------------------------------- 1 | import React, {PureComponent} from "react"; 2 | import {StyleSheet, Text, View, Image, TouchableHighlight, DeviceEventEmitter} from "react-native"; 3 | import {withNavigation} from 'react-navigation'; 4 | 5 | import * as config from "../config"; 6 | import AccountUtils from "../utils/AccountUtils"; 7 | import HintUtils from "../utils/HintUtils"; 8 | import HttpUtils from "../http/HttpUtils"; 9 | 10 | var logoutSubscription; 11 | 12 | class App extends PureComponent { 13 | 14 | constructor() { 15 | super(); 16 | this.state = { 17 | refresh: false 18 | } 19 | } 20 | 21 | componentDidMount() { 22 | this.mounted = true; 23 | logoutSubscription = DeviceEventEmitter.addListener('logout', () => { 24 | if (this.mounted) { 25 | this.props.item.collect = false; 26 | this.setState({ 27 | refresh: !this.state.refresh 28 | }); 29 | } 30 | }); 31 | } 32 | 33 | componentWillUnmount() { 34 | this.mounted = false; 35 | DeviceEventEmitter.removeSubscription(logoutSubscription); 36 | } 37 | 38 | render() { 39 | const {navigation, item} = this.props; 40 | return { 41 | navigation.navigate('Web', { 42 | url: item.link, 43 | title: item.title 44 | }) 45 | }}> 46 | 47 | 48 | 49 | {item.author} 50 | 51 | 52 | {item.fresh ? "新" : ""} 53 | 54 | 55 | {item.isTop ? "置顶" : ""} 56 | 57 | 58 | {item.niceDate} 59 | 60 | 61 | 62 | {this.getImage(item)} 63 | 64 | 65 | {item.title} 66 | 67 | {this.getDesc(item)} 68 | 69 | 70 | 71 | 72 | {item.superChapterName}/{item.chapterName} 73 | 74 | 75 | 79 | 80 | 81 | 82 | ; 83 | } 84 | 85 | getImage(item) { 86 | if (item.envelopePic) { 87 | return ; 91 | } else { 92 | return null; 93 | } 94 | } 95 | 96 | getDesc(item) { 97 | if (item.desc) { 98 | return 99 | {item.desc} 100 | ; 101 | } else { 102 | return null; 103 | } 104 | } 105 | 106 | async switchFavorite() { 107 | const name = await AccountUtils.getUserName(); 108 | if (name === null || name === '') { 109 | this.props.navigation.navigate('Login'); 110 | HintUtils.toast("请先登录"); 111 | } else { 112 | const cookie = await AccountUtils.getCookie(); 113 | const {isFromFavorite, item} = this.props; 114 | let params = isFromFavorite ? { 115 | 'originId': item.originId ? item.originId : -1 116 | } : { 117 | 'Cookie': cookie, 118 | }; 119 | let unCollectPath = isFromFavorite ? 'uncollect/' : 'uncollect_originId/'; 120 | HttpUtils.post('lg/' + (item.collect ? unCollectPath : 'collect/') + item.id + '/json', params) 121 | .then(() => { 122 | HintUtils.toast(item.collect ? "已取消收藏" : "收藏成功"); 123 | if (isFromFavorite) { 124 | DeviceEventEmitter.emit('switchFavorite'); 125 | } else { 126 | item.collect = !item.collect; 127 | this.setState({ 128 | refresh: !this.state.refresh 129 | }); 130 | } 131 | }) 132 | .catch(() => { 133 | }); 134 | } 135 | } 136 | } 137 | 138 | const styles = StyleSheet.create({ 139 | item: { 140 | backgroundColor: "white", 141 | flex: 1, 142 | paddingTop: 5, 143 | paddingBottom: 5, 144 | paddingLeft: 10, 145 | paddingRight: 10 146 | }, 147 | normalText: { 148 | fontSize: 14, 149 | color: config.textColorSecondary, 150 | }, 151 | title: { 152 | color: config.textColorPrimary, 153 | fontSize: 16, 154 | } 155 | }); 156 | 157 | export default withNavigation(App); -------------------------------------------------------------------------------- /src/widget/EmptyView.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import {View, Text, TouchableWithoutFeedback} from 'react-native'; 3 | import * as config from "../config"; 4 | 5 | export default class App extends React.Component { 6 | 7 | render() { 8 | return 9 | 10 | {config.NO_DATA} 11 | {this.props.retry ? {config.CLICK4RETRY} : null} 12 | 13 | 14 | } 15 | } -------------------------------------------------------------------------------- /src/widget/EndView.js: -------------------------------------------------------------------------------- 1 | import React, {Component} from "react"; 2 | import {Text} from "react-native"; 3 | import * as config from "../config"; 4 | 5 | export default class app extends Component { 6 | 7 | render() { 8 | return ( 9 | 10 | {config.NO_MORE_DATA} 11 | 12 | ); 13 | } 14 | } -------------------------------------------------------------------------------- /src/widget/ErrorView.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import {View, Text, TouchableWithoutFeedback} from 'react-native'; 3 | import * as config from "../config"; 4 | 5 | export default class App extends React.Component { 6 | 7 | render() { 8 | return 9 | 10 | {config.LOAD_FAILED} 11 | {this.props.retry ? {config.CLICK4RETRY} : null} 12 | {this.props.error} 13 | 14 | ; 15 | } 16 | } -------------------------------------------------------------------------------- /src/widget/HeaderBar.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import {View, Text, Image, TouchableWithoutFeedback, StyleSheet, Platform} from 'react-native'; 3 | import * as config from "../config"; 4 | import {withNavigation} from 'react-navigation'; 5 | import PropTypes from "prop-types"; 6 | 7 | class App extends React.Component { 8 | 9 | static propTypes = { 10 | title: PropTypes.string, 11 | showBack: PropTypes.bool, 12 | leftView: PropTypes.element, 13 | rightView: PropTypes.element, 14 | onBack: PropTypes.func, 15 | }; 16 | 17 | render() { 18 | const {title, showBack, leftView, rightView, onBack} = this.props; 19 | return 20 | {leftView ? leftView : null} 21 | {showBack ? 22 | 23 | 26 | 27 | : null} 28 | 29 | 32 | {title} 33 | 34 | 35 | {rightView ? rightView : null} 36 | ; 37 | } 38 | 39 | goBack() { 40 | if (this.props.onBack) { 41 | this.props.onBack(); 42 | } else { 43 | this.props.navigation.pop(); 44 | } 45 | } 46 | } 47 | 48 | const styles = StyleSheet.create({ 49 | container: { 50 | height: Platform.OS === 'ios' ? 50 + config.iosPaddingTop : 50, 51 | flexDirection: 'row', 52 | alignItems: 'stretch', 53 | backgroundColor: config.colorPrimary, 54 | paddingTop: Platform.OS === 'ios' ? config.iosPaddingTop : 0, 55 | }, 56 | back: { 57 | height: 15, 58 | width: 15, 59 | marginLeft: 20, 60 | }, 61 | title: { 62 | color: 'white', 63 | fontSize: 18, 64 | fontWeight: 'bold', 65 | textAlignVertical: 'center', 66 | }, 67 | }); 68 | 69 | export default withNavigation(App); -------------------------------------------------------------------------------- /src/widget/LineDivider.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import {View} from 'react-native'; 3 | import * as config from "../config" 4 | 5 | export default class LineDivider extends React.Component { 6 | 7 | render() { 8 | return ; 9 | } 10 | } -------------------------------------------------------------------------------- /src/widget/LoadingView.js: -------------------------------------------------------------------------------- 1 | import React, {Component} from "react"; 2 | import {ActivityIndicator, View, Text} from "react-native"; 3 | import * as config from "../config"; 4 | 5 | export default class app extends Component { 6 | 7 | render() { 8 | return ( 9 | 10 | 16 | {config.LOADING} 17 | 18 | ); 19 | } 20 | } -------------------------------------------------------------------------------- /src/widget/MainHeaderBar.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import {View, Image, TouchableWithoutFeedback} from 'react-native'; 3 | import HeaderBar from "./HeaderBar"; 4 | import {withNavigation} from "react-navigation"; 5 | 6 | class App extends React.Component { 7 | 8 | render() { 9 | return this.props.navigation.toggleDrawer()}> 14 | 15 | 19 | 20 | 21 | } 22 | rightView={ 23 | 24 | this.props.navigation.navigate('Search')}> 26 | 27 | 31 | 32 | 33 | 34 | } 35 | /> 36 | } 37 | } 38 | 39 | export default withNavigation(App); 40 | --------------------------------------------------------------------------------