├── .babelrc ├── .buckconfig ├── .flowconfig ├── .gitattributes ├── .gitignore ├── .watchmanconfig ├── README.md ├── android ├── app │ ├── BUCK │ ├── build.gradle │ ├── proguard-rules.pro │ └── src │ │ └── main │ │ ├── AndroidManifest.xml │ │ ├── assets │ │ └── fonts │ │ │ ├── Entypo.ttf │ │ │ ├── EvilIcons.ttf │ │ │ ├── Feather.ttf │ │ │ ├── FontAwesome.ttf │ │ │ ├── Foundation.ttf │ │ │ ├── Ionicons.ttf │ │ │ ├── MaterialCommunityIcons.ttf │ │ │ ├── MaterialIcons.ttf │ │ │ ├── Octicons.ttf │ │ │ ├── SimpleLineIcons.ttf │ │ │ └── Zocial.ttf │ │ ├── java │ │ └── com │ │ │ └── awesomedemo │ │ │ ├── MainActivity.java │ │ │ └── MainApplication.java │ │ └── res │ │ ├── mipmap-hdpi │ │ └── ic_launcher.png │ │ ├── mipmap-mdpi │ │ └── ic_launcher.png │ │ ├── mipmap-xhdpi │ │ └── ic_launcher.png │ │ ├── mipmap-xxhdpi │ │ └── ic_launcher.png │ │ └── values │ │ ├── strings.xml │ │ └── styles.xml ├── build.gradle ├── gradle.properties ├── gradle │ └── wrapper │ │ ├── gradle-wrapper.jar │ │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── keystores │ ├── BUCK │ └── debug.keystore.properties └── settings.gradle ├── app.json ├── app ├── actions │ ├── ActionTypes.js │ ├── book.js │ ├── rank.js │ └── read.js ├── app.js ├── common │ └── blueTheme.js ├── components │ ├── BookHandler.js │ ├── ListItem.js │ └── Loading.js ├── config.js ├── pages │ ├── book │ │ ├── BookInfo.js │ │ └── Read.js │ ├── find │ │ ├── BookGroup.js │ │ ├── BookGroupDetail.js │ │ ├── BookSearch.js │ │ ├── BookSearchResult.js │ │ ├── BookSortDetail.js │ │ ├── Rank.js │ │ └── RankDetail.js │ └── home │ │ ├── Community.js │ │ ├── Find.js │ │ └── Follow.js ├── reducers │ ├── book.js │ ├── indexReducer.js │ ├── rank.js │ └── read.js ├── res │ ├── default.jpg │ ├── dog_loading.gif │ └── structure_loading.gif ├── root.js ├── store │ └── store.js └── util │ ├── formatUtil.js │ └── modelSchema.js ├── imgs ├── app-release.apk ├── 专题详情.png ├── 书城.jpg ├── 书架.png ├── 书籍详情.png ├── 排行榜.png ├── 排行榜详情.png ├── 推荐专题.png ├── 搜索结果.jpg ├── 搜索页面.png └── 阅读.png ├── index.android.js ├── index.ios.js ├── ios ├── AwesomeDemo-tvOS │ └── Info.plist ├── AwesomeDemo-tvOSTests │ └── Info.plist ├── AwesomeDemo.xcodeproj │ ├── project.pbxproj │ └── xcshareddata │ │ └── xcschemes │ │ ├── AwesomeDemo-tvOS.xcscheme │ │ └── AwesomeDemo.xcscheme ├── AwesomeDemo │ ├── AppDelegate.h │ ├── AppDelegate.m │ ├── Base.lproj │ │ └── LaunchScreen.xib │ ├── Images.xcassets │ │ └── AppIcon.appiconset │ │ │ └── Contents.json │ ├── Info.plist │ └── main.m └── AwesomeDemoTests │ ├── AwesomeDemoTests.m │ └── Info.plist ├── package.json └── yarn.lock /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["react-native"] 3 | } 4 | -------------------------------------------------------------------------------- /.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 | .*/Libraries/react-native/ReactNative.js 16 | 17 | [include] 18 | 19 | [libs] 20 | node_modules/react-native/Libraries/react-native/react-native-interface.js 21 | node_modules/react-native/flow 22 | flow/ 23 | 24 | [options] 25 | emoji=true 26 | 27 | module.system=haste 28 | 29 | experimental.strict_type_args=true 30 | 31 | munge_underscores=true 32 | 33 | 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' 34 | 35 | suppress_type=$FlowIssue 36 | suppress_type=$FlowFixMe 37 | suppress_type=$FixMe 38 | 39 | suppress_comment=\\(.\\|\n\\)*\\$FlowFixMe\\($\\|[^(]\\|(\\(>=0\\.\\(4[0-2]\\|[1-3][0-9]\\|[0-9]\\).[0-9]\\)? *\\(site=[a-z,_]*react_native[a-z,_]*\\)?)\\) 40 | suppress_comment=\\(.\\|\n\\)*\\$FlowIssue\\((\\(>=0\\.\\(4[0-2]\\|[1-3][0-9]\\|[0-9]\\).[0-9]\\)? *\\(site=[a-z,_]*react_native[a-z,_]*\\)?)\\)?:? #[0-9]+ 41 | suppress_comment=\\(.\\|\n\\)*\\$FlowFixedInNextDeploy 42 | suppress_comment=\\(.\\|\n\\)*\\$FlowExpectedError 43 | 44 | unsafe.enable_getters_and_setters=true 45 | 46 | [version] 47 | ^0.42.0 48 | -------------------------------------------------------------------------------- /.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://github.com/fastlane/fastlane/blob/master/fastlane/docs/Gitignore.md 50 | 51 | fastlane/report.xml 52 | fastlane/Preview.html 53 | fastlane/screenshots 54 | -------------------------------------------------------------------------------- /.watchmanconfig: -------------------------------------------------------------------------------- 1 | {} -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # react-native-qingfeng 2 | 参考["简阅" 网络小说阅读器](https://github.com/jsntjinjin/simplereader)实现的一个简单的小说阅读软件,其中实现方式略有不同。实现了书籍信息查看和章节阅读,阅读样式简单调整以及书籍的搜索。 3 | 4 | ## 介绍 5 | 项目基于ReactNative + Redux的框架开发,暂时只适配了Android,后续将适配iOS(现已适配)。项目中的资源API均来自追书神器,纯属共享学习之用,请勿用于商业 6 | 7 | ## TODO 8 | * [ ] 书籍更新提示 9 | * [ ] 缓存书籍到本地 10 | * [ ] 新增更多显示内容(社区讨论内容) 11 | * [ ] 书籍阅读页面的调整,翻页浏览,并且翻页可以自动下一章 12 | * [x] 适配iOS 13 | 14 | ## 体验下载 15 | android下载地址:https://github.com/enowsh/react-native-qingfeng/blob/master/imgs/app-release.apk 16 | 17 | ## 安装 18 | 19 | 1. git clone https://github.com/enowsh/react-native-qingfeng.git 20 | 2. cd react-native-qingfeng 21 | 3. npm install 22 | 4. react-native link 23 | 5. react-native run-android/run-ios 24 | 25 | * 首页 26 | 27 | 28 | 29 | * 排行榜 30 | 31 | 32 | 33 | * 推荐专题 34 | 35 | 36 | 37 | * 搜索 38 | 39 | 40 | 41 | * 书籍详情和阅读页面 42 | 43 | 44 | -------------------------------------------------------------------------------- /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 | lib_deps = [] 12 | 13 | for jarfile in glob(['libs/*.jar']): 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 | 21 | for aarfile in glob(['libs/*.aar']): 22 | name = 'aars__' + aarfile[aarfile.rindex('/') + 1: aarfile.rindex('.aar')] 23 | lib_deps.append(':' + name) 24 | android_prebuilt_aar( 25 | name = name, 26 | aar = aarfile, 27 | ) 28 | 29 | android_library( 30 | name = "all-libs", 31 | exported_deps = lib_deps, 32 | ) 33 | 34 | android_library( 35 | name = "app-code", 36 | srcs = glob([ 37 | "src/main/java/**/*.java", 38 | ]), 39 | deps = [ 40 | ":all-libs", 41 | ":build_config", 42 | ":res", 43 | ], 44 | ) 45 | 46 | android_build_config( 47 | name = "build_config", 48 | package = "com.awesomedemo", 49 | ) 50 | 51 | android_resource( 52 | name = "res", 53 | package = "com.awesomedemo", 54 | res = "src/main/res", 55 | ) 56 | 57 | android_binary( 58 | name = "app", 59 | keystore = "//android/keystores:debug", 60 | manifest = "src/main/AndroidManifest.xml", 61 | package_type = "debug", 62 | deps = [ 63 | ":app-code", 64 | ], 65 | ) 66 | -------------------------------------------------------------------------------- /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 | * // the root of your project, i.e. where "package.json" lives 37 | * root: "../../", 38 | * 39 | * // where to put the JS bundle asset in debug mode 40 | * jsBundleDirDebug: "$buildDir/intermediates/assets/debug", 41 | * 42 | * // where to put the JS bundle asset in release mode 43 | * jsBundleDirRelease: "$buildDir/intermediates/assets/release", 44 | * 45 | * // where to put drawable resources / React Native assets, e.g. the ones you use via 46 | * // require('./image.png')), in debug mode 47 | * resourcesDirDebug: "$buildDir/intermediates/res/merged/debug", 48 | * 49 | * // where to put drawable resources / React Native assets, e.g. the ones you use via 50 | * // require('./image.png')), in release mode 51 | * resourcesDirRelease: "$buildDir/intermediates/res/merged/release", 52 | * 53 | * // by default the gradle tasks are skipped if none of the JS files or assets change; this means 54 | * // that we don't look at files in android/ or ios/ to determine whether the tasks are up to 55 | * // date; if you have any other folders that you want to ignore for performance reasons (gradle 56 | * // indexes the entire tree), add them here. Alternatively, if you have JS files in android/ 57 | * // for example, you might want to remove it from here. 58 | * inputExcludes: ["android/**", "ios/**"], 59 | * 60 | * // override which node gets called and with what additional arguments 61 | * nodeExecutableAndArgs: ["node"], 62 | * 63 | * // supply additional arguments to the packager 64 | * extraPackagerArgs: [] 65 | * ] 66 | */ 67 | 68 | apply from: "../../node_modules/react-native/react.gradle" 69 | 70 | /** 71 | * Set this to true to create two separate APKs instead of one: 72 | * - An APK that only works on ARM devices 73 | * - An APK that only works on x86 devices 74 | * The advantage is the size of the APK is reduced by about 4MB. 75 | * Upload all the APKs to the Play Store and people will download 76 | * the correct one based on the CPU architecture of their device. 77 | */ 78 | def enableSeparateBuildPerCPUArchitecture = false 79 | 80 | /** 81 | * Run Proguard to shrink the Java bytecode in release builds. 82 | */ 83 | def enableProguardInReleaseBuilds = false 84 | 85 | android { 86 | compileSdkVersion 23 87 | buildToolsVersion "23.0.1" 88 | 89 | defaultConfig { 90 | applicationId "com.awesomedemo" 91 | minSdkVersion 16 92 | targetSdkVersion 22 93 | versionCode 1 94 | versionName "1.0" 95 | ndk { 96 | abiFilters "armeabi-v7a", "x86" 97 | } 98 | } 99 | signingConfigs { 100 | release { 101 | storeFile file('my-release-key.keystore') 102 | storePassword 'ReactNativeFeiQiApp' 103 | keyAlias 'my-key-alias' 104 | keyPassword 'ReactNativeFeiQiApp' 105 | } 106 | } 107 | splits { 108 | abi { 109 | reset() 110 | enable enableSeparateBuildPerCPUArchitecture 111 | universalApk false // If true, also generate a universal APK 112 | include "armeabi-v7a", "x86" 113 | } 114 | } 115 | buildTypes { 116 | release { 117 | minifyEnabled enableProguardInReleaseBuilds 118 | proguardFiles getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro" 119 | signingConfig signingConfigs.release 120 | } 121 | } 122 | // applicationVariants are e.g. debug, release 123 | applicationVariants.all { variant -> 124 | variant.outputs.each { output -> 125 | // For each separate APK per architecture, set a unique version code as described here: 126 | // http://tools.android.com/tech-docs/new-build-system/user-guide/apk-splits 127 | def versionCodes = ["armeabi-v7a":1, "x86":2] 128 | def abi = output.getFilter(OutputFile.ABI) 129 | if (abi != null) { // null for the universal-debug, universal-release variants 130 | output.versionCodeOverride = 131 | versionCodes.get(abi) * 1048576 + defaultConfig.versionCode 132 | } 133 | } 134 | } 135 | } 136 | 137 | dependencies { 138 | compile project(':realm') 139 | compile project(':react-native-vector-icons') 140 | compile fileTree(dir: "libs", include: ["*.jar"]) 141 | compile "com.android.support:appcompat-v7:23.0.1" 142 | compile "com.facebook.react:react-native:+" // From node_modules 143 | // For animated GIF support 144 | compile 'com.facebook.fresco:animated-gif:1.0.1' 145 | } 146 | 147 | // Run this once to be able to run the application with BUCK 148 | // puts all compile dependencies into folder libs for BUCK to use 149 | task copyDownloadableDepsToLibs(type: Copy) { 150 | from configurations.compile 151 | into 'libs' 152 | } 153 | -------------------------------------------------------------------------------- /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 | 19 | # Disabling obfuscation is useful if you collect stack traces from production crashes 20 | # (unless you are using a system that supports de-obfuscate the stack traces). 21 | -dontobfuscate 22 | 23 | # React Native 24 | 25 | # Keep our interfaces so they can be used by other ProGuard rules. 26 | # See http://sourceforge.net/p/proguard/bugs/466/ 27 | -keep,allowobfuscation @interface com.facebook.proguard.annotations.DoNotStrip 28 | -keep,allowobfuscation @interface com.facebook.proguard.annotations.KeepGettersAndSetters 29 | -keep,allowobfuscation @interface com.facebook.common.internal.DoNotStrip 30 | 31 | # Do not strip any method/class that is annotated with @DoNotStrip 32 | -keep @com.facebook.proguard.annotations.DoNotStrip class * 33 | -keep @com.facebook.common.internal.DoNotStrip class * 34 | -keepclassmembers class * { 35 | @com.facebook.proguard.annotations.DoNotStrip *; 36 | @com.facebook.common.internal.DoNotStrip *; 37 | } 38 | 39 | -keepclassmembers @com.facebook.proguard.annotations.KeepGettersAndSetters class * { 40 | void set*(***); 41 | *** get*(); 42 | } 43 | 44 | -keep class * extends com.facebook.react.bridge.JavaScriptModule { *; } 45 | -keep class * extends com.facebook.react.bridge.NativeModule { *; } 46 | -keepclassmembers,includedescriptorclasses class * { native ; } 47 | -keepclassmembers class * { @com.facebook.react.uimanager.UIProp ; } 48 | -keepclassmembers class * { @com.facebook.react.uimanager.annotations.ReactProp ; } 49 | -keepclassmembers class * { @com.facebook.react.uimanager.annotations.ReactPropGroup ; } 50 | 51 | -dontwarn com.facebook.react.** 52 | 53 | # TextLayoutBuilder uses a non-public Android constructor within StaticLayout. 54 | # See libs/proxy/src/main/java/com/facebook/fbui/textlayoutbuilder/proxy for details. 55 | -dontwarn android.text.StaticLayout 56 | 57 | # okhttp 58 | 59 | -keepattributes Signature 60 | -keepattributes *Annotation* 61 | -keep class okhttp3.** { *; } 62 | -keep interface okhttp3.** { *; } 63 | -dontwarn okhttp3.** 64 | 65 | # okio 66 | 67 | -keep class sun.misc.Unsafe { *; } 68 | -dontwarn java.nio.file.* 69 | -dontwarn org.codehaus.mojo.animal_sniffer.IgnoreJRERequirement 70 | -dontwarn okio.** 71 | 72 | -------------------------------------------------------------------------------- /android/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 5 | 6 | 7 | 8 | 9 | 10 | 13 | 14 | 20 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /android/app/src/main/assets/fonts/Entypo.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WillianXiang/react-native-qingfeng/cef9b22aeb06398f833892ddf6542b35d3e4545f/android/app/src/main/assets/fonts/Entypo.ttf -------------------------------------------------------------------------------- /android/app/src/main/assets/fonts/EvilIcons.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WillianXiang/react-native-qingfeng/cef9b22aeb06398f833892ddf6542b35d3e4545f/android/app/src/main/assets/fonts/EvilIcons.ttf -------------------------------------------------------------------------------- /android/app/src/main/assets/fonts/Feather.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WillianXiang/react-native-qingfeng/cef9b22aeb06398f833892ddf6542b35d3e4545f/android/app/src/main/assets/fonts/Feather.ttf -------------------------------------------------------------------------------- /android/app/src/main/assets/fonts/FontAwesome.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WillianXiang/react-native-qingfeng/cef9b22aeb06398f833892ddf6542b35d3e4545f/android/app/src/main/assets/fonts/FontAwesome.ttf -------------------------------------------------------------------------------- /android/app/src/main/assets/fonts/Foundation.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WillianXiang/react-native-qingfeng/cef9b22aeb06398f833892ddf6542b35d3e4545f/android/app/src/main/assets/fonts/Foundation.ttf -------------------------------------------------------------------------------- /android/app/src/main/assets/fonts/Ionicons.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WillianXiang/react-native-qingfeng/cef9b22aeb06398f833892ddf6542b35d3e4545f/android/app/src/main/assets/fonts/Ionicons.ttf -------------------------------------------------------------------------------- /android/app/src/main/assets/fonts/MaterialCommunityIcons.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WillianXiang/react-native-qingfeng/cef9b22aeb06398f833892ddf6542b35d3e4545f/android/app/src/main/assets/fonts/MaterialCommunityIcons.ttf -------------------------------------------------------------------------------- /android/app/src/main/assets/fonts/MaterialIcons.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WillianXiang/react-native-qingfeng/cef9b22aeb06398f833892ddf6542b35d3e4545f/android/app/src/main/assets/fonts/MaterialIcons.ttf -------------------------------------------------------------------------------- /android/app/src/main/assets/fonts/Octicons.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WillianXiang/react-native-qingfeng/cef9b22aeb06398f833892ddf6542b35d3e4545f/android/app/src/main/assets/fonts/Octicons.ttf -------------------------------------------------------------------------------- /android/app/src/main/assets/fonts/SimpleLineIcons.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WillianXiang/react-native-qingfeng/cef9b22aeb06398f833892ddf6542b35d3e4545f/android/app/src/main/assets/fonts/SimpleLineIcons.ttf -------------------------------------------------------------------------------- /android/app/src/main/assets/fonts/Zocial.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WillianXiang/react-native-qingfeng/cef9b22aeb06398f833892ddf6542b35d3e4545f/android/app/src/main/assets/fonts/Zocial.ttf -------------------------------------------------------------------------------- /android/app/src/main/java/com/awesomedemo/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.awesomedemo; 2 | 3 | import com.facebook.react.ReactActivity; 4 | 5 | public class MainActivity extends ReactActivity { 6 | 7 | /** 8 | * Returns the name of the main component registered from JavaScript. 9 | * This is used to schedule rendering of the component. 10 | */ 11 | @Override 12 | protected String getMainComponentName() { 13 | return "AwesomeDemo"; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /android/app/src/main/java/com/awesomedemo/MainApplication.java: -------------------------------------------------------------------------------- 1 | package com.awesomedemo; 2 | 3 | import android.app.Application; 4 | 5 | import com.facebook.react.ReactApplication; 6 | import io.realm.react.RealmReactPackage; 7 | import com.oblador.vectoricons.VectorIconsPackage; 8 | import com.facebook.react.ReactNativeHost; 9 | import com.facebook.react.ReactPackage; 10 | import com.facebook.react.shell.MainReactPackage; 11 | import com.facebook.soloader.SoLoader; 12 | 13 | import java.util.Arrays; 14 | import java.util.List; 15 | 16 | public class MainApplication extends Application implements ReactApplication { 17 | 18 | private final ReactNativeHost mReactNativeHost = new ReactNativeHost(this) { 19 | @Override 20 | public boolean getUseDeveloperSupport() { 21 | return BuildConfig.DEBUG; 22 | } 23 | 24 | @Override 25 | protected List getPackages() { 26 | return Arrays.asList( 27 | new MainReactPackage(), 28 | new RealmReactPackage(), 29 | new VectorIconsPackage() 30 | ); 31 | } 32 | }; 33 | 34 | @Override 35 | public ReactNativeHost getReactNativeHost() { 36 | return mReactNativeHost; 37 | } 38 | 39 | @Override 40 | public void onCreate() { 41 | super.onCreate(); 42 | SoLoader.init(this, /* native exopackage */ false); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WillianXiang/react-native-qingfeng/cef9b22aeb06398f833892ddf6542b35d3e4545f/android/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WillianXiang/react-native-qingfeng/cef9b22aeb06398f833892ddf6542b35d3e4545f/android/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WillianXiang/react-native-qingfeng/cef9b22aeb06398f833892ddf6542b35d3e4545f/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WillianXiang/react-native-qingfeng/cef9b22aeb06398f833892ddf6542b35d3e4545f/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 清风 3 | 4 | -------------------------------------------------------------------------------- /android/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 8 | -------------------------------------------------------------------------------- /android/build.gradle: -------------------------------------------------------------------------------- 1 | // Top-level build file where you can add configuration options common to all sub-projects/modules. 2 | 3 | buildscript { 4 | repositories { 5 | jcenter() 6 | } 7 | dependencies { 8 | classpath 'com.android.tools.build:gradle:2.2.3' 9 | 10 | // NOTE: Do not place your application dependencies here; they belong 11 | // in the individual module build.gradle files 12 | } 13 | } 14 | 15 | allprojects { 16 | repositories { 17 | mavenLocal() 18 | jcenter() 19 | maven { 20 | // All of React Native (JS, Obj-C sources, Android binaries) is installed from npm 21 | url "$rootDir/../node_modules/react-native/android" 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /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 | 20 | android.useDeprecatedNdk=true 21 | -------------------------------------------------------------------------------- /android/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WillianXiang/react-native-qingfeng/cef9b22aeb06398f833892ddf6542b35d3e4545f/android/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /android/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | zipStoreBase=GRADLE_USER_HOME 4 | zipStorePath=wrapper/dists 5 | distributionUrl=https\://services.gradle.org/distributions/gradle-2.14.1-all.zip 6 | -------------------------------------------------------------------------------- /android/gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 10 | DEFAULT_JVM_OPTS="" 11 | 12 | APP_NAME="Gradle" 13 | APP_BASE_NAME=`basename "$0"` 14 | 15 | # Use the maximum available, or set MAX_FD != -1 to use that value. 16 | MAX_FD="maximum" 17 | 18 | warn ( ) { 19 | echo "$*" 20 | } 21 | 22 | die ( ) { 23 | echo 24 | echo "$*" 25 | echo 26 | exit 1 27 | } 28 | 29 | # OS specific support (must be 'true' or 'false'). 30 | cygwin=false 31 | msys=false 32 | darwin=false 33 | case "`uname`" in 34 | CYGWIN* ) 35 | cygwin=true 36 | ;; 37 | Darwin* ) 38 | darwin=true 39 | ;; 40 | MINGW* ) 41 | msys=true 42 | ;; 43 | esac 44 | 45 | # For Cygwin, ensure paths are in UNIX format before anything is touched. 46 | if $cygwin ; then 47 | [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"` 48 | fi 49 | 50 | # Attempt to set APP_HOME 51 | # Resolve links: $0 may be a link 52 | PRG="$0" 53 | # Need this for relative symlinks. 54 | while [ -h "$PRG" ] ; do 55 | ls=`ls -ld "$PRG"` 56 | link=`expr "$ls" : '.*-> \(.*\)$'` 57 | if expr "$link" : '/.*' > /dev/null; then 58 | PRG="$link" 59 | else 60 | PRG=`dirname "$PRG"`"/$link" 61 | fi 62 | done 63 | SAVED="`pwd`" 64 | cd "`dirname \"$PRG\"`/" >&- 65 | APP_HOME="`pwd -P`" 66 | cd "$SAVED" >&- 67 | 68 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 69 | 70 | # Determine the Java command to use to start the JVM. 71 | if [ -n "$JAVA_HOME" ] ; then 72 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 73 | # IBM's JDK on AIX uses strange locations for the executables 74 | JAVACMD="$JAVA_HOME/jre/sh/java" 75 | else 76 | JAVACMD="$JAVA_HOME/bin/java" 77 | fi 78 | if [ ! -x "$JAVACMD" ] ; then 79 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 80 | 81 | Please set the JAVA_HOME variable in your environment to match the 82 | location of your Java installation." 83 | fi 84 | else 85 | JAVACMD="java" 86 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 87 | 88 | Please set the JAVA_HOME variable in your environment to match the 89 | location of your Java installation." 90 | fi 91 | 92 | # Increase the maximum file descriptors if we can. 93 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then 94 | MAX_FD_LIMIT=`ulimit -H -n` 95 | if [ $? -eq 0 ] ; then 96 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 97 | MAX_FD="$MAX_FD_LIMIT" 98 | fi 99 | ulimit -n $MAX_FD 100 | if [ $? -ne 0 ] ; then 101 | warn "Could not set maximum file descriptor limit: $MAX_FD" 102 | fi 103 | else 104 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 105 | fi 106 | fi 107 | 108 | # For Darwin, add options to specify how the application appears in the dock 109 | if $darwin; then 110 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 111 | fi 112 | 113 | # For Cygwin, switch paths to Windows format before running java 114 | if $cygwin ; then 115 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 116 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules 158 | function splitJvmOpts() { 159 | JVM_OPTS=("$@") 160 | } 161 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS 162 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" 163 | 164 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" 165 | -------------------------------------------------------------------------------- /android/gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 12 | set DEFAULT_JVM_OPTS= 13 | 14 | set DIRNAME=%~dp0 15 | if "%DIRNAME%" == "" set DIRNAME=. 16 | set APP_BASE_NAME=%~n0 17 | set APP_HOME=%DIRNAME% 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windowz variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | if "%@eval[2+2]" == "4" goto 4NT_args 53 | 54 | :win9xME_args 55 | @rem Slurp the command line arguments. 56 | set CMD_LINE_ARGS= 57 | set _SKIP=2 58 | 59 | :win9xME_args_slurp 60 | if "x%~1" == "x" goto execute 61 | 62 | set CMD_LINE_ARGS=%* 63 | goto execute 64 | 65 | :4NT_args 66 | @rem Get arguments from the 4NT Shell from JP Software 67 | set CMD_LINE_ARGS=%$ 68 | 69 | :execute 70 | @rem Setup the command line 71 | 72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if "%ERRORLEVEL%"=="0" goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 85 | exit /b 1 86 | 87 | :mainEnd 88 | if "%OS%"=="Windows_NT" endlocal 89 | 90 | :omega 91 | -------------------------------------------------------------------------------- /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 = 'AwesomeDemo' 2 | include ':realm' 3 | project(':realm').projectDir = new File(rootProject.projectDir, '../node_modules/realm/android') 4 | include ':react-native-vector-icons' 5 | project(':react-native-vector-icons').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-vector-icons/android') 6 | 7 | include ':app' 8 | -------------------------------------------------------------------------------- /app.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "AwesomeDemo", 3 | "displayName": "AwesomeDemo" 4 | } -------------------------------------------------------------------------------- /app/actions/ActionTypes.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by WAN on 2017/9/8. 3 | */ 4 | 5 | 'use strict'; 6 | // 获取各种排行榜列表 7 | export const GET_RANK_LIST_ACTION = 'GET_RANK_LIST_ACTION'; 8 | //根据id获取排行榜详情信息 周 9 | export const GET_WEEK_RANK_DETAIL_LIST_ACTION = 'GET_WEEK_RANK_DETAIL_LIST_ACTION'; 10 | //根据id获取排行榜详情信息 月 11 | export const GET_MONTH_RANK_DETAIL_LIST_ACTION = 'GET_MONTH_RANK_DETAIL_LIST_ACTION'; 12 | //根据id获取排行榜详情信息 总 13 | export const GET_TOTAL_RANK_DETAIL_LIST_ACTION = 'GET_TOTAL_RANK_DETAIL_LIST_ACTION'; 14 | //获取书籍详情 15 | export const GET_BOOK_DETAIL_ACTION = 'GET_BOOK_DETAIL_ACTION'; 16 | //设置书籍加载信息的状态 17 | export const SET_LOADING_NEW_STATE_ACTION = 'SET_LOADING_NEW_STATE_ACTION'; 18 | //获取书籍的章节目录 19 | export const GET_CHAPTERS_LIST_ACTION = 'GET_CHAPTERS_LIST_ACTION'; 20 | //清除之前获取书籍的章节目录 21 | export const CLEAR_CHAPTERS_LIST_ACTION = 'CLEAR_CHAPTERS_LIST_ACTION'; 22 | //请求资源无效 23 | export const REQUEST_CHAPTERS_SOURCE_INVALID = 'REQUEST_CHAPTERS_SOURCE_INVALID'; 24 | //恢复初始状态 25 | export const RESTORE_REQUEST_CHAPTERS_SOURCE_INVALID = 'RESTORE_REQUEST_CHAPTERS_SOURCE_INVALID'; 26 | //获取章节详情 27 | export const GET_CHAPTER_DETAIL_ACTION = 'GET_CHAPTER_DETAIL_ACTION'; 28 | 29 | //获取数据库中的书籍信息 30 | export const GET_BOOK_SHELVES_ACTION = 'GET_BOOK_SHELVES_ACTION'; 31 | //获取数据库中书籍的最新章节信息 32 | export const GET_SHELVES_BOOK_DETAIL_ACTION = 'GET_SHELVES_BOOK_DETAIL_ACTION'; 33 | 34 | 35 | //书单 36 | //获取书单信息 37 | export const GET_BOOK_LIST_ACTION = 'GET_BOOK_LIST_ACTION'; 38 | //获取书单详情 39 | export const GET_BOOK_LIST_DETAIL_ACTION = 'GET_BOOK_LIST_DETAIL_ACTION'; 40 | 41 | //类别 42 | //获取类别信息 43 | export const GET_CATEGORY_LIST_ACTION = 'GET_CATEGORY_LIST_ACTION'; 44 | //获取二级类别信息 45 | export const GET_CATEGORY_LIST_V2_ACTION = 'GET_CATEGORY_LIST_V2_ACTION'; 46 | //获取类别详情 47 | export const GET_SORT_DETAIL_LIST_ACTION = 'GET_SORT_DETAIL_LIST_ACTION'; 48 | 49 | //搜索 50 | //搜索结果状态 51 | export const SET_BOOK_LOADING_NEW_STATE_ACTION = 'SET_BOOK_LOADING_NEW_STATE_ACTION'; 52 | //获取搜索热词 53 | export const GET_SEARCH_HOT_WORLD_ACTION = 'GET_SEARCH_HOT_WORLD_ACTION'; 54 | //获取搜索结果 55 | export const GET_SEARCH_BOOK_LIST_ACTION = 'GET_SEARCH_BOOK_LIST_ACTION'; -------------------------------------------------------------------------------- /app/actions/book.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by WAN on 2017/9/11. 3 | */ 4 | 5 | 'use strict'; 6 | 7 | import * as types from './ActionTypes'; 8 | import config from '../config'; 9 | 10 | // function getBookDetail(bookId) { 11 | // return(dispatch: any) =>{ 12 | // let apiUrl = config.apiUrl; 13 | // fetch(apiUrl.book.detail,{ 14 | // method: 'GET', 15 | // }).then((response) =>{ 16 | // response.json().then((json) =>{ 17 | // if (json.ok){ 18 | // dispatch({ 19 | // type: types.GET_RANK_LIST_ACTION, 20 | // rankList: json, 21 | // }) 22 | // } else { 23 | // //someThing error 24 | // } 25 | // }); 26 | // }).catch(function(error) { 27 | // //someThing error 28 | // }); 29 | // } 30 | // } 31 | 32 | function getBookShelves() { 33 | let bookshelves = realm.objects('HistoryBook').filtered('isToShow = 1').sorted('sortNum', true); 34 | return(dispatch: any) =>{ 35 | dispatch({ 36 | type: types.GET_BOOK_SHELVES_ACTION, 37 | bookShelves: bookshelves, 38 | }) 39 | } 40 | } 41 | 42 | function deleteBooksFromShelves(bookIds) { 43 | for(let i=0;i { 46 | realm.delete(book); 47 | }); 48 | } 49 | let bookshelves = realm.objects('HistoryBook').filtered('isToShow = 1').sorted('sortNum', true); 50 | return(dispatch: any) =>{ 51 | dispatch({ 52 | type: types.GET_BOOK_SHELVES_ACTION, 53 | bookShelves: bookshelves, 54 | }) 55 | } 56 | } 57 | 58 | function getBooksInfo() { 59 | return(dispatch: any) =>{ 60 | let bookshelves = realm.objects('HistoryBook').filtered('isToShow = 1').sorted('sortNum', true); 61 | for(let i=0;i{ 78 | let apiUrl = config.apiUrl; 79 | fetch(apiUrl.book.detail+bookId,{ 80 | method: 'GET', 81 | }).then((response) =>{ 82 | response.json().then((json) =>{ 83 | if(nowChapterTime!==json.updated){ 84 | realm.write(() => { 85 | realm.create('HistoryBook', {bookId: json._id,nowChapterTime:json.updated}, true) 86 | }) 87 | } 88 | }); 89 | }).catch(function(error) { 90 | //someThing error 91 | }); 92 | } 93 | } 94 | 95 | function getBookDetail(bookId) { 96 | return(dispatch: any) =>{ 97 | let apiUrl = config.apiUrl; 98 | fetch(apiUrl.book.detail+bookId,{ 99 | method: 'GET', 100 | }).then((response) =>{ 101 | response.json().then((json) =>{ 102 | dispatch({ 103 | type: types.GET_BOOK_DETAIL_ACTION, 104 | bookDetail: json, 105 | }) 106 | }); 107 | }).catch(function(error) { 108 | //someThing error 109 | }); 110 | } 111 | } 112 | 113 | function getMonthRankDetail(rankId) { 114 | return(dispatch: any) =>{ 115 | let apiUrl = config.apiUrl; 116 | fetch(apiUrl.discover.chartsDetail+rankId,{ 117 | method: 'GET', 118 | }).then((response) =>{ 119 | response.json().then((json) =>{ 120 | if (json.ok){ 121 | let data = json.ranking; 122 | dispatch({ 123 | type: types.GET_MONTH_RANK_DETAIL_LIST_ACTION, 124 | rankDetailList: data, 125 | }) 126 | } else { 127 | //someThing error 128 | } 129 | }); 130 | }).catch(function(error) { 131 | //someThing error 132 | }); 133 | } 134 | } 135 | 136 | function getTotalRankDetail(rankId) { 137 | return(dispatch: any) =>{ 138 | let apiUrl = config.apiUrl; 139 | fetch(apiUrl.discover.chartsDetail+rankId,{ 140 | method: 'GET', 141 | }).then((response) =>{ 142 | response.json().then((json) =>{ 143 | if (json.ok){ 144 | let data = json.ranking; 145 | dispatch({ 146 | type: types.GET_TOTAL_RANK_DETAIL_LIST_ACTION, 147 | rankDetailList: data, 148 | }) 149 | } else { 150 | //someThing error 151 | } 152 | }); 153 | }).catch(function(error) { 154 | //someThing error 155 | }); 156 | } 157 | } 158 | 159 | function getBookList(pageNum){ 160 | const pageSize = 20; 161 | let start = 0; 162 | if(typeof(pageNum) !== 'undefined' && pageNum>1){ 163 | start = (pageNum-1)*pageSize; 164 | } 165 | return(dispatch: any) =>{ 166 | let apiUrl = config.apiUrl; 167 | fetch(apiUrl.discover.bookList+'?duration=last-seven-days&sort=collectorCount&start='+start,{ 168 | method: 'GET', 169 | }).then((response) =>{ 170 | response.json().then((json) =>{ 171 | if (json.ok){ 172 | let data = json.bookLists; 173 | dispatch({ 174 | type: types.GET_BOOK_LIST_ACTION, 175 | bookLists: data, 176 | pageNumber:pageNum 177 | }) 178 | } else { 179 | //someThing error 180 | } 181 | }); 182 | }).catch(function(error) { 183 | //someThing error 184 | }); 185 | } 186 | } 187 | 188 | function getBookListDetail(bookListId) { 189 | return(dispatch: any) =>{ 190 | let apiUrl = config.apiUrl; 191 | fetch(apiUrl.discover.bookListDetail+bookListId,{ 192 | method: 'GET', 193 | }).then((response) =>{ 194 | response.json().then((json) =>{ 195 | if (json.ok){ 196 | let data = json.bookList; 197 | dispatch({ 198 | type: types.GET_BOOK_LIST_DETAIL_ACTION, 199 | bookList: data, 200 | }) 201 | } else { 202 | //someThing error 203 | } 204 | }); 205 | }).catch(function(error) { 206 | //someThing error 207 | }); 208 | } 209 | } 210 | 211 | function getCategoryList() { 212 | return(dispatch: any) =>{ 213 | let apiUrl = config.apiUrl; 214 | fetch(apiUrl.discover.categoryListV2,{ 215 | method: 'GET', 216 | }).then((response) =>{ 217 | response.json().then((json1) =>{ 218 | if (json1.ok){ 219 | let categoryListV2Temp = json1; 220 | fetch(apiUrl.discover.categoryList,{ 221 | method: 'GET', 222 | }).then((response) =>{ 223 | response.json().then((json2) =>{ 224 | if (json2.ok){ 225 | let categoryListTemp = json2; 226 | dispatch({ 227 | type: types.GET_CATEGORY_LIST_ACTION, 228 | categoryList: categoryListTemp, 229 | categoryListV2:categoryListV2Temp, 230 | }) 231 | } else { 232 | //someThing error 233 | } 234 | }); 235 | }).catch(function(error) { 236 | //someThing error 237 | }); 238 | } else { 239 | //someThing error 240 | } 241 | }); 242 | }).catch(function(error) { 243 | //someThing error 244 | }); 245 | 246 | } 247 | } 248 | 249 | function getCategoryListV2() { 250 | return(dispatch: any) =>{ 251 | let apiUrl = config.apiUrl; 252 | fetch(apiUrl.discover.categoryListV2,{ 253 | method: 'GET', 254 | }).then((response) =>{ 255 | response.json().then((json) =>{ 256 | if (json.ok){ 257 | let data = json; 258 | dispatch({ 259 | type: types.GET_CATEGORY_LIST_V2_ACTION, 260 | categoryListV2: data, 261 | }) 262 | } else { 263 | //someThing error 264 | } 265 | }); 266 | }).catch(function(error) { 267 | //someThing error 268 | }); 269 | } 270 | } 271 | 272 | function getSortDetail(major,pageNum,type,gender) { 273 | const pageSize = 20; 274 | let start = 0; 275 | if(typeof(pageNum) !== 'undefined' && pageNum>1){ 276 | start = (pageNum-1)*pageSize; 277 | } 278 | if(gender === 'press'){ 279 | return(dispatch: any) =>{ 280 | let apiUrl = config.apiUrl; 281 | fetch(apiUrl.discover.categoryBooks+'?type='+type+'&&limit=20&major='+major+'&start='+start+'&gender='+gender,{ 282 | method: 'GET', 283 | }).then((response) =>{ 284 | response.json().then((json) =>{ 285 | if (json.ok){ 286 | let data = json.books; 287 | dispatch({ 288 | type: types.GET_SORT_DETAIL_LIST_ACTION, 289 | sortType:gender, 290 | pressDetailList: data, 291 | pageNumber:pageNum, 292 | }) 293 | } else { 294 | //someThing error 295 | } 296 | }); 297 | }).catch(function(error) { 298 | //someThing error 299 | }); 300 | } 301 | }else{ 302 | return(dispatch: any) =>{ 303 | let apiUrl = config.apiUrl; 304 | fetch(apiUrl.discover.categoryBooks+'?type='+type+'&&limit=20&major='+major+'&start='+start+'&gender='+gender,{ 305 | method: 'GET', 306 | }).then((response) =>{ 307 | response.json().then((json) =>{ 308 | if (json.ok){ 309 | let data = json.books; 310 | if(type === "hot"){ 311 | dispatch({ 312 | type: types.GET_SORT_DETAIL_LIST_ACTION, 313 | sortType:type, 314 | hotDetailList: data, 315 | pageNumber:pageNum, 316 | }) 317 | }else if(type === "new"){ 318 | dispatch({ 319 | type: types.GET_SORT_DETAIL_LIST_ACTION, 320 | sortType:type, 321 | newDetailList: data, 322 | pageNumber:pageNum, 323 | }) 324 | } 325 | else if(type === "reputation"){ 326 | dispatch({ 327 | type: types.GET_SORT_DETAIL_LIST_ACTION, 328 | sortType:type, 329 | reputationDetailList: data, 330 | pageNumber:pageNum, 331 | }) 332 | } 333 | else if(type === "over"){ 334 | dispatch({ 335 | type: types.GET_SORT_DETAIL_LIST_ACTION, 336 | sortType:type, 337 | overDetailList: data, 338 | pageNumber:pageNum, 339 | }) 340 | } 341 | } else { 342 | //someThing error 343 | } 344 | }); 345 | }).catch(function(error) { 346 | //someThing error 347 | }); 348 | } 349 | } 350 | 351 | } 352 | 353 | function getSearchHotWord() { 354 | return(dispatch: any) =>{ 355 | let apiUrl = config.apiUrl; 356 | fetch(apiUrl.search.searchHotWord,{ 357 | method: 'GET', 358 | }).then((response) =>{ 359 | response.json().then((json) =>{ 360 | if (json.ok){ 361 | let data = json.hotWords; 362 | dispatch({ 363 | type: types.GET_SEARCH_HOT_WORLD_ACTION, 364 | searchHotWord: data, 365 | }) 366 | } else { 367 | //someThing error 368 | } 369 | }); 370 | }).catch(function(error) { 371 | //someThing error 372 | }); 373 | } 374 | } 375 | 376 | function getSearchBookListForKeyWord(keyWord,pageNum) { 377 | const pageSize = 20; 378 | let start = 0; 379 | if(typeof(pageNum) !== 'undefined' && pageNum>1){ 380 | start = (pageNum-1)*pageSize; 381 | } 382 | return(dispatch: any) =>{ 383 | let apiUrl = config.apiUrl; 384 | dispatch(setLoadState(true)); 385 | fetch(apiUrl.search.searchBooks+keyWord+'&limit=20&start='+start,{ 386 | method: 'GET', 387 | }).then((response) =>{ 388 | response.json().then((json) =>{ 389 | if (json.ok){ 390 | let data = json.books; 391 | dispatch({ 392 | type: types.GET_SEARCH_BOOK_LIST_ACTION, 393 | searchBookList: data, 394 | pageNumber:pageNum, 395 | }); 396 | dispatch(setLoadState(false)); 397 | } else { 398 | //someThing error 399 | } 400 | }); 401 | }).catch(function(error) { 402 | //someThing error 403 | }); 404 | } 405 | } 406 | 407 | function getSearchBookListForAuthor(author,pageNum) { 408 | const pageSize = 20; 409 | let start = 0; 410 | if(typeof(pageNum) !== 'undefined' && pageNum>1){ 411 | start = (pageNum-1)*pageSize; 412 | } 413 | return(dispatch: any) =>{ 414 | let apiUrl = config.apiUrl; 415 | dispatch(setLoadState(true)); 416 | fetch(apiUrl.book.authorBookList+'?author='+author+'&limit=20&start='+start,{ 417 | method: 'GET', 418 | }).then((response) =>{ 419 | response.json().then((json) =>{ 420 | if (json.ok){ 421 | let data = json.books; 422 | dispatch({ 423 | type: types.GET_SEARCH_BOOK_LIST_ACTION, 424 | searchBookList: data, 425 | pageNumber:pageNum, 426 | }); 427 | dispatch(setLoadState(false)); 428 | } else { 429 | //someThing error 430 | } 431 | }); 432 | }).catch(function(error) { 433 | //someThing error 434 | }); 435 | } 436 | } 437 | 438 | function getSearchBookListForTag(tag,pageNum) { 439 | const pageSize = 20; 440 | let start = 0; 441 | if(typeof(pageNum) !== 'undefined' && pageNum>1){ 442 | start = (pageNum-1)*pageSize; 443 | } 444 | return(dispatch: any) =>{ 445 | let apiUrl = config.apiUrl; 446 | dispatch(setLoadState(true)); 447 | fetch(apiUrl.book.tagBookList+'?tags='+tag+'&limit=20&start='+start,{ 448 | method: 'GET', 449 | }).then((response) =>{ 450 | response.json().then((json) =>{ 451 | if (json.ok){ 452 | let data = json.books; 453 | dispatch({ 454 | type: types.GET_SEARCH_BOOK_LIST_ACTION, 455 | searchBookList: data, 456 | pageNumber:pageNum, 457 | }); 458 | dispatch(setLoadState(false)); 459 | } else { 460 | //someThing error 461 | } 462 | }); 463 | }).catch(function(error) { 464 | //someThing error 465 | }); 466 | } 467 | } 468 | 469 | function setLoadState(state) { 470 | return (dispatch: any) =>{ 471 | dispatch({ 472 | type: types.SET_BOOK_LOADING_NEW_STATE_ACTION, 473 | loadBookState:state 474 | }); 475 | } 476 | } 477 | 478 | module.exports = { 479 | getBookDetail, 480 | getBookShelves, 481 | deleteBooksFromShelves, 482 | getBookList, 483 | getBookListDetail, 484 | getCategoryList, 485 | getCategoryListV2, 486 | getSortDetail, 487 | getSearchHotWord, 488 | getSearchBookListForKeyWord, 489 | getSearchBookListForAuthor, 490 | getSearchBookListForTag, 491 | getBooksInfo 492 | }; 493 | -------------------------------------------------------------------------------- /app/actions/rank.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by WAN on 2017/9/11. 3 | */ 4 | 5 | 'use strict'; 6 | 7 | import * as types from './ActionTypes'; 8 | import config from '../config'; 9 | 10 | function getRankList() { 11 | return(dispatch: any) =>{ 12 | let apiUrl = config.apiUrl; 13 | fetch(apiUrl.discover.charts,{ 14 | method: 'GET', 15 | }).then((response) =>{ 16 | response.json().then((json) =>{ 17 | if (json.ok){ 18 | dispatch({ 19 | type: types.GET_RANK_LIST_ACTION, 20 | rankList: json, 21 | }) 22 | } else { 23 | //someThing error 24 | } 25 | }); 26 | }).catch(function(error) { 27 | //someThing error 28 | }); 29 | } 30 | } 31 | 32 | function getWeekRankDetail(rankId) { 33 | return(dispatch: any) =>{ 34 | let apiUrl = config.apiUrl; 35 | fetch(apiUrl.discover.chartsDetail+rankId,{ 36 | method: 'GET', 37 | }).then((response) =>{ 38 | response.json().then((json) =>{ 39 | if (json.ok){ 40 | let data = json.ranking; 41 | dispatch({ 42 | type: types.GET_WEEK_RANK_DETAIL_LIST_ACTION, 43 | rankDetailList: data, 44 | }) 45 | } else { 46 | //someThing error 47 | } 48 | }); 49 | }).catch(function(error) { 50 | //someThing error 51 | }); 52 | } 53 | } 54 | 55 | function getMonthRankDetail(rankId) { 56 | return(dispatch: any) =>{ 57 | let apiUrl = config.apiUrl; 58 | fetch(apiUrl.discover.chartsDetail+rankId,{ 59 | method: 'GET', 60 | }).then((response) =>{ 61 | response.json().then((json) =>{ 62 | if (json.ok){ 63 | let data = json.ranking; 64 | dispatch({ 65 | type: types.GET_MONTH_RANK_DETAIL_LIST_ACTION, 66 | rankDetailList: data, 67 | }) 68 | } else { 69 | //someThing error 70 | } 71 | }); 72 | }).catch(function(error) { 73 | //someThing error 74 | }); 75 | } 76 | } 77 | 78 | function getTotalRankDetail(rankId) { 79 | return(dispatch: any) =>{ 80 | let apiUrl = config.apiUrl; 81 | fetch(apiUrl.discover.chartsDetail+rankId,{ 82 | method: 'GET', 83 | }).then((response) =>{ 84 | response.json().then((json) =>{ 85 | if (json.ok){ 86 | let data = json.ranking; 87 | dispatch({ 88 | type: types.GET_TOTAL_RANK_DETAIL_LIST_ACTION, 89 | rankDetailList: data, 90 | }) 91 | } else { 92 | //someThing error 93 | } 94 | }); 95 | }).catch(function(error) { 96 | //someThing error 97 | }); 98 | } 99 | } 100 | 101 | module.exports = { 102 | getRankList, 103 | getWeekRankDetail, 104 | getMonthRankDetail, 105 | getTotalRankDetail 106 | }; 107 | -------------------------------------------------------------------------------- /app/actions/read.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by WAN on 2017/9/12. 3 | */ 4 | 5 | 'use strict'; 6 | 7 | import Toast from 'react-native-root-toast'; 8 | import * as types from './ActionTypes'; 9 | import config from '../config'; 10 | 11 | function getBookChapters(bookId) { 12 | return(dispatch: any) =>{ 13 | let apiUrl = config.apiUrl; 14 | dispatch({ 15 | type: types.CLEAR_CHAPTERS_LIST_ACTION, 16 | }); 17 | fetch(apiUrl.read.readBookChapterList+bookId+'?view=chapters',{ 18 | method: 'GET', 19 | }).then((response) =>{ 20 | response.json().then((json) =>{ 21 | if (json.ok){ 22 | dispatch({ 23 | type: types.GET_CHAPTERS_LIST_ACTION, 24 | mixTocChapters: json.mixToc.chapters, 25 | }) 26 | }else{ 27 | Toast.show("此书籍的资源失效了!",{position:Toast.positions.BOTTOM - 35,duration:Toast.durations.SHORT}); 28 | dispatch({ 29 | type: types.REQUEST_CHAPTERS_SOURCE_INVALID, 30 | }); 31 | } 32 | }); 33 | }).catch(function(error) { 34 | //someThing error 35 | }); 36 | } 37 | } 38 | 39 | function restoreRequestInvalidState() { 40 | return(dispatch: any) =>{ 41 | dispatch({ 42 | type: types.RESTORE_REQUEST_CHAPTERS_SOURCE_INVALID, 43 | }); 44 | } 45 | } 46 | 47 | function getBookChapterDetail(chapterUrl) { 48 | let tempChapterUrl = chapterUrl.replace(/\//g, '%2F').replace('?', '%3F'); 49 | return(dispatch: any) =>{ 50 | let apiUrl = config.apiUrl; 51 | dispatch(setLoadState(true)); 52 | fetch(apiUrl.read.readBookChapterDetail+tempChapterUrl,{ 53 | method: 'GET', 54 | }).then((response) =>{ 55 | response.json().then((json) =>{ 56 | if (json.ok){ 57 | let chapter = json.chapter; 58 | dispatch({ 59 | type: types.GET_CHAPTER_DETAIL_ACTION, 60 | chapter: chapter, 61 | }); 62 | dispatch(setLoadState(false)); 63 | } else { 64 | Toast.show("章节信息发生错误!",{position:Toast.positions.BOTTOM - 35,duration:Toast.durations.LONG}); 65 | } 66 | }); 67 | }).catch(function(error) { 68 | //someThing error 69 | }); 70 | } 71 | } 72 | 73 | function setLoadState(state) { 74 | return (dispatch: any) =>{ 75 | dispatch({ 76 | type: types.SET_LOADING_NEW_STATE_ACTION, 77 | loadReadState:state 78 | }); 79 | } 80 | } 81 | 82 | 83 | module.exports = { 84 | getBookChapters, 85 | getBookChapterDetail, 86 | restoreRequestInvalidState 87 | }; 88 | -------------------------------------------------------------------------------- /app/app.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by WAN on 2017/9/6. 3 | */ 4 | 5 | import React,{Component} from 'react'; 6 | 7 | import { 8 | Platform, 9 | } from 'react-native'; 10 | 11 | import { 12 | StackNavigator, 13 | TabNavigator, 14 | } from 'react-navigation'; 15 | 16 | //Home pages 17 | import Follow from "./pages/home/Follow"; 18 | import Community from "./pages/home/Community"; 19 | import Find from "./pages/home/Find"; 20 | //Find pages 21 | import Rank from "./pages/find/Rank"; 22 | import RankDetail from "./pages/find/RankDetail"; 23 | import BookGroup from "./pages/find/BookGroup"; 24 | import BookGroupDetail from "./pages/find/BookGroupDetail"; 25 | import BookSortDetail from "./pages/find/BookSortDetail"; 26 | import BookSearch from "./pages/find/BookSearch"; 27 | import BookSearchResult from "./pages/find/BookSearchResult"; 28 | //Book detail 29 | import BookInfo from "./pages/book/BookInfo"; 30 | import Read from "./pages/book/Read"; 31 | 32 | //首页的tab 33 | const HomeTabs = TabNavigator({ 34 | Follow:{screen:Follow, 35 | navigationOptions:({navigation,screenProps}) =>({ 36 | tabBarLabel:'书架', 37 | })}, 38 | Find:{screen:Find, 39 | navigationOptions:({navigation,screenProps}) =>({ 40 | tabBarLabel:'书城', 41 | })} 42 | },{ 43 | tabBarPosition: 'top', 44 | animationEnabled: true, 45 | swipeEnabled:true, 46 | tabBarOptions:{ 47 | style: { 48 | backgroundColor: '#1B89EB', 49 | height:Platform.OS == "ios"?49:56, 50 | }, 51 | labelStyle: { 52 | fontSize: 16, 53 | }, 54 | activeTintColor: '#fff', 55 | // activeBackgroundColor:'#fff', 56 | inactiveTintColor:'#000', 57 | // inactiveBackgroundColor:'#1B89EB', 58 | // indicatorStyle: { 59 | // height: 0 // 如TabBar下面显示有一条线,可以设高度为0后隐藏 60 | // }, 61 | } 62 | }); 63 | 64 | const AppNavigator = StackNavigator({ 65 | Home: { screen: HomeTabs, 66 | navigationOptions:({navigation,screenProps}) => ({ 67 | // StackNavigator 属性部分 68 | 69 | // title:'Test1', 同步设置导航和tabbar文字,不推荐使用 70 | // headerTitle:'', // 只会设置导航栏文字, 71 | header:null, // 可以自定义导航条内容,如果需要隐藏可以设置为null 72 | // headerBackTitle:null, // 设置跳转页面左侧返回箭头后面的文字,默认是上一个页面的标题。可以自定义,也可以设置为null 73 | // headerTruncatedBackTitle:'', // 设置当上个页面标题不符合返回箭头后的文字时,默认改成"返回"。 74 | // headerRight:{}, // 设置导航条右侧。可以是按钮或者其他。 75 | // headerLeft:{}, // 设置导航条左侧。可以是按钮或者其他。 76 | // headerStyle:{ 77 | // backgroundColor:'#4ECBFC', 78 | // }, // 设置导航条的样式。如果想去掉安卓导航条底部阴影可以添加elevation: 0,iOS去掉阴影是。 79 | // headerStyle:{ 80 | // backgroundColor:'#1B89EB', 81 | // }, // 设置导航条的样式。如果想去掉安卓导航条底部阴影可以添加elevation: 0,iOS去掉阴影是。 82 | // headerTitleStyle:{ 83 | // fontSize:55, 84 | // }, // 设置导航条文字样式。安卓上如果要设置文字居中,只要添加alignSelf:'center'就可以了 85 | // headerBackTitleStyle:{}, // 设置导航条返回文字样式。 86 | // headerTintColor:'#000', // 设置导航栏文字颜色。总感觉和上面重叠了。 87 | gesturesEnabled:true, // 是否支持滑动返回收拾,iOS默认支持,安卓默认关闭 88 | swipeEnabled:true, 89 | animationEnabled:true, 90 | // TabNavigator 属性部分 91 | 92 | // title:'', 同上 93 | tabBarVisible:false, // 是否隐藏标签栏。默认不隐藏(true) 94 | // tabBarIcon: (({tintColor,focused}) => { 95 | // return( 96 | // 100 | // ) 101 | // }), // 设置标签栏的图标。需要单独设置。 102 | //tabBarLabel:'', // 设置标签栏的title。推荐这个方式。 103 | })}, 104 | Rank:{screen:Rank, 105 | navigationOptions:({navigation,screenProps}) => ({ 106 | headerTitle:'排行榜', 107 | headerStyle:{ 108 | backgroundColor:'#1B89EB', 109 | 110 | }, // 设置导航条的样式。如果想去掉安卓导航条底部阴影可以添加elevation: 0,iOS去掉阴影是。 111 | headerBackTitle:null, 112 | headerTitleStyle:{ 113 | alignItems:'center', 114 | justifyContent:'center', 115 | color:'#fff' 116 | } 117 | })}, 118 | RankDetail:{screen:RankDetail, 119 | navigationOptions:({navigation,screenProps}) => ({ 120 | headerStyle:{ 121 | backgroundColor:'#1B89EB', 122 | }, // 设置导航条的样式。如果想去掉安卓导航条底部阴影可以添加elevation: 0,iOS去掉阴影是。 123 | headerBackTitle:null, 124 | headerTintColor:'#fff', 125 | })}, 126 | BookGroup:{screen:BookGroup, 127 | navigationOptions:({navigation,screenProps}) => ({ 128 | headerStyle:{ 129 | backgroundColor:'#1B89EB', 130 | }, // 设置导航条的样式。如果想去掉安卓导航条底部阴影可以添加elevation: 0,iOS去掉阴影是。 131 | headerBackTitle:null, 132 | headerTintColor:'#fff', 133 | })}, 134 | BookGroupDetail:{screen:BookGroupDetail, 135 | navigationOptions:({navigation,screenProps}) => ({ 136 | headerStyle:{ 137 | backgroundColor:'#1B89EB', 138 | }, // 设置导航条的样式。如果想去掉安卓导航条底部阴影可以添加elevation: 0,iOS去掉阴影是。 139 | headerBackTitle:null, 140 | headerTintColor:'#fff', 141 | })}, 142 | BookSortDetail:{screen:BookSortDetail, 143 | navigationOptions:({navigation,screenProps}) => ({ 144 | headerStyle:{ 145 | backgroundColor:'#1B89EB', 146 | }, // 设置导航条的样式。如果想去掉安卓导航条底部阴影可以添加elevation: 0,iOS去掉阴影是。 147 | headerBackTitle:null, 148 | headerTintColor:'#fff', 149 | })}, 150 | BookSearch:{screen:BookSearch, 151 | navigationOptions:({navigation,screenProps}) => ({ 152 | headerStyle:{ 153 | backgroundColor:'#1B89EB', 154 | }, // 设置导航条的样式。如果想去掉安卓导航条底部阴影可以添加elevation: 0,iOS去掉阴影是。 155 | headerBackTitle:null, 156 | headerTintColor:'#fff', 157 | })}, 158 | BookSearchResult:{screen:BookSearchResult, 159 | navigationOptions:({navigation,screenProps}) => ({ 160 | headerStyle:{ 161 | backgroundColor:'#1B89EB', 162 | }, // 设置导航条的样式。如果想去掉安卓导航条底部阴影可以添加elevation: 0,iOS去掉阴影是。 163 | headerBackTitle:null, 164 | headerTintColor:'#fff', 165 | })}, 166 | BookInfo:{screen:BookInfo, 167 | navigationOptions:({navigation,screenProps}) => ({ 168 | headerStyle:{ 169 | backgroundColor:'#1B89EB', 170 | }, // 设置导航条的样式。如果想去掉安卓导航条底部阴影可以添加elevation: 0,iOS去掉阴影是。 171 | headerBackTitle:null, 172 | headerTintColor:'#fff', 173 | })}, 174 | Read: { 175 | screen: Read, 176 | navigationOptions: ({navigation, screenProps}) => ({ 177 | header:null, 178 | headerTintColor:'#fff', 179 | }) 180 | }}, { 181 | initialRouteName: 'Home', // 默认显示界面 182 | navigationOptions: { // 屏幕导航的默认选项, 也可以在组件内用 static navigationOptions 设置(会覆盖此处的设置) 183 | gesturesEnabled:true, 184 | // headerStyle:{ 185 | // backgroundColor:'#4ECBFC', 186 | // paddingTop:54 187 | // }, // 设置导航条的样式。如果想去掉安卓导航条底部阴影可以添加elevation: 0,iOS去掉阴影是。 188 | }, 189 | mode: 'card', // 页面切换模式, 左右是card(相当于iOS中的push效果), 上下是modal(相当于iOS中的modal效果) 190 | headerMode: 'screen', // 导航栏的显示模式, screen: 有渐变透明效果, float: 无透明效果, none: 隐藏导航栏 191 | }); 192 | 193 | export default AppNavigator; 194 | -------------------------------------------------------------------------------- /app/common/blueTheme.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by WAN on 2017/9/14. 3 | */ 4 | 5 | 'use strict'; 6 | 7 | import { 8 | Platform 9 | } from 'react-native' 10 | 11 | const STATUS_BAR_HEIGHT = (Platform.OS === 'ios' ? 20 : 24); 12 | const header_height = (Platform.OS === 'ios' ? 70 : 74); 13 | 14 | module.exports = { 15 | header: { 16 | method: 'POST', 17 | headers: { 18 | 'Accept': 'application/json', 19 | 'Content-Type': 'application/json' 20 | } 21 | }, 22 | css: { 23 | statusBarHeight: STATUS_BAR_HEIGHT, 24 | headerHeight: header_height, 25 | fontSize: { 26 | appTitle: 18, 27 | title: 16, 28 | desc: 12 29 | }, 30 | fontColor: { 31 | appMainColor: '#1B89EB', 32 | title: '#333333', 33 | desc: '#999999', 34 | white: '#ffffff', 35 | author: '#cc9900' 36 | }, 37 | color: { 38 | appBackground: '#1B89EB', 39 | appMainColor: '#ee735c', 40 | appBlack: '#333333', 41 | line: '#e6e6e6', 42 | buttonColor: '#90C5F0', 43 | white: '#ffffff', 44 | black: '#000000' 45 | }, 46 | boxColor: ['#90C5F0', '#91CED5', '#F88F55', '#C0AFD0', '#E78F8F', '#67CCB7', '#F6BC7E'], 47 | readForNight:{ 48 | backgroundColor:'#1D1C21', 49 | textColor:'#888888', 50 | menuBackgroundColor:'#101317', 51 | }, 52 | readForWhite:{ 53 | backgroundColor:'#EBEAEF', 54 | textColor:'#000000', 55 | menuBackgroundColor:'#ffffff', 56 | }, 57 | }, 58 | bookType: { 59 | 'qt': '其他', 60 | 'xhqh': '玄幻奇幻', 61 | 'wxxx': '武侠仙侠', 62 | 'dsyn': '都市异能', 63 | 'lsjs': '历史军事', 64 | 'yxjj': '游戏竞技', 65 | 'khly': '科幻灵异', 66 | 'cyjk': '穿越架空', 67 | 'hmzc': '豪门总裁', 68 | 'xdyq': '现代言情', 69 | 'gdyq': '古代言情', 70 | 'hxyq': '幻想言情', 71 | 'dmtr': '耽美同人', 72 | }, 73 | distillate: [ 74 | {name: '全部', distillate: ''}, 75 | {name: '精品', distillate: 'true'} 76 | ], 77 | discussionSort: [ 78 | {name: '默认排序', sort: 'updated'}, 79 | {name: '最新发布', sort: 'created'}, 80 | {name: '最多评论', sort: 'comment-count'} 81 | ], 82 | reviewSort: [ 83 | {name: '默认排序', sort: 'updated'}, 84 | {name: '最新发布', sort: 'created'}, 85 | {name: '最有用的', sort: 'helpful'}, 86 | {name: '最多评论', sort: 'comment-count'}, 87 | ], 88 | reviewBookType: [ 89 | {name: '全部类型', type: 'all'}, 90 | {name: '玄幻奇幻', type: 'xhqh'}, 91 | {name: '武侠仙侠', type: 'wxxx'}, 92 | {name: '都市异能', type: 'dsyn'}, 93 | {name: '历史军事', type: 'lsjs'}, 94 | {name: '游戏竞技', type: 'yxjj'}, 95 | {name: '科幻灵异', type: 'khly'}, 96 | {name: '穿越架空', type: 'cyjk'}, 97 | {name: '豪门总裁', type: 'hmzc'}, 98 | {name: '现代言情', type: 'xdyq'}, 99 | {name: '古代言情', type: 'gdyq'}, 100 | {name: '幻想言情', type: 'hxyq'}, 101 | {name: '耽美同人', type: 'dmtr'}, 102 | ], 103 | bookDetailSort: [ 104 | {name: '默认排序', sort: 'updated'}, 105 | {name: '最新发布', sort: 'created'}, 106 | {name: '最多评论', sort: 'comment-count'}, 107 | ] 108 | }; -------------------------------------------------------------------------------- /app/components/BookHandler.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by WAN on 2017/9/14. 3 | */ 4 | 5 | import config from '../config'; 6 | 7 | 8 | /** 9 | * 是否已经保存过当前书籍 10 | * @param {string} bookId 书籍id 11 | */ 12 | function hasSaveBook(bookId) { 13 | let book = realm.objectForPrimaryKey('HistoryBook', bookId); 14 | if(typeof(book) === 'undefined'||book.isToShow === 0){ 15 | return false; 16 | }else{ 17 | return true; 18 | } 19 | } 20 | 21 | /** 22 | * 是否已经保存过当前书籍 23 | * @param {string} bookId 书籍id 24 | */ 25 | function saveChapterRecordById(bookId,chapterNum,chapterUrl,chapterTitle) { 26 | 27 | let apiUrl = config.apiUrl; 28 | fetch(apiUrl.book.detail+bookId,{ 29 | method: 'GET', 30 | }).then((response) =>{ 31 | response.json().then((json) =>{ 32 | let data = json; 33 | if(data){ 34 | saveBookToRealm(data,chapterNum,chapterUrl,chapterTitle) 35 | } 36 | }); 37 | }).catch(function(error) { 38 | //someThing error 39 | }); 40 | } 41 | 42 | 43 | /** 44 | * 向数据库中保存当前书籍的记录 45 | */ 46 | function saveBookToRealm(bookDetail,chapterNum, chapterUrl,chapterTitle) { 47 | if(typeof(chapterNum) === 'undefined') chapterNum = 0; 48 | if(typeof(chapterUrl) === 'undefined') chapterUrl = ''; 49 | if(typeof(chapterTitle) === 'undefined') chapterTitle = ''; 50 | let books = realm.objects('HistoryBook').sorted('sortNum'); 51 | let book = realm.objectForPrimaryKey('HistoryBook', bookDetail._id); 52 | realm.write(() => { 53 | if (book) { 54 | if (book.bookId == books[books.length - 1].bookId) { 55 | realm.create('HistoryBook', {bookId: book.bookId, isToShow: 1,historyChapterNum:chapterNum,historyChapterUrl:chapterUrl,historyChapterTitle:chapterTitle,}, true) 56 | } else { 57 | let sortNum = books[books.length - 1].sortNum + 1; 58 | realm.create('HistoryBook', {bookId: book.bookId, isToShow: 1, sortNum: books[books.length - 1].sortNum + 1, historyChapterNum: chapterNum,historyChapterUrl:chapterUrl,historyChapterTitle:chapterTitle}, true) 59 | } 60 | } else { 61 | realm.create('HistoryBook', { 62 | bookId: bookDetail._id, 63 | bookName: bookDetail.title, 64 | bookUrl: bookDetail.cover, 65 | lastChapterTitle: bookDetail.lastChapter, 66 | historyChapterNum: chapterNum, 67 | historyChapterUrl:'', 68 | historyChapterTitle:chapterTitle, 69 | lastChapterTime: bookDetail.updated, 70 | nowChapterTime:bookDetail.updated, 71 | saveTime: new Date(), 72 | sortNum: books && books.length > 0 ? books[books.length - 1].sortNum + 1 : 0, 73 | isToShow: 1 74 | }); 75 | } 76 | }) 77 | } 78 | 79 | function saveSettingToRealm(item) { 80 | let readTheme = ''; 81 | let readTextSize = 18; 82 | if(typeof(item.readTheme)!== 'undefined') readTheme = item.readTheme; 83 | if(typeof(item.readTextSize)!== 'undefined') readTextSize = item.readTextSize; 84 | let setting = realm.objectForPrimaryKey('SystemSetting', 1); 85 | realm.write(() => { 86 | if (setting) { 87 | realm.create('SystemSetting', {settingPrimary:1,readTheme: readTheme, readTextSize: readTextSize,}, true) 88 | } else { 89 | realm.create('SystemSetting', { 90 | readTheme: readTheme, 91 | readTextSize: readTextSize, 92 | settingPrimary:1, 93 | }); 94 | } 95 | }) 96 | } 97 | 98 | /** 99 | * 向数据库中保存当前书籍的记录 100 | */ 101 | // function saveBookToRealm(bookDetail) { 102 | // let books = realm.objects('HistoryBook').sorted('sortNum'); 103 | // let book = realm.objectForPrimaryKey('HistoryBook', bookDetail._id); 104 | // realm.write(() => { 105 | // if(book) { 106 | // realm.create('HistoryBook', {bookId: bookDetail._id, isToShow: 1}, true) 107 | // } else { 108 | // realm.create('HistoryBook', { 109 | // bookId: bookDetail._id, 110 | // bookName: bookDetail.title, 111 | // bookUrl: bookDetail.cover, 112 | // lastChapterTitle: bookDetail.lastChapter, 113 | // historyChapterNum: 0, 114 | // lastChapterTime: bookDetail.updated, 115 | // saveTime: new Date(), 116 | // sortNum: books && books.length > 0 ? books[books.length - 1].sortNum + 1 : 0, 117 | // isToShow: 1 118 | // }); 119 | // } 120 | // }) 121 | // } 122 | 123 | /** 124 | * 从数据库中删除当前书籍的记录 125 | */ 126 | function deleteBookToRealm(bookDetail) { 127 | let book = realm.objectForPrimaryKey('HistoryBook', bookDetail._id); 128 | realm.write(() => { 129 | realm.delete(book); 130 | }) 131 | } 132 | 133 | /** 134 | * 从数据库中删除当前书籍的记录 135 | */ 136 | function deleteBookToRealmById(bookId) { 137 | let book = realm.objectForPrimaryKey('HistoryBook', bookId); 138 | realm.write(() => { 139 | realm.delete(book); 140 | }) 141 | } 142 | 143 | /** 144 | * 添加或删除数据库中相关书籍 145 | */ 146 | function _addOrDeleteBook(bookDetail) { 147 | if (this.state.hasSaveBook) { 148 | this.deleteBookToRealm(bookDetail) 149 | } else { 150 | this.saveBookToRealm(bookDetail) 151 | } 152 | this._hasSaveBook(bookDetail._id) 153 | } 154 | 155 | module.exports = { 156 | saveBookToRealm, 157 | deleteBookToRealm, 158 | deleteBookToRealmById, 159 | hasSaveBook, 160 | saveChapterRecordById, 161 | saveSettingToRealm 162 | }; -------------------------------------------------------------------------------- /app/components/ListItem.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by WAN on 2017/9/8. 3 | */ 4 | 'use strict'; 5 | 6 | import React, { Component } from 'react'; 7 | import { 8 | StyleSheet, 9 | TouchableWithoutFeedback, 10 | TouchableOpacity, 11 | Dimensions, 12 | View, 13 | Text, 14 | Image, 15 | Alert, 16 | } from 'react-native'; 17 | 18 | import Icon from "react-native-vector-icons/FontAwesome"; 19 | 20 | const SCREEN_WIDTH = Dimensions.get('window').width; 21 | 22 | class ListItem extends Component { 23 | constructor(props) { 24 | super(props); 25 | this.state = { 26 | }; 27 | } 28 | 29 | toCourse = () => { 30 | if (!this.props.toCourse) { 31 | return; 32 | } 33 | 34 | this.props.toCourse(); 35 | }; 36 | 37 | render() { 38 | return ( 39 | 40 | 41 | 42 | {this.props.rowData.title} 43 | 44 | 45 | ); 46 | } 47 | } 48 | 49 | const styles = StyleSheet.create({ 50 | container:{ 51 | flex: 1, 52 | flexDirection:'row', 53 | }, 54 | }); 55 | 56 | export default ListItem; -------------------------------------------------------------------------------- /app/components/Loading.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by WAN on 2017/9/28. 3 | */ 4 | 5 | import React, { Component } from 'react'; 6 | import { 7 | StyleSheet, 8 | TouchableOpacity, 9 | Dimensions, 10 | View, 11 | Text, 12 | Image, 13 | Modal, 14 | } from 'react-native'; 15 | 16 | const SCREEN_WIDTH = Dimensions.get('window').width; 17 | const SCREEN_HEIGHT = Dimensions.get('window').height; 18 | 19 | class Loading extends Component { 20 | constructor(props) { 21 | super(props); 22 | this.state = { 23 | }; 24 | } 25 | 26 | render() { 27 | return ( 28 | 29 | {/* 47 | ); 48 | } 49 | } 50 | 51 | const styles = StyleSheet.create({ 52 | container:{ 53 | flex: 1, 54 | flexDirection:'row', 55 | height:SCREEN_HEIGHT, 56 | width:SCREEN_WIDTH, 57 | }, 58 | itemImage: { 59 | alignSelf: 'center', 60 | width: 200, 61 | height: 200, 62 | borderRadius:100 63 | }, 64 | }); 65 | 66 | export default Loading; -------------------------------------------------------------------------------- /app/config.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by WAN on 2017/9/8. 3 | */ 4 | 5 | 'use strict'; 6 | // const apiHost ='http://dev.zsqingnian.com/'; 7 | const apiHost ='https://zsqingnian.com/'; 8 | 9 | const API_BASE_URL = 'http://api.zhuishushenqi.com'; 10 | const IMG_BASE_URL = 'http://statics.zhuishushenqi.com'; 11 | 12 | const config = { 13 | apiUrl: { 14 | book:{ 15 | recommend:API_BASE_URL + '/book/recommend',//GET 首次进入APP,选择性别后,获取推荐列表 url?gender='male' 16 | aBookSource:API_BASE_URL + '/atoc',// 获取正版源(若有) 与 盗版源 17 | detail:API_BASE_URL + '/book/',// GET 书籍详情 + bookId 18 | hotReview:API_BASE_URL + '/post/review/best-by-book',// GET 热门评论 url?book=id 19 | recommendBookList:API_BASE_URL + '/book-list/',// + bookId + '/recommend'}, GET 根据id推荐书单 url?limit=3 20 | authorBookList:API_BASE_URL + '/book/accurate-search',// GET 通过作者查询书名 url?author='' 21 | /** 22 | * GET 根据标签查询书籍列表 23 | * @param tags 24 | * @param start 25 | * @param limit 26 | */ 27 | tagBookList:API_BASE_URL + '/book/by-tags', 28 | /** 29 | * 获取书籍详情讨论列表 30 | * @param book bookId 31 | * @param sort updated(默认排序) 32 | * created(最新发布) 33 | * comment-count(最多评论) 34 | * @param type normal 35 | * vote 36 | * @param start 0 37 | * @param limit 20 38 | */ 39 | discussionList: API_BASE_URL + '/post/by-book', 40 | /** 41 | * 获取书籍详情书评列表 42 | * @param book bookId 43 | * @param sort updated(默认排序) 44 | * created(最新发布) 45 | * comment-count(最多评论) 46 | * @param start 0 47 | * @param limit 20 48 | */ 49 | reviewList: API_BASE_URL + '/post/review/by-book', 50 | }, 51 | read:{ 52 | // GET 获取书的章节信息 http://api.zhuishushenqi.com/mix-atoc/5569ba444127a49f1fa99d29?view=chapters 53 | readBookChapterList:API_BASE_URL + '/mix-atoc/',// + bookId + '?view=chapters'}, 54 | // GET 获取书的章节详情 55 | readBookChapterDetail:'http://chapter2.zhuishushenqi.com/chapter/',// + chapterUrl}, 56 | }, 57 | discover:{ 58 | charts:API_BASE_URL + '/ranking/gender',// GET 排行榜 59 | /** 60 | * GET 通过id获取排行榜详情 61 | * http://api.zhuishushenqi.com/ranking/564d820bc319238a644fb408 62 | * 周榜:id->_id 63 | * 月榜:id->monthRank 64 | * 总榜:id->totalRank 65 | */ 66 | chartsDetail:API_BASE_URL + '/ranking/',// + id}, 67 | /** 68 | * GET 获取主题书单列表 69 | * @param 本周最热:duration=last-seven-days&sort=collectorCount 70 | * @param 最新发布:duration=all&sort=created 71 | * @param 最多收藏:duration=all&sort=collectorCount 72 | * @param start 从多少开始请求 73 | * @param tag 都市、古代、架空、重生、玄幻、网游 74 | * @param gender male、female 75 | * @param limit 20 76 | */ 77 | bookList:API_BASE_URL + '/book-list', 78 | bookListTag:API_BASE_URL + '/book-list/tagType',// GET 获取主题书单标签列表 79 | bookListDetail:API_BASE_URL + '/book-list/',// + bookListId},// GET 获取书单详情 80 | categoryList:API_BASE_URL + '/cats/lv2/statistics',// GET 获取分类 81 | categoryListV2:API_BASE_URL + '/cats/lv2',// GET 获取二级分类 82 | /** 83 | * GET 按分类获取书籍列表 84 | * @param gender male、female 85 | * @param type hot(热门)、new(新书)、reputation(好评)、over(完结) 86 | * @param major 玄幻 87 | * @param start 从多少开始请求 88 | * @param minor 东方玄幻、异界大陆、异界争霸、远古神话 89 | * @param limit 50 90 | */ 91 | categoryBooks:API_BASE_URL + '/book/by-categories', 92 | }, 93 | community:{ 94 | /** 95 | * 获取综合讨论区帖子列表 96 | * 全部、默认排序 http://api.zhuishushenqi.com/post/by-block?block=ramble&duration=all&sort=updated&type=all&start=0&limit=20&distillate= 97 | * 精品、默认排序 http://api.zhuishushenqi.com/post/by-block?block=ramble&duration=all&sort=updated&type=all&start=0&limit=20&distillate=true 98 | * @param block ramble:综合讨论区 99 | * original:原创区 100 | * girl: 女生区 101 | * @param duration all 102 | * @param sort updated(默认排序) 103 | * created(最新发布) 104 | * comment-count(最多评论) 105 | * @param type all 106 | * @param start 0 107 | * @param limit 20 108 | * @param distillate true(精品) 109 | */ 110 | bookDiscussionList: API_BASE_URL + '/post/by-block', 111 | bookDiscussionDetail:API_BASE_URL + '/post/',// + id}, 获取综合讨论区帖子详情 112 | bookCommentBest:API_BASE_URL + '/post/',// + id + '/comment/best'},// 获取神评论列表(综合讨论区、书评区、书荒区皆为同一接口) 113 | /** 114 | * 获取综合讨论区帖子详情内的评论列表 115 | * @param start 0 116 | * @param limit 30 117 | */ 118 | bookDiscussionCommentList:API_BASE_URL + '/post/',// + id + '/comment'}, 119 | /** 120 | * 获取书评区帖子列表 121 | * 全部、全部类型、默认排序 http://api.zhuishushenqi.com/post/review?duration=all&sort=updated&type=all&start=0&limit=20&distillate= 122 | * 精品、玄幻奇幻、默认排序 http://api.zhuishushenqi.com/post/review?duration=all&sort=updated&type=xhqh&start=0&limit=20&distillate=true 123 | * 124 | * @param duration all 125 | * @param sort updated(默认排序) 126 | * created(最新发布) 127 | * helpful(最有用的) 128 | * comment-count(最多评论) 129 | * @param type all(全部类型)、xhqh(玄幻奇幻)、dsyn(都市异能)... 130 | * @param start 0 131 | * @param limit 20 132 | * @param distillate true(精品) 、空字符(全部) 133 | */ 134 | bookReviewList: API_BASE_URL + '/post/review', 135 | // 获取书评区帖子详情 136 | bookReviewDetail:API_BASE_URL + '/post/review/',// + id}, 137 | /** 138 | * 获取书评区、书荒区帖子详情内的评论列表 139 | * @param start 0 140 | * @param limit 30 141 | */ 142 | bookReviewCommentList:API_BASE_URL + '/post/review/',// + id + '/comment'}, 143 | /** 144 | * 获取书荒区帖子列表 145 | * 全部、默认排序 http://api.zhuishushenqi.com/post/help?duration=all&sort=updated&start=0&limit=20&distillate= 146 | * 精品、默认排序 http://api.zhuishushenqi.com/post/help?duration=all&sort=updated&start=0&limit=20&distillate=true 147 | * @param duration all 148 | * @param sort updated(默认排序) 149 | * created(最新发布) 150 | * comment-count(最多评论) 151 | * @param start 0 152 | * @param limit 20 153 | * @param distillate true(精品) 、空字符(全部) 154 | */ 155 | bookHelpList:API_BASE_URL + '/post/help', 156 | // 获取书荒区帖子详情 157 | bookHelpDetail:API_BASE_URL + '/post/help/',// + id}, 158 | }, 159 | search:{ 160 | searchHotWord:API_BASE_URL + '/book/hot-word',// GET 热门关键字 161 | searchAutoComplete:API_BASE_URL + '/book/auto-complete',// GET 关键字补全 162 | searchBooks: API_BASE_URL + '/book/fuzzy-search?query=',// GET 书籍查询 ?query=keyWord 163 | }, 164 | }, 165 | API_BASE_URL: API_BASE_URL, 166 | IMG_BASE_URL: IMG_BASE_URL, 167 | 168 | // 用户偏好推荐 169 | // GET gender: male | female 170 | USER_RECOMMEND: API_BASE_URL + '/book/recommend', 171 | 172 | }; 173 | 174 | module.exports = config; 175 | -------------------------------------------------------------------------------- /app/pages/book/BookInfo.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by WAN on 2017/9/11. 3 | */ 4 | 5 | import React, { Component } from 'react' 6 | import { 7 | View, 8 | Image, 9 | Text, 10 | StatusBar, 11 | StyleSheet, 12 | Platform, 13 | FlatList, 14 | Dimensions, 15 | TouchableOpacity, 16 | PixelRatio 17 | } from 'react-native' 18 | import { connect } from 'react-redux'; 19 | import { Button,ListItem } from 'react-native-elements'; 20 | import config from '../../config'; 21 | import {getBookDetail,getBookShelves} from '../../actions/book'; 22 | import {saveBookToRealm,deleteBookToRealm,hasSaveBook} from '../../components/BookHandler'; 23 | import {dateFormat,wordCountFormat} from '../../util/formatUtil'; 24 | 25 | const SCREEN_WIDTH = Dimensions.get('window').width; 26 | const SCREEN_HEIGHT = Dimensions.get('window').height; 27 | 28 | class BookInfo extends Component { 29 | 30 | constructor(props) { 31 | super(props); 32 | this.state = { 33 | headerTitle:'', 34 | bookIntroVisible:false, 35 | } 36 | } 37 | 38 | static navigationOptions = { 39 | title: '书籍详情',//typeof(this.state.headerTitle)!== 'undefined'?this.state.headerTitle:'' 40 | }; 41 | 42 | componentWillMount(){ 43 | const bookId = this.props.navigation.state.params; 44 | this.props.dispatch(getBookDetail(bookId)); 45 | } 46 | 47 | toBookSearchResult = (keyWord,type) =>{ 48 | const { navigate } = this.props.navigation; 49 | navigate('BookSearchResult',{keyWord:keyWord,type:type}); 50 | }; 51 | 52 | addOrDeleteToBookshelf = (bookInfo) =>{ 53 | let hasSaved = hasSaveBook(bookInfo._id); 54 | if(hasSaved){ 55 | deleteBookToRealm(bookInfo); 56 | }else{ 57 | saveBookToRealm(bookInfo); 58 | } 59 | this.props.dispatch(getBookShelves()); 60 | this.setState({buttonChange:hasSaved}) 61 | }; 62 | 63 | toRead = (bookInfo) =>{ 64 | const { navigate } = this.props.navigation; 65 | navigate('Read',bookInfo._id); 66 | }; 67 | 68 | toggleBookIntro = () =>{ 69 | this.setState({bookIntroVisible:!this.state.bookIntroVisible}) 70 | }; 71 | 72 | renderTags = (tags) =>{ 73 | if(typeof(tags) !== 'undefined'){ 74 | return( 75 | tags.map((item, index) => { 76 | return ( 77 | this.toBookSearchResult(item,'tag')}> 80 | 81 | {item} 82 | 83 | 84 | ); 85 | }) 86 | ); 87 | } 88 | }; 89 | 90 | renderBookInfo = () =>{ 91 | let bookInfo = this.props.bookDetail; 92 | let coverUrl = bookInfo.cover; 93 | return( 94 | 95 | 96 | 97 | 103 | 104 | 105 | {bookInfo.title} 106 | this.toBookSearchResult(bookInfo.author,'author')}>{bookInfo.author} | {bookInfo.minorCate} | {wordCountFormat(bookInfo.wordCount)} 107 | 更新时间:{dateFormat(bookInfo.updated)} 108 | 109 | 110 | 111 | {typeof(bookInfo._id) !== 'undefined' && hasSaveBook(bookInfo._id)?( 112 |