├── .buckconfig ├── .flowconfig ├── .gitattributes ├── .gitignore ├── .watchmanconfig ├── App.js ├── README.md ├── __tests__ └── App.js ├── android ├── app │ ├── BUCK │ ├── build.gradle │ ├── build_defs.bzl │ ├── open.jks │ ├── proguard-rules.pro │ └── src │ │ └── main │ │ ├── AndroidManifest.xml │ │ ├── assets │ │ └── fonts │ │ │ ├── Ionicons.ttf │ │ │ └── MaterialIcons.ttf │ │ ├── java │ │ └── com │ │ │ └── gankrn │ │ │ ├── MainActivity.java │ │ │ └── MainApplication.java │ │ └── res │ │ ├── drawable │ │ └── launch_screen.png │ │ ├── layout │ │ └── launch_screen.xml │ │ ├── mipmap-hdpi │ │ └── ic_launcher.png │ │ ├── mipmap-mdpi │ │ └── ic_launcher.png │ │ ├── mipmap-xhdpi │ │ └── ic_launcher.png │ │ ├── mipmap-xxhdpi │ │ └── ic_launcher.png │ │ ├── mipmap-xxxhdpi │ │ └── ic_launcher.png │ │ └── values │ │ ├── colors.xml │ │ ├── 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 ├── apk └── gankrn.apk ├── app.json ├── app ├── configs │ ├── colors.js │ ├── const.js │ ├── index.js │ └── styles.js ├── http │ ├── api_gank.js │ ├── api_wan_android.js │ ├── http_cache.js │ ├── http_gank_extension.js │ ├── http_gank_utils.js │ ├── http_wan_android_extension.js │ └── http_wan_android_utils.js ├── image │ ├── fuli.png │ ├── icon_day.png │ ├── icon_fm.png │ ├── icon_music.png │ └── icon_rank.png ├── index.js ├── root.js └── view │ ├── MainTab.js │ ├── WebPageView.js │ ├── component │ ├── Button.android.js │ ├── Button.ios.js │ ├── ErrorView.js │ ├── FootLoadMore.js │ ├── FootNoMore.js │ ├── HomeTabBar.js │ ├── QYScrollableTabBar.js │ ├── RnButton.js │ └── WaitLoadingView.js │ ├── gank │ ├── AddGankView.js │ ├── AndroidView.js │ ├── DailyItemView.js │ ├── DailyView.js │ ├── FuliView.js │ ├── GankItemView.js │ ├── GankSortView.js │ └── GankView.js │ ├── personal │ ├── AboutView.js │ ├── CollectItemView.js │ ├── CollectView.js │ ├── LoginView.js │ ├── PersonalItemView.js │ ├── PersonalView.js │ └── RegisterView.js │ ├── photo │ ├── GirlsView.js │ └── PhotoView.js │ └── wanandroid │ ├── HomeView.js │ └── WanItemView.js ├── babel.config.js ├── images ├── 1.png ├── 2.png ├── 3.png ├── 4.png ├── 5.png ├── 6.png ├── github.png └── pyger.png ├── index.js ├── ios ├── GankRN-tvOS │ └── Info.plist ├── GankRN-tvOSTests │ └── Info.plist ├── GankRN.xcodeproj │ ├── project.pbxproj │ └── xcshareddata │ │ └── xcschemes │ │ ├── GankRN-tvOS.xcscheme │ │ └── GankRN.xcscheme ├── GankRN │ ├── AppDelegate.h │ ├── AppDelegate.m │ ├── Base.lproj │ │ └── LaunchScreen.xib │ ├── Images.xcassets │ │ ├── AppIcon.appiconset │ │ │ ├── Contents.json │ │ │ ├── icon-1024.png │ │ │ ├── icon-20@2x.png │ │ │ ├── icon-20@3x.png │ │ │ ├── icon-29@2x.png │ │ │ ├── icon-29@3x.png │ │ │ ├── icon-40@2x.png │ │ │ ├── icon-40@3x.png │ │ │ ├── icon-60@2x.png │ │ │ └── icon-60@3x.png │ │ ├── Contents.json │ │ └── LaunchImage.launchimage │ │ │ ├── Contents.json │ │ │ ├── Default-2436h.png │ │ │ ├── Default-568h@2x~iphone.png │ │ │ └── Default-667h.png │ ├── Info.plist │ └── main.m └── GankRNTests │ ├── GankRNTests.m │ └── Info.plist ├── package-lock.json ├── package.json └── 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 | node_modules/react-native/flow-github/ 28 | 29 | [options] 30 | emoji=true 31 | 32 | esproposal.optional_chaining=enable 33 | esproposal.nullish_coalescing=enable 34 | 35 | module.system=haste 36 | module.system.haste.use_name_reducers=true 37 | # get basename 38 | module.system.haste.name_reducers='^.*/\([a-zA-Z0-9$_.-]+\.js\(\.flow\)?\)$' -> '\1' 39 | # strip .js or .js.flow suffix 40 | module.system.haste.name_reducers='^\(.*\)\.js\(\.flow\)?$' -> '\1' 41 | # strip .ios suffix 42 | module.system.haste.name_reducers='^\(.*\)\.ios$' -> '\1' 43 | module.system.haste.name_reducers='^\(.*\)\.android$' -> '\1' 44 | module.system.haste.name_reducers='^\(.*\)\.native$' -> '\1' 45 | module.system.haste.paths.blacklist=.*/__tests__/.* 46 | module.system.haste.paths.blacklist=.*/__mocks__/.* 47 | module.system.haste.paths.blacklist=/node_modules/react-native/Libraries/Animated/src/polyfills/.* 48 | module.system.haste.paths.whitelist=/node_modules/react-native/Libraries/.* 49 | 50 | munge_underscores=true 51 | 52 | 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' 53 | 54 | module.file_ext=.js 55 | module.file_ext=.jsx 56 | module.file_ext=.json 57 | module.file_ext=.native.js 58 | 59 | suppress_type=$FlowIssue 60 | suppress_type=$FlowFixMe 61 | suppress_type=$FlowFixMeProps 62 | suppress_type=$FlowFixMeState 63 | 64 | suppress_comment=\\(.\\|\n\\)*\\$FlowFixMe\\($\\|[^(]\\|(\\(\\)? *\\(site=[a-z,_]*react_native[a-z,_]*\\)?)\\) 65 | suppress_comment=\\(.\\|\n\\)*\\$FlowIssue\\((\\(\\)? *\\(site=[a-z,_]*react_native[a-z,_]*\\)?)\\)?:? #[0-9]+ 66 | suppress_comment=\\(.\\|\n\\)*\\$FlowFixedInNextDeploy 67 | suppress_comment=\\(.\\|\n\\)*\\$FlowExpectedError 68 | 69 | [version] 70 | ^0.86.0 71 | -------------------------------------------------------------------------------- /.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 | {} -------------------------------------------------------------------------------- /App.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Sample React Native App 3 | * https://github.com/facebook/react-native 4 | * 5 | * @format 6 | * @flow 7 | * @lint-ignore-every XPLATJSCOPYRIGHT1 8 | */ 9 | 10 | import React, {Component} from 'react'; 11 | import {Platform, StyleSheet, Text, View} from 'react-native'; 12 | import SplashScreen from 'react-native-splash-screen' 13 | 14 | const instructions = Platform.select({ 15 | ios: 'Press Cmd+R to reload,\n' + 'Cmd+D or shake for dev menu', 16 | android: 17 | 'Double tap R on your keyboard to reload,\n' + 18 | 'Shake or press menu button for dev menu', 19 | }); 20 | 21 | type Props = {}; 22 | export default class App extends Component { 23 | 24 | componentDidMount() { 25 | SplashScreen.hide(); 26 | } 27 | 28 | render() { 29 | return ( 30 | 31 | SplashScreen 启动屏 32 | Welcome to React Native! 33 | To get started, edit App.js 34 | {instructions} 35 | 36 | ); 37 | } 38 | } 39 | 40 | const styles = StyleSheet.create({ 41 | container: { 42 | flex: 1, 43 | justifyContent: 'center', 44 | alignItems: 'center', 45 | backgroundColor: '#F5FCFF', 46 | }, 47 | welcome: { 48 | fontSize: 20, 49 | textAlign: 'center', 50 | margin: 10, 51 | }, 52 | instructions: { 53 | textAlign: 'center', 54 | color: '#333333', 55 | marginBottom: 5, 56 | }, 57 | }); 58 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # GankRN 2 | 3 | GankRN是[干货集中营](https://gank.io)移动端全家桶系列第二篇,根据 Gank.io 官方提供的api实现的Gank客户端,包含最新数据展示,分类列表读取,妹纸瀑布流图片,提交干货;同时接入了[@鸿洋大神](https://github.com/hongyangAndroid)的[玩Android](https://www.wanandroid.com/)网站数据,目前只接入了首页数据,登录、注册,我的收藏查看,后续版本会集成玩安卓的更多数据 4 | 5 | [同款flutter干货集中营](https://github.com/fujianlian/GankFlutter) 6 | 7 | [同款Kotlin干货集中营](https://github.com/fujianlian/GankKotlin) 8 | 9 | [同款小程序干货集中营](https://github.com/fujianlian/GankMini) 10 | 11 | ## 应用截图 12 | 13 | ![1](./images/1.png) | ![2](./images/2.png) | ![3](./images/3.png) | 14 | | :--: | :--: | :--: | 15 | | 干货营 | 福利 | 干货定制 | 16 | 17 | ![1](./images/4.png) | ![2](./images/5.png) | ![3](./images/6.png) | 18 | | :--: | :--: | :--: | 19 | | 玩安卓 | 我的收藏 | 我的 | 20 | 21 | ## Android app下载 22 | 23 | ![1](./images/pyger.png) | ![2](./images/github.png) | 24 | | :--: | :--: | 25 | | 蒲公英渠道 | github渠道 | 26 | 27 | ## 版本更新日志 28 | 29 | ### V1.0.0[2019-04-04] 30 | 31 | 1、干货营数据接入 32 | 33 | 2、玩安卓首页数据接入 34 | 35 | ## 贡献 36 | 37 | * 如果你在使用过程中遇到问题,欢迎给我提Issue 38 | 39 | * 如果你有好的想法,欢迎pull request 40 | 41 | * 觉得不错的话,顺手[点个Star](https://github.com/fujianlian/GankRN),笔者需要您的支持 42 | 43 | ## 特别感谢 44 | 45 | * api提供:[@代码家](https://github.com/daimajia),[@鸿洋大神](https://github.com/hongyangAndroid) 46 | 47 | * UI参考:[@youlookwhat](https://github.com/youlookwhat/CloudReader) 云阅 48 | 49 | * [干货集中营](https://gank.io/) 50 | 51 | * [玩安卓](https://www.wanandroid.com/) 52 | -------------------------------------------------------------------------------- /__tests__/App.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @format 3 | * @lint-ignore-every XPLATJSCOPYRIGHT1 4 | */ 5 | 6 | import 'react-native'; 7 | import React from 'react'; 8 | import App from '../App'; 9 | 10 | // Note: test renderer must be required after react-native. 11 | import renderer from 'react-test-renderer'; 12 | 13 | it('renders correctly', () => { 14 | renderer.create(); 15 | }); 16 | -------------------------------------------------------------------------------- /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.gankrn", 39 | ) 40 | 41 | android_resource( 42 | name = "res", 43 | package = "com.gankrn", 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 | buildToolsVersion rootProject.ext.buildToolsVersion 99 | 100 | defaultConfig { 101 | applicationId "com.gankrn" 102 | minSdkVersion rootProject.ext.minSdkVersion 103 | targetSdkVersion rootProject.ext.targetSdkVersion 104 | versionCode 1 105 | versionName "1.0.0" 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", "arm64-v8a" 113 | } 114 | } 115 | signingConfigs { 116 | release { 117 | storeFile file(RELEASE_STORE_FILE) 118 | storePassword RELEASE_STORE_PASSWORD 119 | keyAlias RELEASE_KEY_ALIAS 120 | keyPassword RELEASE_KEY_PASSWORD 121 | } 122 | } 123 | buildTypes { 124 | release { 125 | signingConfig signingConfigs.release 126 | minifyEnabled enableProguardInReleaseBuilds 127 | proguardFiles getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro" 128 | } 129 | } 130 | // applicationVariants are e.g. debug, release 131 | applicationVariants.all { variant -> 132 | variant.outputs.each { output -> 133 | // For each separate APK per architecture, set a unique version code as described here: 134 | // http://tools.android.com/tech-docs/new-build-system/user-guide/apk-splits 135 | def versionCodes = ["armeabi-v7a":1, "x86":2, "arm64-v8a": 3] 136 | def abi = output.getFilter(OutputFile.ABI) 137 | if (abi != null) { // null for the universal-debug, universal-release variants 138 | output.versionCodeOverride = 139 | versionCodes.get(abi) * 1048576 + defaultConfig.versionCode 140 | } 141 | } 142 | } 143 | } 144 | 145 | dependencies { 146 | implementation project(':react-native-vector-icons') 147 | implementation project(':react-native-gesture-handler') 148 | implementation fileTree(dir: "libs", include: ["*.jar"]) 149 | implementation "com.android.support:appcompat-v7:${rootProject.ext.supportLibVersion}" 150 | implementation "com.facebook.react:react-native:+" // From node_modules 151 | // 启动页 152 | implementation project(':react-native-splash-screen') 153 | implementation project(':react-native-webview') 154 | // gif 155 | implementation 'com.facebook.fresco:fresco:1.13.0' 156 | implementation 'com.facebook.fresco:animated-gif:1.13.0' 157 | } 158 | 159 | // Run this once to be able to run the application with BUCK 160 | // puts all compile dependencies into folder libs for BUCK to use 161 | task copyDownloadableDepsToLibs(type: Copy) { 162 | from configurations.compile 163 | into 'libs' 164 | } 165 | -------------------------------------------------------------------------------- /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/open.jks: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fujianlian/GankRN/353ef94d670e18401da35540a76783c4332208cf/android/app/open.jks -------------------------------------------------------------------------------- /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/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 7 | 13 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /android/app/src/main/assets/fonts/Ionicons.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fujianlian/GankRN/353ef94d670e18401da35540a76783c4332208cf/android/app/src/main/assets/fonts/Ionicons.ttf -------------------------------------------------------------------------------- /android/app/src/main/assets/fonts/MaterialIcons.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fujianlian/GankRN/353ef94d670e18401da35540a76783c4332208cf/android/app/src/main/assets/fonts/MaterialIcons.ttf -------------------------------------------------------------------------------- /android/app/src/main/java/com/gankrn/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.gankrn; 2 | 3 | import android.os.Bundle; 4 | 5 | import com.facebook.react.ReactActivity; 6 | 7 | import org.devio.rn.splashscreen.SplashScreen; 8 | 9 | import com.facebook.react.ReactActivityDelegate; 10 | import com.facebook.react.ReactRootView; 11 | import com.swmansion.gesturehandler.react.RNGestureHandlerEnabledRootView; 12 | 13 | public class MainActivity extends ReactActivity { 14 | 15 | /** 16 | * Returns the name of the main component registered from JavaScript. 17 | * This is used to schedule rendering of the component. 18 | */ 19 | @Override 20 | protected String getMainComponentName() { 21 | return "GankRN"; 22 | } 23 | 24 | @Override 25 | protected void onCreate(Bundle savedInstanceState) { 26 | SplashScreen.show(this); 27 | super.onCreate(savedInstanceState); 28 | } 29 | 30 | @Override 31 | protected ReactActivityDelegate createReactActivityDelegate() { 32 | return new ReactActivityDelegate(this, getMainComponentName()) { 33 | @Override 34 | protected ReactRootView createRootView() { 35 | return new RNGestureHandlerEnabledRootView(MainActivity.this); 36 | } 37 | }; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /android/app/src/main/java/com/gankrn/MainApplication.java: -------------------------------------------------------------------------------- 1 | package com.gankrn; 2 | 3 | import android.app.Application; 4 | 5 | import com.facebook.react.ReactApplication; 6 | import com.oblador.vectoricons.VectorIconsPackage; 7 | import com.swmansion.gesturehandler.react.RNGestureHandlerPackage; 8 | import com.reactnativecommunity.webview.RNCWebViewPackage; 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 | import org.devio.rn.splashscreen.SplashScreenReactPackage; 18 | 19 | public class MainApplication extends Application implements ReactApplication { 20 | 21 | private final ReactNativeHost mReactNativeHost = new ReactNativeHost(this) { 22 | @Override 23 | public boolean getUseDeveloperSupport() { 24 | return BuildConfig.DEBUG; 25 | } 26 | 27 | @Override 28 | protected List getPackages() { 29 | return Arrays.asList( 30 | new MainReactPackage(), 31 | new VectorIconsPackage(), 32 | new RNGestureHandlerPackage(), 33 | new RNCWebViewPackage(), 34 | new SplashScreenReactPackage() 35 | ); 36 | } 37 | 38 | @Override 39 | protected String getJSMainModuleName() { 40 | return "index"; 41 | } 42 | }; 43 | 44 | @Override 45 | public ReactNativeHost getReactNativeHost() { 46 | return mReactNativeHost; 47 | } 48 | 49 | @Override 50 | public void onCreate() { 51 | super.onCreate(); 52 | SoLoader.init(this, /* native exopackage */ false); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /android/app/src/main/res/drawable/launch_screen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fujianlian/GankRN/353ef94d670e18401da35540a76783c4332208cf/android/app/src/main/res/drawable/launch_screen.png -------------------------------------------------------------------------------- /android/app/src/main/res/layout/launch_screen.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 7 | -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fujianlian/GankRN/353ef94d670e18401da35540a76783c4332208cf/android/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fujianlian/GankRN/353ef94d670e18401da35540a76783c4332208cf/android/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fujianlian/GankRN/353ef94d670e18401da35540a76783c4332208cf/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fujianlian/GankRN/353ef94d670e18401da35540a76783c4332208cf/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fujianlian/GankRN/353ef94d670e18401da35540a76783c4332208cf/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #000000 4 | -------------------------------------------------------------------------------- /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 | 9 | -------------------------------------------------------------------------------- /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 = 27 9 | supportLibVersion = "28.0.0" 10 | } 11 | repositories { 12 | google() 13 | jcenter() 14 | } 15 | dependencies { 16 | classpath 'com.android.tools.build:gradle:3.2.1' 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 | 35 | 36 | task wrapper(type: Wrapper) { 37 | gradleVersion = '4.7' 38 | distributionUrl = distributionUrl.replace("bin", "all") 39 | } 40 | -------------------------------------------------------------------------------- /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=open.jks 20 | RELEASE_KEY_ALIAS=gankrn 21 | RELEASE_STORE_PASSWORD=123456 22 | RELEASE_KEY_PASSWORD=123456 -------------------------------------------------------------------------------- /android/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fujianlian/GankRN/353ef94d670e18401da35540a76783c4332208cf/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-4.7-all.zip 6 | -------------------------------------------------------------------------------- /android/gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Attempt to set APP_HOME 10 | # Resolve links: $0 may be a link 11 | PRG="$0" 12 | # Need this for relative symlinks. 13 | while [ -h "$PRG" ] ; do 14 | ls=`ls -ld "$PRG"` 15 | link=`expr "$ls" : '.*-> \(.*\)$'` 16 | if expr "$link" : '/.*' > /dev/null; then 17 | PRG="$link" 18 | else 19 | PRG=`dirname "$PRG"`"/$link" 20 | fi 21 | done 22 | SAVED="`pwd`" 23 | cd "`dirname \"$PRG\"`/" >/dev/null 24 | APP_HOME="`pwd -P`" 25 | cd "$SAVED" >/dev/null 26 | 27 | APP_NAME="Gradle" 28 | APP_BASE_NAME=`basename "$0"` 29 | 30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 31 | DEFAULT_JVM_OPTS="" 32 | 33 | # Use the maximum available, or set MAX_FD != -1 to use that value. 34 | MAX_FD="maximum" 35 | 36 | warn () { 37 | echo "$*" 38 | } 39 | 40 | die () { 41 | echo 42 | echo "$*" 43 | echo 44 | exit 1 45 | } 46 | 47 | # OS specific support (must be 'true' or 'false'). 48 | cygwin=false 49 | msys=false 50 | darwin=false 51 | nonstop=false 52 | case "`uname`" in 53 | CYGWIN* ) 54 | cygwin=true 55 | ;; 56 | Darwin* ) 57 | darwin=true 58 | ;; 59 | MINGW* ) 60 | msys=true 61 | ;; 62 | NONSTOP* ) 63 | nonstop=true 64 | ;; 65 | esac 66 | 67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 68 | 69 | # Determine the Java command to use to start the JVM. 70 | if [ -n "$JAVA_HOME" ] ; then 71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 72 | # IBM's JDK on AIX uses strange locations for the executables 73 | JAVACMD="$JAVA_HOME/jre/sh/java" 74 | else 75 | JAVACMD="$JAVA_HOME/bin/java" 76 | fi 77 | if [ ! -x "$JAVACMD" ] ; then 78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 79 | 80 | Please set the JAVA_HOME variable in your environment to match the 81 | location of your Java installation." 82 | fi 83 | else 84 | JAVACMD="java" 85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 86 | 87 | Please set the JAVA_HOME variable in your environment to match the 88 | location of your Java installation." 89 | fi 90 | 91 | # Increase the maximum file descriptors if we can. 92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 93 | MAX_FD_LIMIT=`ulimit -H -n` 94 | if [ $? -eq 0 ] ; then 95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 96 | MAX_FD="$MAX_FD_LIMIT" 97 | fi 98 | ulimit -n $MAX_FD 99 | if [ $? -ne 0 ] ; then 100 | warn "Could not set maximum file descriptor limit: $MAX_FD" 101 | fi 102 | else 103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 104 | fi 105 | fi 106 | 107 | # For Darwin, add options to specify how the application appears in the dock 108 | if $darwin; then 109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 110 | fi 111 | 112 | # For Cygwin, switch paths to Windows format before running java 113 | if $cygwin ; then 114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 116 | JAVACMD=`cygpath --unix "$JAVACMD"` 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 | # Escape application args 158 | save () { 159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 160 | echo " " 161 | } 162 | APP_ARGS=$(save "$@") 163 | 164 | # Collect all arguments for the java command, following the shell quoting and substitution rules 165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 166 | 167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong 168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then 169 | cd "$(dirname "$0")" 170 | fi 171 | 172 | exec "$JAVACMD" "$@" 173 | -------------------------------------------------------------------------------- /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 | set DIRNAME=%~dp0 12 | if "%DIRNAME%" == "" set DIRNAME=. 13 | set APP_BASE_NAME=%~n0 14 | set APP_HOME=%DIRNAME% 15 | 16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 17 | set DEFAULT_JVM_OPTS= 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 Windows variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | 53 | :win9xME_args 54 | @rem Slurp the command line arguments. 55 | set CMD_LINE_ARGS= 56 | set _SKIP=2 57 | 58 | :win9xME_args_slurp 59 | if "x%~1" == "x" goto execute 60 | 61 | set CMD_LINE_ARGS=%* 62 | 63 | :execute 64 | @rem Setup the command line 65 | 66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 67 | 68 | @rem Execute Gradle 69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 70 | 71 | :end 72 | @rem End local scope for the variables with windows NT shell 73 | if "%ERRORLEVEL%"=="0" goto mainEnd 74 | 75 | :fail 76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 77 | rem the _cmd.exe /c_ return code! 78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 79 | exit /b 1 80 | 81 | :mainEnd 82 | if "%OS%"=="Windows_NT" endlocal 83 | 84 | :omega 85 | -------------------------------------------------------------------------------- /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 = 'GankRN' 2 | include ':react-native-vector-icons' 3 | project(':react-native-vector-icons').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-vector-icons/android') 4 | include ':react-native-gesture-handler' 5 | project(':react-native-gesture-handler').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-gesture-handler/android') 6 | 7 | include ':app' 8 | 9 | include ':react-native-splash-screen' 10 | project(':react-native-splash-screen').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-splash-screen/android') 11 | 12 | include ':react-native-webview' 13 | project(':react-native-webview').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-webview/android') 14 | -------------------------------------------------------------------------------- /apk/gankrn.apk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fujianlian/GankRN/353ef94d670e18401da35540a76783c4332208cf/apk/gankrn.apk -------------------------------------------------------------------------------- /app.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "GankRN", 3 | "displayName": "GankRN" 4 | } -------------------------------------------------------------------------------- /app/configs/colors.js: -------------------------------------------------------------------------------- 1 | //项目 主色调蓝色、注背景色、底部tab导航条色 2 | export const mainColor = 'red'; 3 | export const mainBackColor = '#FCF8F9'; 4 | export const navBackColor = 'white'; 5 | 6 | //项目 分割线 7 | export const borderColor = '#e0e0e0'; 8 | 9 | //输入框 左边字体颜色、右边字体颜色、placeholder颜色 10 | export const inputLeftColor = '#333333'; 11 | export const inputRightColor = '#999999'; 12 | export const placeholderColor = '#b7b7b7'; 13 | 14 | export const C1 = '#333333'; 15 | export const C2 = '#666666'; 16 | export const C3 = '#9b9b9b'; 17 | export const C4 = '#b7b7b7'; 18 | export const C5 = '#ff8e00'; 19 | export const C6 = '#010101'; 20 | export const C7 = '#f1f1f1'; 21 | export const C8 = '#fdf1da'; 22 | export const C9 = '#d2d2d2'; //(顶部底部导航栏分割线) 23 | export const C10 = '#e0e0e0'; //(列表分割线) 24 | export const C11 = '#f7f7f7'; //(内页背景) 25 | export const C12 = '#ffffff'; 26 | 27 | export const C13 = '#FFECDD'; // 方块背景 28 | export const C14 = '#f3f3f3'; // 底色 29 | 30 | export const C15 = '#874b00'; // 底色 31 | 32 | export const C16 = '#FF2D17'; // 提示信息文本 33 | export const C17 = '#f67100'; // 提示信息文本 34 | export const C18 = '#ffedde'; // 提示信息文本背景 35 | 36 | export const C19 = '#ff8f8f'; // 提示信息文本背景 37 | export const C20 = '#F67710'; // 圆填充色 38 | export const C21 = '#CDCDCD'; // 输入框下划线 -------------------------------------------------------------------------------- /app/configs/const.js: -------------------------------------------------------------------------------- 1 | import Toast from 'react-native-root-toast'; 2 | 3 | //获取屏幕的宽度和高度 4 | export const screenWidth = require('Dimensions').get('window').width; 5 | 6 | export const screenHeight = require('Dimensions').get('window').height; 7 | 8 | export const showToast = (msg) => { 9 | Toast.show(msg, { 10 | duration: Toast.durations.SHORT, // toast显示时长 11 | position: Toast.positions.CENTER, // toast位置 12 | shadow: false, // toast是否出现阴影 13 | animation: true, // toast显示/隐藏的时候是否需要使用动画过渡 14 | hideOnPress: true, // 是否可以通过点击事件对toast进行隐藏 15 | delay: 0, // toast显示的延时 16 | }); 17 | }; 18 | 19 | /** 20 | * 获取wanandroid cookie 21 | * @param username 用户名 22 | * @param password 密码 23 | * @returns {string} 24 | */ 25 | export const getCookie = (username, password) => { 26 | return "loginUserName=" + username + ";loginUserPassword=" + password 27 | }; -------------------------------------------------------------------------------- /app/configs/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 输出 所有 app相关配置 3 | */ 4 | export * from "./colors"; /** 应用所需基础颜色 */ 5 | export * from "./styles"; /** 应用样式相关所需基础参数 */ 6 | export * from "./const"; /** 常用固定参数 */ -------------------------------------------------------------------------------- /app/configs/styles.js: -------------------------------------------------------------------------------- 1 | import {Platform, Dimensions, StyleSheet, PixelRatio} from "react-native" 2 | import {borderColor, mainColor, navBackColor, placeholderColor} from './index'; 3 | import {mainBackColor} from "./colors"; 4 | 5 | /* 6 | 设备的像素密度,例如: 7 | PixelRatio.get() === 1 mdpi Android 设备 (160 dpi) 8 | PixelRatio.get() === 1.5 hdpi Android 设备 (240 dpi) 9 | PixelRatio.get() === 2 iPhone 4, 4S,iPhone 5, 5c, 5s,iPhone 6,xhdpi Android 设备 (320 dpi) 10 | PixelRatio.get() === 3 iPhone 6 plus , xxhdpi Android 设备 (480 dpi) 11 | PixelRatio.get() === 3.5 Nexus 6 */ 12 | 13 | const {width, height} = Dimensions.get("window") 14 | const fontScale = PixelRatio.getFontScale(); //返回字体大小缩放比例 15 | const pixelRatio = PixelRatio.get(); //当前设备的像素密度 16 | const defaultPixel = 2; 17 | console.log(' pixelRatio -->> ', pixelRatio); 18 | //px转换成dp 19 | const w2 = 750 / defaultPixel; 20 | const h2 = 1334 / defaultPixel; 21 | const scale = Math.min(height / h2, width / w2); //获取缩放比例 22 | 23 | const NavBarHeight = Platform.select({android: 50, ios: 70}); 24 | const NavPaddingTop = Platform.select({android: 0, ios: 20}) 25 | const NavBarContentH = Platform.select({android: NavBarHeight, ios: NavBarHeight - 20}) 26 | const lineWidth = StyleSheet.hairlineWidth; 27 | const Textpercent = Platform.select({android: 0.5, ios: 0.32}) 28 | const SizePercent = Platform.select({android: 1.1, ios: 1}) 29 | 30 | const APPBAR_HEIGHT = Platform.OS === 'ios' ? 44 : 56; 31 | const STATUSBAR_HEIGHT = Platform.OS === 'ios' ? 20 : 0; 32 | const HeaderBar = APPBAR_HEIGHT + STATUSBAR_HEIGHT; 33 | 34 | /** 35 | Export Value 36 | **/ 37 | export const W = width; 38 | export const H = height; 39 | 40 | /** 41 | 判断是否为IphoneX 42 | **/ 43 | export function isIphoneX() { 44 | let dimen = Dimensions.get('window'); 45 | return ( 46 | Platform.OS === 'ios' && 47 | !Platform.isPad && 48 | !Platform.isTVOS && 49 | (dimen.height === 812 || dimen.width === 812) 50 | ); 51 | } 52 | 53 | 54 | /* 修正不同屏幕 字体和尺寸 */ 55 | export function getResponsiveSize(x) { 56 | return scaleSize(x); 57 | } 58 | 59 | /* 字号尺寸 */ 60 | export const T1 = scaleSize(36); 61 | export const T2 = scaleSize(32); 62 | export const T3 = scaleSize(30); 63 | export const T4 = scaleSize(26); 64 | export const T5 = scaleSize(24); 65 | export const T6 = scaleSize(22); 66 | export const T7 = scaleSize(16); 67 | export const T8 = scaleSize(28); 68 | export const T9 = scaleSize(18); 69 | export const T10 = scaleSize(40); 70 | export const T11 = scaleSize(26); 71 | export const T12 = scaleSize(34); 72 | export const T13 = scaleSize(46); 73 | 74 | 75 | /* 字号尺寸 */ 76 | export const commonStyle = { 77 | lineWidth: lineWidth, 78 | line: {height: lineWidth, backgroundColor: borderColor}, 79 | boldLine: {height: 0.8, backgroundColor: borderColor}, 80 | verticalLine: {width: lineWidth, backgroundColor: borderColor}, 81 | navBar: { 82 | backgroundColor: navBackColor, 83 | width: W, 84 | height: NavBarHeight, 85 | borderBottomWidth: lineWidth, 86 | borderBottomColor: borderColor, 87 | paddingTop: NavPaddingTop, 88 | flexDirection: 'row', 89 | alignItems: 'center' 90 | }, 91 | navBarHeight: NavBarHeight, 92 | navBackColor: navBackColor, 93 | navBarContentH: NavBarContentH, 94 | navPaddingTop: NavPaddingTop, 95 | centerText: {includeFontPadding: false, textAlign: 'justify', textAlignVertical: 'center'}, 96 | radiusView: { 97 | height: 20, 98 | borderRadius: 10, 99 | borderColor: mainColor, 100 | borderWidth: lineWidth, 101 | justifyContent: 'center', 102 | alignItems: 'center' 103 | }, 104 | radius26View: { 105 | height: 20, 106 | borderRadius: 1, 107 | borderColor: mainColor, 108 | borderWidth: lineWidth, 109 | justifyContent: 'center', 110 | alignItems: 'center' 111 | }, 112 | radiusDefaultView: { 113 | height: 20, 114 | borderRadius: 1, 115 | borderColor: placeholderColor, 116 | borderWidth: lineWidth, 117 | justifyContent: 'center', 118 | alignItems: 'center' 119 | }, 120 | headerBar: HeaderBar, 121 | } 122 | 123 | /** 124 | Function 125 | **/ 126 | function setSpText(size: number) { 127 | size = Math.round((size * scale + 0.5) * pixelRatio / fontScale); 128 | return (size / defaultPixel) * Textpercent; 129 | } 130 | 131 | function scaleSize(size: number) { 132 | let sc; 133 | if (pixelRatio == 2) sc = size / 2; 134 | else { 135 | size = Math.round(size * scale + 0.5); 136 | sc = size / defaultPixel; 137 | } 138 | return sc * SizePercent; 139 | } 140 | 141 | 142 | export const container = { 143 | flex: 1, 144 | flexDirection: 'row', 145 | justifyContent: 'center', 146 | alignItems: 'center', 147 | }; 148 | 149 | export const background = { 150 | flex: 1, 151 | backgroundColor: mainBackColor, 152 | }; 153 | 154 | export const whiteBackground = { 155 | flex: 1, 156 | backgroundColor: "white", 157 | }; -------------------------------------------------------------------------------- /app/http/api_gank.js: -------------------------------------------------------------------------------- 1 | import {getFetch, postFetch} from "./http_gank_extension"; 2 | 3 | const base_url = "http://gank.io/api/"; 4 | 5 | // 生成可用url 6 | const service_url = (path) => { 7 | return base_url + path; 8 | }; 9 | 10 | // 默认每页数据大小 11 | const pageSize = 16; 12 | 13 | /** 14 | * 获取分类列表数据 15 | * @param type {福利 | Android | iOS | 休息视频 | 拓展资源 | 前端 | all} 16 | * @param page 第几页 17 | * @returns {*|Promise<*>|PromiseLike|Promise} 18 | */ 19 | export const getCategoryData = (type, page) => { 20 | return getFetch(service_url(`data/${type}/${pageSize}/${page}`)); 21 | }; 22 | 23 | /** 24 | * 获取最新一天的干货 25 | * @param type {福利 | Android | iOS | 休息视频 | 拓展资源 | 前端 | all} 26 | * @param page 第几页 27 | * @returns {*|Promise<*>|PromiseLike|Promise} 28 | */ 29 | export const getToday = () => { 30 | return getFetch(service_url('today')); 31 | }; 32 | 33 | /** 34 | * 每日数据 35 | * @param date 36 | * @returns {*} 37 | */ 38 | export const getDailyInfo = (date) => { 39 | return getFetch(service_url(`day/${date}`)); 40 | }; 41 | 42 | /** 43 | * 获取历史干货 44 | * @returns {*} 45 | */ 46 | export const getHistory = (page) => { 47 | return getFetch(service_url(`history/content/${pageSize}/${page}`)); 48 | }; 49 | 50 | /** 51 | * 发布干货 52 | * @param map 发布参数 53 | * @returns {*} 54 | */ 55 | export const add2Gank = (map) => { 56 | return postFetch(service_url('add2gank'), map); 57 | }; -------------------------------------------------------------------------------- /app/http/api_wan_android.js: -------------------------------------------------------------------------------- 1 | import {getFetch, postFetch} from "./http_wan_android_extension"; 2 | 3 | const base_url = "https://www.wanandroid.com/"; 4 | 5 | // 生成可用url 6 | const service_url = (path) => { 7 | return base_url + path; 8 | }; 9 | 10 | const qq_music_url = 'https://c.y.qq.com/musichall/fcgi-bin/fcg_yqqhomepagerecommend.fcg'; 11 | 12 | /** 13 | * 获取qq音乐首页banner推荐 14 | */ 15 | export const getQQBanner = () => { 16 | return getFetch(qq_music_url); 17 | }; 18 | 19 | /** 20 | * 首页banner 21 | */ 22 | export const getBanner = () => { 23 | return getFetch(service_url('banner/json')); 24 | }; 25 | 26 | /** 27 | * 首页文章列表 28 | * @param page 第几页 29 | */ 30 | export const getHomeList = (page, cookie) => { 31 | return getFetch(service_url(`article/list/${page}/json`), {}, cookie); 32 | }; 33 | 34 | /** 35 | * 最新项目列表 36 | * @param page 页码 37 | */ 38 | export const getNewProject = (page) => { 39 | return getFetch(service_url(`article/listproject/${page}/json`)); 40 | }; 41 | 42 | export const login = (username, password) => { 43 | let map = new Map(); 44 | map['username'] = username; 45 | map['password'] = password; 46 | return postFetch(service_url(`user/login`), map); 47 | }; 48 | 49 | export const register = (username, password) => { 50 | let map = new Map(); 51 | map['username'] = username; 52 | map['password'] = password; 53 | map['repassword'] = password; 54 | return postFetch(service_url(`user/register`), map); 55 | }; 56 | 57 | export const collectList = (page, cookie) => { 58 | return getFetch(service_url(`lg/collect/list/${page}/json`), {}, cookie); 59 | }; 60 | 61 | /** 62 | * 收藏站内文章 63 | * @param id 文章id 64 | * @param cookie 65 | * @returns {*} 66 | */ 67 | export const collect = (id, cookie) => { 68 | return getFetch(service_url(`https://www.wanandroid.com/lg/collect/${id}/json`), {}, cookie); 69 | }; 70 | 71 | /** 72 | * 取消收藏站内文章 73 | * @param id 文章id 74 | * @param cookie 75 | * @returns {*} 76 | */ 77 | export const cancelCollect = (id, cookie) => { 78 | return getFetch(service_url(`https://www.wanandroid.com/lg/uncollect_originId/${id}/json`), {}, cookie); 79 | }; -------------------------------------------------------------------------------- /app/http/http_cache.js: -------------------------------------------------------------------------------- 1 | import store from "react-native-simple-store"; 2 | 3 | /** 4 | * @param key:key为url的Path 5 | * @param fetchFunc:回调函数 6 | * @param isCache:是否需要缓存 7 | * @returns {value} 8 | */ 9 | const dataCache = (key, fetchFunc, isCache) => { 10 | // 不缓存, 11 | if (!isCache) { 12 | return fetchFunc(); 13 | } 14 | // 需要缓存 15 | return store.get(key).then((value) => { 16 | if (value) { 17 | // 如果在缓存中找到数据,则返回缓存中的数据 18 | return value; 19 | } else { 20 | // 如果在缓存中取不到数据,则从网络请求中获取数据,并将获取到的数据缓存下来 21 | return fetchFunc().then((value) => { 22 | value ? store.save(key, value) : null; 23 | return value; 24 | }); 25 | } 26 | }); 27 | }; 28 | 29 | export {dataCache}; -------------------------------------------------------------------------------- /app/http/http_gank_extension.js: -------------------------------------------------------------------------------- 1 | /** 网络请求工具类的拓展类,便于后期网络层修改维护 **/ 2 | 3 | import HttpUtils from "./http_gank_utils"; 4 | import {dataCache} from "./http_cache"; 5 | 6 | /** 7 | * GET 8 | * 从缓存中读取数据 9 | * @param isCache: 是否缓存 10 | * @param url 请求url 11 | * @param params 请求参数 12 | * @param isCache 是否缓存 13 | * @param callback 是否有回调函数 14 | * @returns {value\promise} 15 | * 返回的值如果从缓存中取到数据就直接换行数据,或则返回promise对象 16 | */ 17 | const fetchData = (isCache, type) => (url, params, callback) => { 18 | const fetchFunc = () => { 19 | let promise = 20 | type === "get" ? HttpUtils.getRequest(url, params) : HttpUtils.postRequest(url, params); 21 | if (callback && typeof callback === "function") { 22 | promise.then((response) => { 23 | return callback(response); 24 | }); 25 | } 26 | return promise; 27 | }; 28 | return dataCache(url, fetchFunc, isCache); 29 | }; 30 | 31 | /** 32 | * GET 请求 33 | * @param url 34 | * @param params 35 | * @param source 36 | * @param callback 37 | * @returns {{promise: Promise}} 38 | */ 39 | const getFetch = fetchData(false, "get"); 40 | 41 | /** 42 | * POST 请求 43 | * @param url 44 | * @param params 45 | * @param callback 46 | * @returns {{promise: Promise}} 47 | */ 48 | const postFetch = fetchData(false, "post"); 49 | 50 | /** 51 | * GET 请求,带缓存策略 52 | * @param url 53 | * @param params 54 | * @param callback 55 | * @returns {{promise: Promise}} 56 | */ 57 | const getFetchFromCache = fetchData(true, "get"); 58 | 59 | export {getFetch, postFetch, getFetchFromCache}; -------------------------------------------------------------------------------- /app/http/http_gank_utils.js: -------------------------------------------------------------------------------- 1 | /** 基于fetch 封装的网络请求工具类 **/ 2 | 3 | import {Component} from "react"; 4 | import {showToast} from "../configs"; 5 | 6 | /** 7 | * fetch 网络请求的header,可自定义header 内容 8 | * @type {{Accept: string, Content-Type: string, accessToken: *}} 9 | */ 10 | let header = { 11 | "Accept": "application/json", 12 | "Content-Type": "application/json" 13 | }; 14 | 15 | /** 16 | * GET 请求时,拼接请求URL 17 | * @param url 请求URL 18 | * @param params 请求参数 19 | * @returns {*} 20 | */ 21 | const handleUrl = (url) => (params) => { 22 | if (params) { 23 | let paramsArray = []; 24 | Object.keys(params).forEach((key) => 25 | paramsArray.push(key + "=" + encodeURIComponent(params[key])) 26 | ); 27 | if (url.search(/\?/) === -1) { 28 | typeof params === "object" ? (url += "?" + paramsArray.join("&")) : url; 29 | } else { 30 | url += "&" + paramsArray.join("&"); 31 | } 32 | } 33 | return url; 34 | }; 35 | 36 | /** 37 | * fetch 网络请求超时处理 38 | * @param originalFetch 原始的fetch 39 | * @param timeout 超时时间 10s 40 | * @returns {Promise.<*>} 41 | */ 42 | const timeoutFetch = (originalFetch, timeout = 10000) => { 43 | let timeoutBlock = () => { 44 | }; 45 | let timeoutPromise = new Promise((resolve, reject) => { 46 | timeoutBlock = () => { 47 | // 请求超时处理 48 | reject("timeout promise"); 49 | }; 50 | }); 51 | 52 | // Promise.race(iterable)方法返回一个promise 53 | // 这个promise在iterable中的任意一个promise被解决或拒绝后,立刻以相同的解决值被解决或以相同的拒绝原因被拒绝。 54 | let abortablePromise = Promise.race([originalFetch, timeoutPromise]); 55 | 56 | setTimeout(() => { 57 | timeoutBlock(); 58 | }, timeout); 59 | 60 | return abortablePromise; 61 | }; 62 | 63 | /** 64 | * 网络请求工具类 65 | */ 66 | export default class HttpUtils extends Component { 67 | /** 68 | * 基于fetch 封装的GET 网络请求 69 | * @param url 请求URL 70 | * @param params 请求参数 71 | * @returns {Promise} 72 | */ 73 | static getRequest = (url, params = {}) => { 74 | return timeoutFetch( 75 | fetch(handleUrl(url)(params), { 76 | method: "GET", 77 | headers: header 78 | }) 79 | ).then((response) => { 80 | if (response.ok) { 81 | return response.json(); 82 | } else { 83 | showToast('服务器繁忙,请稍后再试!') 84 | } 85 | }).then((response) => { 86 | if (response.error) { 87 | showToast(response.msg); 88 | return response; 89 | } else { 90 | return response; 91 | } 92 | }).catch((error) => { 93 | showToast('当前网络不可用,请检查网络设置!') 94 | }); 95 | }; 96 | 97 | /** 98 | * 基于fetch 的 POST 请求 99 | * @param url 请求的URL 100 | * @param params 请求参数 101 | * @returns {Promise} 102 | */ 103 | static postRequest = (url, params = {}) => { 104 | let formData = new FormData(); 105 | Object.keys(params).forEach((key) => { 106 | if (!key.startsWith("_")) { 107 | formData.append(key, params[key]) 108 | } 109 | }); 110 | return timeoutFetch( 111 | fetch(url, { 112 | method: "POST", 113 | body: formData, 114 | } 115 | ) 116 | ).then((response) => { 117 | if (response.ok) { 118 | return response.json(); 119 | } else { 120 | showToast('服务器繁忙,请稍后再试!') 121 | } 122 | }).then((response) => { 123 | if (response.error) { 124 | showToast(response.msg); 125 | return response; 126 | } else { 127 | return response; 128 | } 129 | }).catch((error) => { 130 | showToast('当前网络不可用,请检查网络设置!') 131 | }); 132 | }; 133 | } -------------------------------------------------------------------------------- /app/http/http_wan_android_extension.js: -------------------------------------------------------------------------------- 1 | /** 网络请求工具类的拓展类,便于后期网络层修改维护 **/ 2 | 3 | import HttpUtils from "./http_wan_android_utils"; 4 | import {dataCache} from "./http_cache"; 5 | 6 | /** 7 | * GET 8 | * 从缓存中读取数据 9 | * @param isCache: 是否缓存 10 | * @param url 请求url 11 | * @param params 请求参数 12 | * @param isCache 是否缓存 13 | * @param callback 是否有回调函数 14 | * @returns {value\promise} 15 | * 返回的值如果从缓存中取到数据就直接换行数据,或则返回promise对象 16 | */ 17 | const fetchData = (isCache, type) => (url, params, cookie, callback) => { 18 | const fetchFunc = () => { 19 | let promise = 20 | type === "get" ? HttpUtils.getRequest(url, params, cookie) : HttpUtils.postRequest(url, params); 21 | if (callback && typeof callback === "function") { 22 | promise.then((response) => { 23 | return callback(response); 24 | }); 25 | } 26 | return promise; 27 | }; 28 | return dataCache(url, fetchFunc, isCache); 29 | }; 30 | 31 | /** 32 | * GET 请求 33 | * @param url 34 | * @param params 35 | * @param source 36 | * @param callback 37 | * @returns {{promise: Promise}} 38 | */ 39 | const getFetch = fetchData(false, "get"); 40 | 41 | /** 42 | * POST 请求 43 | * @param url 44 | * @param params 45 | * @param callback 46 | * @returns {{promise: Promise}} 47 | */ 48 | const postFetch = fetchData(false, "post"); 49 | 50 | /** 51 | * GET 请求,带缓存策略 52 | * @param url 53 | * @param params 54 | * @param callback 55 | * @returns {{promise: Promise}} 56 | */ 57 | const getFetchFromCache = fetchData(true, "get"); 58 | 59 | export {getFetch, postFetch, getFetchFromCache}; -------------------------------------------------------------------------------- /app/http/http_wan_android_utils.js: -------------------------------------------------------------------------------- 1 | /** 基于fetch 封装的网络请求工具类 **/ 2 | 3 | import {Component} from "react"; 4 | import {showToast} from "../configs"; 5 | 6 | /** 7 | * GET 请求时,拼接请求URL 8 | * @param url 请求URL 9 | * @param params 请求参数 10 | * @returns {*} 11 | */ 12 | const handleUrl = (url) => (params) => { 13 | if (params) { 14 | let paramsArray = []; 15 | Object.keys(params).forEach((key) => 16 | paramsArray.push(key + "=" + encodeURIComponent(params[key])) 17 | ); 18 | if (url.search(/\?/) === -1) { 19 | typeof params === "object" ? (url += "?" + paramsArray.join("&")) : url; 20 | } else { 21 | url += "&" + paramsArray.join("&"); 22 | } 23 | } 24 | return url; 25 | }; 26 | 27 | /** 28 | * fetch 网络请求超时处理 29 | * @param originalFetch 原始的fetch 30 | * @param timeout 超时时间 10s 31 | * @returns {Promise.<*>} 32 | */ 33 | const timeoutFetch = (originalFetch, timeout = 10000) => { 34 | let timeoutBlock = () => { 35 | }; 36 | let timeoutPromise = new Promise((resolve, reject) => { 37 | timeoutBlock = () => { 38 | // 请求超时处理 39 | reject("timeout promise"); 40 | }; 41 | }); 42 | 43 | // Promise.race(iterable)方法返回一个promise 44 | // 这个promise在iterable中的任意一个promise被解决或拒绝后,立刻以相同的解决值被解决或以相同的拒绝原因被拒绝。 45 | let abortablePromise = Promise.race([originalFetch, timeoutPromise]); 46 | 47 | setTimeout(() => { 48 | timeoutBlock(); 49 | }, timeout); 50 | 51 | return abortablePromise; 52 | }; 53 | 54 | /** 55 | * 网络请求工具类 56 | */ 57 | export default class HttpUtils extends Component { 58 | /** 59 | * 基于fetch 封装的GET 网络请求 60 | * @param url 请求URL 61 | * @param params 请求参数 62 | * @param cookie 63 | * @returns {Promise} 64 | */ 65 | static getRequest = (url, params = {}, cookie = "") => { 66 | return timeoutFetch( 67 | fetch(handleUrl(url)(params), { 68 | method: "GET", 69 | headers: { 70 | "Accept": "application/json", 71 | "Content-Type": "application/json", 72 | "Cookie": cookie 73 | } 74 | }) 75 | ).then((response) => { 76 | if (response.ok) { 77 | return response.json(); 78 | } else { 79 | showToast('服务器繁忙,请稍后再试!') 80 | } 81 | }).then((response) => { 82 | if (url.startsWith('https://www.wanandroid.com/')) { 83 | if (response.errorCode !== 0) { 84 | showToast(response.errorMsg); 85 | return response; 86 | } else { 87 | return response; 88 | } 89 | } else { 90 | if (response.code === 0) { 91 | return response; 92 | } else { 93 | return JSON.parse(qqData); 94 | } 95 | } 96 | }).catch((error) => { 97 | if (url.startsWith('https://www.wanandroid.com/')) { 98 | showToast('当前网络不可用,请检查网络设置!') 99 | } else { 100 | return JSON.parse(qqData); 101 | } 102 | }); 103 | }; 104 | 105 | /** 106 | * 基于fetch 的 POST 请求 107 | * @param url 请求的URL 108 | * @param params 请求参数 109 | * @param cookie 110 | * @returns {Promise} 111 | */ 112 | static postRequest = (url, params = {}, cookie = "") => { 113 | let formData = new FormData(); 114 | Object.keys(params).forEach((key) => { 115 | if (!key.startsWith("_")) { 116 | formData.append(key, params[key]) 117 | } 118 | }); 119 | return timeoutFetch( 120 | fetch(url, { 121 | method: "POST", 122 | body: formData, 123 | headers: {"Cookie": cookie} 124 | }) 125 | ).then((response) => { 126 | if (response.ok) { 127 | return response.json(); 128 | } else { 129 | showToast('服务器繁忙,请稍后再试!') 130 | } 131 | }).then((response) => { 132 | if (response.errorCode !== 0) { 133 | showToast(response.errorMsg); 134 | return response; 135 | } else { 136 | return response; 137 | } 138 | }).catch((error) => { 139 | showToast('当前网络不可用,请检查网络设置!') 140 | }); 141 | }; 142 | } 143 | 144 | const qqData = "{" + 145 | " \"code\": 0," + 146 | " \"data\": {" + 147 | " \"slider\": [" + 148 | " {" + 149 | " \"linkUrl\": \"http://y.qq.com/w/album.html?albummid=001yK59e03vJw8\"," + 150 | " \"picUrl\": \"http://y.gtimg.cn/music/common/upload/MUSIC_FOCUS/1211744.jpg\"," + 151 | " \"id\": 20243" + 152 | " }," + 153 | " {" + 154 | " \"linkUrl\": \"https://y.qq.com/portal/headline/detail.html?zid=1188682\"," + 155 | " \"picUrl\": \"http://y.gtimg.cn/music/common/upload/MUSIC_FOCUS/1211809.jpg\"," + 156 | " \"id\": 20219" + 157 | " }," + 158 | " {" + 159 | " \"linkUrl\": \"https://y.qq.com/msa/362/190_6978.html?openinqqmusic=1\"," + 160 | " \"picUrl\": \"http://y.gtimg.cn/music/common/upload/MUSIC_FOCUS/1208359.jpg\"," + 161 | " \"id\": 20199" + 162 | " }," + 163 | " {" + 164 | " \"linkUrl\": \"https://y.qq.com/apg/zssphy/index.html?mbref=978.20117&openinqqmusic=1\"," + 165 | " \"picUrl\": \"http://y.gtimg.cn/music/common/upload/MUSIC_FOCUS/1207125.jpg\"," + 166 | " \"id\": 20149" + 167 | " }," + 168 | " {" + 169 | " \"linkUrl\": \"http://y.qq.com/w/taoge.html?id=6701831734\"," + 170 | " \"picUrl\": \"http://y.gtimg.cn/music/common/upload/MUSIC_FOCUS/1207484.jpg\"," + 171 | " \"id\": 20189" + 172 | " }" + 173 | " ]" + 174 | " }" + 175 | "}"; -------------------------------------------------------------------------------- /app/image/fuli.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fujianlian/GankRN/353ef94d670e18401da35540a76783c4332208cf/app/image/fuli.png -------------------------------------------------------------------------------- /app/image/icon_day.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fujianlian/GankRN/353ef94d670e18401da35540a76783c4332208cf/app/image/icon_day.png -------------------------------------------------------------------------------- /app/image/icon_fm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fujianlian/GankRN/353ef94d670e18401da35540a76783c4332208cf/app/image/icon_fm.png -------------------------------------------------------------------------------- /app/image/icon_music.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fujianlian/GankRN/353ef94d670e18401da35540a76783c4332208cf/app/image/icon_music.png -------------------------------------------------------------------------------- /app/image/icon_rank.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fujianlian/GankRN/353ef94d670e18401da35540a76783c4332208cf/app/image/icon_rank.png -------------------------------------------------------------------------------- /app/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 干货集中营 React Native App 3 | * https://github.com/fujianlian/GankRN 4 | */ 5 | import React, {Component} from 'react'; 6 | import SplashScreen from 'react-native-splash-screen' 7 | import RootView from './root' 8 | 9 | export default class GankRN extends Component { 10 | 11 | componentDidMount() { 12 | this.timer = setTimeout( 13 | () => { 14 | // 启动页隐藏 15 | SplashScreen.hide(); 16 | }, 17 | 500 18 | ); 19 | } 20 | 21 | componentWillUnmount() { 22 | this.timer && clearTimeout(this.timer); 23 | } 24 | 25 | render() { 26 | return (); 27 | } 28 | }; 29 | 30 | -------------------------------------------------------------------------------- /app/root.js: -------------------------------------------------------------------------------- 1 | import {Reducer, Actions, Router, Scene, Stack} from 'react-native-router-flux'; 2 | 3 | import {View, StatusBar, StyleSheet, Platform, BackHandler} from 'react-native'; 4 | import React, {Component} from 'react'; 5 | import MainTab from './view/MainTab' 6 | import {C1, mainColor} from "./configs"; 7 | import MyWeb from './view/WebPageView' 8 | import PhotoView from './view/photo/PhotoView' 9 | import AddGankView from './view/gank/AddGankView' 10 | import AboutView from './view/personal/AboutView' 11 | import LoginView from './view/personal/LoginView' 12 | import RegisterView from './view/personal/RegisterView' 13 | import CollectView from './view/personal/CollectView' 14 | 15 | const statusBackColor = Platform.select({android: "#AE2C25", ios: 'transparent'}); 16 | 17 | export default class RootView extends Component { 18 | render() { 19 | return ( 20 | 21 | {Platform.OS === 'ios' ? : 22 | } 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | ); 37 | } 38 | } 39 | 40 | const BackTextPadding = Platform.select({android: 5, ios: 2}); 41 | const styles = StyleSheet.create({ 42 | container: { 43 | flex: 1, 44 | }, 45 | }); 46 | 47 | const FrameStyles = { 48 | navigationBarStyle: { 49 | backgroundColor: mainColor, 50 | }, 51 | titleStyle: { 52 | color: "white", 53 | alignSelf: 'center', 54 | }, 55 | rightButtonTextStyle: { 56 | color: mainColor, 57 | textAlign: 'justify', 58 | includeFontPadding: false, 59 | textAlignVertical: 'center', 60 | }, 61 | rightButtonStyle: { 62 | paddingLeft: 10, 63 | justifyContent: 'center' 64 | }, 65 | leftButtonStyle: {}, 66 | backButtonTextStyle: { 67 | paddingTop: BackTextPadding, 68 | paddingLeft: 0, 69 | color: C1, 70 | textAlign: 'justify', 71 | includeFontPadding: false, 72 | textAlignVertical: 'center', 73 | }, 74 | backButtonTintColor: "white", 75 | }; 76 | 77 | export const routerReducerCreate = params => { 78 | const defaultReducer = new Reducer(params); 79 | return (state, action) => { 80 | return defaultReducer(state, action); 81 | }; 82 | }; 83 | 84 | 85 | const backAndroidHandler = () => { 86 | if (Actions.currentScene === 'main') { 87 | BackHandler.exitApp(); 88 | } else { 89 | Actions.pop(); 90 | } 91 | return true; 92 | }; -------------------------------------------------------------------------------- /app/view/MainTab.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 主页面 3 | * @flow 4 | */ 5 | import React, {Component} from 'react'; 6 | 7 | import HomeTab from './wanandroid/HomeView' 8 | import GankTab from './gank/GankView' 9 | import PersonalTab from './personal/PersonalView' 10 | 11 | import ScrollableTabView from 'react-native-scrollable-tab-view'; 12 | import HomeTabBar from '../view/component/HomeTabBar'; 13 | 14 | export default class MainTab extends Component { 15 | 16 | constructor(props) { 17 | super(props); 18 | this.state = { 19 | tabNames: ['干货营', '玩安卓', '我的'], 20 | tabIconNames: ['ios-analytics', 'logo-android', 'ios-contact'], 21 | }; 22 | } 23 | 24 | render() { 25 | let tabNames = this.state.tabNames; 26 | let tabIconNames = this.state.tabIconNames; 27 | return ( 28 | } 30 | tabBarPosition={'bottom'} 31 | locked={true} 32 | initialPage={0} 33 | scrollWithoutAnimation={true}> 34 | 35 | 36 | 37 | 38 | ); 39 | } 40 | } -------------------------------------------------------------------------------- /app/view/WebPageView.js: -------------------------------------------------------------------------------- 1 | import React, {Component} from "react"; 2 | import {BackHandler} from "react-native"; 3 | import {WebView,} from "react-native-webview"; 4 | import {Actions} from 'react-native-router-flux'; 5 | 6 | export default class MyWeb extends Component { 7 | 8 | constructor(props) { 9 | super(props); 10 | this.state = { 11 | backAndroidHandler: false, 12 | }; 13 | this.title = this.props.title; 14 | } 15 | 16 | componentDidMount() { 17 | BackHandler.addEventListener('hardwareBackPress', this._handleBack.bind(this)); 18 | } 19 | 20 | componentWillUnmount() { 21 | BackHandler.removeEventListener('hardwareBackPress', this._handleBack.bind(this)) 22 | } 23 | 24 | render() { 25 | return ( 26 | { 28 | this.webView = r; 29 | }} 30 | source={{uri: this.props.url}} 31 | javaScriptEnabled={true} 32 | useWebKit={true} 33 | onNavigationStateChange={this._onNavigationStateChange.bind(this)} 34 | /> 35 | ); 36 | } 37 | 38 | _onNavigationStateChange(navState) { 39 | let title = navState.title; 40 | if (title.length > 0 && this.title !== title) { 41 | this.title = title; 42 | Actions.refresh({title}) 43 | } 44 | this.setState({ 45 | backAndroidHandler: navState.canGoBack 46 | }); 47 | } 48 | 49 | _handleBack() { 50 | if (this.state.backAndroidHandler) { 51 | this.webView.goBack(); 52 | return true; 53 | } else { 54 | return false; 55 | } 56 | } 57 | 58 | } 59 | -------------------------------------------------------------------------------- /app/view/component/Button.android.js: -------------------------------------------------------------------------------- 1 | const React = require('react'); 2 | const ReactNative = require('react-native'); 3 | const { 4 | TouchableNativeFeedback, 5 | } = ReactNative; 6 | 7 | const Button = (props) => { 8 | return 13 | {props.children} 14 | ; 15 | }; 16 | 17 | module.exports = Button; 18 | -------------------------------------------------------------------------------- /app/view/component/Button.ios.js: -------------------------------------------------------------------------------- 1 | const React = require('react'); 2 | const ReactNative = require('react-native'); 3 | const { 4 | TouchableOpacity, 5 | } = ReactNative; 6 | 7 | const Button = (props) => { 8 | return 9 | {props.children} 10 | ; 11 | }; 12 | 13 | module.exports = Button; 14 | -------------------------------------------------------------------------------- /app/view/component/ErrorView.js: -------------------------------------------------------------------------------- 1 | import React, {Component} from "react"; 2 | import {Text, View} from "react-native"; 3 | import {container} from "../../configs"; 4 | 5 | /** 6 | * 加载失败页面 7 | */ 8 | export default class ErrorView extends Component { 9 | 10 | render() { 11 | return ( 12 | 13 | 14 | Fail: {this.props.error} 15 | 16 | 17 | ); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /app/view/component/FootLoadMore.js: -------------------------------------------------------------------------------- 1 | import React, {Component} from "react"; 2 | import {ActivityIndicator, StyleSheet, Text, View} from "react-native"; 3 | import {C2} from "../../configs"; 4 | 5 | /** 6 | * 列表底部正在加载 7 | */ 8 | export default class FootLoadMore extends Component { 9 | 10 | render() { 11 | return ( 12 | 13 | 14 | {'玩命加载数据~'} 15 | 16 | ); 17 | } 18 | } 19 | 20 | const styles = StyleSheet.create({ 21 | container: { 22 | height: 50, 23 | flexDirection: 'row', 24 | justifyContent: 'center', 25 | alignItems: 'center', 26 | }, 27 | title: { 28 | fontSize: 15, 29 | color: C2, 30 | padding: 15, 31 | marginLeft: 5 32 | }, 33 | }); 34 | -------------------------------------------------------------------------------- /app/view/component/FootNoMore.js: -------------------------------------------------------------------------------- 1 | import React, {Component} from "react"; 2 | import {StyleSheet, Text, View} from "react-native"; 3 | import {C2} from "../../configs"; 4 | 5 | /** 6 | * 列表底部加载完毕 7 | */ 8 | export default class FootLoadMore extends Component { 9 | 10 | render() { 11 | return ( 12 | 13 | {'没有更多数据了~'} 14 | 15 | ); 16 | } 17 | } 18 | 19 | const styles = StyleSheet.create({ 20 | container: { 21 | height: 50, 22 | flexDirection: 'row', 23 | justifyContent: 'center', 24 | alignItems: 'center', 25 | }, 26 | title: { 27 | fontSize: 15, 28 | color: C2, 29 | }, 30 | }); 31 | -------------------------------------------------------------------------------- /app/view/component/HomeTabBar.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Sample React Native App 3 | * https://github.com/facebook/react-native 4 | * @flow 5 | */ 6 | import React, {Component} from 'react'; 7 | import { 8 | StyleSheet, 9 | Text, 10 | TouchableOpacity, 11 | View 12 | } from 'react-native'; 13 | 14 | import Icon from 'react-native-vector-icons/Ionicons'; 15 | import {C2, C9, isIphoneX, mainColor} from "../../configs"; //这个是图标 16 | 17 | const PropTypes = require('prop-types'); 18 | 19 | export default class HomeTabBar extends Component { 20 | static propTypes = { 21 | goToPage: PropTypes.func, // 跳转到对应tab的方法 22 | activeTab: PropTypes.number, // 当前被选中的tab下标 23 | tabNames: PropTypes.array, // 保存Tab名称 24 | tabIconNames: PropTypes.array, // 保存Tab图标 25 | }; // 注意这里有分号 26 | 27 | render() { 28 | return ( 29 | 30 | {/*遍历。系统会提供一个tab和下标 调用一个自定义的方法*/} 31 | {this.props.tabNames.map((tab, i) => this.renderTabOption(tab, i))} 32 | 33 | ); 34 | } 35 | 36 | componentDidMount() { 37 | // Animated.Value监听范围 [0, tab数量-1] 38 | this.props.scrollValue.addListener(this.setAnimationValue); 39 | } 40 | 41 | setAnimationValue({value}) { 42 | } 43 | 44 | // 处理tabbar的颜色和字体及图标 45 | renderTabOption(tab, i) { 46 | let color = this.props.activeTab === i ? mainColor : C2; // 判断i是否是当前选中的tab,设置不同的颜色 47 | return ( 48 | this.props.goToPage(i)} 51 | style={styles.tab} 52 | key={tab === undefined ? 'tab' : tab.toString()}> 53 | 54 | 58 | 59 | {this.props.tabNames[i]} 60 | 61 | 62 | 63 | ); 64 | } 65 | 66 | 67 | } 68 | 69 | const paddingBottom = isIphoneX() ? 15 : 0; 70 | const height = isIphoneX() ? 68 : 52; 71 | 72 | const styles = StyleSheet.create({ 73 | tabs: { 74 | flexDirection: 'row', 75 | height: height, 76 | paddingBottom: paddingBottom, 77 | backgroundColor: 'white', 78 | borderTopColor: C9, 79 | borderTopWidth: 0.3 80 | }, 81 | tab: { 82 | flex: 1, 83 | justifyContent: 'center', 84 | alignItems: 'center', 85 | }, 86 | tabItem: { 87 | flexDirection: 'column', 88 | alignItems: 'center', 89 | }, 90 | 91 | }); -------------------------------------------------------------------------------- /app/view/component/QYScrollableTabBar.js: -------------------------------------------------------------------------------- 1 | import {screenWidth} from "../../configs"; 2 | 3 | const React = require('react'); 4 | const { ViewPropTypes } = ReactNative = require('react-native'); 5 | const PropTypes = require('prop-types'); 6 | const createReactClass = require('create-react-class'); 7 | const { 8 | View, 9 | Animated, 10 | StyleSheet, 11 | ScrollView, 12 | Text, 13 | Platform, 14 | } = ReactNative; 15 | const Button = require('./Button'); 16 | 17 | const QYScrollableTabBar = createReactClass({ 18 | propTypes: { 19 | goToPage: PropTypes.func, 20 | activeTab: PropTypes.number, 21 | tabs: PropTypes.array, 22 | backgroundColor: PropTypes.string, 23 | activeTextColor: PropTypes.string, 24 | inactiveTextColor: PropTypes.string, 25 | scrollOffset: PropTypes.number, 26 | style: ViewPropTypes.style, 27 | tabStyle: ViewPropTypes.style, 28 | tabsContainerStyle: ViewPropTypes.style, 29 | textStyle: Text.propTypes.style, 30 | renderTab: PropTypes.func, 31 | underlineStyle: ViewPropTypes.style, 32 | onScroll: PropTypes.func, 33 | }, 34 | 35 | getDefaultProps() { 36 | return { 37 | scrollOffset: 52, 38 | activeTextColor: 'navy', 39 | inactiveTextColor: 'black', 40 | backgroundColor: null, 41 | style: {}, 42 | tabStyle: {}, 43 | tabsContainerStyle: {}, 44 | underlineStyle: {}, 45 | }; 46 | }, 47 | 48 | getInitialState() { 49 | this._tabsMeasurements = []; 50 | return { 51 | _leftTabUnderline: new Animated.Value(0), 52 | _widthTabUnderline: new Animated.Value(0), 53 | _containerWidth: null, 54 | }; 55 | }, 56 | 57 | componentDidMount() { 58 | this.props.scrollValue.addListener(this.updateView); 59 | }, 60 | 61 | updateView(offset) { 62 | const position = Math.floor(offset.value); 63 | const pageOffset = offset.value % 1; 64 | const tabCount = this.props.tabs.length; 65 | const lastTabPosition = tabCount - 1; 66 | 67 | if (tabCount === 0 || offset.value < 0 || offset.value > lastTabPosition) { 68 | return; 69 | } 70 | 71 | if (this.necessarilyMeasurementsCompleted(position, position === lastTabPosition)) { 72 | this.updateTabPanel(position, pageOffset); 73 | this.updateTabUnderline(position, pageOffset, tabCount); 74 | } 75 | }, 76 | 77 | necessarilyMeasurementsCompleted(position, isLastTab) { 78 | return this._tabsMeasurements[position] && 79 | (isLastTab || this._tabsMeasurements[position + 1]) && 80 | this._tabContainerMeasurements && 81 | this._containerMeasurements; 82 | }, 83 | 84 | updateTabPanel(position, pageOffset) { 85 | const containerWidth = this._containerMeasurements.width; 86 | const tabWidth = this._tabsMeasurements[position].width; 87 | const nextTabMeasurements = this._tabsMeasurements[position + 1]; 88 | const nextTabWidth = nextTabMeasurements && nextTabMeasurements.width || 0; 89 | const tabOffset = this._tabsMeasurements[position].left; 90 | const absolutePageOffset = pageOffset * tabWidth; 91 | let newScrollX = tabOffset + absolutePageOffset; 92 | 93 | // center tab and smooth tab change (for when tabWidth changes a lot between two tabs) 94 | newScrollX -= (containerWidth - (1 - pageOffset) * tabWidth - pageOffset * nextTabWidth) / 2; 95 | newScrollX = newScrollX >= 0 ? newScrollX : 0; 96 | 97 | if (Platform.OS === 'android') { 98 | this._scrollView.scrollTo({x: newScrollX, y: 0, animated: false, }); 99 | } else { 100 | const rightBoundScroll = this._tabContainerMeasurements.width - (this._containerMeasurements.width); 101 | newScrollX = newScrollX > rightBoundScroll ? rightBoundScroll : newScrollX; 102 | this._scrollView.scrollTo({x: newScrollX, y: 0, animated: false, }); 103 | } 104 | 105 | }, 106 | 107 | updateTabUnderline(position, pageOffset, tabCount) { 108 | const lineLeft = this._tabsMeasurements[position].left; 109 | const lineRight = this._tabsMeasurements[position].right; 110 | 111 | if (position < tabCount - 1) { 112 | const nextTabLeft = this._tabsMeasurements[position + 1].left; 113 | const nextTabRight = this._tabsMeasurements[position + 1].right; 114 | 115 | const newLineLeft = (pageOffset * nextTabLeft + (1 - pageOffset) * lineLeft); 116 | const newLineRight = (pageOffset * nextTabRight + (1 - pageOffset) * lineRight); 117 | 118 | this.state._leftTabUnderline.setValue(newLineLeft); 119 | this.state._widthTabUnderline.setValue(newLineRight - newLineLeft); 120 | } else { 121 | this.state._leftTabUnderline.setValue(lineLeft); 122 | this.state._widthTabUnderline.setValue(lineRight - lineLeft); 123 | } 124 | }, 125 | renderDot(page){ 126 | if (page === 0 && this.props.showDot){ 127 | return( 128 | 129 | ) 130 | } 131 | }, 132 | renderTab(name, page, isTabActive, onPressHandler, onLayoutHandler) { 133 | const { activeTextColor, inactiveTextColor, textStyle, } = this.props; 134 | const textColor = isTabActive ? activeTextColor : inactiveTextColor; 135 | const fontWeight = isTabActive ? 'bold' : 'normal'; 136 | 137 | return ; 152 | }, 153 | 154 | measureTab(page, event) { 155 | const { x, width, height, } = event.nativeEvent.layout; 156 | this._tabsMeasurements[page] = {left: x, right: x + width, width, height, }; 157 | this.updateView({value: this.props.scrollValue.__getValue(), }); 158 | }, 159 | 160 | render() { 161 | const tabUnderlineStyle = { 162 | position: 'absolute', 163 | height: 4, 164 | backgroundColor: 'navy', 165 | bottom: 0, 166 | }; 167 | 168 | const dynamicTabUnderline = { 169 | left: this.state._leftTabUnderline, 170 | width: this.state._widthTabUnderline, 171 | }; 172 | 173 | return 177 | { this._scrollView = scrollView; }} 179 | horizontal={true} 180 | showsHorizontalScrollIndicator={false} 181 | showsVerticalScrollIndicator={false} 182 | directionalLockEnabled={true} 183 | bounces={false} 184 | scrollsToTop={false} 185 | scrollEventThrottle = {16} 186 | {...this.props} 187 | > 188 | 193 | {this.props.tabs.map((name, page) => { 194 | const isTabActive = this.props.activeTab === page; 195 | const renderTab = this.props.renderTab || this.renderTab; 196 | return renderTab(name, page, isTabActive, this.props.goToPage, this.measureTab.bind(this, page)); 197 | })} 198 | 199 | 200 | 201 | ; 202 | }, 203 | 204 | componentWillReceiveProps(nextProps) { 205 | // If the tabs change, force the width of the tabs container to be recalculated 206 | if (JSON.stringify(this.props.tabs) !== JSON.stringify(nextProps.tabs) && this.state._containerWidth) { 207 | this.setState({ _containerWidth: null, }); 208 | } 209 | }, 210 | 211 | onTabContainerLayout(e) { 212 | this._tabContainerMeasurements = e.nativeEvent.layout; 213 | let width = this._tabContainerMeasurements.width; 214 | if (width < screenWidth) { 215 | width = screenWidth; 216 | } 217 | this.setState({ _containerWidth: width, }); 218 | this.updateView({value: this.props.scrollValue.__getValue(), }); 219 | }, 220 | 221 | onContainerLayout(e) { 222 | this._containerMeasurements = e.nativeEvent.layout; 223 | this.updateView({value: this.props.scrollValue.__getValue(), }); 224 | }, 225 | }); 226 | 227 | module.exports.QYScrollableTabBar = QYScrollableTabBar; 228 | 229 | const styles = StyleSheet.create({ 230 | tab: { 231 | height: 46, 232 | alignItems: 'center', 233 | justifyContent: 'center', 234 | paddingLeft: 20, 235 | paddingRight: 20, 236 | flexDirection: 'row', 237 | }, 238 | container: { 239 | height: 46, 240 | borderWidth: 0.5, 241 | borderTopWidth: 0, 242 | borderLeftWidth: 0, 243 | borderRightWidth: 0, 244 | borderColor: '#ccc', 245 | }, 246 | tabs: { 247 | flexDirection: 'row', 248 | justifyContent: 'space-around', 249 | }, 250 | }); 251 | -------------------------------------------------------------------------------- /app/view/component/RnButton.js: -------------------------------------------------------------------------------- 1 | import React, {Component} from "react"; 2 | import {Text, StyleSheet, TouchableHighlight, TouchableOpacity, View} from "react-native"; 3 | import {mainColor, screenWidth} from "../../configs"; 4 | 5 | export default class RnButton extends Component { 6 | state = { 7 | status: 1, 8 | disabled: false, 9 | }; 10 | onPress = () => { 11 | const {onPress} = this.props; 12 | onPress ? onPress() : ""; 13 | }; 14 | enable = () => { 15 | this.setState({ 16 | disabled: false, 17 | }) 18 | }; 19 | disabled = () => { 20 | this.setState({ 21 | disabled: true, 22 | }) 23 | }; 24 | 25 | render() { 26 | const {name, backgroundColor} = this.props; 27 | return ( 28 | 34 | {name} 35 | 36 | ); 37 | } 38 | } 39 | 40 | const styles = StyleSheet.create({ 41 | button: { 42 | width: screenWidth - 40, 43 | height: 40, 44 | borderRadius: 5, 45 | backgroundColor: mainColor, 46 | justifyContent: 'center', 47 | overflow: 'hidden', 48 | marginHorizontal: 20, 49 | marginTop: 30 50 | }, 51 | buttonText: { 52 | textAlign: 'center', 53 | color: "white", 54 | fontSize: 16, 55 | fontWeight: "400" 56 | }, 57 | disabled: { 58 | backgroundColor: '#80ff0000', 59 | }, 60 | }); -------------------------------------------------------------------------------- /app/view/component/WaitLoadingView.js: -------------------------------------------------------------------------------- 1 | import React, {Component} from "react"; 2 | import {ActivityIndicator, View} from "react-native"; 3 | import {container, mainColor} from "../../configs"; 4 | 5 | /** 6 | * 等待加载页面 7 | */ 8 | export default class WaitLoadingView extends Component { 9 | 10 | render() { 11 | return ( 12 | 13 | 19 | 20 | ); 21 | } 22 | } -------------------------------------------------------------------------------- /app/view/gank/AddGankView.js: -------------------------------------------------------------------------------- 1 | import React, {Component} from 'react'; 2 | import { 3 | TextInput, 4 | View, 5 | TouchableOpacity, 6 | DeviceEventEmitter, 7 | Picker, Platform 8 | } from 'react-native'; 9 | import {background, C21, mainColor, screenWidth, showToast} from "../../configs"; 10 | import {add2Gank} from "../../http/api_gank"; 11 | import {Actions} from "react-native-router-flux"; 12 | import Icon from 'react-native-vector-icons/Ionicons'; 13 | 14 | const margin = Platform.select({android: 12, ios: 0}); 15 | const pickWidth = Platform.select({android: screenWidth - 12, ios: screenWidth}); 16 | 17 | /** 18 | * 干货发布 19 | */ 20 | export default class AddGankView extends Component { 21 | 22 | constructor(props) { 23 | super(props); 24 | this.state = { 25 | height: 400, 26 | type: "Android", 27 | url: "", 28 | desc: "", 29 | who: "", 30 | } 31 | } 32 | 33 | componentWillMount() { 34 | DeviceEventEmitter.addListener('send', this.send.bind(this)); 35 | } 36 | 37 | 38 | componentWillUnmount() { 39 | DeviceEventEmitter.removeAllListeners('send'); 40 | } 41 | 42 | render() { 43 | return ( 44 | 45 | this.setState({url: text})} 49 | padding={0} 50 | clearButtonMode={'while-editing'} 51 | autoFocus={true} 52 | selectionColor={mainColor} 53 | value={this.state.text} 54 | /> 55 | this.setState({desc: text})} 59 | padding={0} 60 | clearButtonMode={'while-editing'} 61 | selectionColor={mainColor} 62 | value={this.state.text} 63 | /> 64 | this.setState({who: text})} 68 | padding={0} 69 | clearButtonMode={'while-editing'} 70 | selectionColor={mainColor} 71 | value={this.state.text} 72 | /> 73 | this.setState({type: itemValue})}> 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | ); 88 | } 89 | 90 | send() { 91 | if (this.isNull(this.state.url)) { 92 | showToast('请输入网址') 93 | } else if (this.isNull(this.state.desc)) { 94 | showToast('请输入描述') 95 | } else if (this.isNull(this.state.who)) { 96 | showToast('请输入昵称') 97 | } else { 98 | let map = new Map(); 99 | map['url'] = this.state.url; 100 | map['desc'] = this.state.desc; 101 | map['who'] = this.state.who; 102 | map['type'] = this.state.type; 103 | map['debug'] = false; 104 | add2Gank(map).then((data) => { 105 | if (!data.error) { 106 | showToast('发布成功'); 107 | Actions.pop() 108 | } 109 | }); 110 | } 111 | } 112 | 113 | isNull(str) { 114 | if (str === "") return true; 115 | let r = "^[ ]+$"; 116 | let re = new RegExp(r); 117 | return re.test(str); 118 | } 119 | 120 | } 121 | 122 | AddGankView.renderRightButton = (props) => { 123 | return ( 124 | { 128 | DeviceEventEmitter.emit('send'); 129 | }}> 130 | 131 | 132 | ) 133 | }; 134 | 135 | const styles = { 136 | textStyle: { 137 | marginTop: 20, 138 | marginStart: 20, 139 | marginEnd: 20, 140 | height: 40, 141 | width: screenWidth - 40, 142 | borderBottomWidth: 1, 143 | borderBottomColor: C21 144 | } 145 | }; 146 | -------------------------------------------------------------------------------- /app/view/gank/AndroidView.js: -------------------------------------------------------------------------------- 1 | import React, {Component} from "react"; 2 | import {getCategoryData} from "../../http/api_gank"; 3 | import ErrorView from "../component/ErrorView"; 4 | import {UltimateListView} from "react-native-ultimate-listview"; 5 | import GankItemView from "./GankItemView"; 6 | 7 | /** 8 | * 干货定制列表 9 | */ 10 | export default class AndroidView extends Component { 11 | 12 | constructor(props) { 13 | super(props); 14 | this.state = { 15 | isLoading: true, 16 | //网络请求状态 17 | error: false, 18 | errorInfo: "", 19 | }; 20 | } 21 | 22 | //网络请求 23 | fetchData = (page = 1, startFetch, abortFetch) => { 24 | getCategoryData("Android", page) 25 | .then((list) => { 26 | this.setState({ 27 | isLoading: false, 28 | }); 29 | startFetch(list.results, 16) 30 | }) 31 | .catch((err) => { 32 | abortFetch(); 33 | this.setState({ 34 | error: true, 35 | errorInfo: err 36 | }) 37 | }); 38 | }; 39 | 40 | renderData() { 41 | return ( 42 | this.listView = ref} 44 | onFetch={this.fetchData.bind(this)} 45 | keyExtractor={(item, index) => `${index} - ${item}`} 46 | allLoadedText={'没有更多数据了'} 47 | refreshableTitlePull={'下拉刷新...'} 48 | refreshableTitleRelease={'释放刷新...'} 49 | refreshableTitleRefreshing={'正在刷新...'} 50 | waitingSpinnerText={''} 51 | refreshableMode={'advanced'} 52 | item={this.renderItemView.bind(this)} 53 | maxToRenderPerBatch={10} 54 | /> 55 | ); 56 | } 57 | 58 | render() { 59 | if (this.state.error) { 60 | return ; 61 | } 62 | return this.renderData(); 63 | } 64 | 65 | renderItemView(item, index, separator) { 66 | return ( 67 | 68 | ); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /app/view/gank/DailyItemView.js: -------------------------------------------------------------------------------- 1 | import React, {PureComponent} from "react"; 2 | import {Image, Platform, StyleSheet, Text, TouchableNativeFeedback, TouchableHighlight, View} from "react-native"; 3 | import {Actions} from "react-native-router-flux"; 4 | import {C1, C3} from "../../configs"; 5 | 6 | export default class DailyItemView extends PureComponent { 7 | 8 | render(): React.ReactNode { 9 | return this.setPlatform() 10 | } 11 | 12 | setPlatform() { 13 | if (Platform.OS === 'ios') { 14 | return 15 | { 17 | Actions.webView({"url": this.props.item.url, "title": this.props.item.desc}) 18 | }} 19 | underlayColor={'rgba(223,223,223,0.5)'}> 20 | {this.rendItem()} 21 | 22 | 23 | } else { 24 | return { 26 | Actions.webView({"url": this.props.item.url, "title": this.props.item.desc}) 27 | }} 28 | background={TouchableNativeFeedback.SelectableBackground()}> 29 | {this.rendItem()} 30 | 31 | } 32 | } 33 | 34 | rendItem() { 35 | return 36 | 37 | {this.props.item.desc} 45 | {this.props.item.images != null ? 46 | : null} 54 | 55 | 56 | 57 | {this.props.item.who} · {this.props.item.type} 58 | 59 | 60 | {this.props.item.publishedAt.substring(5, 10)} 61 | 62 | 63 | 64 | } 65 | } 66 | 67 | const styles = StyleSheet.create({ 68 | item: { 69 | elevation: 2, 70 | flex: 1, 71 | padding: 15 72 | }, 73 | a: { 74 | flexDirection: 'row', 75 | alignItems: 'center' 76 | }, 77 | text: { 78 | color: C3, fontSize: 12, 79 | } 80 | }); 81 | 82 | const itemStyle = Platform.select({android: [styles.item, styles.itemMargin], ios: styles.item}); 83 | -------------------------------------------------------------------------------- /app/view/gank/DailyView.js: -------------------------------------------------------------------------------- 1 | import React, {Component} from "react"; 2 | import {Platform, Image, Text, TouchableWithoutFeedback, View} from "react-native"; 3 | import {C10, C2, C9, mainColor, screenWidth} from "../../configs"; 4 | 5 | import {getQQBanner} from "../../http/api_wan_android"; 6 | import {getToday} from "../../http/api_gank"; 7 | import Swiper from 'react-native-swiper' 8 | import {Actions} from "react-native-router-flux"; 9 | import {UltimateListView} from 'react-native-ultimate-listview' 10 | import DailyItemView from "./DailyItemView"; 11 | 12 | const h = Platform.select({'android': screenWidth * 0.4, "ios": screenWidth * 0.44}); 13 | 14 | // 每日推荐 15 | export default class DailyView extends Component { 16 | 17 | constructor(props) { 18 | super(props); 19 | this.state = { 20 | data: null, 21 | index: 0 22 | } 23 | } 24 | 25 | componentDidMount() { 26 | getQQBanner() 27 | .then((list) => { 28 | this.setState({ 29 | data: list.data.slider, 30 | }); 31 | }); 32 | this.fetchData() 33 | } 34 | 35 | fetchData = (page, startFetch, abortFetch) => { 36 | getToday().then((list) => { 37 | let category = list.category; 38 | let dataSource = []; 39 | category.map((value, i) => { 40 | dataSource[i] = list.results[category[i]][list.results[category[i]].length - 1]; 41 | }); 42 | startFetch(dataSource, category.length) 43 | }).catch(() => { 44 | abortFetch(); 45 | }); 46 | }; 47 | 48 | render() { 49 | return ( 50 | `${index} - ${item}`} 54 | refreshable={true} 55 | allLoadedText={'没有更多数据了'} 56 | waitingSpinnerText={'加载中...'} 57 | item={this.renderItemView.bind(this)} 58 | pagination={false} 59 | separator={this.space.bind(this)} 60 | /> 61 | ); 62 | } 63 | 64 | space() { 65 | return ; 69 | } 70 | 71 | header() { 72 | return ( 73 | 74 | {this._renderBanner()} 75 | {this._renderMiddle()} 76 | 77 | ) 78 | } 79 | 80 | renderItemView(item, index, separator) { 81 | return ( 82 | 83 | ); 84 | } 85 | 86 | _renderBanner() { 87 | return ( 88 | 89 | {this.state.data != null ? 90 | 91 | { 94 | this.setState( 95 | {index: state.index} 96 | ) 97 | }} 98 | dot={} 108 | activeDot={} 118 | paginationStyle={{bottom: 7,}}> 119 | {this.state.data.map((value, i) => this.renderItem(value, i))} 120 | 121 | : } 122 | ); 123 | } 124 | 125 | renderItem(value, i) { 126 | return ( 127 | { 131 | Actions.webView({"url": value.linkUrl}); 132 | }}> 133 | 134 | 135 | ); 136 | } 137 | 138 | _renderMiddle() { 139 | return ( 140 | 141 | 148 | {this._middleImage("https://gank.io/xiandu", "闲读", require('../../image/icon_fm.png'))} 149 | {this._middleImage("https://github.com/trending", "每日推荐", require('../../image/icon_day.png'))} 150 | {this._middleImage("https://www.wanandroid.com", "玩安卓", require('../../image/icon_music.png'))} 151 | {this._middleImage("https://m.douban.com/movie/nowintheater?loc_id=108288", "热映榜", require('../../image/icon_rank.png'))} 152 | 153 | 159 | {"干货闲读"} 160 | {"每日推荐"} 161 | {"玩安卓"} 162 | {"热映榜"} 163 | 164 | 165 | 166 | ) 167 | } 168 | 169 | _middleImage(url, title, image) { 170 | return { 171 | Actions.webView({"url": url, "title": title}) 172 | }}> 173 | 174 | 175 | } 176 | } 177 | 178 | const styles = { 179 | 180 | slide: { 181 | height: h, 182 | justifyContent: 'center', 183 | }, 184 | 185 | text: { 186 | color: '#fff', 187 | fontSize: 14, 188 | }, 189 | 190 | image: { 191 | flex: 1 192 | }, 193 | 194 | views: { 195 | position: 'absolute', 196 | top: h - 30, 197 | backgroundColor: 'rgba(0,0,0,.3)', 198 | width: screenWidth, 199 | height: 30, 200 | textAlign: 'center', 201 | textAlignVertical: 'center', 202 | justifyContent: 'center', 203 | paddingStart: 10, 204 | paddingEnd: 12 * 4 + 20 205 | }, 206 | middle_text: { 207 | fontSize: 13, 208 | width: 52, 209 | color: C2, 210 | textAlign: "center" 211 | } 212 | }; 213 | -------------------------------------------------------------------------------- /app/view/gank/FuliView.js: -------------------------------------------------------------------------------- 1 | import React, {Component, PureComponent} from "react"; 2 | import { 3 | Image, 4 | StyleSheet, 5 | TouchableHighlight, 6 | Platform, 7 | } from "react-native"; 8 | import {getCategoryData} from "../../http/api_gank"; 9 | import {Actions} from 'react-native-router-flux'; 10 | import {screenWidth} from "../../configs"; 11 | import WaitLoadingView from "../component/WaitLoadingView"; 12 | import ErrorView from "../component/ErrorView"; 13 | import {UltimateListView} from "react-native-ultimate-listview"; 14 | 15 | const RefreshableMode = Platform.select({ios: 'advanced', android: 'basic'}) 16 | 17 | export default class FuliView extends Component { 18 | 19 | constructor(props) { 20 | super(props); 21 | this.state = { 22 | isLoading: true, 23 | //网络请求状态 24 | error: false, 25 | errorInfo: "", 26 | layout: "grid" 27 | }; 28 | } 29 | 30 | //网络请求 31 | fetchData = (page = 1, startFetch, abortFetch) => { 32 | getCategoryData("福利", page) 33 | .then((list) => { 34 | this.setState({ 35 | isLoading: false, 36 | }); 37 | startFetch(list.results, 16) 38 | }) 39 | .catch((err) => { 40 | abortFetch(); 41 | this.setState({ 42 | error: true, 43 | errorInfo: err 44 | }) 45 | }); 46 | }; 47 | 48 | componentDidMount() { 49 | //请求数据 50 | this.fetchData(); 51 | } 52 | 53 | renderData() { 54 | return ( 55 | this.listView = ref} 57 | key={this.state.layout} 58 | onFetch={this.fetchData.bind(this)} 59 | refreshableTitleRefreshing={'正在加载...'} 60 | refreshableTitleRelease={'释放刷新'} 61 | refreshableTitlePull={'下拉刷新'} 62 | waitingSpinnerText={''} 63 | dateTitle={'最近更新:'} 64 | displayDate 65 | keyExtractor={(item, index) => `${index} - ${item}`} 66 | refreshableMode={RefreshableMode} 67 | item={this.renderItemView.bind(this)} 68 | numColumns={2} 69 | maxToRenderPerBatch={6} 70 | updateCellsBatchingPeriod={100} 71 | getItemLayout={(data, index) => ( 72 | {length: screenWidth * 0.65, offset: screenWidth * 0.65 * index, index} 73 | )} 74 | /> 75 | ); 76 | } 77 | 78 | render() { 79 | // 第一次加载等待的view 80 | if (this.state.isLoading && !this.state.error) { 81 | return ; 82 | } else if (this.state.error) { 83 | return ; 84 | } 85 | // 加载数据 86 | return this.renderData(); 87 | } 88 | 89 | renderItemView(item, index, separator) { 90 | return ( 91 | 92 | ); 93 | } 94 | } 95 | 96 | class RenderItemView extends PureComponent { 97 | 98 | render(): React.ReactNode { 99 | let w = screenWidth * 0.5 - 7; 100 | let h = screenWidth * 0.65 - 7; 101 | let style = styles.itemPadding; 102 | return ( 103 | 108 | Actions.photo({"url": this.props.item.url, "title": this.props.item.desc}) 109 | }> 110 | 116 | 117 | ); 118 | } 119 | } 120 | 121 | const styles = StyleSheet.create({ 122 | itemPadding: { 123 | padding: 3.5, 124 | height: screenWidth * 0.65, 125 | width: screenWidth / 2, 126 | }, 127 | }); 128 | -------------------------------------------------------------------------------- /app/view/gank/GankItemView.js: -------------------------------------------------------------------------------- 1 | import React, {PureComponent} from "react"; 2 | import {Image, Platform, StyleSheet, Text, TouchableNativeFeedback, TouchableHighlight, View} from "react-native"; 3 | import {Actions} from "react-native-router-flux"; 4 | import {C1, C3} from "../../configs"; 5 | 6 | export default class GankItemView extends PureComponent { 7 | 8 | render(): React.ReactNode { 9 | return this.setPlatform() 10 | } 11 | 12 | setPlatform() { 13 | if (Platform.OS === 'ios') { 14 | return 15 | { 17 | Actions.webView({"url": this.props.item.url, "title": this.props.item.desc}) 18 | }} 19 | underlayColor={'rgba(223,223,223,0.5)'}> 20 | {this.rendItem()} 21 | 22 | 23 | } else { 24 | return { 26 | Actions.webView({"url": this.props.item.url, "title": this.props.item.desc}) 27 | }} 28 | background={TouchableNativeFeedback.SelectableBackground()}> 29 | {this.rendItem()} 30 | 31 | } 32 | } 33 | 34 | rendItem() { 35 | return 36 | 37 | {this.props.item.desc} 45 | {this.props.item.images != null ? 46 | : null} 54 | 55 | 56 | 57 | {this.props.item.who} · {this.props.item.type} 58 | 59 | 60 | {this.props.item.publishedAt.substring(5, 10)} 61 | 62 | 63 | 64 | } 65 | } 66 | 67 | const styles = StyleSheet.create({ 68 | itemMargin: { 69 | marginHorizontal: 10, 70 | marginTop: 10, 71 | }, 72 | item: { 73 | elevation: 2, 74 | backgroundColor: 'white', 75 | flex: 1, 76 | padding: 10 77 | }, 78 | a: { 79 | flexDirection: 'row', 80 | alignItems: 'center' 81 | }, 82 | text: { 83 | color: C3, fontSize: 12, 84 | } 85 | }); 86 | 87 | const itemStyle = Platform.select({android: [styles.item, styles.itemMargin], ios: styles.item}); 88 | -------------------------------------------------------------------------------- /app/view/gank/GankSortView.js: -------------------------------------------------------------------------------- 1 | import React, {Component} from "react"; 2 | import { 3 | View, 4 | Text, 5 | StyleSheet, 6 | TouchableHighlight, 7 | } from "react-native"; 8 | import {getCategoryData} from "../../http/api_gank"; 9 | import {C2, C9, mainColor} from "../../configs"; 10 | import WaitLoadingView from "../component/WaitLoadingView"; 11 | import ErrorView from "../component/ErrorView"; 12 | import {UltimateListView} from "react-native-ultimate-listview"; 13 | import Icon from 'react-native-vector-icons/Ionicons'; 14 | import GankItemView from "./GankItemView"; 15 | 16 | import ActionSheet from 'react-native-actionsheet' 17 | 18 | const types = ['全部', 'App', 'iOS', "前端", '休息视频', '拓展资源', '瞎推荐', '取消']; 19 | 20 | /** 21 | * 干货定制列表 22 | */ 23 | export default class GankSortView extends Component { 24 | 25 | constructor(props) { 26 | super(props); 27 | this.state = { 28 | isLoading: true, 29 | //网络请求状态 30 | error: false, 31 | errorInfo: "", 32 | column: 1, 33 | layout: "list", 34 | gankType: "App", 35 | gankTitle: "App", 36 | refreshable: false, 37 | }; 38 | } 39 | 40 | //网络请求 41 | fetchData = (page = 1, startFetch, abortFetch) => { 42 | getCategoryData(this.state.gankType, page) 43 | .then((list) => { 44 | this.setState({ 45 | isLoading: false, 46 | }); 47 | startFetch(list.results, 16) 48 | }) 49 | .catch((err) => { 50 | abortFetch(); 51 | console.log("list.results error"); 52 | 53 | this.setState({ 54 | error: true, 55 | errorInfo: err, 56 | }) 57 | }); 58 | }; 59 | 60 | componentDidMount() { 61 | //请求数据 62 | this.fetchData(); 63 | } 64 | 65 | renderData() { 66 | return ( 67 | 68 | this.listView = ref} 71 | key={this.state.layout} 72 | onFetch={this.fetchData.bind(this)} 73 | keyExtractor={(item, index) => `${index} - ${item}`} 74 | refreshable={true} 75 | allLoadedText={'没有更多数据了'} 76 | waitingSpinnerText={'加载中...'} 77 | item={this.renderItemView.bind(this)} 78 | maxToRenderPerBatch={16} 79 | updateCellsBatchingPeriod={100} 80 | /> 81 | 82 | this.actionSheet = o} 84 | title={'选择分类'} 85 | options={types} 86 | cancelButtonIndex={7} 87 | onPress={async (index) => { 88 | let type = index === 0 ? "all" : types[index]; 89 | if (index < 7 && type !== this.state.gankType) { 90 | await this.setState({ 91 | gankType: type, 92 | gankTitle: types[index], 93 | }); 94 | this.listView.onRefresh() 95 | } 96 | }} 97 | /> 98 | 99 | ); 100 | } 101 | 102 | render() { 103 | // 第一次加载等待的view 104 | if (this.state.isLoading && !this.state.error) { 105 | return ; 106 | } else if (this.state.error) { 107 | return ; 108 | } 109 | // 加载数据 110 | return this.renderData(); 111 | } 112 | 113 | renderItemView(item, index, separator) { 114 | return ( 115 | 116 | ); 117 | } 118 | 119 | header() { 120 | return ( 121 | 122 | 123 | {this.state.gankTitle} 124 | { 128 | this.actionSheet.show() 129 | }}> 130 | 131 | 135 | 选择分类 136 | 137 | 138 | 139 | ) 140 | } 141 | } 142 | 143 | const styles = StyleSheet.create({ 144 | header: { 145 | marginHorizontal: 10, 146 | marginTop: 15, 147 | marginBottom: 5, 148 | flexDirection: 'row', 149 | alignContent: 'center', 150 | alignItems: 'center', 151 | }, 152 | line: { 153 | borderRadius: 1, 154 | marginEnd: 10, 155 | width: 2, 156 | height: 20, 157 | backgroundColor: mainColor 158 | }, 159 | text: { 160 | fontSize: 16, 161 | flex: 1, 162 | fontWeight: '500', 163 | color: 'black' 164 | }, 165 | choose: { 166 | flexDirection: 'row', 167 | borderRadius: 3, 168 | borderWidth: 0.5, 169 | alignContent: 'center', 170 | alignItems: 'center', 171 | borderColor: C9, 172 | padding: 5, 173 | backgroundColor: '#f8f8f8' 174 | } 175 | }); -------------------------------------------------------------------------------- /app/view/gank/GankView.js: -------------------------------------------------------------------------------- 1 | import React, {Component} from "react"; 2 | import {TouchableOpacity} from "react-native"; 3 | import {Actions} from 'react-native-router-flux'; 4 | import ScrollableTabView from 'react-native-scrollable-tab-view'; 5 | import {createAppContainer, createStackNavigator} from "react-navigation"; 6 | import {C1, mainBackColor, mainColor} from "../../configs"; 7 | import {QYScrollableTabBar} from "../component/QYScrollableTabBar"; 8 | import Icon from 'react-native-vector-icons/Ionicons'; 9 | import DailyView from './DailyView'; 10 | import FuliView from './FuliView'; 11 | import GankSortView from "./GankSortView"; 12 | import AndroidView from "./AndroidView"; 13 | 14 | class GankView extends Component { 15 | 16 | static navigationOptions = ({navigation}) => ({ 17 | title: `干货营`, 18 | headerTintColor: "white", 19 | headerStyle: {backgroundColor: mainColor}, 20 | headerRight: ( 21 | { 25 | Actions.addGank(); 26 | }}> 27 | 28 | ) 29 | }); 30 | 31 | render() { 32 | return ( 33 | } 35 | initialPage={0} 36 | style={{borderBottomWidth: 0, backgroundColor: mainBackColor}} 37 | tabBarActiveTextColor={mainColor} 38 | tabBarTextStyle={{fontSize: 15}} 39 | tabBarInactiveTextColor={C1} 40 | tabBarUnderlineStyle={{height: 2, backgroundColor: mainColor}} 41 | > 42 | 43 | 44 | 45 | 46 | 47 | ); 48 | } 49 | } 50 | 51 | 52 | const RootStack = createStackNavigator( 53 | { 54 | gank: { 55 | screen: GankView, 56 | }, 57 | }, 58 | { 59 | initialRouteName: 'gank', 60 | } 61 | ); 62 | 63 | const AppContainer = createAppContainer(RootStack); 64 | 65 | export default class GankTab extends React.Component { 66 | render() { 67 | return ; 68 | } 69 | } -------------------------------------------------------------------------------- /app/view/personal/AboutView.js: -------------------------------------------------------------------------------- 1 | import React, {Component} from "react"; 2 | import { 3 | View, 4 | StyleSheet, 5 | Platform, 6 | Text, 7 | ScrollView, TouchableOpacity, DeviceEventEmitter 8 | } from "react-native"; 9 | import {C1, C2, C9, mainBackColor, mainColor, screenWidth} from "../../configs"; 10 | import Icon from 'react-native-vector-icons/Ionicons'; 11 | import {Actions} from "react-native-router-flux"; 12 | 13 | const h = Platform.select({'android': screenWidth * 0.42, "ios": screenWidth * 0.44}); 14 | 15 | /** 16 | * 我的 17 | */ 18 | export default class AboutView extends Component { 19 | 20 | constructor(props) { 21 | super(props); 22 | this.state = {}; 23 | } 24 | 25 | render() { 26 | return ( 27 | 28 | 简介 29 | 30 | 31 | {"本项目是个人学习React Native的练习项目,UI风格仿照的是youlookwhat的云阅,api主要来自鸿洋的玩安卓以及代码家的" + 32 | "干货集中营。"} 33 | 34 | 35 | {"本项目完全开源,如果你觉得有帮助的话,请帮忙点个star。"} 36 | 37 | {this.clickText("项目源码", "https://github.com/fujianlian/GankRN")} 38 | 关于我 39 | 40 | 41 | 42 | {this.clickText("@fujianlian", "https://github.com/fujianlian")} 43 | 44 | 45 | {"如果你在使用中遇到问题,欢迎给我提Issue"} 46 | 47 | 48 | {"如果你有好的想法,欢迎pull request"} 49 | 50 | 特别感谢 51 | 52 | {this.clickText("youlookwhat · 云阅", "https://github.com/youlookwhat/CloudReader")} 53 | {this.clickText("鸿洋 · 玩安卓", "https://www.wanandroid.com")} 54 | {this.clickText("代码家 · 干货集中营", "https://gank.io")} 55 | 56 | ); 57 | } 58 | 59 | clickText(text, url) { 60 | return { 63 | Actions.webView({"url": url}) 64 | }}> 65 | {text} 66 | 67 | } 68 | } 69 | 70 | const styles = StyleSheet.create({ 71 | container: { 72 | flex: 1, 73 | backgroundColor: mainBackColor, 74 | paddingHorizontal: 15 75 | }, 76 | slide: { 77 | height: h, 78 | justifyContent: 'center', 79 | }, 80 | row: { 81 | flexDirection: "row", 82 | alignItems: 'center', 83 | marginBottom: 10, 84 | }, 85 | text: { 86 | color: C2, 87 | fontSize: 16, 88 | lineHeight: 26 89 | }, 90 | textLine: { 91 | color: mainColor, 92 | fontSize: 18, 93 | marginVertical: 5, 94 | textDecorationLine: 'underline' 95 | }, 96 | textTitle: { 97 | color: C1, 98 | fontSize: 18, 99 | marginVertical: 13, 100 | fontWeight: "500" 101 | }, 102 | line: { 103 | height: 0.5, 104 | backgroundColor: C9, 105 | marginBottom: 10, 106 | }, 107 | }); 108 | -------------------------------------------------------------------------------- /app/view/personal/CollectItemView.js: -------------------------------------------------------------------------------- 1 | import React, {PureComponent} from "react"; 2 | import {Platform, StyleSheet, Text, TouchableHighlight, TouchableNativeFeedback, View,} from "react-native"; 3 | import {Actions} from "react-native-router-flux"; 4 | import {C1, C3, mainColor} from "../../configs"; 5 | 6 | export default class CollectItemView extends PureComponent { 7 | 8 | render(): React.ReactNode { 9 | return this.setPlatform() 10 | } 11 | 12 | setPlatform() { 13 | if (Platform.OS === 'ios') { 14 | return ( 15 | { 17 | Actions.webView({"url": this.props.item.link, "title": this.props.item.title}) 18 | }} 19 | underlayColor={'rgba(223,223,223,0.5)'}> 20 | {this.rendItem()} 21 | ) 22 | } else { 23 | return { 25 | Actions.webView({"url": this.props.item.link, "title": this.props.item.title}) 26 | }} 27 | background={TouchableNativeFeedback.SelectableBackground()}> 28 | {this.rendItem()} 29 | 30 | } 31 | } 32 | 33 | rendItem() { 34 | return 35 | 36 | 37 | {this.props.item.chapterName} 38 | 39 | 40 | {this.props.item.title} 43 | 44 | 45 | {this.props.item.niceDate} · {this.props.item.author} 46 | 47 | 48 | } 49 | } 50 | 51 | const styles = StyleSheet.create({ 52 | item: { 53 | backgroundColor: 'white', 54 | flex: 1, 55 | padding: 10 56 | }, 57 | news: { 58 | fontSize: 14, 59 | color: mainColor, 60 | flex: 1, 61 | fontStyle: "italic" 62 | }, 63 | text: { 64 | color: C3, fontSize: 12, marginTop: 10 65 | }, 66 | text1: { 67 | color: C3, fontSize: 14, 68 | }, 69 | title: { 70 | color: C1, 71 | fontSize: 15, 72 | lineHeight: 20, 73 | flex: 1, 74 | marginTop: 10 75 | } 76 | }); -------------------------------------------------------------------------------- /app/view/personal/CollectView.js: -------------------------------------------------------------------------------- 1 | import React, {Component} from "react"; 2 | import ErrorView from "../component/ErrorView"; 3 | import {UltimateListView} from "react-native-ultimate-listview"; 4 | import {collectList} from "../../http/api_wan_android"; 5 | import CollectItemView from "./CollectItemView"; 6 | import {View} from "react-native"; 7 | import {C9} from "../../configs"; 8 | 9 | /** 10 | * 我的收藏 11 | */ 12 | export default class CollectView extends Component { 13 | 14 | constructor(props) { 15 | super(props); 16 | this.state = { 17 | isLoading: true, 18 | //网络请求状态 19 | error: false, 20 | errorInfo: "", 21 | }; 22 | } 23 | 24 | //网络请求 25 | fetchData = (page = 1, startFetch, abortFetch) => { 26 | collectList(page - 1, this.props.cookie) 27 | .then((list) => { 28 | console.log(list); 29 | console.log(list.data); 30 | 31 | this.setState({ 32 | isLoading: false, 33 | }); 34 | startFetch(list.data.datas, 20) 35 | }) 36 | .catch((err) => { 37 | abortFetch(); 38 | this.setState({ 39 | error: true, 40 | errorInfo: err 41 | }) 42 | }); 43 | }; 44 | 45 | renderData() { 46 | return ( 47 | this.listView = ref} 49 | onFetch={this.fetchData.bind(this)} 50 | keyExtractor={(item, index) => `${index} - ${item}`} 51 | allLoadedText={'没有更多数据了'} 52 | waitingSpinnerText={''} 53 | item={this.renderItemView.bind(this)} 54 | separator={this.space.bind(this)} 55 | maxToRenderPerBatch={10} 56 | /> 57 | ); 58 | } 59 | 60 | render() { 61 | if (this.state.error) { 62 | return ; 63 | } 64 | return this.renderData(); 65 | } 66 | 67 | renderItemView(item, index, separator) { 68 | return ( 69 | 70 | ); 71 | } 72 | 73 | space() { 74 | return ; 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /app/view/personal/LoginView.js: -------------------------------------------------------------------------------- 1 | import React, {Component} from 'react'; 2 | import { 3 | TextInput, 4 | View, 5 | Text, 6 | TouchableOpacity, 7 | DeviceEventEmitter, 8 | AsyncStorage 9 | } from 'react-native'; 10 | import {whiteBackground, C21, mainColor, screenWidth, showToast} from "../../configs"; 11 | import {Actions} from "react-native-router-flux"; 12 | import {login} from "../../http/api_wan_android"; 13 | import RnButton from "../component/RnButton"; 14 | 15 | /** 16 | * 登录 17 | */ 18 | export default class LoginView extends Component { 19 | 20 | constructor(props) { 21 | super(props); 22 | this.state = { 23 | username: "", 24 | password: "", 25 | } 26 | } 27 | 28 | componentWillMount() { 29 | DeviceEventEmitter.addListener('register', LoginView.goRegister.bind(this)); 30 | } 31 | 32 | 33 | componentWillUnmount() { 34 | DeviceEventEmitter.removeAllListeners('register'); 35 | } 36 | 37 | render() { 38 | return ( 39 | 40 | this.setState({username: text})} 44 | padding={0} 45 | clearButtonMode={'while-editing'} 46 | autoFocus={true} 47 | selectionColor={mainColor} 48 | value={this.state.text} 49 | /> 50 | this.setState({password: text})} 54 | padding={0} 55 | clearButtonMode={'while-editing'} 56 | selectionColor={mainColor} 57 | value={this.state.text} 58 | /> 59 | { 60 | this.send() 61 | }}/> 62 | 63 | ); 64 | } 65 | 66 | send() { 67 | if (this.isNull(this.state.username)) { 68 | showToast('请输入用户名') 69 | } else if (this.isNull(this.state.password)) { 70 | showToast('请输入密码') 71 | } else { 72 | login(this.state.username, this.state.password).then((data) => { 73 | if (data.errorCode === 0) { 74 | this._storeData(data.data.id) 75 | } 76 | }); 77 | } 78 | } 79 | 80 | static goRegister() { 81 | Actions.register() 82 | } 83 | 84 | isNull(str) { 85 | if (str === "") return true; 86 | let r = "^[ ]+$"; 87 | let re = new RegExp(r); 88 | return re.test(str); 89 | } 90 | 91 | _storeData = async (id) => { 92 | try { 93 | await AsyncStorage.setItem('isLogin', "true"); 94 | await AsyncStorage.setItem('userName', this.state.username); 95 | await AsyncStorage.setItem('id', id + ""); 96 | await AsyncStorage.setItem('userPassword', this.state.password); 97 | showToast('登录成功'); 98 | DeviceEventEmitter.emit('login'); 99 | Actions.pop() 100 | } catch (error) { 101 | console.log(error) 102 | } 103 | } 104 | 105 | } 106 | 107 | LoginView.renderRightButton = (props) => { 108 | return ( 109 | { 113 | DeviceEventEmitter.emit('register'); 114 | }}> 115 | 注册 116 | 117 | ) 118 | }; 119 | 120 | const styles = { 121 | textStyle: { 122 | marginTop: 20, 123 | marginStart: 20, 124 | marginEnd: 20, 125 | height: 40, 126 | width: screenWidth - 40, 127 | borderBottomWidth: 1, 128 | borderBottomColor: C21 129 | }, 130 | btnStyle: { 131 | height: 40, 132 | margin: 20, 133 | flex: 1 134 | }, 135 | loginButton: { 136 | alignSelf: 'center', 137 | width: screenWidth - 40, 138 | height: 40, 139 | marginTop: 12, 140 | } 141 | }; 142 | -------------------------------------------------------------------------------- /app/view/personal/PersonalItemView.js: -------------------------------------------------------------------------------- 1 | import React, {PureComponent} from "react"; 2 | import { 3 | Platform, 4 | StyleSheet, 5 | Text, 6 | TouchableNativeFeedback, 7 | TouchableHighlight, 8 | View, DeviceEventEmitter, 9 | } from "react-native"; 10 | import {Actions} from "react-native-router-flux"; 11 | import {C1, C4} from "../../configs"; 12 | import Icon from 'react-native-vector-icons/Ionicons'; 13 | 14 | export default class PersonalItemView extends PureComponent { 15 | 16 | render(): React.ReactNode { 17 | return this.setPlatform() 18 | } 19 | 20 | setPlatform() { 21 | if (Platform.OS === 'ios') { 22 | return ( 23 | 26 | {this.rendItem(this.props.text)} 27 | ) 28 | } else { 29 | return 32 | {this.rendItem(this.props.text)} 33 | 34 | } 35 | } 36 | 37 | rendItem(text) { 38 | return 39 | 40 | {text} 41 | 42 | 46 | 47 | } 48 | 49 | go() { 50 | if (this.props.text === "我的收藏") { 51 | DeviceEventEmitter.emit('collect'); 52 | } else if (this.props.text === "关于") { 53 | Actions.about() 54 | } else { 55 | Actions.webView({"url": this.props.url, "title": this.props.text}) 56 | } 57 | } 58 | } 59 | 60 | const styles = StyleSheet.create({ 61 | item: { 62 | backgroundColor: 'white', 63 | paddingHorizontal: 15, 64 | marginTop: 2, 65 | alignItems: "center", 66 | height: 50, 67 | flexDirection: 'row' 68 | }, 69 | text: { 70 | fontSize: 16, 71 | color: C1, 72 | flex: 1, 73 | }, 74 | }); -------------------------------------------------------------------------------- /app/view/personal/PersonalView.js: -------------------------------------------------------------------------------- 1 | import React, {Component} from "react"; 2 | import { 3 | View, 4 | StyleSheet, 5 | Platform, 6 | AsyncStorage, 7 | DeviceEventEmitter, 8 | Text, 9 | TouchableHighlight, 10 | TouchableNativeFeedback 11 | } from "react-native"; 12 | import {C1, getCookie, mainBackColor, mainColor, screenWidth} from "../../configs"; 13 | import {createAppContainer, createStackNavigator} from "react-navigation"; 14 | import PersonalItemView from "./PersonalItemView"; 15 | import {Actions} from "react-native-router-flux"; 16 | 17 | /** 18 | * 我的 19 | */ 20 | class PersonalView extends Component { 21 | 22 | constructor(props) { 23 | super(props); 24 | this.state = { 25 | isLogin: false, 26 | id: "", 27 | userName: "", 28 | userPassword: "", 29 | loginText: "玩安卓登录", 30 | }; 31 | } 32 | 33 | static navigationOptions = ({navigation}) => ({ 34 | title: `我的`, 35 | headerTintColor: "white", 36 | headerStyle: {backgroundColor: mainColor}, 37 | }); 38 | 39 | componentWillMount() { 40 | DeviceEventEmitter.addListener('login', this._retrieveData.bind(this)); 41 | DeviceEventEmitter.addListener('collect', this._collect.bind(this)); 42 | this._retrieveData(); 43 | } 44 | 45 | 46 | componentWillUnmount() { 47 | DeviceEventEmitter.removeAllListeners('login'); 48 | DeviceEventEmitter.removeAllListeners('collect'); 49 | } 50 | 51 | render() { 52 | return ( 53 | 54 | {this.setHeader()} 55 | 56 | 57 | 58 | 60 | 61 | ); 62 | } 63 | 64 | setHeader() { 65 | if (Platform.OS === 'ios') { 66 | return ( 67 | 70 | 71 | 72 | {this.state.loginText} 73 | 74 | 75 | {this.state.userName} 76 | 77 | 78 | ) 79 | } else { 80 | return 83 | 84 | 85 | {this.state.loginText} 86 | 87 | 88 | {this.state.userName} 89 | 90 | 91 | 92 | } 93 | } 94 | 95 | _go() { 96 | if (!this.state.isLogin) { 97 | Actions.login() 98 | } 99 | } 100 | 101 | _retrieveData = async () => { 102 | try { 103 | let isLogin = await AsyncStorage.getItem('isLogin'); 104 | if (isLogin !== null && isLogin === "true") { 105 | let id = await AsyncStorage.getItem('id'); 106 | let userName = await AsyncStorage.getItem('userName'); 107 | let userPassword = await AsyncStorage.getItem('userPassword'); 108 | this.setState({ 109 | id: id, 110 | isLogin: true, 111 | userName: userName, 112 | userPassword: userPassword, 113 | loginText: "玩安卓", 114 | }); 115 | } 116 | } catch (error) { 117 | 118 | } 119 | }; 120 | 121 | _collect = async () => { 122 | if (this.state.isLogin) { 123 | Actions.collect({cookie: getCookie(this.state.userName, this.state.userPassword)}) 124 | } else { 125 | Actions.login() 126 | } 127 | }; 128 | } 129 | 130 | const styles = StyleSheet.create({ 131 | container: { 132 | flex: 1, 133 | backgroundColor: mainBackColor, 134 | }, 135 | item: { 136 | backgroundColor: 'white', 137 | paddingHorizontal: 15, 138 | alignItems: "center", 139 | height: 60, 140 | marginBottom: 15, 141 | flexDirection: 'row', 142 | width: screenWidth 143 | }, 144 | text: { 145 | fontSize: 16, 146 | color: C1, 147 | flex: 1, 148 | }, 149 | text1: { 150 | fontSize: 16, 151 | color: C1, 152 | fontWeight: "500" 153 | }, 154 | }); 155 | 156 | const RootStack = createStackNavigator( 157 | { 158 | personal: { 159 | screen: PersonalView, 160 | }, 161 | }, 162 | { 163 | initialRouteName: 'personal', 164 | } 165 | ); 166 | 167 | const AppContainer = createAppContainer(RootStack); 168 | 169 | export default class PersonalTab extends React.Component { 170 | render() { 171 | return ; 172 | } 173 | } -------------------------------------------------------------------------------- /app/view/personal/RegisterView.js: -------------------------------------------------------------------------------- 1 | import React, {Component} from 'react'; 2 | import { 3 | TextInput, 4 | View, 5 | } from 'react-native'; 6 | import {whiteBackground, C21, mainColor, screenWidth, showToast} from "../../configs"; 7 | import {Actions} from "react-native-router-flux"; 8 | import {register} from "../../http/api_wan_android"; 9 | import RnButton from "../component/RnButton"; 10 | 11 | /** 12 | * 注册 13 | */ 14 | export default class LoginView extends Component { 15 | 16 | constructor(props) { 17 | super(props); 18 | this.state = { 19 | username: "", 20 | password: "", 21 | secondPassword: "", 22 | } 23 | } 24 | 25 | render() { 26 | return ( 27 | 28 | this.setState({username: text})} 32 | padding={0} 33 | clearButtonMode={'while-editing'} 34 | autoFocus={true} 35 | selectionColor={mainColor} 36 | value={this.state.text} 37 | /> 38 | this.setState({password: text})} 42 | padding={0} 43 | clearButtonMode={'while-editing'} 44 | selectionColor={mainColor} 45 | value={this.state.text} 46 | /> 47 | this.setState({secondPassword: text})} 51 | padding={0} 52 | clearButtonMode={'while-editing'} 53 | selectionColor={mainColor} 54 | value={this.state.text} 55 | /> 56 | { 57 | this.send() 58 | }}/> 59 | 60 | ); 61 | } 62 | 63 | send() { 64 | if (this.isNull(this.state.username)) { 65 | showToast('请输入用户名') 66 | } else if (this.isNull(this.state.password)) { 67 | showToast('请输入密码') 68 | } else if (this.isNull(this.state.secondPassword)) { 69 | showToast('请再次输入密码') 70 | } else if (this.state.password !== this.state.secondPassword) { 71 | showToast('两次输入密码不一致') 72 | } else { 73 | register(this.state.username, this.state.password).then((data) => { 74 | if (data.errorCode === 0) { 75 | showToast('注册成功'); 76 | Actions.pop() 77 | } 78 | }); 79 | } 80 | } 81 | 82 | isNull(str) { 83 | if (str === "") return true; 84 | let r = "^[ ]+$"; 85 | let re = new RegExp(r); 86 | return re.test(str); 87 | } 88 | 89 | } 90 | 91 | const styles = { 92 | textStyle: { 93 | marginTop: 20, 94 | marginStart: 20, 95 | marginEnd: 20, 96 | height: 40, 97 | width: screenWidth - 40, 98 | borderBottomWidth: 1, 99 | borderBottomColor: C21 100 | }, 101 | btnStyle: { 102 | height: 40, 103 | margin: 20, 104 | flex: 1 105 | }, 106 | loginButton: { 107 | alignSelf: 'center', 108 | width: screenWidth - 40, 109 | height: 40, 110 | marginTop: 12, 111 | } 112 | }; 113 | -------------------------------------------------------------------------------- /app/view/photo/GirlsView.js: -------------------------------------------------------------------------------- 1 | import React, {Component, PureComponent} from "react"; 2 | import { 3 | Image, 4 | StyleSheet, 5 | TouchableHighlight, 6 | TouchableOpacity, 7 | DeviceEventEmitter, 8 | Platform, 9 | } from "react-native"; 10 | import {getCategoryData} from "../../http/api_gank"; 11 | import {Actions} from 'react-native-router-flux'; 12 | import {mainColor, screenWidth} from "../../configs"; 13 | import {createAppContainer, createStackNavigator} from "react-navigation"; 14 | import WaitLoadingView from "../component/WaitLoadingView"; 15 | import ErrorView from "../component/ErrorView"; 16 | import {UltimateListView} from "react-native-ultimate-listview"; 17 | import Icon from 'react-native-vector-icons/Ionicons'; 18 | 19 | const RefreshableMode = Platform.select({ios: 'advanced', android: 'basic'}) 20 | 21 | class GirlsView extends Component { 22 | 23 | constructor(props) { 24 | super(props); 25 | this.state = { 26 | isLoading: true, 27 | //网络请求状态 28 | error: false, 29 | errorInfo: "", 30 | column: 2, 31 | layout: "grid" 32 | }; 33 | } 34 | 35 | static navigationOptions = ({navigation}) => ({ 36 | title: `妹纸`, 37 | headerTintColor: "white", 38 | headerStyle: {backgroundColor: mainColor}, 39 | headerRight: ( 40 | { 43 | DeviceEventEmitter.emit('rightNavBarAction'); 44 | }}> 45 | 46 | ) 47 | }); 48 | 49 | componentWillMount() { 50 | DeviceEventEmitter.addListener('rightNavBarAction', this.setColumn.bind(this)); 51 | } 52 | 53 | componentWillUnmount() { 54 | DeviceEventEmitter.removeAllListeners('rightNavBarAction'); 55 | } 56 | 57 | setColumn() { 58 | let c = this.state.column === 2 ? 1 : 2; 59 | let d = this.state.column === 2 ? 'grid' : 'list'; 60 | this.setState({ 61 | column: c, 62 | layout: d, 63 | }); 64 | } 65 | 66 | //网络请求 67 | fetchData = (page = 1, startFetch, abortFetch) => { 68 | getCategoryData("福利", page) 69 | .then((list) => { 70 | this.setState({ 71 | isLoading: false, 72 | }); 73 | startFetch(list.results, 16) 74 | }) 75 | .catch((err) => { 76 | abortFetch(); 77 | this.setState({ 78 | error: true, 79 | errorInfo: err 80 | }) 81 | }); 82 | }; 83 | 84 | componentDidMount() { 85 | //请求数据 86 | this.fetchData(); 87 | } 88 | 89 | renderData() { 90 | return ( 91 | this.listView = ref} 93 | refreshableTitleRefreshing={'正在加载...'} 94 | key={this.state.layout} 95 | onFetch={this.fetchData.bind(this)} 96 | keyExtractor={(item, index) => `${index} - ${item}`} 97 | refreshableMode={RefreshableMode} 98 | item={this.renderItemView.bind(this)} 99 | numColumns={this.state.column} 100 | maxToRenderPerBatch={this.state.column === 2 ? 3 : 6} 101 | updateCellsBatchingPeriod={100} 102 | getItemLayout={(data, index) => ( 103 | this.state.column === 2 104 | ? {length: screenWidth * 0.60, offset: screenWidth * 0.60 * index, index} 105 | : {length: screenWidth * 0.8, offset: screenWidth * 0.8 * index, index} 106 | )} 107 | /> 108 | ); 109 | } 110 | 111 | render() { 112 | // 第一次加载等待的view 113 | if (this.state.isLoading && !this.state.error) { 114 | return ; 115 | } else if (this.state.error) { 116 | return ; 117 | } 118 | // 加载数据 119 | return this.renderData(); 120 | } 121 | 122 | renderItemView(item, index, separator) { 123 | return ( 124 | 125 | ); 126 | } 127 | } 128 | 129 | class RenderItemView extends PureComponent { 130 | 131 | render(): React.ReactNode { 132 | let isDouble = this.props.column === 2; 133 | let w = isDouble ? screenWidth * 0.5 - 7 : screenWidth - 7; 134 | let h = isDouble ? screenWidth * 0.60 - 7 : screenWidth * 0.8 - 7; 135 | let style = isDouble ? styles.itemPadding : styles.itemPadding1; 136 | return ( 137 | 141 | Actions.photo({"url": this.props.item.url, "title": this.props.item.desc}) 142 | }> 143 | 149 | 150 | ); 151 | } 152 | } 153 | 154 | const styles = StyleSheet.create({ 155 | itemPadding: { 156 | padding: 3.5, 157 | height: screenWidth * 0.60, 158 | width: screenWidth / 2, 159 | }, 160 | itemPadding1: { 161 | padding: 3.5, 162 | height: screenWidth * 0.8, 163 | width: screenWidth, 164 | } 165 | }); 166 | 167 | 168 | const RootStack = createStackNavigator( 169 | { 170 | photo: { 171 | screen: GirlsView, 172 | }, 173 | }, 174 | { 175 | initialRouteName: 'photo', 176 | } 177 | ); 178 | 179 | const AppContainer = createAppContainer(RootStack); 180 | 181 | export default class GirlsTab extends React.Component { 182 | render() { 183 | return ; 184 | } 185 | } -------------------------------------------------------------------------------- /app/view/photo/PhotoView.js: -------------------------------------------------------------------------------- 1 | import React, {Component} from 'react'; 2 | import {Image, View} from 'react-native'; 3 | import {container, screenWidth} from "../../configs"; 4 | 5 | export default class PhotoView extends Component { 6 | 7 | constructor(props) { 8 | super(props); 9 | this.state = { 10 | height: 400, 11 | } 12 | } 13 | 14 | componentWillMount(): void { 15 | Image.getSize(this.props.url, (width, height) => { 16 | this.setState({ 17 | height: height / width * screenWidth, 18 | }); 19 | }) 20 | } 21 | 22 | componentDidMount(): void { 23 | 24 | } 25 | 26 | render() { 27 | return ( 28 | 29 | 32 | 33 | ); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /app/view/wanandroid/HomeView.js: -------------------------------------------------------------------------------- 1 | import React, {Component} from "react"; 2 | import { 3 | Image, 4 | View, 5 | Text, 6 | StyleSheet, 7 | Platform, 8 | TouchableWithoutFeedback, AsyncStorage, DeviceEventEmitter, 9 | } from "react-native"; 10 | import {C9, getCookie, mainColor, screenWidth} from "../../configs"; 11 | import WaitLoadingView from "../component/WaitLoadingView"; 12 | import ErrorView from "../component/ErrorView"; 13 | import {UltimateListView} from "react-native-ultimate-listview"; 14 | import {getBanner, getHomeList} from "../../http/api_wan_android"; 15 | import {createAppContainer, createStackNavigator} from "react-navigation"; 16 | import Swiper from 'react-native-swiper' 17 | import WanItemView from "./WanItemView"; 18 | import {Actions} from "react-native-router-flux"; 19 | 20 | const h = Platform.select({'android': screenWidth * 0.42, "ios": screenWidth * 0.44}); 21 | 22 | /** 23 | * 玩安卓 24 | */ 25 | class HomeView extends Component { 26 | 27 | constructor(props) { 28 | super(props); 29 | this.state = { 30 | isLoading: true, 31 | //网络请求状态 32 | error: false, 33 | errorInfo: "", 34 | column: 1, 35 | layout: "list", 36 | // banner相关 37 | data: null, 38 | index: 0, 39 | userName: "", 40 | userPassword: "", 41 | }; 42 | } 43 | 44 | static navigationOptions = ({navigation}) => ({ 45 | title: `玩安卓`, 46 | headerTintColor: "white", 47 | headerStyle: {backgroundColor: mainColor}, 48 | }); 49 | 50 | componentWillMount() { 51 | DeviceEventEmitter.addListener('login', this._retrieveData.bind(this)); 52 | this._retrieveData(); 53 | } 54 | 55 | 56 | componentWillUnmount() { 57 | DeviceEventEmitter.removeAllListeners('login'); 58 | } 59 | 60 | _retrieveData = async () => { 61 | try { 62 | let isLogin = await AsyncStorage.getItem('isLogin'); 63 | if (isLogin !== null && isLogin === "true") { 64 | let userName = await AsyncStorage.getItem('userName'); 65 | let userPassword = await AsyncStorage.getItem('userPassword'); 66 | this.setState({ 67 | userName: userName, 68 | userPassword: userPassword, 69 | }); 70 | } 71 | } catch (error) { 72 | 73 | } 74 | }; 75 | 76 | fetchData = (page = 1, startFetch, abortFetch) => { 77 | getHomeList(page - 1, getCookie(this.state.userName, this.state.userPassword)) 78 | .then((list) => { 79 | this.setState({ 80 | isLoading: false, 81 | }); 82 | startFetch(list.data.datas, 20) 83 | }) 84 | .catch((err) => { 85 | abortFetch(); 86 | this.setState({ 87 | error: true, 88 | errorInfo: err 89 | }) 90 | }); 91 | }; 92 | 93 | getBanners() { 94 | getBanner() 95 | .then((list) => { 96 | this.setState({ 97 | data: list.data, 98 | }); 99 | }) 100 | .catch((err) => { 101 | this.setState({ 102 | error: true, 103 | errorInfo: err 104 | }) 105 | }); 106 | }; 107 | 108 | componentDidMount() { 109 | this.getBanners(); 110 | this.fetchData(); 111 | } 112 | 113 | renderData() { 114 | return ( 115 | `${index} - ${item}`} 120 | allLoadedText={'没有更多数据了'} 121 | refreshableTitleRefreshing={'正在加载...'} 122 | refreshableTitleRelease={'释放刷新'} 123 | refreshableTitlePull={'下拉刷新'} 124 | waitingSpinnerText={''} 125 | dateTitle={'最近更新:'} 126 | item={this.renderItemView.bind(this)} 127 | separator={this.space.bind(this)} 128 | maxToRenderPerBatch={10} 129 | updateCellsBatchingPeriod={100} 130 | /> 131 | ); 132 | } 133 | 134 | space() { 135 | return ; 139 | } 140 | 141 | render() { 142 | // 第一次加载等待的view 143 | if (this.state.isLoading && !this.state.error) { 144 | return ; 145 | } else if (this.state.error) { 146 | return ; 147 | } 148 | // 加载数据 149 | return this.renderData(); 150 | } 151 | 152 | renderItemView(item, index, separator) { 153 | return ( 154 | 155 | ); 156 | } 157 | 158 | header() { 159 | return ( 160 | 161 | {this.state.data != null ? 162 | 163 | { 167 | this.setState( 168 | {index: state.index} 169 | ) 170 | }} 171 | > 172 | { 173 | this.state.data.map((value, i) => this.renderItem(value, i)) 174 | } 175 | 176 | 177 | {this.state.data[this.state.index].title} 182 | {`${this.state.index + 1}/${this.state.data.length}`} 183 | 184 | : } 185 | 186 | ) 187 | } 188 | 189 | renderItem(value, i) { 190 | return ( 191 | { 195 | Actions.webView({"url": value.url, "title": value.title}); 196 | }}> 197 | 198 | 199 | 200 | ); 201 | } 202 | } 203 | 204 | const styles = StyleSheet.create({ 205 | header: { 206 | height: h, 207 | backgroundColor: '#dfdfdf', 208 | }, 209 | slide: { 210 | height: h, 211 | justifyContent: 'center', 212 | }, 213 | text: { 214 | color: '#fff', 215 | fontSize: 14, 216 | }, 217 | image: { 218 | flex: 1 219 | }, 220 | views: { 221 | alignContent: 'center', 222 | alignItems: 'center', 223 | position: 'absolute', 224 | top: h - 30, 225 | backgroundColor: 'rgba(0,0,0,.3)', 226 | width: screenWidth, 227 | height: 30, 228 | paddingStart: 10, 229 | paddingEnd: 10, 230 | flexDirection: 'row', 231 | } 232 | }); 233 | 234 | const RootStack = createStackNavigator( 235 | { 236 | home: { 237 | screen: HomeView, 238 | }, 239 | }, 240 | { 241 | initialRouteName: 'home', 242 | } 243 | ); 244 | 245 | const AppContainer = createAppContainer(RootStack); 246 | 247 | export default class HomeTab extends React.Component { 248 | render() { 249 | return ; 250 | } 251 | } -------------------------------------------------------------------------------- /app/view/wanandroid/WanItemView.js: -------------------------------------------------------------------------------- 1 | import React, {PureComponent} from "react"; 2 | import {Platform, StyleSheet, Text, TouchableHighlight, TouchableNativeFeedback, View,} from "react-native"; 3 | import {Actions} from "react-native-router-flux"; 4 | import {C1, C3, C4, mainColor} from "../../configs"; 5 | import Icon from 'react-native-vector-icons/MaterialIcons'; 6 | 7 | export default class WanItemView extends PureComponent { 8 | 9 | render(): React.ReactNode { 10 | return this.setPlatform() 11 | } 12 | 13 | setPlatform() { 14 | if (Platform.OS === 'ios') { 15 | return ( 16 | { 18 | Actions.webView({ 19 | "url": this.props.item.link, 20 | "title": this.props.item.title, 21 | "collect": this.props.item.collect, 22 | "isWanAndroid": true, 23 | }) 24 | }} 25 | underlayColor={'rgba(223,223,223,0.5)'}> 26 | {this.rendItem()} 27 | ) 28 | } else { 29 | return { 31 | Actions.webView({ 32 | "url": this.props.item.link, 33 | "title": this.props.item.title, 34 | "collect": this.props.item.collect, 35 | "isWanAndroid": true, 36 | }) 37 | }} 38 | background={TouchableNativeFeedback.SelectableBackground()}> 39 | {this.rendItem()} 40 | 41 | } 42 | } 43 | 44 | rendItem() { 45 | return 46 | 47 | 48 | {this.props.item.fresh ? "new!" : ""} 49 | 50 | 51 | {this.props.item.chapterName} 52 | 53 | 54 | {this.props.item.title} 57 | 58 | 59 | {this.props.item.niceDate} · {this.props.item.author} 60 | 61 | {this.props.item.collect ? 62 | : 63 | } 64 | 65 | 66 | } 67 | } 68 | 69 | const styles = StyleSheet.create({ 70 | item: { 71 | backgroundColor: 'white', 72 | flex: 1, 73 | padding: 10 74 | }, 75 | news: { 76 | fontSize: 14, 77 | color: mainColor, 78 | flex: 1, 79 | fontStyle: "italic" 80 | }, 81 | text: { 82 | color: C3, fontSize: 12, 83 | }, 84 | text1: { 85 | color: C3, fontSize: 14, 86 | }, 87 | title: { 88 | color: C1, 89 | fontSize: 15, 90 | lineHeight: 20, 91 | flex: 1, 92 | marginTop: 10 93 | } 94 | }); -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: ["module:metro-react-native-babel-preset"] 3 | } 4 | -------------------------------------------------------------------------------- /images/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fujianlian/GankRN/353ef94d670e18401da35540a76783c4332208cf/images/1.png -------------------------------------------------------------------------------- /images/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fujianlian/GankRN/353ef94d670e18401da35540a76783c4332208cf/images/2.png -------------------------------------------------------------------------------- /images/3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fujianlian/GankRN/353ef94d670e18401da35540a76783c4332208cf/images/3.png -------------------------------------------------------------------------------- /images/4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fujianlian/GankRN/353ef94d670e18401da35540a76783c4332208cf/images/4.png -------------------------------------------------------------------------------- /images/5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fujianlian/GankRN/353ef94d670e18401da35540a76783c4332208cf/images/5.png -------------------------------------------------------------------------------- /images/6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fujianlian/GankRN/353ef94d670e18401da35540a76783c4332208cf/images/6.png -------------------------------------------------------------------------------- /images/github.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fujianlian/GankRN/353ef94d670e18401da35540a76783c4332208cf/images/github.png -------------------------------------------------------------------------------- /images/pyger.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fujianlian/GankRN/353ef94d670e18401da35540a76783c4332208cf/images/pyger.png -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @format 3 | * @lint-ignore-every XPLATJSCOPYRIGHT1 4 | */ 5 | 6 | import {AppRegistry} from 'react-native'; 7 | import App from './app/index'; 8 | import {name as appName} from './app.json'; 9 | 10 | AppRegistry.registerComponent(appName, () => App); 11 | -------------------------------------------------------------------------------- /ios/GankRN-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/GankRN-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/GankRN.xcodeproj/xcshareddata/xcschemes/GankRN-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/GankRN.xcodeproj/xcshareddata/xcschemes/GankRN.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/GankRN/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 | 10 | @interface AppDelegate : UIResponder 11 | 12 | @property (nonatomic, strong) UIWindow *window; 13 | 14 | @end 15 | -------------------------------------------------------------------------------- /ios/GankRN/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 "RNSplashScreen.h" 13 | 14 | @implementation AppDelegate 15 | 16 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions 17 | { 18 | NSURL *jsCodeLocation; 19 | 20 | jsCodeLocation = [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:@"index" fallbackResource:nil]; 21 | 22 | RCTRootView *rootView = [[RCTRootView alloc] initWithBundleURL:jsCodeLocation 23 | moduleName:@"GankRN" 24 | initialProperties:nil 25 | launchOptions:launchOptions]; 26 | rootView.backgroundColor = [UIColor blackColor]; 27 | 28 | self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds]; 29 | UIViewController *rootViewController = [UIViewController new]; 30 | rootViewController.view = rootView; 31 | self.window.rootViewController = rootViewController; 32 | [self.window makeKeyAndVisible]; 33 | [RNSplashScreen show]; 34 | return YES; 35 | } 36 | 37 | @end 38 | -------------------------------------------------------------------------------- /ios/GankRN/Base.lproj/LaunchScreen.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 24 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /ios/GankRN/Images.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "size" : "20x20", 5 | "idiom" : "iphone", 6 | "filename" : "icon-20@2x.png", 7 | "scale" : "2x" 8 | }, 9 | { 10 | "size" : "20x20", 11 | "idiom" : "iphone", 12 | "filename" : "icon-20@3x.png", 13 | "scale" : "3x" 14 | }, 15 | { 16 | "size" : "29x29", 17 | "idiom" : "iphone", 18 | "filename" : "icon-29@2x.png", 19 | "scale" : "2x" 20 | }, 21 | { 22 | "size" : "29x29", 23 | "idiom" : "iphone", 24 | "filename" : "icon-29@3x.png", 25 | "scale" : "3x" 26 | }, 27 | { 28 | "size" : "40x40", 29 | "idiom" : "iphone", 30 | "filename" : "icon-40@2x.png", 31 | "scale" : "2x" 32 | }, 33 | { 34 | "size" : "40x40", 35 | "idiom" : "iphone", 36 | "filename" : "icon-40@3x.png", 37 | "scale" : "3x" 38 | }, 39 | { 40 | "size" : "60x60", 41 | "idiom" : "iphone", 42 | "filename" : "icon-60@2x.png", 43 | "scale" : "2x" 44 | }, 45 | { 46 | "size" : "60x60", 47 | "idiom" : "iphone", 48 | "filename" : "icon-60@3x.png", 49 | "scale" : "3x" 50 | }, 51 | { 52 | "size" : "1024x1024", 53 | "idiom" : "ios-marketing", 54 | "filename" : "icon-1024.png", 55 | "scale" : "1x" 56 | } 57 | ], 58 | "info" : { 59 | "version" : 1, 60 | "author" : "xcode" 61 | } 62 | } -------------------------------------------------------------------------------- /ios/GankRN/Images.xcassets/AppIcon.appiconset/icon-1024.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fujianlian/GankRN/353ef94d670e18401da35540a76783c4332208cf/ios/GankRN/Images.xcassets/AppIcon.appiconset/icon-1024.png -------------------------------------------------------------------------------- /ios/GankRN/Images.xcassets/AppIcon.appiconset/icon-20@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fujianlian/GankRN/353ef94d670e18401da35540a76783c4332208cf/ios/GankRN/Images.xcassets/AppIcon.appiconset/icon-20@2x.png -------------------------------------------------------------------------------- /ios/GankRN/Images.xcassets/AppIcon.appiconset/icon-20@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fujianlian/GankRN/353ef94d670e18401da35540a76783c4332208cf/ios/GankRN/Images.xcassets/AppIcon.appiconset/icon-20@3x.png -------------------------------------------------------------------------------- /ios/GankRN/Images.xcassets/AppIcon.appiconset/icon-29@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fujianlian/GankRN/353ef94d670e18401da35540a76783c4332208cf/ios/GankRN/Images.xcassets/AppIcon.appiconset/icon-29@2x.png -------------------------------------------------------------------------------- /ios/GankRN/Images.xcassets/AppIcon.appiconset/icon-29@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fujianlian/GankRN/353ef94d670e18401da35540a76783c4332208cf/ios/GankRN/Images.xcassets/AppIcon.appiconset/icon-29@3x.png -------------------------------------------------------------------------------- /ios/GankRN/Images.xcassets/AppIcon.appiconset/icon-40@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fujianlian/GankRN/353ef94d670e18401da35540a76783c4332208cf/ios/GankRN/Images.xcassets/AppIcon.appiconset/icon-40@2x.png -------------------------------------------------------------------------------- /ios/GankRN/Images.xcassets/AppIcon.appiconset/icon-40@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fujianlian/GankRN/353ef94d670e18401da35540a76783c4332208cf/ios/GankRN/Images.xcassets/AppIcon.appiconset/icon-40@3x.png -------------------------------------------------------------------------------- /ios/GankRN/Images.xcassets/AppIcon.appiconset/icon-60@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fujianlian/GankRN/353ef94d670e18401da35540a76783c4332208cf/ios/GankRN/Images.xcassets/AppIcon.appiconset/icon-60@2x.png -------------------------------------------------------------------------------- /ios/GankRN/Images.xcassets/AppIcon.appiconset/icon-60@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fujianlian/GankRN/353ef94d670e18401da35540a76783c4332208cf/ios/GankRN/Images.xcassets/AppIcon.appiconset/icon-60@3x.png -------------------------------------------------------------------------------- /ios/GankRN/Images.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /ios/GankRN/Images.xcassets/LaunchImage.launchimage/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "orientation" : "portrait", 5 | "idiom" : "iphone", 6 | "extent" : "full-screen", 7 | "minimum-system-version" : "12.0", 8 | "subtype" : "2688h", 9 | "scale" : "3x" 10 | }, 11 | { 12 | "orientation" : "landscape", 13 | "idiom" : "iphone", 14 | "extent" : "full-screen", 15 | "minimum-system-version" : "12.0", 16 | "subtype" : "2688h", 17 | "scale" : "3x" 18 | }, 19 | { 20 | "orientation" : "portrait", 21 | "idiom" : "iphone", 22 | "extent" : "full-screen", 23 | "minimum-system-version" : "12.0", 24 | "subtype" : "1792h", 25 | "scale" : "2x" 26 | }, 27 | { 28 | "orientation" : "landscape", 29 | "idiom" : "iphone", 30 | "extent" : "full-screen", 31 | "minimum-system-version" : "12.0", 32 | "subtype" : "1792h", 33 | "scale" : "2x" 34 | }, 35 | { 36 | "extent" : "full-screen", 37 | "idiom" : "iphone", 38 | "subtype" : "2436h", 39 | "filename" : "Default-2436h.png", 40 | "minimum-system-version" : "11.0", 41 | "orientation" : "portrait", 42 | "scale" : "3x" 43 | }, 44 | { 45 | "orientation" : "landscape", 46 | "idiom" : "iphone", 47 | "extent" : "full-screen", 48 | "minimum-system-version" : "11.0", 49 | "subtype" : "2436h", 50 | "scale" : "3x" 51 | }, 52 | { 53 | "orientation" : "portrait", 54 | "idiom" : "iphone", 55 | "extent" : "full-screen", 56 | "minimum-system-version" : "8.0", 57 | "subtype" : "736h", 58 | "scale" : "3x" 59 | }, 60 | { 61 | "orientation" : "landscape", 62 | "idiom" : "iphone", 63 | "extent" : "full-screen", 64 | "minimum-system-version" : "8.0", 65 | "subtype" : "736h", 66 | "scale" : "3x" 67 | }, 68 | { 69 | "extent" : "full-screen", 70 | "idiom" : "iphone", 71 | "subtype" : "667h", 72 | "filename" : "Default-667h.png", 73 | "minimum-system-version" : "8.0", 74 | "orientation" : "portrait", 75 | "scale" : "2x" 76 | }, 77 | { 78 | "orientation" : "portrait", 79 | "idiom" : "iphone", 80 | "extent" : "full-screen", 81 | "minimum-system-version" : "7.0", 82 | "scale" : "2x" 83 | }, 84 | { 85 | "extent" : "full-screen", 86 | "idiom" : "iphone", 87 | "subtype" : "retina4", 88 | "filename" : "Default-568h@2x~iphone.png", 89 | "minimum-system-version" : "7.0", 90 | "orientation" : "portrait", 91 | "scale" : "2x" 92 | }, 93 | { 94 | "orientation" : "portrait", 95 | "idiom" : "iphone", 96 | "extent" : "full-screen", 97 | "scale" : "1x" 98 | }, 99 | { 100 | "orientation" : "portrait", 101 | "idiom" : "iphone", 102 | "extent" : "full-screen", 103 | "scale" : "2x" 104 | }, 105 | { 106 | "orientation" : "portrait", 107 | "idiom" : "iphone", 108 | "extent" : "full-screen", 109 | "subtype" : "retina4", 110 | "scale" : "2x" 111 | } 112 | ], 113 | "info" : { 114 | "version" : 1, 115 | "author" : "xcode" 116 | } 117 | } -------------------------------------------------------------------------------- /ios/GankRN/Images.xcassets/LaunchImage.launchimage/Default-2436h.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fujianlian/GankRN/353ef94d670e18401da35540a76783c4332208cf/ios/GankRN/Images.xcassets/LaunchImage.launchimage/Default-2436h.png -------------------------------------------------------------------------------- /ios/GankRN/Images.xcassets/LaunchImage.launchimage/Default-568h@2x~iphone.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fujianlian/GankRN/353ef94d670e18401da35540a76783c4332208cf/ios/GankRN/Images.xcassets/LaunchImage.launchimage/Default-568h@2x~iphone.png -------------------------------------------------------------------------------- /ios/GankRN/Images.xcassets/LaunchImage.launchimage/Default-667h.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fujianlian/GankRN/353ef94d670e18401da35540a76783c4332208cf/ios/GankRN/Images.xcassets/LaunchImage.launchimage/Default-667h.png -------------------------------------------------------------------------------- /ios/GankRN/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleDisplayName 8 | 干货集中营 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.0 21 | CFBundleSignature 22 | ???? 23 | CFBundleVersion 24 | 1 25 | LSRequiresIPhoneOS 26 | 27 | NSAppTransportSecurity 28 | 29 | NSAllowsArbitraryLoads 30 | 31 | NSExceptionDomains 32 | 33 | localhost 34 | 35 | NSExceptionAllowsInsecureHTTPLoads 36 | 37 | 38 | 39 | 40 | NSLocationWhenInUseUsageDescription 41 | 42 | UIAppFonts 43 | 44 | AntDesign.ttf 45 | Entypo.ttf 46 | EvilIcons.ttf 47 | Feather.ttf 48 | FontAwesome.ttf 49 | FontAwesome5_Brands.ttf 50 | FontAwesome5_Regular.ttf 51 | FontAwesome5_Solid.ttf 52 | Foundation.ttf 53 | Ionicons.ttf 54 | MaterialCommunityIcons.ttf 55 | MaterialIcons.ttf 56 | Octicons.ttf 57 | SimpleLineIcons.ttf 58 | Zocial.ttf 59 | 60 | UIRequiredDeviceCapabilities 61 | 62 | armv7 63 | 64 | UISupportedInterfaceOrientations 65 | 66 | UIInterfaceOrientationPortrait 67 | UIInterfaceOrientationLandscapeLeft 68 | UIInterfaceOrientationLandscapeRight 69 | 70 | UIViewControllerBasedStatusBarAppearance 71 | 72 | 73 | 74 | -------------------------------------------------------------------------------- /ios/GankRN/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/GankRNTests/GankRNTests.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 GankRNTests : XCTestCase 18 | 19 | @end 20 | 21 | @implementation GankRNTests 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 | -------------------------------------------------------------------------------- /ios/GankRNTests/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 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "GankRN", 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 | "@expo/vector-icons": "^9.0.0", 11 | "react": "16.6.3", 12 | "react-native": "0.58.6", 13 | "react-native-actionsheet": "^2.4.2", 14 | "react-native-gesture-handler": "^1.1.0", 15 | "react-native-root-toast": "^3.0.2", 16 | "react-native-router-flux": "^4.0.6", 17 | "react-native-scrollable-tab-view": "^0.10.0", 18 | "react-native-simple-store": "^1.3.0", 19 | "react-native-splash-screen": "^3.2.0", 20 | "react-native-swiper": "^1.5.14", 21 | "react-native-ultimate-listview": "^3.3.0", 22 | "react-native-vector-icons": "^6.4.1", 23 | "react-native-webview": "^5.3.0", 24 | "react-navigation": "^3.3.2" 25 | }, 26 | "devDependencies": { 27 | "babel-core": "^7.0.0-bridge.0", 28 | "babel-jest": "24.1.0", 29 | "jest": "24.1.0", 30 | "metro-react-native-babel-preset": "0.53.0", 31 | "react-test-renderer": "16.6.3" 32 | }, 33 | "jest": { 34 | "preset": "react-native" 35 | } 36 | } 37 | --------------------------------------------------------------------------------