├── .watchmanconfig
├── .gitattributes
├── app.json
├── src
├── images
│ ├── logo.jpg
│ └── index.js
├── store
│ └── index.js
├── utils
│ ├── LanguageUtil.js
│ ├── Color.js
│ ├── httpUtil.js
│ ├── AuthUtil.js
│ ├── screenUtil.js
│ ├── language
│ │ ├── zh-Hant.js
│ │ ├── zh-Hans.js
│ │ └── en.js
│ ├── Utility.js
│ ├── storageUtil.js
│ └── Toast.js
├── reducers
│ ├── system.js
│ ├── project.js
│ ├── index.js
│ ├── guide.js
│ ├── wxArticle.js
│ ├── coin.js
│ ├── user.js
│ ├── collect.js
│ ├── search.js
│ └── home.js
├── styles
│ └── globalStyles.js
├── component
│ ├── ProgressBar.js
│ ├── ListFooter.js
│ ├── LoadingView.js
│ ├── ArticleTabComponent.js
│ ├── BottomTabBar.js
│ ├── CommonFlatList.js
│ ├── Banner.js
│ ├── Touchable.js
│ ├── NavBar.js
│ ├── ArticleItemRow.js
│ └── ArticleFlatList.js
├── screen
│ ├── article
│ │ ├── WebViewScreen.js
│ │ ├── ArticleTabScreen.js
│ │ ├── SearchArticleScreen.js
│ │ └── SearchScreen.js
│ ├── tabs
│ │ ├── ProjectScreen.js
│ │ ├── WxArticleScreen.js
│ │ ├── HomeScreen.js
│ │ └── SystemScreen.js
│ └── drawer
│ │ ├── LanguageScreen.js
│ │ ├── WebsitesScreen.js
│ │ ├── CollectScreen.js
│ │ ├── AboutScreen.js
│ │ ├── CoinDetailScreen.js
│ │ └── LoginScreen.js
├── actions
│ ├── actionType.js
│ └── action-creator.js
├── api
│ └── index.js
├── service
│ └── setAxios.js
└── index.js
├── .eslintrc.js
├── screenshot
├── iOS_01.png
├── iOS_02.png
├── iOS_03.png
├── iOS_04.png
├── iOS_05.png
├── iOS_06.png
├── iOS_07.png
├── iOS_08.png
├── iOS_09.png
├── iOS_10.png
├── iOS_11.png
├── iOS_12.png
├── android_01.png
├── android_02.png
├── android_03.png
├── android_04.png
├── android_05.png
├── android_06.png
├── android_07.png
├── android_08.png
├── android_09.png
├── android_10.png
├── android_11.png
└── android_12.png
├── babel.config.js
├── android
├── app
│ ├── debug.keystore
│ ├── src
│ │ ├── main
│ │ │ ├── res
│ │ │ │ ├── values
│ │ │ │ │ ├── strings.xml
│ │ │ │ │ └── styles.xml
│ │ │ │ ├── mipmap-hdpi
│ │ │ │ │ ├── ic_launcher.png
│ │ │ │ │ └── ic_launcher_round.png
│ │ │ │ ├── mipmap-mdpi
│ │ │ │ │ ├── ic_launcher.png
│ │ │ │ │ └── ic_launcher_round.png
│ │ │ │ ├── mipmap-xhdpi
│ │ │ │ │ ├── ic_launcher.png
│ │ │ │ │ └── ic_launcher_round.png
│ │ │ │ ├── mipmap-xxhdpi
│ │ │ │ │ ├── splash_bg.png
│ │ │ │ │ ├── ic_launcher.png
│ │ │ │ │ └── ic_launcher_round.png
│ │ │ │ ├── mipmap-xxxhdpi
│ │ │ │ │ ├── ic_launcher.png
│ │ │ │ │ └── ic_launcher_round.png
│ │ │ │ └── layout
│ │ │ │ │ └── launch_screen.xml
│ │ │ ├── assets
│ │ │ │ └── fonts
│ │ │ │ │ ├── Entypo.ttf
│ │ │ │ │ ├── Feather.ttf
│ │ │ │ │ ├── Zocial.ttf
│ │ │ │ │ ├── AntDesign.ttf
│ │ │ │ │ ├── EvilIcons.ttf
│ │ │ │ │ ├── Fontisto.ttf
│ │ │ │ │ ├── Foundation.ttf
│ │ │ │ │ ├── Ionicons.ttf
│ │ │ │ │ ├── Octicons.ttf
│ │ │ │ │ ├── FontAwesome.ttf
│ │ │ │ │ ├── MaterialIcons.ttf
│ │ │ │ │ ├── SimpleLineIcons.ttf
│ │ │ │ │ ├── FontAwesome5_Brands.ttf
│ │ │ │ │ ├── FontAwesome5_Regular.ttf
│ │ │ │ │ ├── FontAwesome5_Solid.ttf
│ │ │ │ │ └── MaterialCommunityIcons.ttf
│ │ │ ├── AndroidManifest.xml
│ │ │ └── java
│ │ │ │ └── com
│ │ │ │ └── rn_wanandroid
│ │ │ │ ├── MainActivity.java
│ │ │ │ └── MainApplication.java
│ │ └── debug
│ │ │ └── AndroidManifest.xml
│ ├── proguard-rules.pro
│ ├── build_defs.bzl
│ └── BUCK
├── keystores
│ └── app-key.jks
├── gradle
│ └── wrapper
│ │ ├── gradle-wrapper.jar
│ │ └── gradle-wrapper.properties
├── settings.gradle
├── build.gradle
├── gradle.properties
└── gradlew.bat
├── ios
├── RN_WanAndroid
│ ├── Images.xcassets
│ │ ├── Contents.json
│ │ ├── LaunchImage.launchimage
│ │ │ ├── splash_bg.png
│ │ │ └── Contents.json
│ │ └── AppIcon.appiconset
│ │ │ └── Contents.json
│ ├── AppDelegate.h
│ ├── main.m
│ ├── AppDelegate.m
│ ├── Info.plist
│ └── Base.lproj
│ │ └── LaunchScreen.xib
├── RN_WanAndroid.xcworkspace
│ ├── contents.xcworkspacedata
│ └── xcshareddata
│ │ └── IDEWorkspaceChecks.plist
├── RN_WanAndroidTests
│ ├── Info.plist
│ └── RN_WanAndroidTests.m
├── RN_WanAndroid-tvOSTests
│ └── Info.plist
├── RN_WanAndroid-tvOS
│ └── Info.plist
├── Podfile
└── RN_WanAndroid.xcodeproj
│ └── xcshareddata
│ └── xcschemes
│ ├── RN_WanAndroid.xcscheme
│ └── RN_WanAndroid-tvOS.xcscheme
├── .buckconfig
├── .prettierrc.js
├── __tests__
└── App-test.js
├── metro.config.js
├── index.js
├── .gitignore
├── package.json
├── App.js
├── .flowconfig
└── README.md
/.watchmanconfig:
--------------------------------------------------------------------------------
1 | {}
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | *.pbxproj -text
2 |
--------------------------------------------------------------------------------
/app.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "RN_WanAndroid",
3 | "displayName": "RN_WanAndroid"
4 | }
--------------------------------------------------------------------------------
/src/images/logo.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aijason/RN_WanAndroid/HEAD/src/images/logo.jpg
--------------------------------------------------------------------------------
/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | root: true,
3 | extends: '@react-native-community',
4 | };
5 |
--------------------------------------------------------------------------------
/screenshot/iOS_01.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aijason/RN_WanAndroid/HEAD/screenshot/iOS_01.png
--------------------------------------------------------------------------------
/screenshot/iOS_02.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aijason/RN_WanAndroid/HEAD/screenshot/iOS_02.png
--------------------------------------------------------------------------------
/screenshot/iOS_03.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aijason/RN_WanAndroid/HEAD/screenshot/iOS_03.png
--------------------------------------------------------------------------------
/screenshot/iOS_04.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aijason/RN_WanAndroid/HEAD/screenshot/iOS_04.png
--------------------------------------------------------------------------------
/screenshot/iOS_05.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aijason/RN_WanAndroid/HEAD/screenshot/iOS_05.png
--------------------------------------------------------------------------------
/screenshot/iOS_06.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aijason/RN_WanAndroid/HEAD/screenshot/iOS_06.png
--------------------------------------------------------------------------------
/screenshot/iOS_07.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aijason/RN_WanAndroid/HEAD/screenshot/iOS_07.png
--------------------------------------------------------------------------------
/screenshot/iOS_08.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aijason/RN_WanAndroid/HEAD/screenshot/iOS_08.png
--------------------------------------------------------------------------------
/screenshot/iOS_09.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aijason/RN_WanAndroid/HEAD/screenshot/iOS_09.png
--------------------------------------------------------------------------------
/screenshot/iOS_10.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aijason/RN_WanAndroid/HEAD/screenshot/iOS_10.png
--------------------------------------------------------------------------------
/screenshot/iOS_11.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aijason/RN_WanAndroid/HEAD/screenshot/iOS_11.png
--------------------------------------------------------------------------------
/screenshot/iOS_12.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aijason/RN_WanAndroid/HEAD/screenshot/iOS_12.png
--------------------------------------------------------------------------------
/babel.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | presets: ['module:metro-react-native-babel-preset'],
3 | };
4 |
--------------------------------------------------------------------------------
/screenshot/android_01.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aijason/RN_WanAndroid/HEAD/screenshot/android_01.png
--------------------------------------------------------------------------------
/screenshot/android_02.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aijason/RN_WanAndroid/HEAD/screenshot/android_02.png
--------------------------------------------------------------------------------
/screenshot/android_03.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aijason/RN_WanAndroid/HEAD/screenshot/android_03.png
--------------------------------------------------------------------------------
/screenshot/android_04.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aijason/RN_WanAndroid/HEAD/screenshot/android_04.png
--------------------------------------------------------------------------------
/screenshot/android_05.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aijason/RN_WanAndroid/HEAD/screenshot/android_05.png
--------------------------------------------------------------------------------
/screenshot/android_06.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aijason/RN_WanAndroid/HEAD/screenshot/android_06.png
--------------------------------------------------------------------------------
/screenshot/android_07.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aijason/RN_WanAndroid/HEAD/screenshot/android_07.png
--------------------------------------------------------------------------------
/screenshot/android_08.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aijason/RN_WanAndroid/HEAD/screenshot/android_08.png
--------------------------------------------------------------------------------
/screenshot/android_09.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aijason/RN_WanAndroid/HEAD/screenshot/android_09.png
--------------------------------------------------------------------------------
/screenshot/android_10.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aijason/RN_WanAndroid/HEAD/screenshot/android_10.png
--------------------------------------------------------------------------------
/screenshot/android_11.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aijason/RN_WanAndroid/HEAD/screenshot/android_11.png
--------------------------------------------------------------------------------
/screenshot/android_12.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aijason/RN_WanAndroid/HEAD/screenshot/android_12.png
--------------------------------------------------------------------------------
/android/app/debug.keystore:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aijason/RN_WanAndroid/HEAD/android/app/debug.keystore
--------------------------------------------------------------------------------
/android/keystores/app-key.jks:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aijason/RN_WanAndroid/HEAD/android/keystores/app-key.jks
--------------------------------------------------------------------------------
/android/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | RN玩安卓
3 |
4 |
--------------------------------------------------------------------------------
/ios/RN_WanAndroid/Images.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "version" : 1,
4 | "author" : "xcode"
5 | }
6 | }
--------------------------------------------------------------------------------
/android/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aijason/RN_WanAndroid/HEAD/android/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/android/app/src/main/assets/fonts/Entypo.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aijason/RN_WanAndroid/HEAD/android/app/src/main/assets/fonts/Entypo.ttf
--------------------------------------------------------------------------------
/android/app/src/main/assets/fonts/Feather.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aijason/RN_WanAndroid/HEAD/android/app/src/main/assets/fonts/Feather.ttf
--------------------------------------------------------------------------------
/android/app/src/main/assets/fonts/Zocial.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aijason/RN_WanAndroid/HEAD/android/app/src/main/assets/fonts/Zocial.ttf
--------------------------------------------------------------------------------
/.buckconfig:
--------------------------------------------------------------------------------
1 |
2 | [android]
3 | target = Google Inc.:Google APIs:23
4 |
5 | [maven_repositories]
6 | central = https://repo1.maven.org/maven2
7 |
--------------------------------------------------------------------------------
/android/app/src/main/assets/fonts/AntDesign.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aijason/RN_WanAndroid/HEAD/android/app/src/main/assets/fonts/AntDesign.ttf
--------------------------------------------------------------------------------
/android/app/src/main/assets/fonts/EvilIcons.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aijason/RN_WanAndroid/HEAD/android/app/src/main/assets/fonts/EvilIcons.ttf
--------------------------------------------------------------------------------
/android/app/src/main/assets/fonts/Fontisto.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aijason/RN_WanAndroid/HEAD/android/app/src/main/assets/fonts/Fontisto.ttf
--------------------------------------------------------------------------------
/android/app/src/main/assets/fonts/Foundation.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aijason/RN_WanAndroid/HEAD/android/app/src/main/assets/fonts/Foundation.ttf
--------------------------------------------------------------------------------
/android/app/src/main/assets/fonts/Ionicons.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aijason/RN_WanAndroid/HEAD/android/app/src/main/assets/fonts/Ionicons.ttf
--------------------------------------------------------------------------------
/android/app/src/main/assets/fonts/Octicons.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aijason/RN_WanAndroid/HEAD/android/app/src/main/assets/fonts/Octicons.ttf
--------------------------------------------------------------------------------
/.prettierrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | bracketSpacing: false,
3 | jsxBracketSameLine: true,
4 | singleQuote: true,
5 | trailingComma: 'all',
6 | };
7 |
--------------------------------------------------------------------------------
/android/app/src/main/assets/fonts/FontAwesome.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aijason/RN_WanAndroid/HEAD/android/app/src/main/assets/fonts/FontAwesome.ttf
--------------------------------------------------------------------------------
/android/app/src/main/assets/fonts/MaterialIcons.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aijason/RN_WanAndroid/HEAD/android/app/src/main/assets/fonts/MaterialIcons.ttf
--------------------------------------------------------------------------------
/android/app/src/main/assets/fonts/SimpleLineIcons.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aijason/RN_WanAndroid/HEAD/android/app/src/main/assets/fonts/SimpleLineIcons.ttf
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aijason/RN_WanAndroid/HEAD/android/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aijason/RN_WanAndroid/HEAD/android/app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aijason/RN_WanAndroid/HEAD/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-xxhdpi/splash_bg.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aijason/RN_WanAndroid/HEAD/android/app/src/main/res/mipmap-xxhdpi/splash_bg.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aijason/RN_WanAndroid/HEAD/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aijason/RN_WanAndroid/HEAD/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/android/app/src/main/assets/fonts/FontAwesome5_Brands.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aijason/RN_WanAndroid/HEAD/android/app/src/main/assets/fonts/FontAwesome5_Brands.ttf
--------------------------------------------------------------------------------
/android/app/src/main/assets/fonts/FontAwesome5_Regular.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aijason/RN_WanAndroid/HEAD/android/app/src/main/assets/fonts/FontAwesome5_Regular.ttf
--------------------------------------------------------------------------------
/android/app/src/main/assets/fonts/FontAwesome5_Solid.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aijason/RN_WanAndroid/HEAD/android/app/src/main/assets/fonts/FontAwesome5_Solid.ttf
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aijason/RN_WanAndroid/HEAD/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aijason/RN_WanAndroid/HEAD/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/src/images/index.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Created by hjs on 2019-09-20
3 | */
4 | const images = {
5 | logoIcon: require('./logo.jpg'),
6 | };
7 |
8 | export default images;
9 |
--------------------------------------------------------------------------------
/android/app/src/main/assets/fonts/MaterialCommunityIcons.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aijason/RN_WanAndroid/HEAD/android/app/src/main/assets/fonts/MaterialCommunityIcons.ttf
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aijason/RN_WanAndroid/HEAD/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aijason/RN_WanAndroid/HEAD/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aijason/RN_WanAndroid/HEAD/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/ios/RN_WanAndroid/Images.xcassets/LaunchImage.launchimage/splash_bg.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aijason/RN_WanAndroid/HEAD/ios/RN_WanAndroid/Images.xcassets/LaunchImage.launchimage/splash_bg.png
--------------------------------------------------------------------------------
/android/settings.gradle:
--------------------------------------------------------------------------------
1 | rootProject.name = 'RN_WanAndroid'
2 | apply from: file("../node_modules/@react-native-community/cli-platform-android/native_modules.gradle"); applyNativeModulesSettingsGradle(settings)
3 | include ':app'
4 |
--------------------------------------------------------------------------------
/android/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | distributionBase=GRADLE_USER_HOME
2 | distributionPath=wrapper/dists
3 | distributionUrl=https\://services.gradle.org/distributions/gradle-5.5-all.zip
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 |
--------------------------------------------------------------------------------
/src/store/index.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Created by hjs on 2019-09-17
3 | */
4 | import {createStore, applyMiddleware} from 'redux';
5 | import thunk from 'redux-thunk';
6 | import rootReducer from '../reducers';
7 |
8 | const index = createStore(rootReducer, applyMiddleware(thunk));
9 | export default index;
10 |
--------------------------------------------------------------------------------
/ios/RN_WanAndroid.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/ios/RN_WanAndroid.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/__tests__/App-test.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @format
3 | */
4 |
5 | import 'react-native';
6 | import React from 'react';
7 | import App from '../App';
8 |
9 | // Note: test renderer must be required after react-native.
10 | import renderer from 'react-test-renderer';
11 |
12 | it('renders correctly', () => {
13 | renderer.create();
14 | });
15 |
--------------------------------------------------------------------------------
/metro.config.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Metro configuration for React Native
3 | * https://github.com/facebook/react-native
4 | *
5 | * @format
6 | */
7 |
8 | module.exports = {
9 | transformer: {
10 | getTransformOptions: async () => ({
11 | transform: {
12 | experimentalImportSupport: false,
13 | inlineRequires: false,
14 | },
15 | }),
16 | },
17 | };
18 |
--------------------------------------------------------------------------------
/android/app/src/debug/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/src/utils/LanguageUtil.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Created by huangjunsheng on 2019-10-16
3 | */
4 | import I18n from 'react-native-i18n';
5 | import en from './language/en';
6 | import zhHans from './language/zh-Hans';
7 | import zhHant from './language/zh-Hant';
8 |
9 | I18n.defaultLocale = 'zhHans';
10 |
11 | I18n.fallbacks = true;
12 |
13 | I18n.translations = {
14 | en,
15 | zhHans,
16 | zhHant,
17 | };
18 |
19 | export default I18n;
20 |
--------------------------------------------------------------------------------
/ios/RN_WanAndroid/AppDelegate.h:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (c) Facebook, Inc. and its affiliates.
3 | *
4 | * This source code is licensed under the MIT license found in the
5 | * LICENSE file in the root directory of this source tree.
6 | */
7 |
8 | #import
9 | #import
10 |
11 | @interface AppDelegate : UIResponder
12 |
13 | @property (nonatomic, strong) UIWindow *window;
14 |
15 | @end
16 |
--------------------------------------------------------------------------------
/ios/RN_WanAndroid/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 |
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @format
3 | */
4 |
5 | import {AppRegistry} from 'react-native';
6 | import App from './App';
7 | import {name as appName} from './app.json';
8 |
9 | console.disableYellowBox = true;
10 |
11 | if (!__DEV__) {
12 | global.console = {
13 | info: () => {},
14 | log: () => {},
15 | warn: () => {},
16 | debug: () => {},
17 | error: () => {},
18 | assert: () => {},
19 | };
20 | }
21 |
22 | AppRegistry.registerComponent(appName, () => App);
23 |
--------------------------------------------------------------------------------
/android/app/src/main/res/layout/launch_screen.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/src/reducers/system.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Created by hjs on 2019-09-22
3 | */
4 | import actionTypes from '../actions/actionType';
5 |
6 | const initialStore = {
7 | systemData: [],
8 | };
9 |
10 | const user = (state = initialStore, action) => {
11 | switch (action.type) {
12 | case actionTypes.FETCH_TO_SYSTEM_DATA:
13 | return {
14 | ...state,
15 | systemData: action.systemData,
16 | };
17 | default:
18 | return state;
19 | }
20 | };
21 |
22 | export default user;
23 |
--------------------------------------------------------------------------------
/src/utils/Color.js:
--------------------------------------------------------------------------------
1 | const Color = {
2 | WHITE: '#FFFFFF',
3 | RED: '#FF0000',
4 | TEXT_MAIN: '#222222',
5 | TEXT_DARK: '#666666',
6 | TEXT_LIGHT: '#999999',
7 | THEME: '#2196F3', // 默认APP主题颜色
8 | SPLIT_LINE: '#E8E8E8', // 页面中分割线颜色
9 | DEFAULT_BG: '#f1f2f3', // 所有页面背景颜色
10 | COLLECT: '#FA8072', // 收藏颜色
11 | GOLD: '#FFD700', // 金色
12 | TEXT_TAB_INACTIVE: '#E6E6FA',
13 | WX_GREEN: '#3CB371',
14 | ICON_GRAY: '#E0E0E0',
15 | GUIDE_BG: '#F8F8F8',
16 | };
17 |
18 | export default Color;
19 |
--------------------------------------------------------------------------------
/android/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/src/reducers/project.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Created by hjs on 2019-09-22
3 | */
4 | import actionTypes from '../actions/actionType';
5 |
6 | const initialStore = {
7 | projectTabs: [],
8 | };
9 |
10 | const wxArticle = (state = initialStore, action) => {
11 | switch (action.type) {
12 | case actionTypes.FETCH_PROJECT_TABS:
13 | return {
14 | ...state,
15 | projectTabs: action.projectTabs,
16 | };
17 | default:
18 | return state;
19 | }
20 | };
21 |
22 | export default wxArticle;
23 |
--------------------------------------------------------------------------------
/src/reducers/index.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Created by hjs on 2019-09-17
3 | */
4 | import {combineReducers} from 'redux';
5 | import home from './home';
6 | import user from './user';
7 | import system from './system';
8 | import wxArticle from './wxArticle';
9 | import guide from './guide';
10 | import project from './project';
11 | import search from './search';
12 | import collect from './collect';
13 | import coin from './coin';
14 |
15 | export default combineReducers({
16 | home,
17 | user,
18 | system,
19 | wxArticle,
20 | guide,
21 | project,
22 | search,
23 | collect,
24 | coin,
25 | });
26 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/src/reducers/guide.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Created by huangjunsheng on 2019-09-25
3 | */
4 | import actionTypes from '../actions/actionType';
5 |
6 | const initialStore = {
7 | selectIndex: 0, // 左侧选中的索引值
8 | guideData: [], // 导航数据
9 | };
10 |
11 | const guide = (state = initialStore, action) => {
12 | switch (action.type) {
13 | case actionTypes.FETCH_GUIDE_DATA:
14 | return {
15 | ...state,
16 | guideData: action.guideData,
17 | };
18 | case actionTypes.UPDATE_SELECT_INDEX:
19 | return {
20 | ...state,
21 | selectIndex: action.selectIndex,
22 | };
23 | default:
24 | return state;
25 | }
26 | };
27 |
28 | export default guide;
29 |
--------------------------------------------------------------------------------
/src/reducers/wxArticle.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Created by hjs on 2019-09-22
3 | */
4 | import actionTypes from '../actions/actionType';
5 |
6 | const initialStore = {
7 | articleTabs: [],
8 | isShowLoading: false,
9 | };
10 |
11 | const wxArticle = (state = initialStore, action) => {
12 | switch (action.type) {
13 | case actionTypes.FETCH_WX_ARTICLE_TABS:
14 | return {
15 | ...state,
16 | articleTabs: action.articleTabs,
17 | };
18 | case actionTypes.FETCH_ARTICLE_LOADING:
19 | return {
20 | ...state,
21 | isShowLoading: action.isShowLoading,
22 | }
23 | default:
24 | return state;
25 | }
26 | };
27 |
28 | export default wxArticle;
29 |
--------------------------------------------------------------------------------
/ios/RN_WanAndroidTests/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | BNDL
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleSignature
20 | ????
21 | CFBundleVersion
22 | 1
23 |
24 |
25 |
--------------------------------------------------------------------------------
/ios/RN_WanAndroid-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 |
--------------------------------------------------------------------------------
/src/styles/globalStyles.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Created by huangjunsheng on 2019-09-20
3 | */
4 | import {StyleSheet} from 'react-native';
5 | import Color from '../utils/Color';
6 | import {DEVICE_WIDTH, getRealDP as dp} from '../utils/screenUtil';
7 |
8 | export default StyleSheet.create({
9 | container: {
10 | flex: 1,
11 | backgroundColor: Color.DEFAULT_BG,
12 | },
13 | splitLine: {
14 | height: dp(1),
15 | width: DEVICE_WIDTH - dp(56),
16 | marginLeft: dp(28),
17 | backgroundColor: Color.SPLIT_LINE,
18 | },
19 | circleSpecialWrapper: {
20 | width: dp(120),
21 | height: dp(120),
22 | borderRadius: dp(60),
23 | justifyContent: 'center',
24 | alignItems: 'center',
25 | padding: dp(10),
26 | },
27 | specialText: {
28 | fontSize: dp(20),
29 | color: Color.WHITE,
30 | textAlign: 'center',
31 | fontWeight: 'bold',
32 | lineHeight: dp(23),
33 | },
34 | articleTitle: {
35 | fontSize: dp(28),
36 | color: Color.TEXT_MAIN,
37 | fontWeight: 'bold',
38 | },
39 | });
40 |
--------------------------------------------------------------------------------
/.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 |
24 | # Android/IntelliJ
25 | #
26 | build/
27 | .idea
28 | .gradle
29 | local.properties
30 | *.iml
31 |
32 | # node.js
33 | #
34 | node_modules/
35 | npm-debug.log
36 | yarn-error.log
37 |
38 | # BUCK
39 | buck-out/
40 | \.buckd/
41 | *.keystore
42 | !debug.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 |
58 | # CocoaPods
59 | /ios/Pods/
60 |
--------------------------------------------------------------------------------
/android/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
3 |
4 |
5 |
6 |
13 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/ios/RN_WanAndroid/Images.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "iphone",
5 | "size" : "20x20",
6 | "scale" : "2x"
7 | },
8 | {
9 | "idiom" : "iphone",
10 | "size" : "20x20",
11 | "scale" : "3x"
12 | },
13 | {
14 | "idiom" : "iphone",
15 | "size" : "29x29",
16 | "scale" : "2x"
17 | },
18 | {
19 | "idiom" : "iphone",
20 | "size" : "29x29",
21 | "scale" : "3x"
22 | },
23 | {
24 | "idiom" : "iphone",
25 | "size" : "40x40",
26 | "scale" : "2x"
27 | },
28 | {
29 | "idiom" : "iphone",
30 | "size" : "40x40",
31 | "scale" : "3x"
32 | },
33 | {
34 | "idiom" : "iphone",
35 | "size" : "60x60",
36 | "scale" : "2x"
37 | },
38 | {
39 | "idiom" : "iphone",
40 | "size" : "60x60",
41 | "scale" : "3x"
42 | },
43 | {
44 | "idiom" : "ios-marketing",
45 | "size" : "1024x1024",
46 | "scale" : "1x"
47 | }
48 | ],
49 | "info" : {
50 | "version" : 1,
51 | "author" : "xcode"
52 | }
53 | }
--------------------------------------------------------------------------------
/src/utils/httpUtil.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Created by hjs on 2019-09-17
3 | */
4 | import axios from 'axios';
5 | import qs from 'querystring';
6 |
7 | /**
8 | * 网络请求工具类
9 | */
10 | export default class httpUtil {
11 | static get(url, params) {
12 | return new Promise(async (resolve, reject) => {
13 | try {
14 | let query = await qs.stringify(params);
15 | let res = null;
16 | if (!params) {
17 | res = await axios.get(url);
18 | } else {
19 | res = await axios.get(url + '?' + query);
20 | }
21 | resolve(res);
22 | } catch (error) {
23 | const errorMsg = `请求报错路径: ${url} \n请求报错信息: ${error}`;
24 | console.log(errorMsg);
25 | reject(error);
26 | }
27 | });
28 | }
29 |
30 | static post(url, params) {
31 | return new Promise(async (resolve, reject) => {
32 | try {
33 | let res = await axios.post(url, params);
34 | resolve(res);
35 | } catch (error) {
36 | const errorMsg = `请求报错路径: ${url} \n请求报错信息: ${error}`;
37 | console.log(errorMsg);
38 | reject(error);
39 | }
40 | });
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/android/build.gradle:
--------------------------------------------------------------------------------
1 | // Top-level build file where you can add configuration options common to all sub-projects/modules.
2 |
3 | buildscript {
4 | ext {
5 | buildToolsVersion = "28.0.3"
6 | minSdkVersion = 16
7 | compileSdkVersion = 28
8 | targetSdkVersion = 28
9 | }
10 | repositories {
11 | google()
12 | jcenter()
13 | }
14 | dependencies {
15 | classpath("com.android.tools.build:gradle:3.4.2")
16 |
17 | // NOTE: Do not place your application dependencies here; they belong
18 | // in the individual module build.gradle files
19 | }
20 | }
21 |
22 | allprojects {
23 | repositories {
24 | mavenLocal()
25 | maven {
26 | // All of React Native (JS, Obj-C sources, Android binaries) is installed from npm
27 | url("$rootDir/../node_modules/react-native/android")
28 | }
29 | maven {
30 | // Android JSC is installed from npm
31 | url("$rootDir/../node_modules/jsc-android/dist")
32 | }
33 |
34 | google()
35 | jcenter()
36 | maven { url 'https://jitpack.io' }
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/android/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 |
3 | # IDE (e.g. Android Studio) users:
4 | # Gradle settings configured through the IDE *will override*
5 | # any settings specified in this file.
6 |
7 | # For more details on how to configure your build environment visit
8 | # http://www.gradle.org/docs/current/userguide/build_environment.html
9 |
10 | # Specifies the JVM arguments used for the daemon process.
11 | # The setting is particularly useful for tweaking memory settings.
12 | # Default value: -Xmx10248m -XX:MaxPermSize=256m
13 | # org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
14 |
15 | # When configured, Gradle will run in incubating parallel mode.
16 | # This option should only be used with decoupled projects. More details, visit
17 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
18 | # org.gradle.parallel=true
19 |
20 | APP_RELEASE_STORE_FILE=../keystores/app-key.jks
21 | APP_RELEASE_KEY_ALIAS=appKey
22 | APP_RELEASE_STORE_PASSWORD=rn123456
23 | APP_RELEASE_KEY_PASSWORD=rn123456
24 |
25 | android.useAndroidX=true
26 | android.enableJetifier=true
27 |
--------------------------------------------------------------------------------
/android/app/src/main/java/com/rn_wanandroid/MainActivity.java:
--------------------------------------------------------------------------------
1 | package com.rn_wanandroid;
2 |
3 | import android.os.Bundle;
4 |
5 | import com.facebook.react.ReactActivity;
6 | import com.facebook.react.ReactActivityDelegate;
7 | import com.facebook.react.ReactRootView;
8 | import com.swmansion.gesturehandler.react.RNGestureHandlerEnabledRootView;
9 |
10 | import org.devio.rn.splashscreen.SplashScreen;
11 |
12 | public class MainActivity extends ReactActivity {
13 |
14 | @Override
15 | protected void onCreate(Bundle savedInstanceState) {
16 | SplashScreen.show(this, true);
17 | super.onCreate(savedInstanceState);
18 | }
19 |
20 | /**
21 | * Returns the name of the main component registered from JavaScript. This is used to schedule
22 | * rendering of the component.
23 | */
24 | @Override
25 | protected String getMainComponentName() {
26 | return "RN_WanAndroid";
27 | }
28 |
29 | @Override
30 | protected ReactActivityDelegate createReactActivityDelegate() {
31 | return new ReactActivityDelegate(this, getMainComponentName()) {
32 | @Override
33 | protected ReactRootView createRootView() {
34 | return new RNGestureHandlerEnabledRootView(MainActivity.this);
35 | }
36 | };
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/src/reducers/coin.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Created by hjs on 2019-09-27
3 | */
4 | import actionTypes from '../actions/actionType';
5 |
6 | const initialState = {
7 | page: 1,
8 | coinList: {},
9 | dataSource: [], // 文章列表数据源
10 | isRenderFooter: false, // 是否渲染列表footer
11 | isFullData: false, // 是否加载完全部数据
12 | coinCount: 0, // 总积分
13 | rank: 0, // 当前排名
14 | };
15 |
16 | const coin = (state = initialState, action) => {
17 | switch (action.type) {
18 | case actionTypes.FETCH_MY_COIN_LIST:
19 | return {
20 | ...state,
21 | page: 1,
22 | coinList: action.coinList,
23 | dataSource: action.coinList.datas,
24 | isRenderFooter: !!action.coinList.total, // 只有total为0是不渲染footer
25 | isFullData: action.coinList.curPage === action.coinList.pageCount,
26 | };
27 | case actionTypes.FETCH_MY_COIN_LIST_MORE:
28 | return {
29 | ...state,
30 | page: ++state.page,
31 | dataSource: state.dataSource.concat(action.coinList.datas),
32 | isRenderFooter: true,
33 | isFullData: !action.coinList.datas.length,
34 | };
35 | case actionTypes.FETCH_MY_COIN_INFO:
36 | return {
37 | ...state,
38 | coinCount: action.coinInfo.coinCount,
39 | rank: action.coinInfo.rank,
40 | };
41 | default:
42 | return state;
43 | }
44 | };
45 |
46 | export default coin;
47 |
--------------------------------------------------------------------------------
/src/utils/AuthUtil.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Created by hjs on 2019-09-20
3 | */
4 | import StorageUtil from './storageUtil';
5 |
6 | const userInfoKey = '@userInfo';
7 | const cookieKey = '@cookie';
8 | const themeColorKey = '@themeColor';
9 | const appLanguage = '@appLanguage';
10 |
11 | class AuthUtil {
12 | static saveAppLanguage = language => {
13 | return StorageUtil.save(appLanguage, language);
14 | };
15 |
16 | static getAppLanguage = () => {
17 | return StorageUtil.get(appLanguage);
18 | };
19 |
20 | static saveThemeColor = color => {
21 | return StorageUtil.save(themeColorKey, color);
22 | };
23 |
24 | static getThemeColor = () => {
25 | return StorageUtil.get(themeColorKey);
26 | };
27 |
28 | static saveUserInfo = info => {
29 | return StorageUtil.save(userInfoKey, info);
30 | };
31 |
32 | static getUserInfo = () => {
33 | return StorageUtil.get(userInfoKey);
34 | };
35 |
36 | static removeUserInfo = () => {
37 | return StorageUtil.delete(userInfoKey);
38 | };
39 |
40 | static saveCookie(cookie) {
41 | return StorageUtil.save(cookieKey, cookie);
42 | }
43 |
44 | static getCookie = () => {
45 | return StorageUtil.get(cookieKey);
46 | };
47 |
48 | static removeCookie = () => {
49 | return StorageUtil.delete(cookieKey);
50 | };
51 |
52 | static removeAllKeys = async () => {
53 | return StorageUtil.delete(await StorageUtil.keys());
54 | };
55 | }
56 |
57 | export default AuthUtil;
58 |
--------------------------------------------------------------------------------
/src/component/ProgressBar.js:
--------------------------------------------------------------------------------
1 | import React, {PureComponent} from 'react';
2 | import {ProgressBarAndroid, ProgressViewIOS} from 'react-native';
3 | import PropTypes from 'prop-types';
4 | import {getRealDP as dp, isAndroid} from '../utils/screenUtil';
5 | import Color from '../utils/Color';
6 | import {connect} from 'react-redux';
7 |
8 | const propTypes = {
9 | progress: PropTypes.number.isRequired,
10 | };
11 | const defaultProps = {
12 | progress: 0,
13 | };
14 |
15 | /**
16 | * ProgressBar 用于WebView展示时上方进度条
17 | */
18 | class ProgressBar extends PureComponent {
19 | constructor(props) {
20 | super(props);
21 | this.state = {};
22 | }
23 |
24 | render() {
25 | const {progress, themeColor} = this.props;
26 | if (progress === 1) {
27 | return null;
28 | }
29 | if (isAndroid) {
30 | return (
31 |
37 | );
38 | }
39 | return (
40 |
45 | );
46 | }
47 | }
48 |
49 | ProgressBar.propTypes = propTypes;
50 | ProgressBar.defaultProps = defaultProps;
51 |
52 | const mapStateToProps = state => {
53 | return {
54 | themeColor: state.user.themeColor,
55 | };
56 | };
57 |
58 | export default connect(mapStateToProps)(ProgressBar);
59 |
--------------------------------------------------------------------------------
/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.rn_wanandroid",
39 | )
40 |
41 | android_resource(
42 | name = "res",
43 | package = "com.rn_wanandroid",
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 |
--------------------------------------------------------------------------------
/ios/RN_WanAndroid/AppDelegate.m:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (c) Facebook, Inc. and its affiliates.
3 | *
4 | * This source code is licensed under the MIT license found in the
5 | * LICENSE file in the root directory of this source tree.
6 | */
7 |
8 | #import "AppDelegate.h"
9 |
10 | #import
11 | #import
12 | #import
13 |
14 | @implementation AppDelegate
15 |
16 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
17 | {
18 | RCTBridge *bridge = [[RCTBridge alloc] initWithDelegate:self launchOptions:launchOptions];
19 | RCTRootView *rootView = [[RCTRootView alloc] initWithBridge:bridge
20 | moduleName:@"RN_WanAndroid"
21 | initialProperties:nil];
22 |
23 | rootView.backgroundColor = [[UIColor alloc] initWithRed:1.0f green:1.0f blue:1.0f alpha:1];
24 |
25 | self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
26 | UIViewController *rootViewController = [UIViewController new];
27 | rootViewController.view = rootView;
28 | self.window.rootViewController = rootViewController;
29 | [self.window makeKeyAndVisible];
30 | return YES;
31 | }
32 |
33 | - (NSURL *)sourceURLForBridge:(RCTBridge *)bridge
34 | {
35 | #if DEBUG
36 | return [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:@"index" fallbackResource:nil];
37 | #else
38 | return [[NSBundle mainBundle] URLForResource:@"main" withExtension:@"jsbundle"];
39 | #endif
40 | }
41 |
42 | @end
43 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "RN_WanAndroid",
3 | "version": "0.0.1",
4 | "private": true,
5 | "scripts": {
6 | "android": "react-native run-android",
7 | "ios": "react-native run-ios",
8 | "build:android": "cd android && ./gradlew assembleRelease",
9 | "start": "react-native start",
10 | "test": "jest",
11 | "lint": "eslint ."
12 | },
13 | "dependencies": {
14 | "@react-native-community/async-storage": "^1.6.2",
15 | "axios": "^0.19.0",
16 | "lodash": "^4.17.19",
17 | "querystring": "^0.2.0",
18 | "react": "16.9.0",
19 | "react-native": "0.61.3",
20 | "react-native-gesture-handler": "^1.5.0",
21 | "react-native-i18n": "git+https://github.com/aijason/react-native-i18n",
22 | "react-native-reanimated": "^1.3.2",
23 | "react-native-splash-screen": "^3.2.0",
24 | "react-native-swiper": "^1.6.0-nightly.5",
25 | "react-native-vector-icons": "^6.6.0",
26 | "react-native-webview": "^7.4.3",
27 | "react-navigation": "^4.0.10",
28 | "react-navigation-drawer": "^2.3.3",
29 | "react-navigation-stack": "^1.10.3",
30 | "react-navigation-tabs": "^2.5.6",
31 | "react-redux": "^7.1.1",
32 | "redux": "^4.0.4",
33 | "redux-thunk": "^2.3.0"
34 | },
35 | "devDependencies": {
36 | "@babel/core": "^7.6.4",
37 | "@babel/runtime": "^7.6.3",
38 | "@react-native-community/eslint-config": "^0.0.5",
39 | "babel-jest": "^24.9.0",
40 | "eslint": "^6.6.0",
41 | "jest": "^24.9.0",
42 | "metro-react-native-babel-preset": "^0.56.3",
43 | "react-test-renderer": "16.9.0"
44 | },
45 | "jest": {
46 | "preset": "react-native"
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/src/screen/article/WebViewScreen.js:
--------------------------------------------------------------------------------
1 | import React, {PureComponent} from 'react';
2 | import {Share, View} from 'react-native';
3 | import WebView from 'react-native-webview';
4 | import ProgressBar from '../../component/ProgressBar';
5 | import globalStyles from '../../styles/globalStyles';
6 | import NavBar from '../../component/NavBar';
7 |
8 | /**
9 | * WebViewScreen
10 | */
11 | class WebViewScreen extends PureComponent {
12 | constructor(props) {
13 | super(props);
14 | this.state = {
15 | progress: 0,
16 | };
17 | this.onShare = this.onShare.bind(this);
18 | }
19 |
20 | async onShare(title, url) {
21 | try {
22 | await Share.share({
23 | message: `${title}:${url}`,
24 | });
25 | } catch (error) {
26 | alert(error.message);
27 | }
28 | }
29 |
30 | render() {
31 | const {navigation} = this.props;
32 | const url = navigation.getParam('url', 'https://www.wanandroid.com/');
33 | const title = navigation.getParam('title', '');
34 | return (
35 |
36 | {
41 | this.onShare(title, url);
42 | }}
43 | />
44 |
45 | {
48 | this.setState({progress: nativeEvent.progress});
49 | }}
50 | />
51 |
52 | );
53 | }
54 | }
55 |
56 | export default WebViewScreen;
57 |
--------------------------------------------------------------------------------
/src/utils/screenUtil.js:
--------------------------------------------------------------------------------
1 | import {PixelRatio, Dimensions, Platform, StatusBar} from 'react-native';
2 |
3 | export let DEVICE_WIDTH = Dimensions.get('window').width;
4 | export let DEVICE_HEIGHT = Dimensions.get('window').height;
5 | export const isAndroid = Platform.OS === 'android';
6 |
7 | /**
8 | * 本项目设计基准像素为750 * 1334,使用时视情况调整
9 | * 按比例将设计的px转换成适应不同屏幕的dp
10 | * @param designPx 设计稿标注的px值
11 | * @returns {number}
12 | */
13 | export function getRealDP(designPx) {
14 | return PixelRatio.roundToNearestPixel((designPx / 750) * DEVICE_WIDTH);
15 | }
16 |
17 | // 是否iphoneX系列(iPhone X, XS, XS Max & XR)
18 | export function isIphoneX() {
19 | const dimen = Dimensions.get('window');
20 | return (
21 | Platform.OS === 'ios' &&
22 | !Platform.isPad &&
23 | !Platform.isTVOS &&
24 | (dimen.height === 812 ||
25 | dimen.width === 812 ||
26 | (dimen.height === 896 || dimen.width === 896))
27 | );
28 | }
29 |
30 | // 获取状态栏高度
31 | export function getStatusBarHeight() {
32 | return Platform.select({
33 | ios: ifIphoneX(44, 20),
34 | android: StatusBar.currentHeight,
35 | });
36 | }
37 |
38 | // 适配iphoneX屏幕底部距离
39 | export function getBottomSpace() {
40 | return ifIphoneX(34, 0);
41 | }
42 |
43 | /**
44 | * 根据是否是iPhoneX返回不同的样式
45 | * @param iphoneXStyle
46 | * @param iosStyle
47 | * @param androidStyle
48 | * @returns {*}
49 | */
50 | export function ifIphoneX(iphoneXStyle, iosStyle = {}, androidStyle) {
51 | if (isIphoneX()) {
52 | return iphoneXStyle;
53 | } else if (Platform.OS === 'ios') {
54 | return iosStyle;
55 | } else {
56 | if (androidStyle) {
57 | return androidStyle;
58 | }
59 | return iosStyle;
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/ios/RN_WanAndroid-tvOS/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 | APPL
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleSignature
20 | ????
21 | CFBundleVersion
22 | 1
23 | LSRequiresIPhoneOS
24 |
25 | NSAppTransportSecurity
26 |
27 | NSExceptionDomains
28 |
29 | localhost
30 |
31 | NSExceptionAllowsInsecureHTTPLoads
32 |
33 |
34 |
35 |
36 | NSLocationWhenInUseUsageDescription
37 |
38 | UILaunchStoryboardName
39 | LaunchScreen
40 | UIRequiredDeviceCapabilities
41 |
42 | armv7
43 |
44 | UISupportedInterfaceOrientations
45 |
46 | UIInterfaceOrientationPortrait
47 | UIInterfaceOrientationLandscapeLeft
48 | UIInterfaceOrientationLandscapeRight
49 |
50 | UIViewControllerBasedStatusBarAppearance
51 |
52 |
53 |
54 |
--------------------------------------------------------------------------------
/src/screen/tabs/ProjectScreen.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Created by huangjunsheng on 2019-09-16
3 | */
4 | import React, {PureComponent} from 'react';
5 | import {View} from 'react-native';
6 | import {connect} from 'react-redux';
7 | import NavBar from '../../component/NavBar';
8 | import globalStyles from '../../styles/globalStyles';
9 | import {fetchProjectTabs, updateArticleLoading} from '../../actions';
10 | import ArticleTabComponent from '../../component/ArticleTabComponent';
11 | import LoadingView from '../../component/LoadingView';
12 | import {i18n} from '../../utils/Utility';
13 |
14 | /**
15 | * 项目
16 | */
17 | class ProjectScreen extends PureComponent {
18 | componentDidMount() {
19 | updateArticleLoading(true);
20 | fetchProjectTabs();
21 | }
22 |
23 | render() {
24 | const {navigation, projectTabs, isShowLoading} = this.props;
25 | return (
26 |
27 | navigation.toggleDrawer()}
33 | onRightPress={() => navigation.navigate('Search')}
34 | />
35 |
39 |
40 |
41 | );
42 | }
43 | }
44 |
45 | const mapStateToProps = state => {
46 | return {
47 | projectTabs: state.project.projectTabs,
48 | isShowLoading: state.wxArticle.isShowLoading,
49 | language: state.user.language,
50 | };
51 | };
52 |
53 | export default connect(mapStateToProps)(ProjectScreen);
54 |
--------------------------------------------------------------------------------
/src/utils/language/zh-Hant.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Created by huangjunsheng on 2019-10-16
3 | */
4 | export default {
5 | 'please-login-first': '請先登錄',
6 | language: '多語言',
7 | 'search-for-more-dry-goods': '搜索更多乾貨',
8 | 'popular-search': '熱門搜索',
9 | 'please-enter-search-keywords': '請輸入搜索關鍵字',
10 | 'about-author': '關於作者',
11 | 'wanAndroid-client-based-on-Facebook-react-native':
12 | '基於Facebook React-Native的玩安卓用戶端',
13 | email: '郵箱',
14 | 'This project is for learning purposes only, not for commercial purposes':
15 | '本項目僅供學習使用,不得用作商業目的',
16 | 'points-details': '積分明細',
17 | 'points-rule': '積分規則',
18 | 'my-collection': '我的收藏',
19 | 'my-points': '我的積分',
20 | 'frequently-used-websites': '常用網站',
21 | 'theme-color': '主題',
22 | 'share-app': '分享',
23 | settings: '設定',
24 | logout: '登出',
25 | 'share-app-desc':
26 | '分享一個使用React Native開發的玩安卓應用,點擊下載:https://github.com/aijason/RN_WanAndroid/releases/download/v1.0.2/RN_WanAndroid-release.apk',
27 | tips: '提示',
28 | cancel: '取消',
29 | confirm: '確認',
30 | 'Are you sure to log out': '確認退出登录嗎',
31 | 'not-logged-in': '未登錄',
32 | 'set-up-themes': '設定主題',
33 | 'User name cannot be empty': '用戶名不能為空',
34 | 'Password cannot be empty': '密碼不能為空',
35 | 'Confirm password cannot be empty': '確認密碼不能為空',
36 | login: '登入',
37 | userName: '用戶名',
38 | password: '密碼',
39 | 'New users? To': '新用戶?去 ',
40 | register: '注册',
41 | confirmPassword: '確認密碼',
42 | 'Playing with life loading': '玩命加載中...',
43 | Navigation: '導航',
44 | wanAndroid: '玩安卓',
45 | project: '項目',
46 | system: '體系',
47 | publicAccount: '公眾號',
48 | Loading: '正在加載中...',
49 | 'Have been collected': '已收藏',
50 | Article: '文章',
51 | home: '首頁',
52 | 'all-loaded': '已加載全部',
53 | };
54 |
--------------------------------------------------------------------------------
/src/utils/language/zh-Hans.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Created by huangjunsheng on 2019-10-16
3 | */
4 | export default {
5 | 'please-login-first': '请先登录',
6 | language: '多语言',
7 | 'search-for-more-dry-goods': '搜索更多干货',
8 | 'popular-search': '热门搜索',
9 | 'please-enter-search-keywords': '请输入搜索关键词',
10 | 'about-author': '关于作者',
11 | 'wanAndroid-client-based-on-Facebook-react-native':
12 | '基于Facebook React-Native的玩安卓客户端',
13 | email: '邮箱',
14 | 'This project is for learning purposes only, not for commercial purposes':
15 | '本项目仅供学习使用,不得用作商业目的',
16 | 'points-details': '积分明细',
17 | 'points-rule': '积分规则',
18 | 'my-collection': '我的收藏',
19 | 'my-points': '我的积分',
20 | 'frequently-used-websites': '常用网站',
21 | 'theme-color': '主题',
22 | 'share-app': '分享',
23 | settings: '设置',
24 | logout: '退出登录',
25 | 'share-app-desc':
26 | '分享一个使用React Native开发的玩安卓应用, 点击下载:https://github.com/aijason/RN_WanAndroid/releases/download/v1.0.2/RN_WanAndroid-release.apk',
27 | tips: '提示',
28 | cancel: '取消',
29 | confirm: '确认',
30 | 'Are you sure to log out': '确认退出登录吗',
31 | 'not-logged-in': '未登录',
32 | 'set-up-themes': '设置主题',
33 | 'User name cannot be empty': '用户名不能为空',
34 | 'Password cannot be empty': '密码不能为空',
35 | 'Confirm password cannot be empty': '确认密码不能为空',
36 | login: '登录',
37 | userName: '用户名',
38 | password: '密码',
39 | 'New users? To': '新用户? 去 ',
40 | register: '注册',
41 | confirmPassword: '确认密码',
42 | 'Playing with life loading': '玩命加载中...',
43 | Navigation: '导航',
44 | wanAndroid: '玩安卓',
45 | project: '项目',
46 | system: '体系',
47 | publicAccount: '公众号',
48 | Loading: '正在加载中...',
49 | 'Have been collected': '已收藏',
50 | Article: '文章',
51 | home: '首页',
52 | 'all-loaded': '已加载全部',
53 | };
54 |
--------------------------------------------------------------------------------
/src/component/ListFooter.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Created by hjs on 2019-09-20
3 | */
4 | import React, {Component} from 'react';
5 | import {Text, View, StyleSheet, ActivityIndicator} from 'react-native';
6 | import PropTypes from 'prop-types';
7 | import {DEVICE_WIDTH, getRealDP as dp} from '../utils/screenUtil';
8 | import Color from '../utils/Color';
9 | import {i18n} from '../utils/Utility';
10 |
11 | const propTypes = {
12 | isRenderFooter: PropTypes.bool.isRequired,
13 | isFullData: PropTypes.bool.isRequired,
14 | indicatorColor: PropTypes.string,
15 | };
16 |
17 | const defaultProps = {
18 | isRenderFooter: false,
19 | isFullData: false,
20 | indicatorColor: Color.THEME,
21 | };
22 |
23 | class ListFooter extends Component {
24 | render() {
25 | if (!this.props.isRenderFooter) {
26 | return null;
27 | }
28 | if (this.props.isFullData) {
29 | return (
30 |
31 | {i18n('all-loaded')}
32 |
33 | );
34 | } else {
35 | return (
36 |
37 |
38 |
39 | {i18n('Playing with life loading')}
40 |
41 |
42 | );
43 | }
44 | }
45 | }
46 |
47 | const styles = StyleSheet.create({
48 | footer: {
49 | flexDirection: 'row',
50 | width: DEVICE_WIDTH,
51 | height: dp(80),
52 | alignItems: 'center',
53 | justifyContent: 'center',
54 | },
55 | });
56 |
57 | ListFooter.propTypes = propTypes;
58 | ListFooter.defaultProps = defaultProps;
59 |
60 | export default ListFooter;
61 |
--------------------------------------------------------------------------------
/src/reducers/user.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Created by hjs on 2019-09-20
3 | */
4 | import actionTypes from '../actions/actionType';
5 | import Color from '../utils/Color';
6 |
7 | const initialStore = {
8 | isLogin: false, // 是否已登录
9 | userInfo: {
10 | admin: false,
11 | chapterTops: [],
12 | collectIds: [],
13 | email: '',
14 | icon: '',
15 | id: '',
16 | nickname: '',
17 | password: '',
18 | publicName: '',
19 | token: '',
20 | type: '',
21 | username: '',
22 | }, // 用户信息
23 | themeColor: Color.THEME, // 用户设置APP主题色
24 | language: 'zhHans', // APP默认语言
25 | };
26 |
27 | const user = (state = initialStore, action) => {
28 | switch (action.type) {
29 | case actionTypes.FETCH_TO_REGISTER:
30 | return {
31 | ...state,
32 | isLogin: false,
33 | };
34 | case actionTypes.FETCH_TO_LOGIN:
35 | return {
36 | ...state,
37 | isLogin: true,
38 | userInfo: action.userInfo,
39 | };
40 | case actionTypes.FETCH_TO_LOGIN_FAILURE:
41 | return {
42 | ...state,
43 | isLogin: false,
44 | };
45 | case actionTypes.FETCH_TO_LOGOUT:
46 | return {
47 | ...state,
48 | isLogin: false,
49 | };
50 | case actionTypes.CHANGE_THEME_COLOR:
51 | return {
52 | ...state,
53 | themeColor: action.themeColor,
54 | };
55 | case actionTypes.INITIAL_AUTH_INFO:
56 | return {
57 | ...state,
58 | ...action.initialInfo,
59 | };
60 | case actionTypes.SWITCH_APP_LANGUAGE:
61 | return {
62 | ...state,
63 | language: action.language,
64 | };
65 | default:
66 | return state;
67 | }
68 | };
69 |
70 | export default user;
71 |
--------------------------------------------------------------------------------
/src/reducers/collect.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Created by hjs on 2019-09-27
3 | */
4 | import actionTypes from '../actions/actionType';
5 |
6 | const initialState = {
7 | page: 1, // 第一页page为0
8 | collectData: {},
9 | dataSource: [], // 文章列表数据源
10 | isRenderFooter: false, // 是否渲染列表footer
11 | isFullData: false, // 是否加载完全部数据
12 | };
13 |
14 | const collect = (state = initialState, action) => {
15 | switch (action.type) {
16 | case actionTypes.FETCH_COLLECT_ARTICLE:
17 | return {
18 | ...state,
19 | page: 1,
20 | collectData: action.collectData,
21 | dataSource: action.collectData.datas,
22 | isRenderFooter: !!action.collectData.total, // 只有total为0是不渲染footer
23 | isFullData: action.collectData.curPage === action.collectData.pageCount,
24 | };
25 | case actionTypes.FETCH_COLLECT_ARTICLE_MORE:
26 | return {
27 | ...state,
28 | page: ++state.page,
29 | dataSource: state.dataSource.concat(action.collectData.datas),
30 | isRenderFooter: true,
31 | isFullData: !action.collectData.datas.length,
32 | };
33 | case actionTypes.FETCH_MYCOLLECT_ADD_COLLECT:
34 | let addCollectDataSource = [...state.dataSource];
35 | addCollectDataSource[action.index].collect = true;
36 | return {
37 | ...state,
38 | dataSource: addCollectDataSource,
39 | };
40 | case actionTypes.FETCH_MYCOLLECT_CANCEL_COLLECT:
41 | let cancelCollectDataSource = [...state.dataSource];
42 | cancelCollectDataSource[action.index].collect = false;
43 | return {
44 | ...state,
45 | dataSource: cancelCollectDataSource,
46 | };
47 | default:
48 | return state;
49 | }
50 | };
51 |
52 | export default collect;
53 |
--------------------------------------------------------------------------------
/src/screen/article/ArticleTabScreen.js:
--------------------------------------------------------------------------------
1 | import React, {PureComponent} from 'react';
2 | import {View} from 'react-native';
3 | import globalStyles from '../../styles/globalStyles';
4 | import NavBar from '../../component/NavBar';
5 | import ArticleTabComponent from '../../component/ArticleTabComponent';
6 | import {updateArticleLoading} from '../../actions';
7 | import LoadingView from '../../component/LoadingView';
8 | import {connect} from 'react-redux';
9 |
10 | /**
11 | * ArticleTabScreen
12 | */
13 | class ArticleTabScreen extends PureComponent {
14 | constructor(props) {
15 | super(props);
16 | this.state = {
17 | articleTabs: [],
18 | };
19 | }
20 |
21 | componentDidMount() {
22 | const {navigation} = this.props;
23 | updateArticleLoading(true);
24 | const articleTabs = navigation.getParam('articleTabs', []);
25 | this.timer && clearTimeout(this.timer);
26 | this.timer = setTimeout(() => {
27 | this.setState({articleTabs});
28 | }, 100);
29 | }
30 |
31 | componentWillUnmount() {
32 | this.timer && clearTimeout(this.timer);
33 | }
34 |
35 | render() {
36 | const {navigation, isShowLoading} = this.props;
37 | const title = navigation.getParam('title', '');
38 | return (
39 |
40 |
41 |
45 |
46 |
47 | );
48 | }
49 | }
50 |
51 | const mapStateToProps = state => {
52 | return {
53 | isShowLoading: state.wxArticle.isShowLoading,
54 | };
55 | };
56 |
57 | export default connect(mapStateToProps)(ArticleTabScreen);
58 |
--------------------------------------------------------------------------------
/src/screen/tabs/WxArticleScreen.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Created by huangjunsheng on 2019-09-16
3 | */
4 | import React, {PureComponent} from 'react';
5 | import {View} from 'react-native';
6 | import {connect} from 'react-redux';
7 | import NavBar from '../../component/NavBar';
8 | import Color from '../../utils/Color';
9 | import globalStyles from '../../styles/globalStyles';
10 | import {fetchWxArticleTabs, updateArticleLoading} from '../../actions';
11 | import ArticleTabComponent from '../../component/ArticleTabComponent';
12 | import LoadingView from '../../component/LoadingView';
13 | import {i18n} from '../../utils/Utility';
14 |
15 | /**
16 | * 微信公众号
17 | */
18 | class WxArticleScreen extends PureComponent {
19 | constructor(props) {
20 | super(props);
21 | this.state = {};
22 | }
23 |
24 | componentDidMount() {
25 | updateArticleLoading(true);
26 | fetchWxArticleTabs();
27 | }
28 |
29 | render() {
30 | const {navigation, articleTabs, isShowLoading} = this.props;
31 | return (
32 |
33 | navigation.toggleDrawer()}
39 | onRightPress={() => navigation.navigate('Search')}
40 | />
41 |
46 |
47 |
48 | );
49 | }
50 | }
51 |
52 | const mapStateToProps = state => {
53 | return {
54 | articleTabs: state.wxArticle.articleTabs,
55 | isShowLoading: state.wxArticle.isShowLoading,
56 | language: state.user.language,
57 | };
58 | };
59 |
60 | export default connect(mapStateToProps)(WxArticleScreen);
61 |
--------------------------------------------------------------------------------
/App.js:
--------------------------------------------------------------------------------
1 | import React, {PureComponent} from 'react';
2 | import {StatusBar, View} from 'react-native';
3 | import {Provider} from 'react-redux';
4 | import SplashScreen from 'react-native-splash-screen';
5 | import {pickBy, identity} from 'lodash';
6 | import AppNavigator from './src';
7 | import {setAxios} from './src/service/setAxios';
8 | import store from './src/store';
9 | import AuthUtil from './src/utils/AuthUtil';
10 | import {switchAPPLanguage, toInitialAuthInfo} from './src/actions';
11 | import Toast from './src/utils/Toast';
12 |
13 | class App extends PureComponent {
14 | constructor(props) {
15 | super(props);
16 | this.state = {};
17 | this.initialInfo = this.initialInfo.bind(this);
18 | }
19 |
20 | static async getDerivedStateFromProps() {
21 | const language = await AuthUtil.getAppLanguage();
22 | if (language) {
23 | await switchAPPLanguage(language); // 设置app语言环境
24 | } else {
25 | await switchAPPLanguage('zhHans'); // 默认中文
26 | }
27 | }
28 |
29 | componentDidMount() {
30 | SplashScreen.hide();
31 | setAxios(); // 网络设置
32 | global.toast = this.refs.toast; // 全局引用赋值
33 | this.initialInfo();
34 | }
35 |
36 | async initialInfo() {
37 | const userInfo = await AuthUtil.getUserInfo();
38 | const themeColor = await AuthUtil.getThemeColor();
39 | const authInfo = pickBy(
40 | {
41 | isLogin: !!userInfo,
42 | userInfo,
43 | themeColor,
44 | },
45 | identity,
46 | );
47 | if (Object.keys(authInfo).length === 0) {
48 | return;
49 | }
50 | console.log('初始化缓存信息', authInfo);
51 | toInitialAuthInfo(authInfo);
52 | }
53 |
54 | render() {
55 | return (
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 | );
64 | }
65 | }
66 |
67 | export default App;
68 |
--------------------------------------------------------------------------------
/src/component/LoadingView.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Created by huangjunsheng on 2019-10-04
3 | */
4 |
5 | import React, {Component} from 'react';
6 | import {StyleSheet, Text, View, ActivityIndicator} from 'react-native';
7 | import PropTypes from 'prop-types';
8 | import {
9 | DEVICE_HEIGHT,
10 | DEVICE_WIDTH,
11 | getRealDP as dp,
12 | } from '../utils/screenUtil';
13 | import Color from '../utils/Color';
14 | import {i18n} from '../utils/Utility';
15 |
16 | const propTypes = {
17 | isShowLoading: PropTypes.bool.isRequired,
18 | };
19 |
20 | const defaultProps = {
21 | isShowLoading: false,
22 | };
23 |
24 | class LoadingView extends Component {
25 | render() {
26 | const {isShowLoading} = this.props;
27 | if (!isShowLoading) {
28 | return null;
29 | }
30 | return (
31 |
32 |
33 |
34 |
35 | {i18n('Loading')}
36 |
37 |
38 |
39 | );
40 | }
41 | }
42 |
43 | const styles = StyleSheet.create({
44 | loadingContainer: {
45 | position: 'absolute',
46 | left: 0,
47 | top: 0,
48 | backgroundColor: 'transparent',
49 | width: DEVICE_WIDTH,
50 | height: DEVICE_HEIGHT,
51 | justifyContent: 'center',
52 | alignItems: 'center',
53 | },
54 | loadingWrapper: {
55 | width: dp(200),
56 | height: dp(200),
57 | backgroundColor: 'rgba(0,0,0,0.05)',
58 | },
59 | loadingContent: {
60 | width: dp(200),
61 | height: dp(200),
62 | backgroundColor: 'rgba(0,0,0,0.6)',
63 | justifyContent: 'center',
64 | alignItems: 'center',
65 | borderRadius: 7,
66 | },
67 | loadingText: {
68 | marginLeft: dp(20),
69 | color: Color.WHITE,
70 | marginTop: dp(20),
71 | fontSize: dp(28),
72 | },
73 | });
74 |
75 | LoadingView.propTypes = propTypes;
76 | LoadingView.defaultProps = defaultProps;
77 |
78 | export default LoadingView;
79 |
--------------------------------------------------------------------------------
/src/reducers/search.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Created by hjs on 2019-09-27
3 | */
4 | import actionTypes from '../actions/actionType';
5 |
6 | const initialState = {
7 | page: 1, // 第一页page为0
8 | hotKey: [], // 搜索热词
9 | articleData: {},
10 | dataSource: [], // 文章列表数据源
11 | isRenderFooter: false, // 是否渲染列表footer
12 | isFullData: false, // 是否加载完全部数据
13 | };
14 |
15 | const search = (state = initialState, action) => {
16 | switch (action.type) {
17 | case actionTypes.FETCH_SEARCH_HOT_KEY:
18 | return {
19 | ...state,
20 | hotKey: action.hotKey,
21 | };
22 | case actionTypes.FETCH_SEARCH_ARTICLE:
23 | return {
24 | ...state,
25 | page: 1,
26 | articleData: action.articleData,
27 | dataSource: action.articleData.datas,
28 | isRenderFooter: !!action.articleData.total, // 只有total为0是不渲染footer
29 | isFullData: action.articleData.curPage === action.articleData.pageCount,
30 | };
31 | case actionTypes.FETCH_SEARCH_ARTICLE_MORE:
32 | return {
33 | ...state,
34 | page: ++state.page,
35 | dataSource: state.dataSource.concat(action.articleData.datas),
36 | isRenderFooter: true,
37 | isFullData: !action.articleData.datas.length,
38 | };
39 | case actionTypes.CLEAR_SEARCH_ARTICLE:
40 | return {
41 | ...state,
42 | page: 1,
43 | articleData: {},
44 | dataSource: [],
45 | isRenderFooter: false,
46 | isFullData: false,
47 | };
48 | case actionTypes.FETCH_SEARCH_ARTICLE_ADD_COLLECT:
49 | let addCollectDataSource = [...state.dataSource];
50 | addCollectDataSource[action.index].collect = true;
51 | return {
52 | ...state,
53 | dataSource: addCollectDataSource,
54 | };
55 | case actionTypes.FETCH_SEARCH_ARTICLE_CANCEL_COLLECT:
56 | let cancelCollectDataSource = [...state.dataSource];
57 | cancelCollectDataSource[action.index].collect = false;
58 | return {
59 | ...state,
60 | dataSource: cancelCollectDataSource,
61 | };
62 | default:
63 | return state;
64 | }
65 | };
66 |
67 | export default search;
68 |
--------------------------------------------------------------------------------
/src/reducers/home.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Created by hjs on 2019-09-17
3 | */
4 | import actionTypes from '../actions/actionType';
5 |
6 | const initialStore = {
7 | page: 1, // 第一页page为0
8 | dataSource: [], // 列表数据源
9 | homeList: {},
10 | homeBanner: [], // 首页轮播数据
11 | isRenderFooter: false, // 是否渲染列表footer
12 | isFullData: false, // 是否加载完全部数据
13 | websites: [], // 常用网站数据
14 | };
15 |
16 | const home = (state = initialStore, action) => {
17 | switch (action.type) {
18 | case actionTypes.FETCH_HOME_BANNER:
19 | return {
20 | ...state,
21 | homeBanner: action.homeBanner,
22 | };
23 | case actionTypes.FETCH_HOME_LIST:
24 | return {
25 | ...state,
26 | page: 1,
27 | homeList: action.homeList,
28 | dataSource: action.homeList.datas,
29 | isRenderFooter: !!action.homeList.total, // 只有total为0是不渲染footer
30 | isFullData: action.homeList.curPage === action.homeList.pageCount, // 数据最后一页显示"已加载全部"
31 | };
32 | case actionTypes.FETCH_HOME_LIST_FAILURE:
33 | return state;
34 | case actionTypes.FETCH_HOME_LIST_MORE:
35 | return {
36 | ...state,
37 | page: ++state.page,
38 | dataSource: state.dataSource.concat(action.homeList.datas),
39 | isRenderFooter: !!action.homeList.total, // 只有total为0是不渲染footer
40 | isFullData: action.homeList.datas.length === 0,
41 | };
42 | case actionTypes.FETCH_OFTEN_USED_WEBSITES:
43 | return {
44 | ...state,
45 | websites: action.websites,
46 | };
47 | case actionTypes.FETCH_HOME_ADD_COLLECT:
48 | let addCollectDataSource = [...state.dataSource];
49 | addCollectDataSource[action.index].collect = true;
50 | return {
51 | ...state,
52 | dataSource: addCollectDataSource,
53 | };
54 | case actionTypes.FETCH_HOME_CANCEL_COLLECT:
55 | let cancelCollectDataSource = [...state.dataSource];
56 | cancelCollectDataSource[action.index].collect = false;
57 | return {
58 | ...state,
59 | dataSource: cancelCollectDataSource,
60 | };
61 | default:
62 | return state;
63 | }
64 | };
65 |
66 | export default home;
67 |
--------------------------------------------------------------------------------
/src/utils/language/en.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Created by huangjunsheng on 2019-10-16
3 | */
4 | export default {
5 | 'please-login-first': 'Please login first',
6 | language: 'Language',
7 | 'search-for-more-dry-goods': 'Search for more dry goods',
8 | 'popular-search': 'Popular search',
9 | 'please-enter-search-keywords': 'Please enter search keywords',
10 | 'about-author': 'About author',
11 | 'wanAndroid-client-based-on-Facebook-react-native':
12 | 'wanAndroid client based on Facebook react native',
13 | email: 'Email',
14 | 'This project is for learning purposes only, not for commercial purposes':
15 | 'This project is for learning purposes only, not for commercial purposes',
16 | 'points-details': 'Points details',
17 | 'points-rule': 'Points rule',
18 | 'my-collection': 'My collection',
19 | 'my-points': 'My points',
20 | 'frequently-used-websites': 'Frequently used websites',
21 | 'theme-color': 'Theme',
22 | 'share-app': 'Share',
23 | settings: 'Settings',
24 | logout: 'Logout',
25 | 'share-app-desc':
26 | 'Share WanAndroid application developed with react native,Click to download:https://github.com/aijason/RN_WanAndroid/releases/download/v1.0.2/RN_WanAndroid-release.apk',
27 | tips: 'Tips',
28 | cancel: 'Cancel',
29 | confirm: 'Confirm',
30 | 'Are you sure to log out': 'Are you sure to log out',
31 | 'not-logged-in': 'Not logged in',
32 | 'set-up-themes': 'set up themes',
33 | 'User name cannot be empty': 'User name cannot be empty',
34 | 'Password cannot be empty': 'Password cannot be empty',
35 | 'Confirm password cannot be empty': 'Confirm password cannot be empty',
36 | login: 'Log In',
37 | userName: 'User name',
38 | password: 'Password',
39 | 'New users? To': 'New users? To ',
40 | register: 'register',
41 | confirmPassword: 'Confirm password',
42 | 'Playing with life loading': 'Playing with life loading...',
43 | Navigation: 'Navigation',
44 | wanAndroid: 'WanAndroid',
45 | project: 'Project',
46 | system: 'System',
47 | publicAccount: 'PublicAccount',
48 | Loading: 'Loading...',
49 | 'Have been collected': 'Have been collected',
50 | Article: 'Article',
51 | home: 'Home',
52 | 'all-loaded': 'All loaded',
53 | };
54 |
--------------------------------------------------------------------------------
/ios/RN_WanAndroidTests/RN_WanAndroidTests.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"
16 |
17 | @interface RN_WanAndroidTests : XCTestCase
18 |
19 | @end
20 |
21 | @implementation RN_WanAndroidTests
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 | #ifdef DEBUG
44 | RCTSetLogFunction(^(RCTLogLevel level, RCTLogSource source, NSString *fileName, NSNumber *lineNumber, NSString *message) {
45 | if (level >= RCTLogLevelError) {
46 | redboxError = message;
47 | }
48 | });
49 | #endif
50 |
51 | while ([date timeIntervalSinceNow] > 0 && !foundElement && !redboxError) {
52 | [[NSRunLoop mainRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.1]];
53 | [[NSRunLoop mainRunLoop] runMode:NSRunLoopCommonModes beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.1]];
54 |
55 | foundElement = [self findSubviewInView:vc.view matching:^BOOL(UIView *view) {
56 | if ([view.accessibilityLabel isEqualToString:TEXT_TO_LOOK_FOR]) {
57 | return YES;
58 | }
59 | return NO;
60 | }];
61 | }
62 |
63 | #ifdef DEBUG
64 | RCTSetLogFunction(RCTDefaultLogFunction);
65 | #endif
66 |
67 | XCTAssertNil(redboxError, @"RedBox error: %@", redboxError);
68 | XCTAssertTrue(foundElement, @"Couldn't find element with text '%@' in %d seconds", TEXT_TO_LOOK_FOR, TIMEOUT_SECONDS);
69 | }
70 |
71 |
72 | @end
73 |
--------------------------------------------------------------------------------
/.flowconfig:
--------------------------------------------------------------------------------
1 | [ignore]
2 | ; We fork some components by platform
3 | .*/*[.]android.js
4 |
5 | ; Ignore "BUCK" generated dirs
6 | /\.buckd/
7 |
8 | ; Ignore polyfills
9 | node_modules/react-native/Libraries/polyfills/.*
10 |
11 | ; These should not be required directly
12 | ; require from fbjs/lib instead: require('fbjs/lib/warning')
13 | node_modules/warning/.*
14 |
15 | ; Flow doesn't support platforms
16 | .*/Libraries/Utilities/LoadingView.js
17 |
18 | [untyped]
19 | .*/node_modules/@react-native-community/cli/.*/.*
20 |
21 | [include]
22 |
23 | [libs]
24 | node_modules/react-native/Libraries/react-native/react-native-interface.js
25 | node_modules/react-native/flow/
26 |
27 | [options]
28 | emoji=true
29 |
30 | esproposal.optional_chaining=enable
31 | esproposal.nullish_coalescing=enable
32 |
33 | module.file_ext=.js
34 | module.file_ext=.json
35 | module.file_ext=.ios.js
36 |
37 | munge_underscores=true
38 |
39 | module.name_mapper='^react-native$' -> '/node_modules/react-native/Libraries/react-native/react-native-implementation'
40 | module.name_mapper='^react-native/\(.*\)$' -> '/node_modules/react-native/\1'
41 | 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\)$' -> '/node_modules/react-native/Libraries/Image/RelativeImageStub'
42 |
43 | suppress_type=$FlowIssue
44 | suppress_type=$FlowFixMe
45 | suppress_type=$FlowFixMeProps
46 | suppress_type=$FlowFixMeState
47 |
48 | suppress_comment=\\(.\\|\n\\)*\\$FlowFixMe\\($\\|[^(]\\|(\\(\\)? *\\(site=[a-z,_]*react_native\\(_ios\\)?_\\(oss\\|fb\\)[a-z,_]*\\)?)\\)
49 | suppress_comment=\\(.\\|\n\\)*\\$FlowIssue\\((\\(\\)? *\\(site=[a-z,_]*react_native\\(_ios\\)?_\\(oss\\|fb\\)[a-z,_]*\\)?)\\)?:? #[0-9]+
50 | suppress_comment=\\(.\\|\n\\)*\\$FlowExpectedError
51 |
52 | [lints]
53 | sketchy-null-number=warn
54 | sketchy-null-mixed=warn
55 | sketchy-number=warn
56 | untyped-type-import=warn
57 | nonstrict-import=warn
58 | deprecated-type=warn
59 | unsafe-getters-setters=warn
60 | inexact-spread=warn
61 | unnecessary-invariant=warn
62 | signature-verification-failure=warn
63 | deprecated-utility=error
64 |
65 | [strict]
66 | deprecated-type
67 | nonstrict-import
68 | sketchy-null
69 | unclear-type
70 | unsafe-getters-setters
71 | untyped-import
72 | untyped-type-import
73 |
74 | [version]
75 | ^0.105.0
76 |
--------------------------------------------------------------------------------
/ios/RN_WanAndroid/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleDisplayName
8 | RN_WanAndroid
9 | CFBundleExecutable
10 | $(EXECUTABLE_NAME)
11 | CFBundleIdentifier
12 | $(PRODUCT_BUNDLE_IDENTIFIER)
13 | CFBundleInfoDictionaryVersion
14 | 6.0
15 | CFBundleName
16 | $(PRODUCT_NAME)
17 | CFBundlePackageType
18 | APPL
19 | CFBundleShortVersionString
20 | 1.0
21 | CFBundleSignature
22 | ????
23 | CFBundleVersion
24 | 1
25 | LSRequiresIPhoneOS
26 |
27 | 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 | Fontisto.ttf
53 | Foundation.ttf
54 | Ionicons.ttf
55 | MaterialCommunityIcons.ttf
56 | MaterialIcons.ttf
57 | Octicons.ttf
58 | SimpleLineIcons.ttf
59 | Zocial.ttf
60 |
61 | UIRequiredDeviceCapabilities
62 |
63 | armv7
64 |
65 | UISupportedInterfaceOrientations
66 |
67 | UIInterfaceOrientationPortrait
68 | UIInterfaceOrientationLandscapeLeft
69 | UIInterfaceOrientationLandscapeRight
70 |
71 | UIViewControllerBasedStatusBarAppearance
72 |
73 |
74 |
75 |
--------------------------------------------------------------------------------
/src/screen/drawer/LanguageScreen.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Created by huangjunsheng on 2019-10-16
3 | */
4 | import React, {PureComponent} from 'react';
5 | import {StyleSheet, Text, View} from 'react-native';
6 | import Color from '../../utils/Color';
7 | import NavBar from '../../component/NavBar';
8 | import globalStyles from '../../styles/globalStyles';
9 | import {getRealDP as dp} from '../../utils/screenUtil';
10 | import {connect} from 'react-redux';
11 | import Icon from 'react-native-vector-icons/Ionicons';
12 | import Touchable from '../../component/Touchable';
13 | import {switchAPPLanguage} from '../../actions';
14 | import {getLanguageList, i18n} from '../../utils/Utility';
15 |
16 | /**
17 | * 多语言设置
18 | */
19 | class LanguageScreen extends PureComponent {
20 | constructor(props) {
21 | super(props);
22 | this.state = {};
23 | this.renderLanguageItem = this.renderLanguageItem.bind(this);
24 | }
25 |
26 | renderLanguageItem(item) {
27 | const {language} = this.props;
28 | return (
29 | switchAPPLanguage(item.languageCode)}>
34 |
35 | {item.language}
36 | {language === item.languageCode ? (
37 |
38 | ) : null}
39 |
40 |
41 | );
42 | }
43 |
44 | render() {
45 | const {navigation} = this.props;
46 | return (
47 |
48 |
49 | {getLanguageList().map(el => this.renderLanguageItem(el))}
50 |
51 | );
52 | }
53 | }
54 |
55 | const styles = StyleSheet.create({
56 | itemWrapper: {
57 | height: dp(120),
58 | flexDirection: 'row',
59 | justifyContent: 'space-between',
60 | alignItems: 'center',
61 | paddingHorizontal: dp(28),
62 | backgroundColor: Color.WHITE,
63 | borderBottomWidth: dp(1),
64 | borderBottomColor: Color.SPLIT_LINE,
65 | },
66 | languageText: {
67 | fontSize: dp(30),
68 | color: Color.TEXT_MAIN,
69 | },
70 | });
71 |
72 | const mapStateToProps = state => {
73 | return {
74 | language: state.user.language,
75 | };
76 | };
77 |
78 | export default connect(mapStateToProps)(LanguageScreen);
79 |
--------------------------------------------------------------------------------
/src/actions/actionType.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Created by hjs on 2019-09-17
3 | */
4 |
5 | const actionTypes = {
6 | // HOME
7 | FETCH_HOME_BANNER: 'FETCH_HOME_BANNER', // 请求首页轮播
8 | FETCH_HOME_LIST: 'FETCH_HOME_LIST', // 请求首页列表
9 | FETCH_HOME_LIST_FAILURE: 'FETCH_HOME_LIST_FAILURE', // 请求首页列表失败
10 | FETCH_HOME_LIST_MORE: 'FETCH_HOME_LIST_MORE', // 请求首页列表上拉加载更多
11 | FETCH_OFTEN_USED_WEBSITES: 'FETCH_OFTEN_USED_WEBSITES', // 请求常用网站数据
12 | FETCH_HOME_ADD_COLLECT: 'FETCH_HOME_ADD_COLLECT', // 请求收藏首页文章
13 | FETCH_HOME_CANCEL_COLLECT: 'FETCH_HOME_CANCEL_COLLECT', // 取消收藏首页文章
14 | // USER
15 | FETCH_TO_REGISTER: 'FETCH_TO_REGISTER', // 请求注册
16 | FETCH_TO_REGISTER_FAILURE: 'FETCH_TO_REGISTER_FAILURE', // 请求注册失败
17 | FETCH_TO_LOGIN: 'FETCH_TO_LOGIN', // 请求登录
18 | FETCH_TO_LOGIN_FAILURE: 'FETCH_TO_LOGIN_FAILURE', // 请求登录失败
19 | FETCH_TO_LOGOUT: 'FETCH_TO_LOGOUT', // 请求登出
20 | CHANGE_THEME_COLOR: 'CHANGE_THEME_COLOR', // 改变应用主题色
21 | INITIAL_AUTH_INFO: 'INITIAL_AUTH_INFO', // 初始化个人设置信息
22 | SWITCH_APP_LANGUAGE: 'SWITCH_APP_LANGUAGE', // 切换应用语言
23 | // SYSTEM
24 | FETCH_TO_SYSTEM_DATA: 'FETCH_TO_SYSTEM_DATA', // 请求体系数据
25 | // WX_ARTICLE
26 | FETCH_WX_ARTICLE_TABS: 'FETCH_WX_ARTICLE_TABS', // 请求公众号Tab列表
27 | FETCH_WX_ARTICLE_LIST: 'FETCH_WX_ARTICLE_LIST', // 请求某个公众号下的列表
28 | FETCH_ARTICLE_LOADING: 'FETCH_ARTICLE_LOADING', // Article loading
29 | // GUIDE
30 | FETCH_GUIDE_DATA: 'FETCH_GUIDE_DATA', // 请求导航数据
31 | UPDATE_SELECT_INDEX: 'UPDATE_SELECT_INDEX', // 更新选中索引值
32 | // PROJECT
33 | FETCH_PROJECT_TABS: 'FETCH_PROJECT_TABS', // 获取项目分类
34 | // SEARCH
35 | FETCH_SEARCH_HOT_KEY: 'FETCH_SEARCH_HOT_KEY', // 获取搜索热词
36 | FETCH_SEARCH_ARTICLE: 'FETCH_SEARCH_ARTICLE', // 根据关键词搜索文章
37 | FETCH_SEARCH_ARTICLE_MORE: 'FETCH_SEARCH_ARTICLE_MORE', // 根据关键词搜索文章列表上拉加载更多
38 | CLEAR_SEARCH_ARTICLE: 'CLEAR_SEARCH_ARTICLE', // 清空上一次加载记录
39 | FETCH_SEARCH_ARTICLE_ADD_COLLECT: 'FETCH_SEARCH_ARTICLE_ADD_COLLECT', // 搜索页收藏
40 | FETCH_SEARCH_ARTICLE_CANCEL_COLLECT: 'FETCH_SEARCH_ARTICLE_CANCEL_COLLECT', // 搜索页取消收藏
41 | // COLLECT
42 | FETCH_COLLECT_ARTICLE: 'FETCH_COLLECT_ARTICLE', // 请求收藏文章列表
43 | FETCH_COLLECT_ARTICLE_MORE: 'FETCH_COLLECT_ARTICLE_MORE', // 请求收藏文章列表上拉加载更多
44 | FETCH_MYCOLLECT_CANCEL_COLLECT: 'FETCH_MYCOLLECT_CANCEL_COLLECT', // 我的收藏页面取消收藏
45 | FETCH_MYCOLLECT_ADD_COLLECT: 'FETCH_MYCOLLECT_ADD_COLLECT', // 我的收藏页面收藏
46 | // COIN
47 | FETCH_MY_COIN_LIST: 'FETCH_MY_COIN_LIST', // 请求我的积分明细列表
48 | FETCH_MY_COIN_LIST_MORE: 'FETCH_MY_COIN_LIST_MORE', // 请求我的积分明细列表上拉加载更多
49 | FETCH_MY_COIN_INFO: 'FETCH_MY_COIN_INFO', // 获取个人积分,需要登录后访问
50 | };
51 |
52 | export default actionTypes;
53 |
--------------------------------------------------------------------------------
/android/app/src/main/java/com/rn_wanandroid/MainApplication.java:
--------------------------------------------------------------------------------
1 | package com.rn_wanandroid;
2 |
3 | import android.app.Application;
4 | import android.content.Context;
5 | import com.facebook.react.PackageList;
6 | import com.facebook.react.ReactApplication;
7 | import com.facebook.react.ReactNativeHost;
8 | import com.facebook.react.ReactPackage;
9 | import com.facebook.soloader.SoLoader;
10 | import java.lang.reflect.InvocationTargetException;
11 | import java.util.List;
12 |
13 | public class MainApplication extends Application implements ReactApplication {
14 |
15 | private final ReactNativeHost mReactNativeHost =
16 | new ReactNativeHost(this) {
17 | @Override
18 | public boolean getUseDeveloperSupport() {
19 | return BuildConfig.DEBUG;
20 | }
21 |
22 | @Override
23 | protected List getPackages() {
24 | @SuppressWarnings("UnnecessaryLocalVariable")
25 | List packages = new PackageList(this).getPackages();
26 | // Packages that cannot be autolinked yet can be added manually here, for example:
27 | // packages.add(new MyReactNativePackage());
28 | return packages;
29 | }
30 |
31 | @Override
32 | protected String getJSMainModuleName() {
33 | return "index";
34 | }
35 | };
36 |
37 | @Override
38 | public ReactNativeHost getReactNativeHost() {
39 | return mReactNativeHost;
40 | }
41 |
42 | @Override
43 | public void onCreate() {
44 | super.onCreate();
45 | SoLoader.init(this, /* native exopackage */ false);
46 | initializeFlipper(this); // Remove this line if you don't want Flipper enabled
47 | }
48 |
49 | /**
50 | * Loads Flipper in React Native templates.
51 | *
52 | * @param context
53 | */
54 | private static void initializeFlipper(Context context) {
55 | if (BuildConfig.DEBUG) {
56 | try {
57 | /*
58 | We use reflection here to pick up the class that initializes Flipper,
59 | since Flipper library is not available in release mode
60 | */
61 | Class> aClass = Class.forName("com.facebook.flipper.ReactNativeFlipper");
62 | aClass.getMethod("initializeFlipper", Context.class).invoke(null, context);
63 | } catch (ClassNotFoundException e) {
64 | e.printStackTrace();
65 | } catch (NoSuchMethodException e) {
66 | e.printStackTrace();
67 | } catch (IllegalAccessException e) {
68 | e.printStackTrace();
69 | } catch (InvocationTargetException e) {
70 | e.printStackTrace();
71 | }
72 | }
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/src/component/ArticleTabComponent.js:
--------------------------------------------------------------------------------
1 | import React, {PureComponent} from 'react';
2 | import {createAppContainer} from 'react-navigation';
3 | import PropTypes from 'prop-types';
4 | import {createMaterialTopTabNavigator} from 'react-navigation-tabs';
5 | import Color from '../utils/Color';
6 | import {getRealDP as dp} from '../utils/screenUtil';
7 | import ArticleFlatList from './ArticleFlatList';
8 | import {connect} from 'react-redux';
9 |
10 | const propTypes = {
11 | articleTabs: PropTypes.array.isRequired,
12 | navigation: PropTypes.object.isRequired,
13 | isWxArticle: PropTypes.bool,
14 | };
15 | const defaultProps = {
16 | articleTabs: [],
17 | isWxArticle: false,
18 | };
19 |
20 | class ArticleTabComponent extends PureComponent {
21 | constructor(props) {
22 | super(props);
23 | this.state = {};
24 | }
25 | createTabs() {
26 | const {articleTabs, navigation, isWxArticle} = this.props;
27 | let routeConfigMap = {};
28 | articleTabs.map(
29 | el =>
30 | (routeConfigMap[el.name] = {
31 | screen: () => (
32 |
37 | ),
38 | }),
39 | );
40 | return routeConfigMap;
41 | }
42 |
43 | render() {
44 | const {articleTabs, themeColor} = this.props;
45 | if (!articleTabs.length) {
46 | return null;
47 | }
48 | const TabComponent = createAppContainer(
49 | createMaterialTopTabNavigator(this.createTabs(), {
50 | lazy: true,
51 | swipeEnabled: true,
52 | animationEnabled: true,
53 | backBehavior: 'none',
54 | tabBarOptions: {
55 | activeTintColor: Color.WHITE,
56 | inactiveTintColor: Color.TEXT_TAB_INACTIVE,
57 | scrollEnabled: true,
58 | tabStyle: {
59 | height: dp(70),
60 | width: dp(270),
61 | },
62 | labelStyle: {
63 | fontSize: dp(28),
64 | paddingBottom: dp(25),
65 | fontWeight: 'bold',
66 | },
67 | indicatorStyle: {
68 | height: dp(4),
69 | backgroundColor: Color.WHITE,
70 | width: dp(100),
71 | marginLeft: dp(85),
72 | }, // 下划线样式
73 | style: {
74 | backgroundColor: themeColor,
75 | height: dp(80),
76 | },
77 | },
78 | }),
79 | );
80 | return ;
81 | }
82 | }
83 |
84 | ArticleTabComponent.propTypes = propTypes;
85 | ArticleTabComponent.defaultProps = defaultProps;
86 |
87 | const mapStateToProps = state => {
88 | return {
89 | themeColor: state.user.themeColor,
90 | };
91 | };
92 |
93 | export default connect(mapStateToProps)(ArticleTabComponent);
94 |
--------------------------------------------------------------------------------
/src/api/index.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Created by hjs on 2019-09-17
3 | */
4 | import httpUtil from '../utils/httpUtil';
5 |
6 | // 首页轮播
7 | export async function getHomeBanner() {
8 | return await httpUtil.get('banner/json');
9 | }
10 |
11 | // 首页文章列表
12 | export function getHomeList(page = 0) {
13 | return httpUtil.get(`article/list/${page}/json`);
14 | }
15 |
16 | // 首页文章收藏 id:文章id
17 | export function addCollectArticle(id) {
18 | return httpUtil.post(`lg/collect/${id}/json`, {id});
19 | }
20 |
21 | // 首页文章取消收藏 id:文章id
22 | export function cancelCollectArticle(id) {
23 | return httpUtil.post(`lg/uncollect_originId/${id}/json`, {id});
24 | }
25 |
26 | /**
27 | * 我的收藏页-取消收藏
28 | * @param id:拼接在链接上
29 | * @param originId:列表页下发,无则为-1
30 | */
31 | export function cancelMyCollectArticle(id, originId = -1) {
32 | return httpUtil.post(`lg/uncollect/${id}/json`, {id, originId});
33 | }
34 |
35 | /**
36 | * 注册
37 | * @params username,password,repassword
38 | */
39 | export function goToRegister(params) {
40 | return httpUtil.post('user/register', params);
41 | }
42 |
43 | /**
44 | * 登录
45 | * @params username,password
46 | */
47 | export function goToLogin(params) {
48 | return httpUtil.post('user/login', params);
49 | }
50 |
51 | // 退出登录
52 | export function goToLogout() {
53 | return httpUtil.get('user/logout/json');
54 | }
55 |
56 | // 体系
57 | export function getSystemData() {
58 | return httpUtil.get('tree/json');
59 | }
60 |
61 | // 获取公众号Tabs列表
62 | export function getWxArticleTabs() {
63 | return httpUtil.get('wxarticle/chapters/json');
64 | }
65 |
66 | /**
67 | * 查看某个公众号历史数据
68 | * @param id 公众号ID
69 | * @param page 公众号页码
70 | */
71 | export function getWxArticleList(id, page) {
72 | return httpUtil.get(`wxarticle/list/${id}/${page}/json`);
73 | }
74 |
75 | // 获取导航数据
76 | export function getGuideData() {
77 | return httpUtil.get('navi/json');
78 | }
79 |
80 | // 获取导航数据
81 | export function getProjectTree() {
82 | return httpUtil.get('project/tree/json');
83 | }
84 |
85 | // 常用网站
86 | export function getOftenUsedWebsites() {
87 | return httpUtil.get('friend/json');
88 | }
89 |
90 | // 搜索热词
91 | export function getSearchHotKey() {
92 | return httpUtil.get('hotkey/json');
93 | }
94 |
95 | /**
96 | * 根据关键词搜索文章
97 | * @param k 关键词
98 | * @param page 文章页码
99 | */
100 | export function getSearchArticle(k, page = 0) {
101 | return httpUtil.post(`article/query/${page}/json`, {k});
102 | }
103 |
104 | // 我收藏的文章列表
105 | export function getCollectArticleList(page = 0) {
106 | return httpUtil.get(`lg/collect/list/${page}/json`);
107 | }
108 |
109 | // 获取个人积分获取列表,需要登录后访问
110 | export function getMyCoinList(page = 1) {
111 | return httpUtil.get(`lg/coin/list/${page}/json`);
112 | }
113 |
114 | export function getMyCoinInfo() {
115 | return httpUtil.get('lg/coin/userinfo/json');
116 | }
117 |
--------------------------------------------------------------------------------
/ios/Podfile:
--------------------------------------------------------------------------------
1 | platform :ios, '9.0'
2 | require_relative '../node_modules/@react-native-community/cli-platform-ios/native_modules'
3 |
4 | target 'RN_WanAndroid' do
5 | # Pods for RN_WanAndroid
6 | pod 'FBLazyVector', :path => "../node_modules/react-native/Libraries/FBLazyVector"
7 | pod 'FBReactNativeSpec', :path => "../node_modules/react-native/Libraries/FBReactNativeSpec"
8 | pod 'RCTRequired', :path => "../node_modules/react-native/Libraries/RCTRequired"
9 | pod 'RCTTypeSafety', :path => "../node_modules/react-native/Libraries/TypeSafety"
10 | pod 'React', :path => '../node_modules/react-native/'
11 | pod 'React-Core', :path => '../node_modules/react-native/'
12 | pod 'React-CoreModules', :path => '../node_modules/react-native/React/CoreModules'
13 | pod 'React-Core/DevSupport', :path => '../node_modules/react-native/'
14 | pod 'React-RCTActionSheet', :path => '../node_modules/react-native/Libraries/ActionSheetIOS'
15 | pod 'React-RCTAnimation', :path => '../node_modules/react-native/Libraries/NativeAnimation'
16 | pod 'React-RCTBlob', :path => '../node_modules/react-native/Libraries/Blob'
17 | pod 'React-RCTImage', :path => '../node_modules/react-native/Libraries/Image'
18 | pod 'React-RCTLinking', :path => '../node_modules/react-native/Libraries/LinkingIOS'
19 | pod 'React-RCTNetwork', :path => '../node_modules/react-native/Libraries/Network'
20 | pod 'React-RCTSettings', :path => '../node_modules/react-native/Libraries/Settings'
21 | pod 'React-RCTText', :path => '../node_modules/react-native/Libraries/Text'
22 | pod 'React-RCTVibration', :path => '../node_modules/react-native/Libraries/Vibration'
23 | pod 'React-Core/RCTWebSocket', :path => '../node_modules/react-native/'
24 |
25 | pod 'React-cxxreact', :path => '../node_modules/react-native/ReactCommon/cxxreact'
26 | pod 'React-jsi', :path => '../node_modules/react-native/ReactCommon/jsi'
27 | pod 'React-jsiexecutor', :path => '../node_modules/react-native/ReactCommon/jsiexecutor'
28 | pod 'React-jsinspector', :path => '../node_modules/react-native/ReactCommon/jsinspector'
29 | pod 'ReactCommon/jscallinvoker', :path => "../node_modules/react-native/ReactCommon"
30 | pod 'ReactCommon/turbomodule/core', :path => "../node_modules/react-native/ReactCommon"
31 | pod 'Yoga', :path => '../node_modules/react-native/ReactCommon/yoga'
32 |
33 | pod 'DoubleConversion', :podspec => '../node_modules/react-native/third-party-podspecs/DoubleConversion.podspec'
34 | pod 'glog', :podspec => '../node_modules/react-native/third-party-podspecs/glog.podspec'
35 | pod 'Folly', :podspec => '../node_modules/react-native/third-party-podspecs/Folly.podspec'
36 |
37 | pod 'RNVectorIcons', :path => '../node_modules/react-native-vector-icons'
38 |
39 | target 'RN_WanAndroidTests' do
40 | inherit! :search_paths
41 | # Pods for testing
42 | end
43 |
44 | use_native_modules!
45 | end
46 |
47 | target 'RN_WanAndroid-tvOS' do
48 | # Pods for RN_WanAndroid-tvOS
49 |
50 | target 'RN_WanAndroid-tvOSTests' do
51 | inherit! :search_paths
52 | # Pods for testing
53 | end
54 |
55 | end
56 |
--------------------------------------------------------------------------------
/ios/RN_WanAndroid/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" : "splash_bg.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 | "orientation" : "portrait",
70 | "idiom" : "iphone",
71 | "extent" : "full-screen",
72 | "minimum-system-version" : "8.0",
73 | "subtype" : "667h",
74 | "scale" : "2x"
75 | },
76 | {
77 | "orientation" : "portrait",
78 | "idiom" : "iphone",
79 | "extent" : "full-screen",
80 | "minimum-system-version" : "7.0",
81 | "scale" : "2x"
82 | },
83 | {
84 | "orientation" : "portrait",
85 | "idiom" : "iphone",
86 | "extent" : "full-screen",
87 | "minimum-system-version" : "7.0",
88 | "subtype" : "retina4",
89 | "scale" : "2x"
90 | },
91 | {
92 | "orientation" : "portrait",
93 | "idiom" : "iphone",
94 | "extent" : "full-screen",
95 | "scale" : "1x"
96 | },
97 | {
98 | "orientation" : "portrait",
99 | "idiom" : "iphone",
100 | "extent" : "full-screen",
101 | "scale" : "2x"
102 | },
103 | {
104 | "orientation" : "portrait",
105 | "idiom" : "iphone",
106 | "extent" : "full-screen",
107 | "subtype" : "retina4",
108 | "scale" : "2x"
109 | }
110 | ],
111 | "info" : {
112 | "version" : 1,
113 | "author" : "xcode"
114 | }
115 | }
--------------------------------------------------------------------------------
/src/component/BottomTabBar.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Created by huangjunsheng on 2019-10-02
3 | */
4 | import React, {PureComponent} from 'react';
5 | import {
6 | StyleSheet,
7 | Text,
8 | TouchableOpacity,
9 | View,
10 | TouchableNativeFeedback,
11 | } from 'react-native';
12 | import {connect} from 'react-redux';
13 | import Icon from 'react-native-vector-icons/Ionicons';
14 | import {DEVICE_WIDTH, getRealDP as dp, isAndroid} from '../utils/screenUtil';
15 | import Color from '../utils/Color';
16 | import {i18n} from '../utils/Utility';
17 |
18 | class BottomTabBar extends PureComponent {
19 | render() {
20 | const {route, focused, themeColor} = this.props;
21 | const {routeName} = route;
22 | let tabBarLabel, tabBarIconName, tabBarIconSize;
23 | switch (routeName) {
24 | case 'Home':
25 | tabBarLabel = i18n('home');
26 | tabBarIconName = 'ios-home';
27 | tabBarIconSize = dp(50);
28 | break;
29 | case 'System':
30 | tabBarLabel = i18n('system');
31 | tabBarIconName = 'ios-school';
32 | tabBarIconSize = dp(55);
33 | break;
34 | case 'WxArticle':
35 | tabBarLabel = i18n('publicAccount');
36 | tabBarIconName = 'ios-people';
37 | tabBarIconSize = dp(64);
38 | break;
39 | case 'Guide':
40 | tabBarLabel = i18n('Navigation');
41 | tabBarIconName = 'ios-rocket';
42 | tabBarIconSize = dp(50);
43 | break;
44 | default:
45 | tabBarLabel = i18n('project');
46 | tabBarIconName = 'ios-paper';
47 | tabBarIconSize = dp(50);
48 | break;
49 | }
50 | const tabBarColor = focused ? themeColor : Color.TEXT_LIGHT;
51 | const content = (
52 |
53 |
54 |
59 |
60 |
61 | {tabBarLabel}
62 |
63 |
64 | );
65 | if (isAndroid) {
66 | return (
67 |
73 | {content}
74 |
75 | );
76 | }
77 | return (
78 |
79 | {content}
80 |
81 | );
82 | }
83 | }
84 |
85 | const styles = StyleSheet.create({
86 | tabBarWrapper: {
87 | width: DEVICE_WIDTH / 5,
88 | height: dp(100),
89 | justifyContent: 'center',
90 | alignItems: 'center',
91 | },
92 | tabBarLabel: {
93 | fontSize: dp(20),
94 | },
95 | iconWrapper: {
96 | width: dp(65),
97 | height: dp(65),
98 | justifyContent: 'center',
99 | alignItems: 'center',
100 | },
101 | });
102 |
103 | const mapStateToProps = state => {
104 | return {
105 | themeColor: state.user.themeColor,
106 | language: state.user.language,
107 | };
108 | };
109 |
110 | export default connect(mapStateToProps)(BottomTabBar);
111 |
--------------------------------------------------------------------------------
/src/utils/Utility.js:
--------------------------------------------------------------------------------
1 | import {Platform, ToastAndroid} from 'react-native';
2 | import {getRealDP as dp, getStatusBarHeight, isAndroid} from './screenUtil';
3 | import Color from './Color';
4 | import LanguageUtil from './LanguageUtil';
5 |
6 | /**
7 | * 吐司方法
8 | * @param info 吐司文案信息
9 | * @return
10 | */
11 | export function showToast(info) {
12 | if (isAndroid) {
13 | return ToastAndroid.show(info, ToastAndroid.SHORT);
14 | }
15 | if (!global.toast) {
16 | return;
17 | }
18 | global.toast.show(info);
19 | }
20 |
21 | /**
22 | * 获取默认"react-navigation"导航样式
23 | * @param navigation
24 | * @param backgroundColor
25 | * @return 导航样式
26 | */
27 | export function getNavBarStyles(navigation, backgroundColor = Color.THEME) {
28 | return {
29 | title: navigation.getParam('title', ''),
30 | headerStyle: Platform.select({
31 | ios: {
32 | backgroundColor: backgroundColor,
33 | height: dp(110),
34 | },
35 | android: {
36 | backgroundColor: backgroundColor,
37 | height: dp(110) + getStatusBarHeight(),
38 | paddingTop: getStatusBarHeight(),
39 | },
40 | }),
41 | };
42 | }
43 |
44 | /**
45 | * 获取首页专题背景色
46 | * @param index 专题id
47 | * @return 背景色
48 | */
49 | export function getChapterBgColor(index) {
50 | const indexStr = index.toString();
51 | const id = indexStr.substr(indexStr.length - 1);
52 | const colors = [
53 | '#79CDCD',
54 | '#71C671',
55 | '#4169E1',
56 | '#3CB371',
57 | '#EE82EE',
58 | '#F4A460',
59 | '#FF7256',
60 | '#5F9EA0',
61 | '#79CDCD',
62 | '#BC8F8F',
63 | ];
64 | return colors[id];
65 | }
66 |
67 | /**
68 | * APP主题色数据
69 | */
70 | export function getThemeColorDataSource() {
71 | return [
72 | {name: '默认主题色', color: '#2196F3'},
73 | {name: '皇家蓝', color: '#4169E1'},
74 | {name: '军校蓝', color: '#5F9EA0'},
75 | {name: '深卡其布', color: '#BDB76B'},
76 | {name: '玫瑰棕色', color: '#BC8F8F'},
77 | {name: '印度红', color: '#CD5C5C'},
78 | {name: '深石板灰', color: '#2F4F4F'},
79 | {name: '海洋绿', color: '#2E8B57'},
80 | {name: '暗淡的灰色', color: '#696969'},
81 | {name: '橙色', color: '#FFA500'},
82 | {name: '粉红色', color: '#FFC0CB'},
83 | {name: '深粉色', color: '#FF1493'},
84 | {name: '兰花的紫色', color: '#DA70D6'},
85 | {name: '适中的紫色', color: '#9370DB'},
86 | {name: '紫色', color: '#800080'},
87 | {name: '纯黑', color: '#000000'},
88 | ];
89 | }
90 |
91 | export function i18n(Text) {
92 | return LanguageUtil.t(Text);
93 | }
94 |
95 | /**
96 | * 抽屉列表数据
97 | */
98 | export function getDrawerData() {
99 | return [
100 | {iconName: 'md-trending-up', title: i18n('my-points')},
101 | {iconName: 'md-heart', title: i18n('my-collection')},
102 | {iconName: 'md-globe', title: i18n('frequently-used-websites')},
103 | {iconName: 'md-person', title: i18n('about-author')},
104 | {iconName: 'md-share', title: i18n('share-app')},
105 | {iconName: 'md-settings', title: i18n('settings')},
106 | {iconName: 'md-power', title: i18n('logout')},
107 | ];
108 | }
109 |
110 | export function getLanguageList() {
111 | return [
112 | {language: '简体中文', languageCode: 'zhHans'},
113 | {language: '繁体中文', languageCode: 'zhHant'},
114 | {language: 'English', languageCode: 'en'},
115 | ];
116 | }
117 |
--------------------------------------------------------------------------------
/android/gradlew.bat:
--------------------------------------------------------------------------------
1 | @rem
2 | @rem Copyright 2015 the original author or authors.
3 | @rem
4 | @rem Licensed under the Apache License, Version 2.0 (the "License");
5 | @rem you may not use this file except in compliance with the License.
6 | @rem You may obtain a copy of the License at
7 | @rem
8 | @rem http://www.apache.org/licenses/LICENSE-2.0
9 | @rem
10 | @rem Unless required by applicable law or agreed to in writing, software
11 | @rem distributed under the License is distributed on an "AS IS" BASIS,
12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | @rem See the License for the specific language governing permissions and
14 | @rem limitations under the License.
15 | @rem
16 |
17 | @if "%DEBUG%" == "" @echo off
18 | @rem ##########################################################################
19 | @rem
20 | @rem Gradle startup script for Windows
21 | @rem
22 | @rem ##########################################################################
23 |
24 | @rem Set local scope for the variables with windows NT shell
25 | if "%OS%"=="Windows_NT" setlocal
26 |
27 | set DIRNAME=%~dp0
28 | if "%DIRNAME%" == "" set DIRNAME=.
29 | set APP_BASE_NAME=%~n0
30 | set APP_HOME=%DIRNAME%
31 |
32 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
33 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
34 |
35 | @rem Find java.exe
36 | if defined JAVA_HOME goto findJavaFromJavaHome
37 |
38 | set JAVA_EXE=java.exe
39 | %JAVA_EXE% -version >NUL 2>&1
40 | if "%ERRORLEVEL%" == "0" goto init
41 |
42 | echo.
43 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
44 | echo.
45 | echo Please set the JAVA_HOME variable in your environment to match the
46 | echo location of your Java installation.
47 |
48 | goto fail
49 |
50 | :findJavaFromJavaHome
51 | set JAVA_HOME=%JAVA_HOME:"=%
52 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
53 |
54 | if exist "%JAVA_EXE%" goto init
55 |
56 | echo.
57 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
58 | echo.
59 | echo Please set the JAVA_HOME variable in your environment to match the
60 | echo location of your Java installation.
61 |
62 | goto fail
63 |
64 | :init
65 | @rem Get command-line arguments, handling Windows variants
66 |
67 | if not "%OS%" == "Windows_NT" goto win9xME_args
68 |
69 | :win9xME_args
70 | @rem Slurp the command line arguments.
71 | set CMD_LINE_ARGS=
72 | set _SKIP=2
73 |
74 | :win9xME_args_slurp
75 | if "x%~1" == "x" goto execute
76 |
77 | set CMD_LINE_ARGS=%*
78 |
79 | :execute
80 | @rem Setup the command line
81 |
82 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
83 |
84 | @rem Execute Gradle
85 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
86 |
87 | :end
88 | @rem End local scope for the variables with windows NT shell
89 | if "%ERRORLEVEL%"=="0" goto mainEnd
90 |
91 | :fail
92 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
93 | rem the _cmd.exe /c_ return code!
94 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
95 | exit /b 1
96 |
97 | :mainEnd
98 | if "%OS%"=="Windows_NT" endlocal
99 |
100 | :omega
101 |
--------------------------------------------------------------------------------
/src/utils/storageUtil.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Created by hjs on 2019-09-19
3 | */
4 | import {merge} from 'lodash';
5 | import AsyncStorage from '@react-native-community/async-storage';
6 |
7 | const storageUtil = {
8 | /**
9 | * Get a one or more value for a key or array of keys from AsyncStorage
10 | * @param {String|Array} key A key or array of keys
11 | * @return {Promise}
12 | */
13 | get(key) {
14 | if (!Array.isArray(key)) {
15 | return AsyncStorage.getItem(key).then(value => {
16 | return JSON.parse(value);
17 | });
18 | } else {
19 | return AsyncStorage.multiGet(key).then(values => {
20 | return values.map(value => {
21 | return JSON.parse(value[1]);
22 | });
23 | });
24 | }
25 | },
26 |
27 | /**
28 | * Save a key value pair or a series of key value pairs to AsyncStorage.
29 | * @param {String|Array} key The key or an array of key/value pairs
30 | * @param {Any} value The value to save
31 | * @return {Promise}
32 | */
33 | save(key, value) {
34 | if (!Array.isArray(key)) {
35 | return AsyncStorage.setItem(key, JSON.stringify(value));
36 | } else {
37 | const pairs = key.map(function(pair) {
38 | return [pair[0], JSON.stringify(pair[1])];
39 | });
40 | return AsyncStorage.multiSet(pairs);
41 | }
42 | },
43 |
44 | /**
45 | * Updates the value in the store for a given key in AsyncStorage. If the value is a string it will be replaced. If the value is an object it will be deep merged.
46 | * @param {String} key The key
47 | * @param {Value} value The value to update with
48 | * @return {Promise}
49 | */
50 | update(key, value) {
51 | return storageUtil.get(key).then(item => {
52 | value = typeof value === 'string' ? value : merge({}, item, value);
53 | return AsyncStorage.setItem(key, JSON.stringify(value));
54 | });
55 | },
56 |
57 | /**
58 | * Delete the value for a given key in AsyncStorage.
59 | * @param {String|Array} key The key or an array of keys to be deleted
60 | * @return {Promise}
61 | */
62 | delete(key) {
63 | if (Array.isArray(key)) {
64 | return AsyncStorage.multiRemove(key);
65 | } else {
66 | return AsyncStorage.removeItem(key);
67 | }
68 | },
69 |
70 | /**
71 | * Get all keys in AsyncStorage.
72 | * @return {Promise} A promise which when it resolves gets passed the saved keys in AsyncStorage.
73 | */
74 | keys() {
75 | return AsyncStorage.getAllKeys();
76 | },
77 |
78 | /**
79 | * Push a value onto an array stored in AsyncStorage by key or create a new array in AsyncStorage for a key if it's not yet defined.
80 | * @param {String} key They key
81 | * @param {Any} value The value to push onto the array
82 | * @return {Promise}
83 | */
84 | push(key, value) {
85 | return storageUtil.get(key).then(currentValue => {
86 | if (currentValue === null) {
87 | // if there is no current value populate it with the new value
88 | return storageUtil.save(key, [value]);
89 | }
90 | if (Array.isArray(currentValue)) {
91 | return storageUtil.save(key, [...currentValue, value]);
92 | }
93 | throw new Error(
94 | `Existing value for key "${key}" must be of type null or Array, received ${typeof currentValue}.`,
95 | );
96 | });
97 | },
98 | };
99 |
100 | export default storageUtil;
101 |
--------------------------------------------------------------------------------
/src/service/setAxios.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Created by hjs on 2019-09-17
3 | */
4 | import axios from 'axios';
5 | import {get} from 'lodash';
6 | import qs from 'querystring';
7 | import AuthUtil from '../utils/AuthUtil';
8 | const BASE_URL = 'https://www.wanandroid.com/';
9 | const headers = {
10 | Accept: 'application/json;charset=utf-8',
11 | 'Content-Type': 'multipart/form-data;charset=utf-8',
12 | };
13 |
14 | /**
15 | * 打印请求响应时的关键日志信息
16 | * @param response 接口响应信息
17 | */
18 | async function handleShowResponseLog(response) {
19 | const {config, data, request} = response;
20 | console.log('response', response);
21 | const cookie = get(response, 'headers.set-cookie[0]', '');
22 | if (cookie) {
23 | await AuthUtil.saveCookie(cookie);
24 | }
25 | console.log('请求的url: ', `${config.method}:${request.responseURL}`);
26 | if (config.method === 'post') {
27 | console.log('请求的body: ', qs.parse(config.data));
28 | }
29 | console.log('返回值: ', data);
30 | }
31 |
32 | export function setAxios() {
33 | axios.defaults.headers = headers;
34 | axios.defaults.baseURL = BASE_URL;
35 | axios.defaults.timeout = 6000; // 请求超时时间
36 | axios.interceptors.request.use(
37 | async config => {
38 | const Cookie = await AuthUtil.getCookie();
39 | if (config.method === 'post') {
40 | let data = new FormData();
41 | for (const i in config.data) {
42 | data.append(i, config.data[i]);
43 | }
44 | config.data = data;
45 | }
46 | if (config.url !== 'user/login' && Cookie) {
47 | config.headers = {Cookie};
48 | }
49 | return config;
50 | },
51 | function(error) {
52 | return Promise.reject(error);
53 | },
54 | );
55 | axios.interceptors.response.use(
56 | async response => {
57 | const {data} = response;
58 | await handleShowResponseLog(response);
59 | if (data.errorCode === 0) {
60 | return Promise.resolve(data);
61 | }
62 | return Promise.reject(data.errorMsg || '请求失败');
63 | },
64 | err => {
65 | console.log('err', err);
66 | if (err && err.response) {
67 | switch (err.response.status) {
68 | case 400:
69 | err.message = '请求错误(400)';
70 | break;
71 | case 401:
72 | err.message = '未授权,请重新登录(401)';
73 | break;
74 | case 403:
75 | err.message = '拒绝访问(403)';
76 | break;
77 | case 404:
78 | err.message = '请求出错(404)';
79 | break;
80 | case 408:
81 | err.message = '请求超时(408)';
82 | break;
83 | case 500:
84 | err.message = '服务器错误(500)';
85 | break;
86 | case 501:
87 | err.message = '服务未实现(501)';
88 | break;
89 | case 502:
90 | err.message = '网络错误(502)';
91 | break;
92 | case 503:
93 | err.message = '服务不可用(503)';
94 | break;
95 | case 504:
96 | err.message = '网络超时(504)';
97 | break;
98 | case 505:
99 | err.message = 'HTTP版本不受支持(505)';
100 | break;
101 | default:
102 | err.message = `连接出错(${err.response.status})!`;
103 | }
104 | } else {
105 | err.message = '连接服务器失败!';
106 | }
107 | return Promise.reject(err.message);
108 | },
109 | );
110 | }
111 |
--------------------------------------------------------------------------------
/src/screen/drawer/WebsitesScreen.js:
--------------------------------------------------------------------------------
1 | import React, {PureComponent} from 'react';
2 | import {StyleSheet, Text, View} from 'react-native';
3 | import {connect} from 'react-redux';
4 | import globalStyles from '../../styles/globalStyles';
5 | import NavBar from '../../component/NavBar';
6 | import {fetchOftenUsedWebsites} from '../../actions';
7 | import {DEVICE_WIDTH, getRealDP as dp} from '../../utils/screenUtil';
8 | import Color from '../../utils/Color';
9 | import {getChapterBgColor, i18n} from '../../utils/Utility';
10 | import Touchable from '../../component/Touchable';
11 | import Icon from 'react-native-vector-icons/Ionicons';
12 | import CommonFlatList from '../../component/CommonFlatList';
13 |
14 | /**
15 | * 常用网站
16 | */
17 | class WebsitesScreen extends PureComponent {
18 | constructor(props) {
19 | super(props);
20 | this.state = {
21 | isRefreshing: false,
22 | };
23 | this.renderItem = this.renderItem.bind(this);
24 | this.onRefresh = this.onRefresh.bind(this);
25 | }
26 |
27 | componentDidMount() {
28 | fetchOftenUsedWebsites();
29 | }
30 |
31 | async onRefresh() {
32 | this.setState({isRefreshing: true});
33 | await fetchOftenUsedWebsites();
34 | this.setState({isRefreshing: false});
35 | }
36 |
37 | renderItem({item}) {
38 | const {navigation} = this.props;
39 | return (
40 |
41 |
47 | navigation.navigate('WebView', {title: item.name, url: item.link})
48 | }>
49 | {item.name}
50 |
51 |
52 |
53 | );
54 | }
55 |
56 | renderSeparator = () => ;
57 |
58 | render() {
59 | const {navigation, websites} = this.props;
60 | return (
61 |
62 |
66 | item.id.toString()}
69 | renderItem={this.renderItem}
70 | ItemSeparatorComponent={this.renderSeparator}
71 | ListHeaderComponent={this.renderSeparator}
72 | ListFooterComponent={this.renderSeparator}
73 | isRefreshing={this.state.isRefreshing}
74 | toRefresh={this.onRefresh}
75 | />
76 |
77 | );
78 | }
79 | }
80 |
81 | const styles = StyleSheet.create({
82 | itemContainer: {
83 | alignItems: 'center',
84 | },
85 | itemWrapper: {
86 | width: DEVICE_WIDTH * 0.9,
87 | flexDirection: 'row',
88 | backgroundColor: Color.WHITE,
89 | borderRadius: dp(60),
90 | paddingVertical: dp(40),
91 | paddingHorizontal: dp(50),
92 | justifyContent: 'space-between',
93 | },
94 | nameText: {
95 | fontSize: dp(32),
96 | color: Color.WHITE,
97 | fontWeight: 'bold',
98 | },
99 | });
100 |
101 | const mapStateToProps = state => {
102 | return {
103 | websites: state.home.websites,
104 | themeColor: state.user.themeColor,
105 | };
106 | };
107 |
108 | export default connect(mapStateToProps)(WebsitesScreen);
109 |
--------------------------------------------------------------------------------
/src/screen/drawer/CollectScreen.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Created by huangjunsheng on 2019-09-29
3 | */
4 | import React, {PureComponent} from 'react';
5 | import {View} from 'react-native';
6 | import globalStyles from '../../styles/globalStyles';
7 | import NavBar from '../../component/NavBar';
8 | import {
9 | fetchCollectArticleList,
10 | fetchCollectArticleMore,
11 | fetchMyCollectAddCollect,
12 | fetchMyCollectCancelCollect,
13 | } from '../../actions';
14 | import {getRealDP as dp} from '../../utils/screenUtil';
15 | import {connect} from 'react-redux';
16 | import ArticleItemRow from '../../component/ArticleItemRow';
17 | import ListFooter from '../../component/ListFooter';
18 | import CommonFlatList from '../../component/CommonFlatList';
19 | import {i18n} from '../../utils/Utility';
20 |
21 | class CollectScreen extends PureComponent {
22 | constructor(props) {
23 | super(props);
24 | this.state = {
25 | isRefreshing: false,
26 | };
27 | this.renderItem = this.renderItem.bind(this);
28 | this.renderFooter = this.renderFooter.bind(this);
29 | this.onRefresh = this.onRefresh.bind(this);
30 | this.onEndReached = this.onEndReached.bind(this);
31 | }
32 |
33 | componentDidMount() {
34 | fetchCollectArticleList();
35 | }
36 |
37 | async onRefresh() {
38 | this.setState({isRefreshing: true});
39 | await fetchCollectArticleList(this.state.key);
40 | this.setState({isRefreshing: false});
41 | }
42 |
43 | onEndReached() {
44 | const {isFullData} = this.props;
45 | if (isFullData) {
46 | return;
47 | }
48 | fetchCollectArticleMore(this.props.page);
49 | }
50 |
51 | renderItem({item, index}) {
52 | const {navigation} = this.props;
53 | return (
54 | {
58 | if (item.collect) {
59 | fetchMyCollectCancelCollect(item.id, item.originId, index);
60 | } else {
61 | fetchMyCollectAddCollect(item.originId, index);
62 | }
63 | }}
64 | />
65 | );
66 | }
67 |
68 | renderFooter() {
69 | const {isRenderFooter, isFullData, themeColor} = this.props;
70 | return (
71 |
76 | );
77 | }
78 |
79 | render() {
80 | const {navigation, dataSource} = this.props;
81 | return (
82 |
83 |
84 | item.id.toString()}
87 | renderItem={this.renderItem}
88 | ListHeaderComponent={() => }
89 | ListFooterComponent={this.renderFooter}
90 | onEndReached={this.onEndReached}
91 | isRefreshing={this.state.isRefreshing}
92 | toRefresh={this.onRefresh}
93 | />
94 |
95 | );
96 | }
97 | }
98 |
99 | const mapStateToProps = state => {
100 | return {
101 | page: state.collect.page,
102 | dataSource: state.collect.dataSource,
103 | isRenderFooter: state.collect.isRenderFooter,
104 | isFullData: state.collect.isFullData,
105 | themeColor: state.user.themeColor,
106 | };
107 | };
108 |
109 | export default connect(mapStateToProps)(CollectScreen);
110 |
--------------------------------------------------------------------------------
/src/component/CommonFlatList.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Created by huangjunsheng on 2019-10-04
3 | */
4 | import React, {PureComponent} from 'react';
5 | import {
6 | View,
7 | FlatList,
8 | RefreshControl,
9 | StyleSheet,
10 | TouchableWithoutFeedback,
11 | } from 'react-native';
12 | import PropTypes from 'prop-types';
13 | import Color from '../utils/Color';
14 | import {connect} from 'react-redux';
15 | import {DEVICE_HEIGHT, getRealDP as dp} from '../utils/screenUtil';
16 | import Icon from 'react-native-vector-icons/Ionicons';
17 | import {i18n} from '../utils/Utility';
18 |
19 | const propTypes = {
20 | toRefresh: PropTypes.func,
21 | };
22 |
23 | const defaultProps = {
24 | toRefresh: () => {},
25 | };
26 |
27 | /**
28 | * FlatList通用组件
29 | */
30 | class CommonFlatList extends PureComponent {
31 | constructor(props) {
32 | super(props);
33 | this.state = {
34 | isShowTop: false, // 显示置顶按钮
35 | };
36 | this.handleScrollToTop = this.handleScrollToTop.bind(this);
37 | this._onScroll = this._onScroll.bind(this);
38 | }
39 |
40 | handleScrollToTop() {
41 | this.flatListRef &&
42 | this.flatListRef.scrollToOffset({animated: true, offset: 0});
43 | }
44 |
45 | _onScroll(e) {
46 | const scrollY = e.nativeEvent.contentOffset.y;
47 | if (scrollY > DEVICE_HEIGHT) {
48 | this.setState({isShowTop: true});
49 | } else {
50 | this.setState({isShowTop: false});
51 | }
52 | }
53 |
54 | render() {
55 | const {isRefreshing, toRefresh, themeColor} = this.props;
56 | return (
57 |
58 | {
60 | this.flatListRef = comp;
61 | }}
62 | onScroll={this._onScroll}
63 | onEndReachedThreshold={0.2}
64 | refreshControl={
65 |
73 | }
74 | {...this.props}
75 | />
76 | {this.state.isShowTop ? (
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 | ) : null}
85 |
86 | );
87 | }
88 | }
89 |
90 | const styles = StyleSheet.create({
91 | container: {
92 | flex: 1,
93 | },
94 | fixAndroidStyle: {
95 | position: 'absolute',
96 | bottom: dp(80),
97 | right: dp(45),
98 | width: dp(120),
99 | height: dp(120),
100 | backgroundColor: 'rgba(0,0,0,0.005)', // android View设置borderRadius需要外加一层带背景色的View
101 | },
102 | topStyle: {
103 | width: dp(120),
104 | height: dp(120),
105 | borderRadius: dp(60),
106 | justifyContent: 'center',
107 | alignItems: 'center',
108 | opacity: 0.8,
109 | },
110 | });
111 |
112 | CommonFlatList.propTypes = propTypes;
113 | CommonFlatList.defaultProps = defaultProps;
114 |
115 | const mapStateToProps = state => {
116 | return {
117 | themeColor: state.user.themeColor,
118 | };
119 | };
120 |
121 | export default connect(mapStateToProps)(CommonFlatList);
122 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import {createAppContainer} from 'react-navigation';
3 | import {createStackNavigator} from 'react-navigation-stack';
4 | import {createDrawerNavigator} from 'react-navigation-drawer';
5 | import {createBottomTabNavigator} from 'react-navigation-tabs';
6 | import {getRealDP as dp} from './utils/screenUtil';
7 | // import StackViewStyleInterpolator from 'react-navigation-stack/lib/commonjs/views/StackView/StackViewStyleInterpolator';
8 | import BottomTabBar from './component/BottomTabBar';
9 | // Screen
10 | import HomeScreen from './screen/tabs/HomeScreen';
11 | import SystemScreen from './screen/tabs/SystemScreen';
12 | import WxArticleScreen from './screen/tabs/WxArticleScreen';
13 | import GuideScreen from './screen/tabs/GuideScreen';
14 | import ProjectScreen from './screen/tabs/ProjectScreen';
15 | import DrawerScreen from './screen/drawer/DrawerScreen';
16 | import WebViewScreen from './screen/article/WebViewScreen';
17 | import LoginScreen from './screen/drawer/LoginScreen';
18 | import RegisterScreen from './screen/drawer/RegisterScreen';
19 | import ArticleTabScreen from './screen/article/ArticleTabScreen';
20 | import WebsitesScreen from './screen/drawer/WebsitesScreen';
21 | import SearchScreen from './screen/article/SearchScreen';
22 | import SearchArticleScreen from './screen/article/SearchArticleScreen';
23 | import AboutScreen from './screen/drawer/AboutScreen';
24 | import CollectScreen from './screen/drawer/CollectScreen';
25 | import CoinDetailScreen from './screen/drawer/CoinDetailScreen';
26 | import SettingScreen from './screen/drawer/SettingScreen';
27 | import LanguageScreen from './screen/drawer/LanguageScreen';
28 |
29 | const TabScreens = createBottomTabNavigator(
30 | {
31 | Home: HomeScreen,
32 | System: SystemScreen,
33 | WxArticle: WxArticleScreen,
34 | Guide: GuideScreen,
35 | Project: ProjectScreen,
36 | },
37 | {
38 | defaultNavigationOptions: {
39 | tabBarButtonComponent: props => ,
40 | },
41 | tabBarOptions: {
42 | showLabel: false,
43 | showIcon: false,
44 | style: {
45 | height: dp(100),
46 | borderTopWidth: 0,
47 | },
48 | },
49 | },
50 | );
51 |
52 | const drawerNavigator = createDrawerNavigator(
53 | {
54 | Tabs: TabScreens,
55 | },
56 | {
57 | drawerWidth: dp(600),
58 | contentComponent: DrawerScreen,
59 | hideStatusBar: false,
60 | statusBarAnimation: 'fade',
61 | defaultNavigationOptions: {
62 | drawerLockMode: 'unlocked',
63 | },
64 | },
65 | );
66 |
67 | const RootStack = createStackNavigator(
68 | {
69 | Home: drawerNavigator,
70 | WebView: WebViewScreen,
71 | Login: LoginScreen,
72 | Register: RegisterScreen,
73 | ArticleTab: ArticleTabScreen,
74 | Websites: WebsitesScreen,
75 | Search: SearchScreen,
76 | SearchArticle: SearchArticleScreen,
77 | About: AboutScreen,
78 | Collect: CollectScreen,
79 | CoinDetail: CoinDetailScreen,
80 | Setting: SettingScreen,
81 | Language: LanguageScreen,
82 | },
83 | {
84 | initialRouteName: 'Home',
85 | mode: 'modal',
86 | // 用于设置安卓卡片式跳转
87 | // transitionConfig: () => ({
88 | // screenInterpolator: StackViewStyleInterpolator.forHorizontal,
89 | // }),
90 | navigationOptions: () => ({
91 | gesturesEnabled: true,
92 | }),
93 | // 默认导航头样式设置
94 | defaultNavigationOptions: {
95 | header: null,
96 | },
97 | },
98 | );
99 |
100 | export default createAppContainer(RootStack);
101 |
--------------------------------------------------------------------------------
/src/component/Banner.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Created by huangjunsheng on 2019-09-19
3 | */
4 | import React, {PureComponent} from 'react';
5 | import {View, Image, Text, StyleSheet} from 'react-native';
6 | import PropTypes from 'prop-types';
7 | import Swiper from 'react-native-swiper';
8 | import Touchable from './Touchable';
9 | import {DEVICE_WIDTH, getRealDP as dp} from '../utils/screenUtil';
10 | import Color from '../utils/Color';
11 |
12 | const propTypes = {
13 | bannerArr: PropTypes.array.isRequired,
14 | navigation: PropTypes.object.isRequired,
15 | };
16 |
17 | const defaultProps = {
18 | bannerArr: [],
19 | };
20 |
21 | /**
22 | * 首页轮播组件
23 | */
24 | class Banner extends PureComponent {
25 | constructor(props) {
26 | super(props);
27 | this.state = {
28 | currentBannerIndex: 0,
29 | };
30 | this.getCurrentImgIndex = this.getCurrentImgIndex.bind(this);
31 | this.toShowWebView = this.toShowWebView.bind(this);
32 | }
33 |
34 | getCurrentImgIndex(imageIndex) {
35 | this.setState({currentBannerIndex: imageIndex});
36 | }
37 |
38 | toShowWebView(info) {
39 | const {navigation} = this.props;
40 | const {title, url} = info;
41 | navigation.navigate('WebView', {
42 | title,
43 | url,
44 | });
45 | }
46 |
47 | render() {
48 | const {bannerArr} = this.props;
49 | if (!bannerArr.length) {
50 | return ;
51 | }
52 | return (
53 |
54 |
62 | {bannerArr.map(el => (
63 | this.toShowWebView(el)}>
67 |
68 |
69 | ))}
70 |
71 |
72 |
73 | {bannerArr[this.state.currentBannerIndex].title}
74 |
75 |
76 | {this.state.currentBannerIndex + 1}/{bannerArr.length}
77 |
78 |
79 |
80 | );
81 | }
82 | }
83 |
84 | const imageHeight = dp(380);
85 | const styles = StyleSheet.create({
86 | defaultBg: {
87 | height: imageHeight,
88 | backgroundColor: Color.DEFAULT_BG,
89 | },
90 | bannerContainer: {
91 | height: imageHeight,
92 | backgroundColor: Color.DEFAULT_BG,
93 | },
94 | imgCarousel: {
95 | height: imageHeight,
96 | },
97 | imgBanner: {
98 | width: DEVICE_WIDTH,
99 | height: imageHeight,
100 | resizeMode: 'stretch',
101 | },
102 | bannerHint: {
103 | flex: 1,
104 | width: DEVICE_WIDTH,
105 | flexDirection: 'row',
106 | justifyContent: 'space-between',
107 | alignItems: 'center',
108 | paddingHorizontal: dp(20),
109 | backgroundColor: 'rgba(0,0,0,0.3)',
110 | height: dp(50),
111 | bottom: 0,
112 | left: 0,
113 | position: 'absolute',
114 | },
115 | bannerText: {
116 | color: Color.WHITE,
117 | fontSize: dp(28),
118 | },
119 | });
120 |
121 | Banner.propTypes = propTypes;
122 | Banner.defaultProps = defaultProps;
123 |
124 | export default Banner;
125 |
--------------------------------------------------------------------------------
/src/component/Touchable.js:
--------------------------------------------------------------------------------
1 | import React, {PureComponent} from 'react';
2 | import {
3 | TouchableHighlight,
4 | TouchableNativeFeedback,
5 | TouchableWithoutFeedback,
6 | TouchableOpacity,
7 | } from 'react-native';
8 | import PropTypes from 'prop-types';
9 | import {isAndroid} from '../utils/screenUtil';
10 |
11 | const propTypes = {
12 | isHighlight: PropTypes.bool, // 用于使用TouchableHighlight组件
13 | isNativeFeedback: PropTypes.bool, // 仅用于安卓平台,设置原生触摸响应效果
14 | isWithoutFeedback: PropTypes.bool, // 用于使用TouchableWithoutFeedback组件, 不推荐使用
15 | children: PropTypes.node,
16 | activeOpacity: PropTypes.number, // 用于TouchableOpacity和TouchableHighlight组件,设置触摸响应时不透明度
17 | underlayColor: PropTypes.string, // 仅用于TouchableHighlight组件,设置触摸响应时底层颜色
18 | onPress: PropTypes.func.isRequired, // 当触摸操作结束时调用, 处理触摸响应事件
19 | isPreventDouble: PropTypes.bool, // 是否开启防重复点击(加减计数,筛选器,选择器,展开收起等情况需禁用防重,设置为false)
20 | };
21 |
22 | const defaultProps = {
23 | isHighlight: false,
24 | isNativeFeedback: false,
25 | isWithoutFeedback: false,
26 | children: null,
27 | activeOpacity: 0.85,
28 | underlayColor: '#e6e6e6',
29 | onPress: () => {},
30 | isPreventDouble: true,
31 | };
32 |
33 | const preventDoublePress = {
34 | lastPressTime: 1,
35 | onPress(callback) {
36 | const curTime = new Date().getTime();
37 | if (curTime - this.lastPressTime > 500) {
38 | this.lastPressTime = curTime;
39 | callback();
40 | }
41 | },
42 | };
43 |
44 | /**
45 | * 组件名称:防重复点击触摸组件
46 | * 注意事项:默认使用TouchableOpacity组件效果
47 | * */
48 | class Touchable extends PureComponent {
49 | constructor(props) {
50 | super(props);
51 | this.state = {};
52 | this.onPreventDoublePress = this.onPreventDoublePress.bind(this);
53 | }
54 |
55 | /**
56 | * 防重复点击处理事件
57 | * */
58 | onPreventDoublePress() {
59 | const {onPress, isPreventDouble} = this.props;
60 | if (!onPress) {
61 | return;
62 | }
63 | if (isPreventDouble) {
64 | preventDoublePress.onPress(onPress);
65 | } else {
66 | onPress();
67 | }
68 | }
69 |
70 | render() {
71 | const {
72 | children,
73 | isHighlight,
74 | isNativeFeedback,
75 | isWithoutFeedback,
76 | activeOpacity,
77 | underlayColor,
78 | } = this.props;
79 |
80 | /** TouchableHighlight 触摸底色高亮响应效果 */
81 | if (isHighlight) {
82 | return (
83 |
87 | {children}
88 |
89 | );
90 | }
91 |
92 | /** TouchableNativeFeedback Android原生触摸响应效果 */
93 | if (isAndroid && isNativeFeedback) {
94 | return (
95 |
100 | {children}
101 |
102 | );
103 | }
104 |
105 | /** TouchableWithoutFeedback 无视觉反馈触摸响应效果, 建议少使用 */
106 | if (isWithoutFeedback) {
107 | return (
108 |
111 | {children}
112 |
113 | );
114 | }
115 |
116 | /** TouchableOpacity Android/IOS双平台默认触摸响应效果 */
117 | return (
118 |
122 | {children}
123 |
124 | );
125 | }
126 | }
127 |
128 | Touchable.propTypes = propTypes;
129 | Touchable.defaultProps = defaultProps;
130 |
131 | export default Touchable;
132 |
--------------------------------------------------------------------------------
/ios/RN_WanAndroid/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 |
--------------------------------------------------------------------------------
/src/screen/article/SearchArticleScreen.js:
--------------------------------------------------------------------------------
1 | import React, {PureComponent} from 'react';
2 | import {View} from 'react-native';
3 | import {connect} from 'react-redux';
4 | import globalStyles from '../../styles/globalStyles';
5 | import NavBar from '../../component/NavBar';
6 | import {
7 | clearSearchArticle,
8 | fetchSearchArticle,
9 | fetchSearchArticleAddCollect,
10 | fetchSearchArticleCancelCollect,
11 | fetchSearchArticleMore,
12 | } from '../../actions';
13 | import ArticleItemRow from '../../component/ArticleItemRow';
14 | import ListFooter from '../../component/ListFooter';
15 | import {getRealDP as dp} from '../../utils/screenUtil';
16 | import CommonFlatList from '../../component/CommonFlatList';
17 | import {i18n, showToast} from '../../utils/Utility';
18 |
19 | /**
20 | * 搜索文章结果页
21 | */
22 | class SearchArticleScreen extends PureComponent {
23 | constructor(props) {
24 | super(props);
25 | const {navigation} = props;
26 | const key = navigation.getParam('key', '');
27 | this.state = {
28 | key,
29 | isRefreshing: false,
30 | };
31 | this.renderItem = this.renderItem.bind(this);
32 | this.renderFooter = this.renderFooter.bind(this);
33 | this.onRefresh = this.onRefresh.bind(this);
34 | this.onEndReached = this.onEndReached.bind(this);
35 | }
36 |
37 | componentDidMount() {
38 | fetchSearchArticle(this.state.key);
39 | }
40 |
41 | async onRefresh() {
42 | this.setState({isRefreshing: true});
43 | await fetchSearchArticle(this.state.key);
44 | this.setState({isRefreshing: false});
45 | }
46 |
47 | onEndReached() {
48 | const {isFullData} = this.props;
49 | if (isFullData) {
50 | return;
51 | }
52 | fetchSearchArticleMore(this.state.key, this.props.page);
53 | }
54 |
55 | renderItem({item, index}) {
56 | const {navigation, isLogin} = this.props;
57 | return (
58 | {
62 | if (!isLogin) {
63 | showToast(i18n('please-login-first'));
64 | return navigation.navigate('Login');
65 | }
66 | if (item.collect) {
67 | fetchSearchArticleCancelCollect(item.id, index);
68 | } else {
69 | fetchSearchArticleAddCollect(item.id, index);
70 | }
71 | }}
72 | />
73 | );
74 | }
75 |
76 | renderFooter() {
77 | const {isRenderFooter, isFullData, themeColor} = this.props;
78 | return (
79 |
84 | );
85 | }
86 |
87 | render() {
88 | const {navigation, dataSource} = this.props;
89 | return (
90 |
91 | {
95 | clearSearchArticle();
96 | navigation.goBack();
97 | }}
98 | />
99 | item.id.toString()}
102 | renderItem={this.renderItem}
103 | ListHeaderComponent={() => }
104 | ListFooterComponent={this.renderFooter}
105 | onEndReached={this.onEndReached}
106 | isRefreshing={this.state.isRefreshing}
107 | toRefresh={this.onRefresh}
108 | />
109 |
110 | );
111 | }
112 | }
113 |
114 | const mapStateToProps = state => {
115 | return {
116 | page: state.search.page,
117 | dataSource: state.search.dataSource,
118 | isRenderFooter: state.search.isRenderFooter,
119 | isFullData: state.search.isFullData,
120 | themeColor: state.user.themeColor,
121 | isLogin: state.user.isLogin,
122 | };
123 | };
124 |
125 | export default connect(mapStateToProps)(SearchArticleScreen);
126 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # React Native 版 WanAndroid 客户端
2 |
3 | **Github 项目地址:[https://github.com/aijason/RN_WanAndroid](https://github.com/aijason/RN_WanAndroid)**
4 |
5 | **开源不易,如果喜欢的话希望给个 `Star` 或 `Fork` ^_^ ,谢谢~~**
6 |
7 | ## 前言
8 | 这几年主要从事 `React Native` 的开发工作,最早接触到ReactNative的版本是`0.28^`,到最近的`0.61.2^`版本,跨度长达三年之久,爬过无数的坑,但还是对RN的未来充满信心,这次也非常感谢`WanAndroid开放API`,让我工作之余,用最新的RN版本体验下开发的乐趣!
9 |
10 | ## 简介
11 | [RN_WanAndroid](https://github.com/aijason/RN_WanAndroid) 采用 `React Native` 框架编写,基于0.61.2^版本。结合 `Axios` + `React-Navigation` + `Redux` 等开源框架设计的项目,项目代码结构清晰并且有详细注释,如有任何疑问和建议请提 [Issues](https://github.com/aijason/RN_WanAndroid/issues) 或联系 qq: **977854695** ,**项目会持续迭代维护,也欢迎PR ^_^,努力打造一款优秀的React Native框架的 [WanAndroid](http://www.wanandroid.com/) 客户端**。
12 |
13 | ## API
14 | [**玩 Android 开放 API**](http://www.wanandroid.com/blog/show/2)
15 |
16 | ## 下载体验
17 |
18 | - 点击[](https://github.com/aijason/RN_WanAndroid/releases/download/v1.0.2/RN_WanAndroid-release.apk) 下载
19 |
20 | ## IOS项目截图
21 |
22 | |  |  |  |  |
23 | | --- | --- | --- | --- |
24 | |  |  |  |  |
25 | |  |  |  |  |
26 |
27 | ## Android项目截图
28 |
29 | |  |  |  |  |
30 | | --- | --- | --- | --- |
31 | |  |  |  |  |
32 | |  |  |  |  |
33 |
34 | ## 主要功能
35 |
36 | - 首页、体系、公众号、导航、项目五大模块;
37 | - 登录注册功能;
38 | - 搜索功能:热门搜索、搜索历史文章;
39 | - 收藏功能:添加收藏、取消收藏;
40 | - 浏览文章、分享文章;
41 | - 查看常用网站;
42 | - 自定义切换主题颜色功能;
43 | - 多语言切换功能;
44 | - 我的积分明细;
45 | - 关于模块。
46 |
47 | ## 主要开源框架
48 |
49 | - [axios](https://github.com/axios/axios)
50 | - [redux](https://github.com/reduxjs/redux)
51 | - [react-native](https://github.com/facebook/react-native)
52 | - [react-redux](https://github.com/reduxjs/react-redux)
53 | - [redux-thunk](https://github.com/reduxjs/redux-thunk)
54 | - [react-native-gesture-handler](https://github.com/kmagiera/react-native-gesture-handler)
55 | - [react-native-reanimated](https://github.com/kmagiera/react-native-reanimated)
56 | - [react-native-splash-screen](https://github.com/crazycodeboy/react-native-splash-screen)
57 | - [react-native-swiper](https://github.com/leecade/react-native-swiper)
58 | - [react-native-vector-icons](https://github.com/oblador/react-native-vector-icons)
59 | - [react-native-webview](https://github.com/react-native-community/react-native-webview)
60 | - [react-navigation](https://github.com/react-navigation/react-navigation)
61 | - [react-navigation-drawer](https://github.com/react-navigation/drawer)
62 | - [react-navigation-stack](https://github.com/react-navigation/stack)
63 | - [react-navigation-tabs](https://github.com/react-navigation/tabs)
64 | - [react-native-i18n](https://github.com/aijason/react-native-i18n)
65 | - [@react-native-community/async-storage](https://github.com/react-native-community/async-storage)
66 |
67 | ## Thanks
68 |
69 | **感谢所有优秀的开源项目 ^_^** 。
70 |
71 | ## Statement
72 | **项目中的 API 均来自于 [www.wanandroid.com](http://www.wanandroid.com/) 网站,纯属学习交流使用,不得用于商业用途。**
73 |
74 | ## LICENSE
75 |
76 | ```
77 | Copyright 2019 aijason
78 |
79 | Licensed under the Apache License, Version 2.0 (the "License");
80 | you may not use this file except in compliance with the License.
81 | You may obtain a copy of the License at
82 |
83 | http://www.apache.org/licenses/LICENSE-2.0
84 |
85 | Unless required by applicable law or agreed to in writing, software
86 | distributed under the License is distributed on an "AS IS" BASIS,
87 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
88 | See the License for the specific language governing permissions and
89 | limitations under the License.
90 | ```
91 |
--------------------------------------------------------------------------------
/src/screen/article/SearchScreen.js:
--------------------------------------------------------------------------------
1 | import React, {PureComponent} from 'react';
2 | import {View, TextInput, StyleSheet, Text, Keyboard} from 'react-native';
3 | import {connect} from 'react-redux';
4 | import Icon from 'react-native-vector-icons/Ionicons';
5 | import globalStyles from '../../styles/globalStyles';
6 | import NavBar from '../../component/NavBar';
7 | import {DEVICE_WIDTH, getRealDP as dp} from '../../utils/screenUtil';
8 | import Color from '../../utils/Color';
9 | import {fetchSearchHotKey} from '../../actions';
10 | import {getChapterBgColor, i18n, showToast} from '../../utils/Utility';
11 | import Touchable from '../../component/Touchable';
12 |
13 | /**
14 | * SearchScreen
15 | */
16 | class SearchScreen extends PureComponent {
17 | constructor(props) {
18 | super(props);
19 | this.state = {
20 | searchKey: '',
21 | };
22 | }
23 |
24 | componentDidMount() {
25 | fetchSearchHotKey();
26 | }
27 |
28 | render() {
29 | const {navigation, hotKey} = this.props;
30 | return (
31 |
32 | (
35 |
36 | {
43 | this.setState({searchKey});
44 | }}
45 | />
46 |
47 | )}
48 | rightIcon={'md-search'}
49 | onRightPress={() => {
50 | Keyboard.dismiss();
51 | if (!this.state.searchKey) {
52 | return showToast(i18n('please-enter-search-keywords'));
53 | }
54 | navigation.navigate('SearchArticle', {key: this.state.searchKey});
55 | }}
56 | />
57 |
58 |
59 | {i18n('popular-search')}
60 |
61 |
62 | {hotKey.map(el => (
63 |
66 | navigation.navigate('SearchArticle', {key: el.name})
67 | }
68 | style={[
69 | styles.hotKeyItem,
70 | {backgroundColor: getChapterBgColor(el.id)},
71 | ]}>
72 | {el.name}
73 |
74 | ))}
75 |
76 |
77 | );
78 | }
79 | }
80 |
81 | const styles = StyleSheet.create({
82 | textInputWrapper: {
83 | width: DEVICE_WIDTH - dp(260),
84 | },
85 | textInputStyle: {
86 | fontSize: dp(32),
87 | color: Color.WHITE,
88 | padding: 0,
89 | },
90 | hotKeyTitleWrapper: {
91 | height: dp(100),
92 | paddingHorizontal: dp(28),
93 | alignItems: 'center',
94 | flexDirection: 'row',
95 | },
96 | hotKeyTitleText: {
97 | fontSize: dp(38),
98 | color: Color.TEXT_MAIN,
99 | marginLeft: dp(15),
100 | },
101 | hotKeyWrapper: {
102 | paddingHorizontal: dp(28),
103 | flexWrap: 'wrap',
104 | flexDirection: 'row',
105 | },
106 | hotKeyItem: {
107 | paddingHorizontal: dp(30),
108 | paddingVertical: dp(15),
109 | borderRadius: dp(35),
110 | justifyContent: 'center',
111 | alignItems: 'center',
112 | marginRight: dp(20),
113 | marginBottom: dp(20),
114 | },
115 | hotKeyText: {
116 | fontSize: dp(30),
117 | color: Color.WHITE,
118 | fontWeight: 'bold',
119 | },
120 | });
121 |
122 | const mapStateToProps = state => {
123 | return {
124 | hotKey: state.search.hotKey,
125 | themeColor: state.user.themeColor,
126 | };
127 | };
128 |
129 | export default connect(mapStateToProps)(SearchScreen);
130 |
--------------------------------------------------------------------------------
/src/utils/Toast.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Created by hjs on 2019-09-17
3 | */
4 | import React, {Component} from 'react';
5 | import {
6 | StyleSheet,
7 | View,
8 | Animated,
9 | Dimensions,
10 | Text,
11 | ViewPropTypes as RNViewPropTypes,
12 | } from 'react-native';
13 |
14 | import PropTypes from 'prop-types';
15 | const ViewPropTypes = RNViewPropTypes || View.propTypes;
16 | export const DURATION = {
17 | LENGTH_SHORT: 1500,
18 | FOREVER: 0,
19 | };
20 |
21 | const {height, width} = Dimensions.get('window');
22 |
23 | class Toast extends Component {
24 | constructor(props) {
25 | super(props);
26 | this.state = {
27 | isShow: false,
28 | text: '',
29 | opacityValue: new Animated.Value(this.props.opacity),
30 | };
31 | }
32 |
33 | show(text, duration, callback) {
34 | this.duration =
35 | typeof duration === 'number' ? duration : DURATION.LENGTH_SHORT;
36 | this.callback = callback;
37 | this.setState({
38 | isShow: true,
39 | text: text,
40 | });
41 |
42 | this.animation = Animated.timing(this.state.opacityValue, {
43 | toValue: this.props.opacity,
44 | duration: this.props.fadeInDuration,
45 | });
46 | this.animation.start(() => {
47 | this.isShow = true;
48 | if (duration !== DURATION.FOREVER) {
49 | this.close();
50 | }
51 | });
52 | }
53 |
54 | close(duration) {
55 | let delay = typeof duration === 'undefined' ? this.duration : duration;
56 |
57 | if (delay === DURATION.FOREVER) {
58 | delay = this.props.defaultCloseDelay || 250;
59 | }
60 |
61 | if (!this.isShow && !this.state.isShow) {
62 | return;
63 | }
64 | this.timer && clearTimeout(this.timer);
65 | this.timer = setTimeout(() => {
66 | this.animation = Animated.timing(this.state.opacityValue, {
67 | toValue: 0.0,
68 | duration: this.props.fadeOutDuration,
69 | });
70 | this.animation.start(() => {
71 | this.setState({
72 | isShow: false,
73 | });
74 | this.isShow = false;
75 | if (typeof this.callback === 'function') {
76 | this.callback();
77 | }
78 | });
79 | }, delay);
80 | }
81 |
82 | componentWillUnmount() {
83 | this.animation && this.animation.stop();
84 | this.timer && clearTimeout(this.timer);
85 | }
86 |
87 | render() {
88 | let pos;
89 | switch (this.props.position) {
90 | case 'top':
91 | pos = this.props.positionValue;
92 | break;
93 | case 'center':
94 | pos = height / 2;
95 | break;
96 | case 'bottom':
97 | pos = height - this.props.positionValue;
98 | break;
99 | }
100 |
101 | const view = this.state.isShow ? (
102 |
103 |
109 | {React.isValidElement(this.state.text) ? (
110 | this.state.text
111 | ) : (
112 | {this.state.text}
113 | )}
114 |
115 |
116 | ) : null;
117 | return view;
118 | }
119 | }
120 |
121 | const styles = StyleSheet.create({
122 | container: {
123 | position: 'absolute',
124 | left: 0,
125 | right: 0,
126 | elevation: 999,
127 | alignItems: 'center',
128 | zIndex: 10000,
129 | },
130 | content: {
131 | backgroundColor: '#696969',
132 | borderRadius: 5,
133 | padding: 10,
134 | },
135 | text: {
136 | color: 'white',
137 | },
138 | });
139 |
140 | Toast.propTypes = {
141 | style: ViewPropTypes.style,
142 | position: PropTypes.oneOf(['top', 'center', 'bottom']),
143 | textStyle: Text.propTypes.style,
144 | positionValue: PropTypes.number,
145 | fadeInDuration: PropTypes.number,
146 | fadeOutDuration: PropTypes.number,
147 | opacity: PropTypes.number,
148 | };
149 |
150 | Toast.defaultProps = {
151 | position: 'bottom',
152 | textStyle: styles.text,
153 | positionValue: 200,
154 | fadeInDuration: 500,
155 | fadeOutDuration: 500,
156 | opacity: 0.9,
157 | };
158 |
159 | export default Toast;
160 |
--------------------------------------------------------------------------------
/src/screen/drawer/AboutScreen.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Created by huangjunsheng on 2019-09-29
3 | */
4 | import React, {PureComponent} from 'react';
5 | import {Image, StyleSheet, Text, View} from 'react-native';
6 | import Color from '../../utils/Color';
7 | import NavBar from '../../component/NavBar';
8 | import globalStyles from '../../styles/globalStyles';
9 | import {
10 | DEVICE_WIDTH,
11 | getBottomSpace,
12 | getRealDP as dp,
13 | } from '../../utils/screenUtil';
14 | import images from '../../images';
15 | import {connect} from 'react-redux';
16 | import {i18n} from '../../utils/Utility';
17 |
18 | /**
19 | * 关于作者
20 | */
21 | class AboutScreen extends PureComponent {
22 | render() {
23 | const {navigation, themeColor} = this.props;
24 | const gitHubStr = 'https://github.com/aijason';
25 | const csdnStr = 'https://blog.csdn.net/u010379595';
26 | return (
27 |
28 |
29 |
30 |
31 |
32 | {i18n('wanAndroid-client-based-on-Facebook-react-native')}
33 |
34 |
35 |
36 | {i18n('email')}:
37 | 977854695@qq.com
38 |
39 |
40 |
41 |
42 | CSDN:
43 | {
46 | navigation.navigate('WebView', {
47 | title: 'CSDN',
48 | url: csdnStr,
49 | });
50 | }}>
51 | {csdnStr}
52 |
53 |
54 |
55 |
56 |
57 | GitHub:
58 | {
61 | navigation.navigate('WebView', {
62 | title: 'GitHub',
63 | url: gitHubStr,
64 | });
65 | }}>
66 | {gitHubStr}
67 |
68 |
69 |
70 |
71 | {i18n(
72 | 'This project is for learning purposes only, not for commercial purposes',
73 | )}
74 |
75 |
76 |
77 | );
78 | }
79 | }
80 |
81 | const styles = StyleSheet.create({
82 | content: {
83 | flex: 1,
84 | backgroundColor: Color.WHITE,
85 | alignItems: 'center',
86 | paddingVertical: dp(150),
87 | },
88 | logo: {
89 | width: dp(180),
90 | height: dp(189),
91 | borderRadius: dp(90),
92 | marginBottom: dp(50),
93 | },
94 | desc: {
95 | fontSize: dp(30),
96 | color: Color.TEXT_MAIN,
97 | marginTop: dp(20),
98 | marginBottom: dp(100),
99 | },
100 | blogStyle: {
101 | width: dp(200),
102 | height: dp(200),
103 | borderRadius: dp(100),
104 | backgroundColor: '#FF7256',
105 | justifyContent: 'center',
106 | alignItems: 'center',
107 | marginBottom: dp(20),
108 | marginHorizontal: dp(20),
109 | },
110 | githubStyle: {
111 | width: dp(200),
112 | height: dp(200),
113 | borderRadius: dp(100),
114 | backgroundColor: '#5F9EA0',
115 | justifyContent: 'center',
116 | alignItems: 'center',
117 | marginBottom: dp(20),
118 | marginHorizontal: dp(20),
119 | },
120 | item: {
121 | paddingHorizontal: dp(50),
122 | marginBottom: dp(50),
123 | width: DEVICE_WIDTH,
124 | },
125 | itemText: {
126 | fontSize: dp(30),
127 | color: Color.TEXT_MAIN,
128 | },
129 | bottomText: {
130 | fontSize: dp(24),
131 | color: Color.TEXT_MAIN,
132 | position: 'absolute',
133 | marginVertical: dp(50),
134 | paddingHorizontal: dp(28),
135 | bottom: getBottomSpace(),
136 | },
137 | underlineText: {
138 | textDecorationLine: 'underline',
139 | },
140 | });
141 |
142 | const mapStateToProps = state => {
143 | return {
144 | themeColor: state.user.themeColor,
145 | };
146 | };
147 |
148 | export default connect(mapStateToProps)(AboutScreen);
149 |
--------------------------------------------------------------------------------
/src/screen/tabs/HomeScreen.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Created by huangjunsheng on 2019-09-16
3 | */
4 | import React, {PureComponent} from 'react';
5 | import {View} from 'react-native';
6 | import {connect} from 'react-redux';
7 | import {
8 | fetchHomeAddCollect,
9 | fetchHomeBanner,
10 | fetchHomeCancelCollect,
11 | fetchHomeList,
12 | fetchHomeListMore,
13 | } from '../../actions';
14 | import NavBar from '../../component/NavBar';
15 | import Banner from '../../component/Banner';
16 | import globalStyles from '../../styles/globalStyles';
17 | import {getRealDP as dp} from '../../utils/screenUtil';
18 | import ListFooter from '../../component/ListFooter';
19 | import ArticleItemRow from '../../component/ArticleItemRow';
20 | import {i18n, showToast} from '../../utils/Utility';
21 | import CommonFlatList from '../../component/CommonFlatList';
22 |
23 | /**
24 | * 首页
25 | */
26 | class HomeScreen extends PureComponent {
27 | constructor(props) {
28 | super(props);
29 | this.state = {
30 | isRefreshing: false,
31 | };
32 | this.renderItem = this.renderItem.bind(this);
33 | this.renderHeader = this.renderHeader.bind(this);
34 | this.renderFooter = this.renderFooter.bind(this);
35 | this.onFetchData = this.onFetchData.bind(this);
36 | this.onRefresh = this.onRefresh.bind(this);
37 | this.onEndReached = this.onEndReached.bind(this);
38 | }
39 |
40 | async componentDidMount() {
41 | await this.onFetchData();
42 | }
43 |
44 | async onFetchData() {
45 | await Promise.all([fetchHomeBanner(), fetchHomeList()]);
46 | }
47 |
48 | async onRefresh() {
49 | this.setState({isRefreshing: true});
50 | await Promise.all([fetchHomeBanner(), fetchHomeList()]);
51 | this.setState({isRefreshing: false});
52 | }
53 |
54 | onEndReached() {
55 | const {isFullData} = this.props;
56 | if (isFullData) {
57 | return;
58 | }
59 | fetchHomeListMore(this.props.page);
60 | }
61 |
62 | renderItem({item, index}) {
63 | const {navigation, isLogin} = this.props;
64 | return (
65 | {
69 | if (!isLogin) {
70 | showToast(i18n('please-login-first'));
71 | return navigation.navigate('Login');
72 | }
73 | if (item.collect) {
74 | fetchHomeCancelCollect(item.id, index);
75 | } else {
76 | fetchHomeAddCollect(item.id, index);
77 | }
78 | }}
79 | />
80 | );
81 | }
82 |
83 | renderHeader() {
84 | const {navigation, homeBanner} = this.props;
85 | return (
86 |
87 |
88 |
89 |
90 | );
91 | }
92 |
93 | renderFooter() {
94 | const {isRenderFooter, isFullData, themeColor} = this.props;
95 | return (
96 |
101 | );
102 | }
103 |
104 | render() {
105 | const {navigation, dataSource} = this.props;
106 | return (
107 |
108 | navigation.toggleDrawer()}
114 | onRightPress={() => navigation.navigate('Search')}
115 | />
116 | item.id.toString()}
119 | renderItem={this.renderItem}
120 | ListHeaderComponent={this.renderHeader}
121 | ListFooterComponent={this.renderFooter}
122 | onEndReached={this.onEndReached}
123 | isRefreshing={this.state.isRefreshing}
124 | toRefresh={this.onRefresh}
125 | />
126 |
127 | );
128 | }
129 | }
130 |
131 | const mapStateToProps = state => {
132 | return {
133 | page: state.home.page,
134 | dataSource: state.home.dataSource,
135 | homeBanner: state.home.homeBanner,
136 | homeList: state.home.homeList,
137 | isRenderFooter: state.home.isRenderFooter,
138 | isFullData: state.home.isFullData,
139 | isLogin: state.user.isLogin,
140 | themeColor: state.user.themeColor,
141 | language: state.user.language,
142 | };
143 | };
144 |
145 | export default connect(mapStateToProps)(HomeScreen);
146 |
--------------------------------------------------------------------------------
/src/component/NavBar.js:
--------------------------------------------------------------------------------
1 | import React, {PureComponent} from 'react';
2 | import {
3 | Image,
4 | StyleSheet,
5 | Text,
6 | TouchableNativeFeedback,
7 | View,
8 | } from 'react-native';
9 | import PropTypes from 'prop-types';
10 | import {connect} from 'react-redux';
11 | import Icon from 'react-native-vector-icons/Ionicons';
12 | import {
13 | DEVICE_WIDTH,
14 | getRealDP as dp,
15 | getStatusBarHeight,
16 | isAndroid,
17 | } from '../utils/screenUtil';
18 | import Color from '../utils/Color';
19 | import Touchable from './Touchable';
20 | import images from '../images';
21 |
22 | const propTypes = {
23 | title: PropTypes.string,
24 | titleView: PropTypes.func,
25 | leftIcon: PropTypes.string,
26 | onLeftPress: PropTypes.func,
27 | rightIcon: PropTypes.string,
28 | onRightPress: PropTypes.func,
29 | navigation: PropTypes.object.isRequired,
30 | navBarStyle: PropTypes.oneOfType([
31 | PropTypes.number,
32 | PropTypes.object,
33 | PropTypes.array,
34 | ]),
35 | };
36 | const defaultProps = {
37 | title: '',
38 | leftIcon: 'md-arrow-back',
39 | noStatusBarHeight: false,
40 | navBarStyle: {},
41 | };
42 |
43 | const feedBackBackground = TouchableNativeFeedback.Ripple(
44 | 'rgba(50,50,50,0.3)',
45 | true,
46 | );
47 |
48 | /**
49 | * NavBar 导航头组件
50 | */
51 | class NavBar extends PureComponent {
52 | constructor(props) {
53 | super(props);
54 | this.state = {};
55 | this.handleLeftBtnClick = this.handleLeftBtnClick.bind(this);
56 | }
57 |
58 | handleLeftBtnClick() {
59 | const {onLeftPress, navigation} = this.props;
60 | if (onLeftPress && typeof onLeftPress === 'function') {
61 | return onLeftPress();
62 | }
63 | return navigation.goBack();
64 | }
65 |
66 | render() {
67 | const {
68 | leftIcon,
69 | rightIcon,
70 | navBarStyle,
71 | titleView,
72 | title,
73 | onRightPress,
74 | isLogin,
75 | themeColor,
76 | } = this.props;
77 | return (
78 |
80 | {/* 左侧按钮 */}
81 |
85 |
86 | {leftIcon === 'md-person' && isLogin ? (
87 |
92 | ) : (
93 |
94 | )}
95 |
96 |
97 |
98 | {/* 中间标题 */}
99 | {titleView ? (
100 | titleView()
101 | ) : (
102 |
103 |
104 | {title}
105 |
106 |
107 | )}
108 |
109 | {/* 右侧按钮 */}
110 | {rightIcon ? (
111 |
115 |
116 |
117 |
118 |
119 | ) : (
120 |
121 | )}
122 |
123 | );
124 | }
125 | }
126 |
127 | const styles = StyleSheet.create({
128 | container: {
129 | height: dp(100) + getStatusBarHeight(),
130 | flexDirection: 'row',
131 | alignItems: 'center',
132 | justifyContent: 'space-between',
133 | paddingTop: getStatusBarHeight(),
134 | paddingHorizontal: dp(28),
135 | },
136 | titleWrapper: {
137 | width: DEVICE_WIDTH - dp(260),
138 | alignItems: 'center',
139 | },
140 | title: {
141 | fontWeight: 'bold',
142 | fontSize: dp(38),
143 | color: Color.WHITE,
144 | },
145 | iconWrapper: {
146 | height: dp(60),
147 | width: dp(60),
148 | borderRadius: dp(30),
149 | alignItems: 'center',
150 | justifyContent: 'center',
151 | },
152 | myPhoto: {
153 | height: dp(58),
154 | width: dp(58),
155 | borderRadius: dp(29),
156 | borderWidth: dp(2),
157 | borderColor: Color.WHITE,
158 | },
159 | });
160 |
161 | NavBar.propTypes = propTypes;
162 | NavBar.defaultProps = defaultProps;
163 |
164 | const mapStateToProps = state => {
165 | return {
166 | isLogin: state.user.isLogin,
167 | themeColor: state.user.themeColor,
168 | language: state.user.language,
169 | };
170 | };
171 |
172 | export default connect(mapStateToProps)(NavBar);
173 |
--------------------------------------------------------------------------------
/src/screen/tabs/SystemScreen.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Created by huangjunsheng on 2019-09-16
3 | */
4 | import React, {PureComponent} from 'react';
5 | import {StyleSheet, Text, View} from 'react-native';
6 | import {connect} from 'react-redux';
7 | import Icon from 'react-native-vector-icons/Ionicons';
8 | import NavBar from '../../component/NavBar';
9 | import Color from '../../utils/Color';
10 | import globalStyles from '../../styles/globalStyles';
11 | import {fetchSystemData} from '../../actions';
12 | import {getRealDP as dp} from '../../utils/screenUtil';
13 | import {getChapterBgColor, i18n} from '../../utils/Utility';
14 | import Touchable from '../../component/Touchable';
15 | import CommonFlatList from '../../component/CommonFlatList';
16 |
17 | /**
18 | * 知识体系
19 | */
20 | class SystemScreen extends PureComponent {
21 | constructor(props) {
22 | super(props);
23 | this.state = {
24 | isRefreshing: false,
25 | };
26 | this.onRefresh = this.onRefresh.bind(this);
27 | this.renderItem = this.renderItem.bind(this);
28 | }
29 |
30 | componentDidMount() {
31 | fetchSystemData();
32 | }
33 |
34 | async onRefresh() {
35 | this.setState({isRefreshing: true});
36 | await fetchSystemData();
37 | this.setState({isRefreshing: false});
38 | }
39 |
40 | renderItem({item}) {
41 | const {navigation} = this.props;
42 | return (
43 | {
45 | navigation.navigate('ArticleTab', {
46 | articleTabs: item.children,
47 | title: item.name,
48 | });
49 | }}>
50 |
51 |
52 | {item.name}
53 |
54 |
55 | {item.children.map(el => (
56 |
57 |
62 | {el.name}
63 |
64 |
65 | ))}
66 |
67 |
72 |
73 |
74 |
75 |
76 | );
77 | }
78 |
79 | render() {
80 | const {navigation, systemData} = this.props;
81 | return (
82 |
83 | navigation.toggleDrawer()}
89 | onRightPress={() => navigation.navigate('Search')}
90 | />
91 | item.id.toString()}
94 | renderItem={this.renderItem}
95 | ListHeaderComponent={() => }
96 | isRefreshing={this.state.isRefreshing}
97 | toRefresh={this.onRefresh}
98 | />
99 |
100 | );
101 | }
102 | }
103 |
104 | const styles = StyleSheet.create({
105 | itemWrapper: {
106 | flex: 1,
107 | alignItems: 'center',
108 | },
109 | itemContent: {
110 | width: dp(700),
111 | borderRadius: dp(30),
112 | backgroundColor: Color.WHITE,
113 | alignItems: 'center',
114 | marginBottom: dp(20),
115 | paddingHorizontal: dp(20),
116 | paddingTop: dp(20),
117 | paddingBottom: dp(10),
118 | justifyContent: 'space-between',
119 | },
120 | title: {
121 | fontSize: dp(32),
122 | color: Color.TEXT_MAIN,
123 | textAlign: 'center',
124 | fontWeight: 'bold',
125 | },
126 | content: {
127 | flexDirection: 'row',
128 | justifyContent: 'space-between',
129 | alignItems: 'center',
130 | },
131 | leftContent: {
132 | width: dp(620),
133 | flexWrap: 'wrap',
134 | flexDirection: 'row',
135 | paddingTop: dp(20),
136 | },
137 | tabItemWrapper: {
138 | paddingHorizontal: dp(30),
139 | paddingVertical: dp(10),
140 | borderRadius: dp(30),
141 | marginRight: dp(15),
142 | marginBottom: dp(20),
143 | },
144 | tabItemText: {
145 | fontSize: dp(28),
146 | color: Color.WHITE,
147 | },
148 | });
149 |
150 | const mapStateToProps = state => {
151 | return {
152 | systemData: state.system.systemData,
153 | themeColor: state.user.themeColor,
154 | language: state.user.language,
155 | };
156 | };
157 |
158 | export default connect(mapStateToProps)(SystemScreen);
159 |
--------------------------------------------------------------------------------
/src/component/ArticleItemRow.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Created by huangjunsheng on 2019-09-25
3 | */
4 | import React, {PureComponent} from 'react';
5 | import {View, Text, Image, StyleSheet} from 'react-native';
6 | import PropTypes from 'prop-types';
7 | import {getRealDP as dp} from '../utils/screenUtil';
8 | import Color from '../utils/Color';
9 | import globalStyles from '../styles/globalStyles';
10 | import Touchable from './Touchable';
11 | import Icon from 'react-native-vector-icons/Ionicons';
12 | import {getChapterBgColor, i18n} from '../utils/Utility';
13 |
14 | const propTypes = {
15 | navigation: PropTypes.object.isRequired,
16 | item: PropTypes.object.isRequired,
17 | isWxArticle: PropTypes.bool.isRequired, // 公众号文章
18 | onCollectPress: PropTypes.func.isRequired, // 收藏
19 | };
20 |
21 | const defaultProps = {
22 | isWxArticle: false,
23 | onCollectPress: () => {},
24 | };
25 |
26 | class ArticleItemRow extends PureComponent {
27 | constructor(props) {
28 | super(props);
29 | this.state = {};
30 | }
31 | render() {
32 | const {navigation, item, isWxArticle, onCollectPress} = this.props;
33 | return (
34 |
37 | navigation.navigate('WebView', {
38 | title: item.title,
39 | url: item.link,
40 | })
41 | }>
42 |
43 |
44 |
45 | {item.title}
46 |
47 |
48 | {item.desc}
49 |
50 |
51 |
52 |
57 |
58 |
59 |
60 |
65 |
66 | {item.niceDate}
67 |
68 | {item.author || item.shareUser || item.chapterName}
69 |
70 |
71 |
72 |
73 |
74 | {item.envelopePic ? (
75 |
80 | ) : (
81 |
82 |
91 |
92 | {item.superChapterName || i18n('Article')}
93 |
94 |
95 |
96 | )}
97 |
98 |
99 |
100 | );
101 | }
102 | }
103 |
104 | const styles = StyleSheet.create({
105 | container: {
106 | flex: 1,
107 | alignItems: 'center',
108 | },
109 | itemWrapper: {
110 | width: dp(700),
111 | backgroundColor: Color.WHITE,
112 | paddingHorizontal: dp(20),
113 | paddingTop: dp(25),
114 | paddingBottom: dp(13),
115 | flexDirection: 'row',
116 | justifyContent: 'space-between',
117 | marginBottom: dp(20),
118 | borderRadius: dp(20),
119 | },
120 | itemLeftWrapper: {
121 | flex: 1,
122 | width: dp(520),
123 | justifyContent: 'space-between',
124 | },
125 | itemRightWrapper: {
126 | justifyContent: 'center',
127 | },
128 | image: {
129 | height: dp(200),
130 | width: dp(120),
131 | backgroundColor: Color.ICON_GRAY,
132 | },
133 | title: {
134 | fontSize: dp(28),
135 | color: Color.TEXT_MAIN,
136 | fontWeight: 'bold',
137 | maxWidth: dp(520),
138 | },
139 | desc: {
140 | fontSize: dp(26),
141 | color: Color.TEXT_DARK,
142 | marginVertical: dp(12),
143 | maxWidth: dp(520),
144 | },
145 | likeTime: {
146 | width: dp(520),
147 | flexDirection: 'row',
148 | alignItems: 'center',
149 | },
150 | dateText: {
151 | fontSize: dp(24),
152 | color: Color.TEXT_LIGHT,
153 | },
154 | timeIconWrapper: {
155 | marginHorizontal: dp(20),
156 | },
157 | });
158 |
159 | ArticleItemRow.propTypes = propTypes;
160 | ArticleItemRow.defaultProps = defaultProps;
161 |
162 | export default ArticleItemRow;
163 |
--------------------------------------------------------------------------------
/src/screen/drawer/CoinDetailScreen.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Created by huangjunsheng on 2019-10-08
3 | */
4 | import React, {PureComponent} from 'react';
5 | import {View, StyleSheet, Text} from 'react-native';
6 | import {connect} from 'react-redux';
7 | import globalStyles from '../../styles/globalStyles';
8 | import NavBar from '../../component/NavBar';
9 | import {
10 | fetchMyCoinInfo,
11 | fetchMyCoinList,
12 | fetchMyCoinListMore,
13 | } from '../../actions';
14 | import CommonFlatList from '../../component/CommonFlatList';
15 | import {
16 | DEVICE_WIDTH,
17 | getBottomSpace,
18 | getRealDP as dp,
19 | } from '../../utils/screenUtil';
20 | import ListFooter from '../../component/ListFooter';
21 | import Color from '../../utils/Color';
22 | import {i18n} from '../../utils/Utility';
23 | /**
24 | * 积分明细列表
25 | */
26 | class CoinDetailScreen extends PureComponent {
27 | constructor(props) {
28 | super(props);
29 | this.state = {
30 | isRefreshing: false,
31 | };
32 | this.onRefresh = this.onRefresh.bind(this);
33 | this.onEndReached = this.onEndReached.bind(this);
34 | this.renderItem = this.renderItem.bind(this);
35 | this.renderHeader = this.renderHeader.bind(this);
36 | this.renderFooter = this.renderFooter.bind(this);
37 | }
38 |
39 | componentDidMount() {
40 | fetchMyCoinInfo();
41 | fetchMyCoinList();
42 | }
43 |
44 | async onRefresh() {
45 | this.setState({isRefreshing: true});
46 | await fetchMyCoinList();
47 | this.setState({isRefreshing: false});
48 | }
49 |
50 | onEndReached() {
51 | const {isFullData} = this.props;
52 | if (isFullData) {
53 | return;
54 | }
55 | fetchMyCoinListMore(this.props.page);
56 | }
57 |
58 | renderItem({item}) {
59 | return (
60 |
61 |
62 | {item.reason}
63 | {item.desc}
64 |
65 | {`+${item.coinCount}`}
66 |
67 | );
68 | }
69 |
70 | renderHeader() {
71 | const {themeColor, coinCount} = this.props;
72 | return (
73 |
74 | {coinCount}
75 |
76 | );
77 | }
78 |
79 | renderFooter() {
80 | const {isRenderFooter, isFullData, themeColor} = this.props;
81 | return (
82 |
87 | );
88 | }
89 |
90 | renderSeparator() {
91 | return (
92 |
93 |
94 |
95 | );
96 | }
97 |
98 | render() {
99 | const {navigation, dataSource} = this.props;
100 | return (
101 |
102 | {
107 | navigation.navigate('WebView', {
108 | title: i18n('points-rule'),
109 | url: 'https://www.wanandroid.com/blog/show/2653',
110 | });
111 | }}
112 | />
113 | item.id.toString()}
116 | renderItem={this.renderItem}
117 | ListHeaderComponent={this.renderHeader}
118 | ListFooterComponent={this.renderFooter}
119 | ItemSeparatorComponent={this.renderSeparator}
120 | onEndReached={this.onEndReached}
121 | isRefreshing={this.state.isRefreshing}
122 | toRefresh={this.onRefresh}
123 | />
124 |
125 | );
126 | }
127 | }
128 |
129 | const styles = StyleSheet.create({
130 | container: {
131 | flex: 1,
132 | backgroundColor: Color.DEFAULT_BG,
133 | marginBottom: getBottomSpace(),
134 | },
135 | headerWrapper: {
136 | height: dp(200),
137 | justifyContent: 'center',
138 | alignItems: 'center',
139 | },
140 | coinText: {
141 | fontSize: dp(75),
142 | fontWeight: 'bold',
143 | color: Color.WHITE,
144 | },
145 | itemWrapper: {
146 | width: DEVICE_WIDTH,
147 | flexDirection: 'row',
148 | backgroundColor: Color.WHITE,
149 | paddingVertical: dp(28),
150 | paddingHorizontal: dp(28),
151 | justifyContent: 'space-between',
152 | alignItems: 'center',
153 | },
154 | coinCountText: {
155 | fontSize: dp(30),
156 | color: Color.WX_GREEN,
157 | fontWeight: 'bold',
158 | },
159 | coinDescText: {
160 | fontSize: dp(28),
161 | color: Color.TEXT_DARK,
162 | marginTop: dp(20),
163 | },
164 | coinReasonText: {
165 | fontSize: dp(30),
166 | color: Color.TEXT_MAIN,
167 | },
168 | });
169 |
170 | const mapStateToProps = state => {
171 | return {
172 | dataSource: state.coin.dataSource,
173 | isRenderFooter: state.coin.isRenderFooter,
174 | isFullData: state.coin.isFullData,
175 | page: state.coin.page,
176 | coinCount: state.coin.coinCount,
177 | themeColor: state.user.themeColor,
178 | };
179 | };
180 |
181 | export default connect(mapStateToProps)(CoinDetailScreen);
182 |
--------------------------------------------------------------------------------
/src/component/ArticleFlatList.js:
--------------------------------------------------------------------------------
1 | import React, {PureComponent} from 'react';
2 | import {View} from 'react-native';
3 | import PropTypes from 'prop-types';
4 | import globalStyles from '../styles/globalStyles';
5 | import {fetchWxArticleList, updateArticleLoading} from '../actions';
6 | import {getRealDP as dp} from '../utils/screenUtil';
7 | import ArticleItemRow from './ArticleItemRow';
8 | import {connect} from 'react-redux';
9 | import {addCollectArticle, cancelCollectArticle} from '../api';
10 | import {i18n, showToast} from '../utils/Utility';
11 | import ListFooter from './ListFooter';
12 | import CommonFlatList from './CommonFlatList';
13 |
14 | const propTypes = {
15 | chapterId: PropTypes.number.isRequired,
16 | navigation: PropTypes.object.isRequired,
17 | isWxArticle: PropTypes.bool,
18 | };
19 |
20 | const defaultProps = {
21 | isWxArticle: false,
22 | };
23 |
24 | /**
25 | * ArticleFlatList
26 | */
27 | class ArticleFlatList extends PureComponent {
28 | constructor(props) {
29 | super(props);
30 | this.state = {
31 | isRefreshing: false,
32 | dataSource: [],
33 | isRenderFooter: true,
34 | isFullData: false,
35 | };
36 | this.page = 1;
37 | this.renderFooter = this.renderFooter.bind(this);
38 | this.renderItem = this.renderItem.bind(this);
39 | this.onRefresh = this.onRefresh.bind(this);
40 | this.onEndReached = this.onEndReached.bind(this);
41 | }
42 |
43 | componentDidMount() {
44 | const {chapterId} = this.props;
45 | fetchWxArticleList(chapterId)
46 | .then(res => {
47 | updateArticleLoading(false);
48 | this.setState({dataSource: res.datas});
49 | })
50 | .catch(e => {});
51 | }
52 |
53 | onRefresh() {
54 | const {chapterId} = this.props;
55 | this.page = 1;
56 | this.setState({isRefreshing: true});
57 | fetchWxArticleList(chapterId)
58 | .then(res => {
59 | this.setState({
60 | dataSource: res.datas,
61 | isRefreshing: false,
62 | isRenderFooter: !!res.total, // 只有total为0是不渲染footer
63 | isFullData: res.curPage === res.pageCount,
64 | });
65 | })
66 | .catch(e => {
67 | this.setState({isRefreshing: false});
68 | });
69 | }
70 |
71 | onEndReached() {
72 | const {chapterId} = this.props;
73 | const {dataSource, isFullData} = this.state;
74 | if (isFullData) {
75 | return;
76 | }
77 | fetchWxArticleList(chapterId, ++this.page)
78 | .then(res => {
79 | this.setState({
80 | dataSource: dataSource.concat(res.datas),
81 | isRenderFooter: true,
82 | isFullData: !res.datas.length,
83 | });
84 | })
85 | .catch(e => {
86 | this.setState({isRefreshing: false});
87 | });
88 | }
89 |
90 | renderFooter() {
91 | const {themeColor} = this.props;
92 | return (
93 |
98 | );
99 | }
100 |
101 | renderItem({item, index}) {
102 | const {navigation, isWxArticle, isLogin} = this.props;
103 | return (
104 | {
109 | if (!isLogin) {
110 | showToast(i18n('please-login-first'));
111 | return navigation.navigate('Login');
112 | }
113 | if (item.collect) {
114 | cancelCollectArticle(item.id)
115 | .then(res => {
116 | let addCollectDataSource = [...this.state.dataSource];
117 | addCollectDataSource[index].collect = false;
118 | this.setState({dataSource: addCollectDataSource});
119 | })
120 | .catch(e => {});
121 | } else {
122 | addCollectArticle(item.id)
123 | .then(res => {
124 | showToast(i18n('Have been collected'));
125 | let addCollectDataSource = [...this.state.dataSource];
126 | addCollectDataSource[index].collect = true;
127 | this.setState({dataSource: addCollectDataSource});
128 | })
129 | .catch(e => {});
130 | }
131 | }}
132 | />
133 | );
134 | }
135 |
136 | render() {
137 | const {dataSource} = this.state;
138 | if (!dataSource.length) {
139 | return null;
140 | }
141 | return (
142 |
143 | item.id.toString()}
146 | renderItem={this.renderItem}
147 | ListHeaderComponent={() => }
148 | ListFooterComponent={this.renderFooter}
149 | onEndReached={this.onEndReached}
150 | isRefreshing={this.state.isRefreshing}
151 | toRefresh={this.onRefresh}
152 | />
153 |
154 | );
155 | }
156 | }
157 |
158 | ArticleFlatList.propTypes = propTypes;
159 | ArticleFlatList.defaultProps = defaultProps;
160 |
161 | const mapStateToProps = state => {
162 | return {
163 | themeColor: state.user.themeColor,
164 | isLogin: state.user.isLogin,
165 | };
166 | };
167 |
168 | export default connect(mapStateToProps)(ArticleFlatList);
169 |
--------------------------------------------------------------------------------
/ios/RN_WanAndroid.xcodeproj/xcshareddata/xcschemes/RN_WanAndroid.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 |
--------------------------------------------------------------------------------
/src/actions/action-creator.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Created by huangjunsheng on 2019-09-22
3 | */
4 | import actionTypes from './actionType';
5 |
6 | export function getHomeBannerAction(homeBanner) {
7 | return {
8 | type: actionTypes.FETCH_HOME_BANNER,
9 | homeBanner,
10 | };
11 | }
12 |
13 | export function getHomeListAction(homeList) {
14 | return {
15 | type: actionTypes.FETCH_HOME_LIST,
16 | homeList,
17 | };
18 | }
19 |
20 | export function getHomeListFailureAction() {
21 | return {
22 | type: actionTypes.FETCH_HOME_LIST_FAILURE,
23 | };
24 | }
25 |
26 | export function getHomeListMoreAction(homeList) {
27 | return {
28 | type: actionTypes.FETCH_HOME_LIST_MORE,
29 | homeList,
30 | };
31 | }
32 |
33 | export function getToRegisterAction(userInfo) {
34 | return {
35 | type: actionTypes.FETCH_TO_REGISTER,
36 | userInfo,
37 | };
38 | }
39 |
40 | export function getToLoginAction(userInfo) {
41 | return {
42 | type: actionTypes.FETCH_TO_LOGIN,
43 | userInfo,
44 | };
45 | }
46 |
47 | export function getToLoginFailureAction() {
48 | return {
49 | type: actionTypes.FETCH_TO_LOGIN_FAILURE,
50 | };
51 | }
52 |
53 | export function getToLogoutAction() {
54 | return {
55 | type: actionTypes.FETCH_TO_LOGOUT,
56 | };
57 | }
58 |
59 | export function getChangeThemeColorAction(themeColor) {
60 | return {
61 | type: actionTypes.CHANGE_THEME_COLOR,
62 | themeColor,
63 | };
64 | }
65 |
66 | export function getInitialAuthInfoAction(initialInfo) {
67 | return {
68 | type: actionTypes.INITIAL_AUTH_INFO,
69 | initialInfo,
70 | };
71 | }
72 |
73 | export function getSystemDataAction(systemData) {
74 | return {
75 | type: actionTypes.FETCH_TO_SYSTEM_DATA,
76 | systemData,
77 | };
78 | }
79 |
80 | export function getWxArticleTabsAction(articleTabs) {
81 | return {
82 | type: actionTypes.FETCH_WX_ARTICLE_TABS,
83 | articleTabs,
84 | };
85 | }
86 |
87 | export function getWxArticleListAction(articleList) {
88 | return {
89 | type: actionTypes.FETCH_WX_ARTICLE_LIST,
90 | articleList,
91 | };
92 | }
93 |
94 | export function getGuideDataAction(guideData) {
95 | return {
96 | type: actionTypes.FETCH_GUIDE_DATA,
97 | guideData,
98 | };
99 | }
100 |
101 | export function updateSelectIndexAction(selectIndex) {
102 | return {
103 | type: actionTypes.UPDATE_SELECT_INDEX,
104 | selectIndex,
105 | };
106 | }
107 |
108 | export function getProjectTabsAction(projectTabs) {
109 | return {
110 | type: actionTypes.FETCH_PROJECT_TABS,
111 | projectTabs,
112 | };
113 | }
114 |
115 | export function getArticleLoadingAction(isShowLoading) {
116 | return {
117 | type: actionTypes.FETCH_ARTICLE_LOADING,
118 | isShowLoading,
119 | };
120 | }
121 |
122 | export function getOftenUsedWebsitesAction(websites) {
123 | return {
124 | type: actionTypes.FETCH_OFTEN_USED_WEBSITES,
125 | websites,
126 | };
127 | }
128 |
129 | export function getSearchHotKeyAction(hotKey) {
130 | return {
131 | type: actionTypes.FETCH_SEARCH_HOT_KEY,
132 | hotKey,
133 | };
134 | }
135 |
136 | export function getSearchArticleAction(articleData) {
137 | return {
138 | type: actionTypes.FETCH_SEARCH_ARTICLE,
139 | articleData,
140 | };
141 | }
142 |
143 | export function getSearchArticleMoreAction(articleData) {
144 | return {
145 | type: actionTypes.FETCH_SEARCH_ARTICLE_MORE,
146 | articleData,
147 | };
148 | }
149 |
150 | export function clearSearchArticleAction() {
151 | return {
152 | type: actionTypes.CLEAR_SEARCH_ARTICLE,
153 | };
154 | }
155 |
156 | export function getCollectArticleListAction(collectData) {
157 | return {
158 | type: actionTypes.FETCH_COLLECT_ARTICLE,
159 | collectData,
160 | };
161 | }
162 |
163 | export function getCollectArticleMoreAction(collectData) {
164 | return {
165 | type: actionTypes.FETCH_COLLECT_ARTICLE_MORE,
166 | collectData,
167 | };
168 | }
169 |
170 | export function getHomeAddCollectAction(index) {
171 | return {
172 | type: actionTypes.FETCH_HOME_ADD_COLLECT,
173 | index,
174 | };
175 | }
176 |
177 | export function getHomeCancelCollectAction(index) {
178 | return {
179 | type: actionTypes.FETCH_HOME_CANCEL_COLLECT,
180 | index,
181 | };
182 | }
183 |
184 | export function getMyCollectCancelCollectAction(index) {
185 | return {
186 | type: actionTypes.FETCH_MYCOLLECT_CANCEL_COLLECT,
187 | index,
188 | };
189 | }
190 |
191 | export function getMyCollectAddCollectAction(index) {
192 | return {
193 | type: actionTypes.FETCH_MYCOLLECT_ADD_COLLECT,
194 | index,
195 | };
196 | }
197 |
198 | export function getSearchArticleAddCollectAction(index) {
199 | return {
200 | type: actionTypes.FETCH_SEARCH_ARTICLE_ADD_COLLECT,
201 | index,
202 | };
203 | }
204 |
205 | export function getSearchArticleCancelCollectAction(index) {
206 | return {
207 | type: actionTypes.FETCH_SEARCH_ARTICLE_CANCEL_COLLECT,
208 | index,
209 | };
210 | }
211 |
212 | export function getMyCoinListAction(coinList) {
213 | return {
214 | type: actionTypes.FETCH_MY_COIN_LIST,
215 | coinList,
216 | };
217 | }
218 |
219 | export function getMyCoinListMoreAction(coinList) {
220 | return {
221 | type: actionTypes.FETCH_MY_COIN_LIST_MORE,
222 | coinList,
223 | };
224 | }
225 |
226 | export function getMyCoinInfoAction(coinInfo) {
227 | return {
228 | type: actionTypes.FETCH_MY_COIN_INFO,
229 | coinInfo,
230 | };
231 | }
232 |
233 | export function getSwitchAPPLanguageAction(language) {
234 | return {
235 | type: actionTypes.SWITCH_APP_LANGUAGE,
236 | language,
237 | };
238 | }
239 |
--------------------------------------------------------------------------------
/ios/RN_WanAndroid.xcodeproj/xcshareddata/xcschemes/RN_WanAndroid-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 |
--------------------------------------------------------------------------------
/src/screen/drawer/LoginScreen.js:
--------------------------------------------------------------------------------
1 | import React, {PureComponent} from 'react';
2 | import {StyleSheet, Text, TextInput, View, ScrollView} from 'react-native';
3 | import {connect} from 'react-redux';
4 | import NavBar from '../../component/NavBar';
5 | import {DEVICE_WIDTH, getRealDP as dp} from '../../utils/screenUtil';
6 | import Color from '../../utils/Color';
7 | import {i18n, showToast} from '../../utils/Utility';
8 | import {fetchToLogin} from '../../actions';
9 | import Icon from 'react-native-vector-icons/Ionicons';
10 | import Touchable from '../../component/Touchable';
11 |
12 | const propTypes = {};
13 | const defaultProps = {};
14 |
15 | /**
16 | * LoginScreen
17 | */
18 | class LoginScreen extends PureComponent {
19 | constructor(props) {
20 | super(props);
21 | this.state = {
22 | userName: '',
23 | password: '',
24 | isSecure: true,
25 | };
26 | this.toLogin = this.toLogin.bind(this);
27 | }
28 |
29 | toLogin() {
30 | const {navigation} = this.props;
31 | if (this.state.userName === '') {
32 | showToast(i18n('User name cannot be empty'));
33 | } else if (this.state.password === '') {
34 | showToast(i18n('Password cannot be empty'));
35 | } else {
36 | fetchToLogin(
37 | {
38 | username: this.state.userName,
39 | password: this.state.password,
40 | },
41 | navigation,
42 | );
43 | }
44 | }
45 |
46 | render() {
47 | const {navigation, themeColor} = this.props;
48 | return (
49 |
50 |
51 |
52 |
53 |
54 |
55 | {
63 | this.setState({userName: text});
64 | }}
65 | />
66 |
67 |
68 |
69 | {
76 | this.setState({password: text});
77 | }}
78 | />
79 | {
84 | this.setState({isSecure: !this.state.isSecure});
85 | }}>
86 |
91 |
92 |
93 |
96 | {i18n('login')}
97 |
98 | {
100 | navigation.navigate('Register');
101 | }}>
102 |
103 |
104 | {i18n('New users? To')}
105 |
106 | {i18n('register')}
107 |
108 |
109 |
110 |
111 |
112 | );
113 | }
114 | }
115 |
116 | const styles = StyleSheet.create({
117 | container: {
118 | flex: 1,
119 | backgroundColor: Color.WHITE,
120 | },
121 | content: {
122 | flex: 1,
123 | alignItems: 'center',
124 | paddingTop: dp(100),
125 | },
126 | textInputWrapper: {
127 | flexDirection: 'row',
128 | alignItems: 'center',
129 | },
130 | textInput: {
131 | width: DEVICE_WIDTH * 0.7,
132 | height: dp(80),
133 | margin: dp(20),
134 | borderColor: Color.ICON_GRAY,
135 | borderBottomWidth: dp(1),
136 | paddingLeft: dp(5),
137 | paddingRight: dp(50),
138 | color: Color.TEXT_MAIN,
139 | },
140 | login: {
141 | width: DEVICE_WIDTH * 0.7,
142 | marginTop: dp(60),
143 | padding: dp(30),
144 | borderRadius: dp(50),
145 | },
146 | loginText: {
147 | color: 'white',
148 | textAlign: 'center',
149 | fontSize: dp(32),
150 | },
151 | eye: {
152 | position: 'absolute',
153 | right: dp(20),
154 | backgroundColor: Color.WHITE,
155 | padding: dp(5),
156 | },
157 | register: {
158 | marginTop: dp(40),
159 | fontSize: dp(28),
160 | },
161 | });
162 |
163 | LoginScreen.propTypes = propTypes;
164 | LoginScreen.defaultProps = defaultProps;
165 |
166 | const mapStateToProps = state => {
167 | return {
168 | isLogin: state.user.isLogin,
169 | userInfo: state.user.userInfo,
170 | themeColor: state.user.themeColor,
171 | };
172 | };
173 |
174 | export default connect(mapStateToProps)(LoginScreen);
175 |
--------------------------------------------------------------------------------