├── .watchmanconfig
├── .gitattributes
├── .babelrc
├── app.json
├── Reading_Logo.png
├── app
├── img
│ ├── splash.png
│ ├── about_logo.png
│ ├── arrow_left.png
│ ├── share_icon_moments.png
│ └── share_icon_wechat.png
├── utils
│ ├── ItemsUtil.js
│ ├── UrlUtil.js
│ ├── NavigationUtil.js
│ ├── ToastUtil.js
│ ├── FormatUtil.js
│ └── RequestUtil.js
├── constants
│ ├── Urls.js
│ └── ActionTypes.js
├── reducers
│ ├── index.js
│ ├── category.js
│ └── read.js
├── sagas
│ ├── index.js
│ ├── category.js
│ ├── read.js
│ └── _spec_
│ │ ├── category.spec.js
│ │ └── read.spec.js
├── actions
│ ├── category.js
│ └── read.js
├── root.js
├── components
│ ├── LoadingView.js
│ ├── ImageButton.js
│ ├── Button.js
│ ├── Loading.js
│ └── GridView.js
├── pages
│ ├── MainPage
│ │ ├── Footer.js
│ │ ├── ItemListView.js
│ │ ├── EmptyView.js
│ │ ├── ItemCell.js
│ │ └── Main.js
│ ├── Splash.js
│ ├── Feedback
│ │ └── Feedback.js
│ ├── About
│ │ └── About.js
│ ├── Category
│ │ └── Category.js
│ └── ItemDetail
│ │ └── WebViewPage.js
├── store
│ └── configure-store.js
└── containers
│ ├── MainContainer.js
│ ├── CategoryContainer.js
│ └── app.js
├── .travis
└── secrets.tar.enc
├── ios
├── Bugly.framework
│ ├── Bugly
│ ├── Modules
│ │ └── module.modulemap
│ └── Headers
│ │ ├── BuglyLog.h
│ │ ├── BuglyConfig.h
│ │ └── Bugly.h
├── reading
│ ├── Fonts
│ │ └── Ionicons.ttf
│ ├── Images.xcassets
│ │ ├── Contents.json
│ │ ├── AppIcon.appiconset
│ │ │ ├── Icon-40.png
│ │ │ ├── Icon-76.png
│ │ │ ├── Icon-40@2x.png
│ │ │ ├── Icon-40@3x.png
│ │ │ ├── Icon-60@2x.png
│ │ │ ├── Icon-60@3x.png
│ │ │ ├── Icon-76@2x.png
│ │ │ ├── Icon-83.5@2x.png
│ │ │ ├── Icon-Small.png
│ │ │ ├── Icon-Small@2x.png
│ │ │ ├── Icon-Small@3x.png
│ │ │ └── Contents.json
│ │ └── LaunchImage.launchimage
│ │ │ ├── Default320x480.png
│ │ │ ├── Default640x960.png
│ │ │ ├── Default1024x768.png
│ │ │ ├── Default1125x2436.png
│ │ │ ├── Default1242x2208.png
│ │ │ ├── Default1536x2048.png
│ │ │ ├── Default1920x1080.png
│ │ │ ├── Default2048x1536.png
│ │ │ ├── Default2208x1242.png
│ │ │ ├── Default2436x1125.png
│ │ │ ├── Default3840x2160.png
│ │ │ ├── Default640x1136.png
│ │ │ ├── Default750x1334.png
│ │ │ ├── Default768x1024.png
│ │ │ └── Contents.json
│ ├── AppDelegate.h
│ ├── main.m
│ ├── AppDelegate.m
│ └── Info.plist
├── reading-tvOS
│ └── Info.plist
└── reading.xcodeproj
│ └── xcshareddata
│ └── xcschemes
│ ├── reading.xcscheme
│ └── reading-tvOS.xcscheme
├── screenshot
├── iReading_Main.png
├── iReading_Article.png
├── iReading_Category.png
├── iReading_iOS_Main.png
└── iReading_iOS_Share.png
├── android
├── app
│ ├── src
│ │ └── main
│ │ │ ├── res
│ │ │ ├── values
│ │ │ │ ├── strings.xml
│ │ │ │ ├── colors.xml
│ │ │ │ └── styles.xml
│ │ │ ├── mipmap-hdpi
│ │ │ │ └── ic_launcher.png
│ │ │ ├── mipmap-mdpi
│ │ │ │ └── ic_launcher.png
│ │ │ ├── mipmap-xhdpi
│ │ │ │ ├── ic_launcher.png
│ │ │ │ └── launch_screen.png
│ │ │ ├── mipmap-xxhdpi
│ │ │ │ └── ic_launcher.png
│ │ │ └── layout
│ │ │ │ └── launch_screen.xml
│ │ │ ├── assets
│ │ │ └── fonts
│ │ │ │ └── Ionicons.ttf
│ │ │ ├── java
│ │ │ └── com
│ │ │ │ └── reading
│ │ │ │ ├── ReadingNativeModuleCallExceptionHandler.java
│ │ │ │ ├── ReactNativeJSCrashReceiver.java
│ │ │ │ ├── wxapi
│ │ │ │ └── WXEntryActivity.java
│ │ │ │ ├── MainActivity.java
│ │ │ │ └── MainApplication.java
│ │ │ └── AndroidManifest.xml
│ ├── BUCK
│ ├── proguard-rules.pro
│ ├── react.gradle
│ └── build.gradle
├── gradle
│ └── wrapper
│ │ ├── gradle-wrapper.jar
│ │ └── gradle-wrapper.properties
├── keystores
│ ├── debug.keystore.properties
│ └── BUCK
├── reading.iml
├── settings.gradle
├── build.gradle
├── gradle.properties
├── gradlew.bat
└── gradlew
├── tsconfig.json
├── .buckconfig
├── .codeclimate.yml
├── .eslintrc
├── index.js
├── .gitignore
├── CONTRIBUTING.md
├── .flowconfig
├── package.json
├── .travis.yml
├── README.md
└── LICENSE
/.watchmanconfig:
--------------------------------------------------------------------------------
1 | {}
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | *.pbxproj -text
2 |
--------------------------------------------------------------------------------
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": ["react-native"]
3 | }
4 |
--------------------------------------------------------------------------------
/app.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "reading",
3 | "displayName": "iReading"
4 | }
--------------------------------------------------------------------------------
/Reading_Logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/attentiveness/reading/HEAD/Reading_Logo.png
--------------------------------------------------------------------------------
/app/img/splash.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/attentiveness/reading/HEAD/app/img/splash.png
--------------------------------------------------------------------------------
/.travis/secrets.tar.enc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/attentiveness/reading/HEAD/.travis/secrets.tar.enc
--------------------------------------------------------------------------------
/app/img/about_logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/attentiveness/reading/HEAD/app/img/about_logo.png
--------------------------------------------------------------------------------
/app/img/arrow_left.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/attentiveness/reading/HEAD/app/img/arrow_left.png
--------------------------------------------------------------------------------
/ios/Bugly.framework/Bugly:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/attentiveness/reading/HEAD/ios/Bugly.framework/Bugly
--------------------------------------------------------------------------------
/screenshot/iReading_Main.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/attentiveness/reading/HEAD/screenshot/iReading_Main.png
--------------------------------------------------------------------------------
/app/img/share_icon_moments.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/attentiveness/reading/HEAD/app/img/share_icon_moments.png
--------------------------------------------------------------------------------
/app/img/share_icon_wechat.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/attentiveness/reading/HEAD/app/img/share_icon_wechat.png
--------------------------------------------------------------------------------
/ios/reading/Fonts/Ionicons.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/attentiveness/reading/HEAD/ios/reading/Fonts/Ionicons.ttf
--------------------------------------------------------------------------------
/ios/reading/Images.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "version" : 1,
4 | "author" : "xcode"
5 | }
6 | }
--------------------------------------------------------------------------------
/screenshot/iReading_Article.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/attentiveness/reading/HEAD/screenshot/iReading_Article.png
--------------------------------------------------------------------------------
/screenshot/iReading_Category.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/attentiveness/reading/HEAD/screenshot/iReading_Category.png
--------------------------------------------------------------------------------
/screenshot/iReading_iOS_Main.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/attentiveness/reading/HEAD/screenshot/iReading_iOS_Main.png
--------------------------------------------------------------------------------
/screenshot/iReading_iOS_Share.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/attentiveness/reading/HEAD/screenshot/iReading_iOS_Share.png
--------------------------------------------------------------------------------
/android/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | iReading
3 |
4 |
--------------------------------------------------------------------------------
/android/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/attentiveness/reading/HEAD/android/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "allowJs": true
4 | },
5 | "exclude": [
6 | "node_modules"
7 | ]
8 | }
--------------------------------------------------------------------------------
/.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/Ionicons.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/attentiveness/reading/HEAD/android/app/src/main/assets/fonts/Ionicons.ttf
--------------------------------------------------------------------------------
/.codeclimate.yml:
--------------------------------------------------------------------------------
1 | engines:
2 | eslint:
3 | enabled: true
4 | config:
5 | config: ./.eslintrc
6 | ratings:
7 | paths:
8 | - app/**
9 | - "**.js"
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/attentiveness/reading/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/attentiveness/reading/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/attentiveness/reading/HEAD/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-xhdpi/launch_screen.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/attentiveness/reading/HEAD/android/app/src/main/res/mipmap-xhdpi/launch_screen.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/attentiveness/reading/HEAD/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/android/app/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #3e9ce9
4 |
--------------------------------------------------------------------------------
/android/keystores/debug.keystore.properties:
--------------------------------------------------------------------------------
1 | key.store=debug.keystore
2 | key.alias=androiddebugkey
3 | key.store.password=android
4 | key.alias.password=android
5 |
--------------------------------------------------------------------------------
/ios/reading/Images.xcassets/AppIcon.appiconset/Icon-40.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/attentiveness/reading/HEAD/ios/reading/Images.xcassets/AppIcon.appiconset/Icon-40.png
--------------------------------------------------------------------------------
/ios/reading/Images.xcassets/AppIcon.appiconset/Icon-76.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/attentiveness/reading/HEAD/ios/reading/Images.xcassets/AppIcon.appiconset/Icon-76.png
--------------------------------------------------------------------------------
/ios/reading/Images.xcassets/AppIcon.appiconset/Icon-40@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/attentiveness/reading/HEAD/ios/reading/Images.xcassets/AppIcon.appiconset/Icon-40@2x.png
--------------------------------------------------------------------------------
/ios/reading/Images.xcassets/AppIcon.appiconset/Icon-40@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/attentiveness/reading/HEAD/ios/reading/Images.xcassets/AppIcon.appiconset/Icon-40@3x.png
--------------------------------------------------------------------------------
/ios/reading/Images.xcassets/AppIcon.appiconset/Icon-60@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/attentiveness/reading/HEAD/ios/reading/Images.xcassets/AppIcon.appiconset/Icon-60@2x.png
--------------------------------------------------------------------------------
/ios/reading/Images.xcassets/AppIcon.appiconset/Icon-60@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/attentiveness/reading/HEAD/ios/reading/Images.xcassets/AppIcon.appiconset/Icon-60@3x.png
--------------------------------------------------------------------------------
/ios/reading/Images.xcassets/AppIcon.appiconset/Icon-76@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/attentiveness/reading/HEAD/ios/reading/Images.xcassets/AppIcon.appiconset/Icon-76@2x.png
--------------------------------------------------------------------------------
/ios/reading/Images.xcassets/AppIcon.appiconset/Icon-83.5@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/attentiveness/reading/HEAD/ios/reading/Images.xcassets/AppIcon.appiconset/Icon-83.5@2x.png
--------------------------------------------------------------------------------
/ios/reading/Images.xcassets/AppIcon.appiconset/Icon-Small.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/attentiveness/reading/HEAD/ios/reading/Images.xcassets/AppIcon.appiconset/Icon-Small.png
--------------------------------------------------------------------------------
/ios/reading/Images.xcassets/AppIcon.appiconset/Icon-Small@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/attentiveness/reading/HEAD/ios/reading/Images.xcassets/AppIcon.appiconset/Icon-Small@2x.png
--------------------------------------------------------------------------------
/ios/reading/Images.xcassets/AppIcon.appiconset/Icon-Small@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/attentiveness/reading/HEAD/ios/reading/Images.xcassets/AppIcon.appiconset/Icon-Small@3x.png
--------------------------------------------------------------------------------
/ios/reading/Images.xcassets/LaunchImage.launchimage/Default320x480.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/attentiveness/reading/HEAD/ios/reading/Images.xcassets/LaunchImage.launchimage/Default320x480.png
--------------------------------------------------------------------------------
/ios/reading/Images.xcassets/LaunchImage.launchimage/Default640x960.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/attentiveness/reading/HEAD/ios/reading/Images.xcassets/LaunchImage.launchimage/Default640x960.png
--------------------------------------------------------------------------------
/ios/reading/Images.xcassets/LaunchImage.launchimage/Default1024x768.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/attentiveness/reading/HEAD/ios/reading/Images.xcassets/LaunchImage.launchimage/Default1024x768.png
--------------------------------------------------------------------------------
/ios/reading/Images.xcassets/LaunchImage.launchimage/Default1125x2436.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/attentiveness/reading/HEAD/ios/reading/Images.xcassets/LaunchImage.launchimage/Default1125x2436.png
--------------------------------------------------------------------------------
/ios/reading/Images.xcassets/LaunchImage.launchimage/Default1242x2208.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/attentiveness/reading/HEAD/ios/reading/Images.xcassets/LaunchImage.launchimage/Default1242x2208.png
--------------------------------------------------------------------------------
/ios/reading/Images.xcassets/LaunchImage.launchimage/Default1536x2048.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/attentiveness/reading/HEAD/ios/reading/Images.xcassets/LaunchImage.launchimage/Default1536x2048.png
--------------------------------------------------------------------------------
/ios/reading/Images.xcassets/LaunchImage.launchimage/Default1920x1080.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/attentiveness/reading/HEAD/ios/reading/Images.xcassets/LaunchImage.launchimage/Default1920x1080.png
--------------------------------------------------------------------------------
/ios/reading/Images.xcassets/LaunchImage.launchimage/Default2048x1536.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/attentiveness/reading/HEAD/ios/reading/Images.xcassets/LaunchImage.launchimage/Default2048x1536.png
--------------------------------------------------------------------------------
/ios/reading/Images.xcassets/LaunchImage.launchimage/Default2208x1242.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/attentiveness/reading/HEAD/ios/reading/Images.xcassets/LaunchImage.launchimage/Default2208x1242.png
--------------------------------------------------------------------------------
/ios/reading/Images.xcassets/LaunchImage.launchimage/Default2436x1125.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/attentiveness/reading/HEAD/ios/reading/Images.xcassets/LaunchImage.launchimage/Default2436x1125.png
--------------------------------------------------------------------------------
/ios/reading/Images.xcassets/LaunchImage.launchimage/Default3840x2160.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/attentiveness/reading/HEAD/ios/reading/Images.xcassets/LaunchImage.launchimage/Default3840x2160.png
--------------------------------------------------------------------------------
/ios/reading/Images.xcassets/LaunchImage.launchimage/Default640x1136.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/attentiveness/reading/HEAD/ios/reading/Images.xcassets/LaunchImage.launchimage/Default640x1136.png
--------------------------------------------------------------------------------
/ios/reading/Images.xcassets/LaunchImage.launchimage/Default750x1334.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/attentiveness/reading/HEAD/ios/reading/Images.xcassets/LaunchImage.launchimage/Default750x1334.png
--------------------------------------------------------------------------------
/ios/reading/Images.xcassets/LaunchImage.launchimage/Default768x1024.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/attentiveness/reading/HEAD/ios/reading/Images.xcassets/LaunchImage.launchimage/Default768x1024.png
--------------------------------------------------------------------------------
/android/keystores/BUCK:
--------------------------------------------------------------------------------
1 | keystore(
2 | name = "debug",
3 | properties = "debug.keystore.properties",
4 | store = "debug.keystore",
5 | visibility = [
6 | "PUBLIC",
7 | ],
8 | )
9 |
--------------------------------------------------------------------------------
/android/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Sun Mar 19 15:07:48 CST 2017
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 | distributionUrl=https\://services.gradle.org/distributions/gradle-3.3-all.zip
7 |
--------------------------------------------------------------------------------
/ios/Bugly.framework/Modules/module.modulemap:
--------------------------------------------------------------------------------
1 | framework module Bugly {
2 | umbrella header "Bugly.h"
3 |
4 | export *
5 | module * { export * }
6 |
7 | link framework "Foundation"
8 | link framework "Security"
9 | link framework "SystemConfiguration"
10 | link "c++"
11 | link "z"
12 | }
13 |
--------------------------------------------------------------------------------
/android/app/src/main/res/layout/launch_screen.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
--------------------------------------------------------------------------------
/ios/reading/AppDelegate.h:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (c) 2015-present, Facebook, Inc.
3 | *
4 | * This source code is licensed under the MIT license found in the
5 | * LICENSE file in the root directory of this source tree.
6 | */
7 |
8 | #import
9 |
10 | @interface AppDelegate : UIResponder
11 |
12 | @property (nonatomic, strong) UIWindow *window;
13 |
14 | @end
15 |
--------------------------------------------------------------------------------
/app/utils/ItemsUtil.js:
--------------------------------------------------------------------------------
1 | const _ = require('lodash');
2 |
3 | export const getArticleList = list =>
4 | (list === undefined ? [] : removeExpiredItem(list));
5 |
6 | export const removeExpiredItem = (list) => {
7 | _.remove(list, item => item.expire);
8 | return list || [];
9 | };
10 |
11 | export const getTypeName = (typeList, typeId) =>
12 | _.head(_.filter(typeList, o => o.id === typeId.toString())).name;
13 |
--------------------------------------------------------------------------------
/ios/reading/main.m:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (c) 2015-present, Facebook, Inc.
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 |
--------------------------------------------------------------------------------
/android/app/src/main/java/com/reading/ReadingNativeModuleCallExceptionHandler.java:
--------------------------------------------------------------------------------
1 | package com.reading;
2 |
3 | import com.facebook.react.bridge.NativeModuleCallExceptionHandler;
4 | import com.tencent.bugly.crashreport.CrashReport;
5 |
6 | /**
7 | * Created by caolicheng on 2016/10/25.
8 | */
9 |
10 | public class ReadingNativeModuleCallExceptionHandler implements NativeModuleCallExceptionHandler {
11 | @Override
12 | public void handleException(Exception e) {
13 | CrashReport.postCatchedException(e);
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "airbnb",
3 | "env": {
4 | "browser": true,
5 | "node": true,
6 | "jest": true
7 | },
8 | "parser": "babel-eslint",
9 | "rules": {
10 | "comma-dangle": 0,
11 | "react/prop-types": 0,
12 | "no-use-before-define": 0,
13 | "radix": 0,
14 | "no-param-reassign": 0,
15 | "react/jsx-filename-extension": 0,
16 | "no-plusplus": 0,
17 | "react/require-default-props": 0,
18 | "react/forbid-prop-types": 0,
19 | "prefer-destructuring": 0,
20 | "react/jsx-no-bind": 0
21 | }
22 | }
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | /**
2 | *
3 | * Copyright 2015-present reading
4 | *
5 | * Licensed under the Apache License, Version 2.0 (the "License");
6 | * you may not use this file except in compliance with the License.
7 | * You may obtain a copy of the License at
8 | *
9 | * http://www.apache.org/licenses/LICENSE-2.0
10 | *
11 | * Unless required by applicable law or agreed to in writing, software
12 | * distributed under the License is distributed on an "AS IS" BASIS,
13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | * See the License for the specific language governing permissions and
15 | * limitations under the License.
16 | *
17 | */
18 | import { AppRegistry } from 'react-native';
19 | import Root from './app/root';
20 |
21 | AppRegistry.registerComponent('reading', () => Root);
22 |
--------------------------------------------------------------------------------
/android/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
13 |
14 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/app/constants/Urls.js:
--------------------------------------------------------------------------------
1 | /**
2 | *
3 | * Copyright 2016-present reading
4 | *
5 | * Licensed under the Apache License, Version 2.0 (the "License");
6 | * you may not use this file except in compliance with the License.
7 | * You may obtain a copy of the License at
8 | *
9 | * http://www.apache.org/licenses/LICENSE-2.0
10 | *
11 | * Unless required by applicable law or agreed to in writing, software
12 | * distributed under the License is distributed on an "AS IS" BASIS,
13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | * See the License for the specific language governing permissions and
15 | * limitations under the License.
16 | *
17 | */
18 | export const WEXIN_ARTICLE_LIST = 'http://route.showapi.com/582-2';
19 | export const WEXIN_ARTICLE_TYPE = 'http://route.showapi.com/582-1';
20 |
--------------------------------------------------------------------------------
/app/reducers/index.js:
--------------------------------------------------------------------------------
1 | /**
2 | *
3 | * Copyright 2016-present reading
4 | *
5 | * Licensed under the Apache License, Version 2.0 (the "License");
6 | * you may not use this file except in compliance with the License.
7 | * You may obtain a copy of the License at
8 | *
9 | * http://www.apache.org/licenses/LICENSE-2.0
10 | *
11 | * Unless required by applicable law or agreed to in writing, software
12 | * distributed under the License is distributed on an "AS IS" BASIS,
13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | * See the License for the specific language governing permissions and
15 | * limitations under the License.
16 | *
17 | */
18 | import { combineReducers } from 'redux';
19 | import read from './read';
20 | import category from './category';
21 |
22 | const rootReducer = combineReducers({
23 | read,
24 | category
25 | });
26 |
27 | export default rootReducer;
28 |
--------------------------------------------------------------------------------
/android/reading.iml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/app/sagas/index.js:
--------------------------------------------------------------------------------
1 | /**
2 | *
3 | * Copyright 2016-present reading
4 | *
5 | * Licensed under the Apache License, Version 2.0 (the "License");
6 | * you may not use this file except in compliance with the License.
7 | * You may obtain a copy of the License at
8 | *
9 | * http://www.apache.org/licenses/LICENSE-2.0
10 | *
11 | * Unless required by applicable law or agreed to in writing, software
12 | * distributed under the License is distributed on an "AS IS" BASIS,
13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | * See the License for the specific language governing permissions and
15 | * limitations under the License.
16 | *
17 | */
18 | import { all, fork } from 'redux-saga/effects';
19 |
20 | import { watchRequestTypeList } from './category';
21 | import { watchRequestArticleList } from './read';
22 |
23 | export default function* rootSaga() {
24 | yield all([fork(watchRequestTypeList), fork(watchRequestArticleList)]);
25 | }
26 |
--------------------------------------------------------------------------------
/app/utils/UrlUtil.js:
--------------------------------------------------------------------------------
1 | /**
2 | *
3 | * Copyright 2016-present reading
4 | *
5 | * Licensed under the Apache License, Version 2.0 (the "License");
6 | * you may not use this file except in compliance with the License.
7 | * You may obtain a copy of the License at
8 | *
9 | * http://www.apache.org/licenses/LICENSE-2.0
10 | *
11 | * Unless required by applicable law or agreed to in writing, software
12 | * distributed under the License is distributed on an "AS IS" BASIS,
13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | * See the License for the specific language governing permissions and
15 | * limitations under the License.
16 | *
17 | */
18 | const getUrl = (url) => {
19 | if (url.indexOf('?') === -1) {
20 | return `${url}?showapi_appid=85481&showapi_sign=8c715c0272e241bbbb54280a522334b2`;
21 | }
22 | return `${url}&showapi_appid=85481&showapi_sign=8c715c0272e241bbbb54280a522334b2`;
23 | };
24 |
25 | export default getUrl;
26 |
--------------------------------------------------------------------------------
/android/app/src/main/java/com/reading/ReactNativeJSCrashReceiver.java:
--------------------------------------------------------------------------------
1 | package com.reading;
2 |
3 | import android.content.BroadcastReceiver;
4 | import android.content.Context;
5 | import android.content.Intent;
6 |
7 | import com.tencent.bugly.crashreport.CrashReport;
8 |
9 | /**
10 | * Created by caolicheng on 2016/9/29.
11 | */
12 |
13 | public class ReactNativeJSCrashReceiver extends BroadcastReceiver {
14 | @Override
15 | public void onReceive(Context context, Intent intent) {
16 | if (intent.getAction().equals("com.richardcao.android.REACT_NATIVE_CRASH_REPORT_ACTION")) {
17 | Throwable js = (Throwable) intent.getSerializableExtra("JavascriptException");
18 | Throwable e = (Throwable) intent.getSerializableExtra("Exception");
19 | if (js != null) {
20 | CrashReport.postCatchedException(js);
21 | }
22 | if (e != null) {
23 | CrashReport.postCatchedException(e);
24 | }
25 | }
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/app/utils/NavigationUtil.js:
--------------------------------------------------------------------------------
1 | /**
2 | *
3 | * Copyright 2016-present reading
4 | *
5 | * Licensed under the Apache License, Version 2.0 (the "License");
6 | * you may not use this file except in compliance with the License.
7 | * You may obtain a copy of the License at
8 | *
9 | * http://www.apache.org/licenses/LICENSE-2.0
10 | *
11 | * Unless required by applicable law or agreed to in writing, software
12 | * distributed under the License is distributed on an "AS IS" BASIS,
13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | * See the License for the specific language governing permissions and
15 | * limitations under the License.
16 | *
17 | */
18 | import { NavigationActions } from 'react-navigation';
19 |
20 | const reset = (navigation, routeName) => {
21 | const resetAction = NavigationActions.reset({
22 | index: 0,
23 | actions: [NavigationActions.navigate({ routeName })]
24 | });
25 | navigation.dispatch(resetAction);
26 | };
27 |
28 | export default {
29 | reset
30 | };
31 |
--------------------------------------------------------------------------------
/android/settings.gradle:
--------------------------------------------------------------------------------
1 | rootProject.name = 'reading'
2 | include ':react-native-splash-screen'
3 | project(':react-native-splash-screen').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-splash-screen/android')
4 |
5 | include ':app', ':RCTWeChat', ':react-native-code-push', ':RNDeviceInfo', ':react-native-vector-icons', ':react-native-exceptions-manager'
6 | project(':RCTWeChat').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-wechat/android')
7 | project(':react-native-code-push').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-code-push/android/app')
8 | project(':RNDeviceInfo').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-device-info/android')
9 | project(':react-native-vector-icons').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-vector-icons/android')
10 | project(':react-native-exceptions-manager').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-exceptions-manager/android/app')
--------------------------------------------------------------------------------
/android/build.gradle:
--------------------------------------------------------------------------------
1 | // Top-level build file where you can add configuration options common to all sub-projects/modules.
2 |
3 | buildscript {
4 | repositories {
5 | jcenter()
6 | maven {
7 | url 'https://maven.google.com/'
8 | name 'Google'
9 | }
10 | }
11 | dependencies {
12 | classpath 'com.android.tools.build:gradle:2.2.3'
13 | classpath 'com.tencent.mm:AndResGuard-gradle-plugin:1.2.10'
14 | // NOTE: Do not place your application dependencies here; they belong
15 | // in the individual module build.gradle files
16 | }
17 | }
18 |
19 | allprojects {
20 | repositories {
21 | mavenLocal()
22 | jcenter()
23 | maven {
24 | // All of React Native (JS, Obj-C sources, Android binaries) is installed from npm
25 | url "$rootDir/../node_modules/react-native/android"
26 | }
27 | maven {
28 | url 'https://maven.google.com/'
29 | name 'Google'
30 | }
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/app/constants/ActionTypes.js:
--------------------------------------------------------------------------------
1 | /**
2 | *
3 | * Copyright 2016-present reading
4 | *
5 | * Licensed under the Apache License, Version 2.0 (the "License");
6 | * you may not use this file except in compliance with the License.
7 | * You may obtain a copy of the License at
8 | *
9 | * http://www.apache.org/licenses/LICENSE-2.0
10 | *
11 | * Unless required by applicable law or agreed to in writing, software
12 | * distributed under the License is distributed on an "AS IS" BASIS,
13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | * See the License for the specific language governing permissions and
15 | * limitations under the License.
16 | *
17 | */
18 | export const REQUEST_ARTICLE_LIST = 'REQUEST_ARTICLE_LIST';
19 | export const FETCH_ARTICLE_LIST = 'FETCH_ARTICLE_LIST';
20 | export const RECEIVE_ARTICLE_LIST = 'RECEIVE_ARTICLE_LIST';
21 | export const REQUEST_TYPE_LIST = 'REQUEST_TYPE_LIST';
22 | export const FETCH_TYPE_LIST = 'FETCH_TYPE_LIST';
23 | export const RECEIVE_TYPE_LIST = 'RECEIVE_TYPE_LIST';
24 |
--------------------------------------------------------------------------------
/app/actions/category.js:
--------------------------------------------------------------------------------
1 | /**
2 | *
3 | * Copyright 2016-present reading
4 | *
5 | * Licensed under the Apache License, Version 2.0 (the "License");
6 | * you may not use this file except in compliance with the License.
7 | * You may obtain a copy of the License at
8 | *
9 | * http://www.apache.org/licenses/LICENSE-2.0
10 | *
11 | * Unless required by applicable law or agreed to in writing, software
12 | * distributed under the License is distributed on an "AS IS" BASIS,
13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | * See the License for the specific language governing permissions and
15 | * limitations under the License.
16 | *
17 | */
18 | import * as types from '../constants/ActionTypes';
19 |
20 | export function requestTypeList() {
21 | return {
22 | type: types.REQUEST_TYPE_LIST
23 | };
24 | }
25 |
26 | export function fetchTypeList() {
27 | return {
28 | type: types.FETCH_TYPE_LIST
29 | };
30 | }
31 |
32 | export function receiveTypeList(typeList) {
33 | return {
34 | type: types.RECEIVE_TYPE_LIST,
35 | typeList
36 | };
37 | }
38 |
--------------------------------------------------------------------------------
/app/root.js:
--------------------------------------------------------------------------------
1 | /**
2 | *
3 | * Copyright 2016-present reading
4 | *
5 | * Licensed under the Apache License, Version 2.0 (the "License");
6 | * you may not use this file except in compliance with the License.
7 | * You may obtain a copy of the License at
8 | *
9 | * http://www.apache.org/licenses/LICENSE-2.0
10 | *
11 | * Unless required by applicable law or agreed to in writing, software
12 | * distributed under the License is distributed on an "AS IS" BASIS,
13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | * See the License for the specific language governing permissions and
15 | * limitations under the License.
16 | *
17 | */
18 | import React from 'react';
19 | import { Provider } from 'react-redux';
20 | import configureStore from './store/configure-store';
21 | import rootSaga from './sagas/index';
22 | import App from './containers/app';
23 |
24 | const store = configureStore();
25 |
26 | // run root saga
27 | store.runSaga(rootSaga);
28 |
29 | const Root = () => (
30 |
31 |
32 |
33 | );
34 |
35 | export default Root;
36 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # OSX
2 | #
3 | .DS_Store
4 |
5 | # Xcode
6 | #
7 | build/
8 | *.pbxuser
9 | !default.pbxuser
10 | *.mode1v3
11 | !default.mode1v3
12 | *.mode2v3
13 | !default.mode2v3
14 | *.perspectivev3
15 | !default.perspectivev3
16 | xcuserdata
17 | *.xccheckout
18 | *.moved-aside
19 | DerivedData
20 | *.hmap
21 | *.ipa
22 | *.xcuserstate
23 | project.xcworkspace
24 |
25 | # Android/IntelliJ
26 | #
27 | build/
28 | .idea
29 | .gradle
30 | local.properties
31 | *.iml
32 | android/app/reading.keystore
33 |
34 | # node.js
35 | #
36 | node_modules/
37 | npm-debug.log
38 | yarn-error.log
39 |
40 | # BUCK
41 | buck-out/
42 | \.buckd/
43 | *.keystore
44 |
45 | # Others
46 | .vscode/
47 | typings/
48 |
49 | # fastlane
50 | #
51 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the
52 | # screenshots whenever they are needed.
53 | # For more information about the recommended setup visit:
54 | # https://docs.fastlane.tools/best-practices/source-control/
55 |
56 | */fastlane/report.xml
57 | */fastlane/Preview.html
58 | */fastlane/screenshots
59 |
60 | # Bundle artifact
61 | *.jsbundle
62 |
--------------------------------------------------------------------------------
/android/app/src/main/java/com/reading/wxapi/WXEntryActivity.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2016-present reading
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.reading.wxapi;
18 |
19 | import android.app.Activity;
20 | import android.os.Bundle;
21 |
22 | import com.theweflex.react.WeChatModule;
23 |
24 | /**
25 | * Created by richardcao on 16/1/28.
26 | */
27 | public class WXEntryActivity extends Activity {
28 | @Override
29 | protected void onCreate(Bundle savedInstanceState) {
30 | super.onCreate(savedInstanceState);
31 | WeChatModule.handleIntent(getIntent());
32 | finish();
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/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 | org.gradle.jvmargs=-Xmx1536m
15 |
16 | # When configured, Gradle will run in incubating parallel mode.
17 | # This option should only be used with decoupled projects. More details, visit
18 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
19 | # org.gradle.parallel=true
20 |
21 | android.useDeprecatedNdk=true
22 |
23 | READING_RELEASE_STORE_FILE=reading.keystore
24 | READING_RELEASE_KEY_ALIAS=reading
25 | READING_RELEASE_STORE_PASSWORD=jiushigan
26 | READING_RELEASE_KEY_PASSWORD=jiushigan
27 |
28 | googlePlayServicesVersion=11.8.0
29 |
--------------------------------------------------------------------------------
/app/reducers/category.js:
--------------------------------------------------------------------------------
1 | /**
2 | *
3 | * Copyright 2016-present reading
4 | *
5 | * Licensed under the Apache License, Version 2.0 (the "License");
6 | * you may not use this file except in compliance with the License.
7 | * You may obtain a copy of the License at
8 | *
9 | * http://www.apache.org/licenses/LICENSE-2.0
10 | *
11 | * Unless required by applicable law or agreed to in writing, software
12 | * distributed under the License is distributed on an "AS IS" BASIS,
13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | * See the License for the specific language governing permissions and
15 | * limitations under the License.
16 | *
17 | */
18 | import * as types from '../constants/ActionTypes';
19 |
20 | const initialState = {
21 | loading: false,
22 | typeList: {}
23 | };
24 |
25 | export default function category(state = initialState, action) {
26 | switch (action.type) {
27 | case types.FETCH_TYPE_LIST:
28 | return Object.assign({}, state, {
29 | loading: true
30 | });
31 | case types.RECEIVE_TYPE_LIST:
32 | return Object.assign({}, state, {
33 | loading: false,
34 | typeList: action.typeList
35 | });
36 | default:
37 | return state;
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/app/components/LoadingView.js:
--------------------------------------------------------------------------------
1 | /**
2 | *
3 | * Copyright 2015-present reading
4 | *
5 | * Licensed under the Apache License, Version 2.0 (the "License");
6 | * you may not use this file except in compliance with the License.
7 | * You may obtain a copy of the License at
8 | *
9 | * http://www.apache.org/licenses/LICENSE-2.0
10 | *
11 | * Unless required by applicable law or agreed to in writing, software
12 | * distributed under the License is distributed on an "AS IS" BASIS,
13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | * See the License for the specific language governing permissions and
15 | * limitations under the License.
16 | *
17 | */
18 | import React from 'react';
19 | import { ActivityIndicator, Text, StyleSheet, View } from 'react-native';
20 |
21 | const LoadingView = () => (
22 |
23 |
24 | 数据加载中...
25 |
26 | );
27 |
28 | const styles = StyleSheet.create({
29 | loading: {
30 | flex: 1,
31 | alignItems: 'center',
32 | justifyContent: 'center',
33 | backgroundColor: 'white'
34 | },
35 | loadingText: {
36 | marginTop: 10,
37 | textAlign: 'center'
38 | }
39 | });
40 |
41 | export default LoadingView;
42 |
--------------------------------------------------------------------------------
/app/utils/ToastUtil.js:
--------------------------------------------------------------------------------
1 | /**
2 | *
3 | * Copyright 2016-present reading
4 | *
5 | * Licensed under the Apache License, Version 2.0 (the "License");
6 | * you may not use this file except in compliance with the License.
7 | * You may obtain a copy of the License at
8 | *
9 | * http://www.apache.org/licenses/LICENSE-2.0
10 | *
11 | * Unless required by applicable law or agreed to in writing, software
12 | * distributed under the License is distributed on an "AS IS" BASIS,
13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | * See the License for the specific language governing permissions and
15 | * limitations under the License.
16 | *
17 | */
18 | import { Alert, ToastAndroid, Platform } from 'react-native';
19 |
20 | const showShort = (content, isAlert) => {
21 | if (!content) {
22 | return;
23 | }
24 | if (isAlert || Platform.OS === 'ios') {
25 | Alert.alert('提示', content.toString());
26 | } else {
27 | ToastAndroid.show(content.toString(), ToastAndroid.SHORT);
28 | }
29 | };
30 |
31 | const showLong = (content, isAlert) => {
32 | if (isAlert || Platform.OS === 'ios') {
33 | Alert.alert('提示', content.toString());
34 | } else {
35 | ToastAndroid.show(content.toString(), ToastAndroid.LONG);
36 | }
37 | };
38 |
39 | export default {
40 | showShort,
41 | showLong
42 | };
43 |
--------------------------------------------------------------------------------
/app/pages/MainPage/Footer.js:
--------------------------------------------------------------------------------
1 | /**
2 | *
3 | * Copyright 2016-present reading
4 | *
5 | * Licensed under the Apache License, Version 2.0 (the "License");
6 | * you may not use this file except in compliance with the License.
7 | * You may obtain a copy of the License at
8 | *
9 | * http://www.apache.org/licenses/LICENSE-2.0
10 | *
11 | * Unless required by applicable law or agreed to in writing, software
12 | * distributed under the License is distributed on an "AS IS" BASIS,
13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | * See the License for the specific language governing permissions and
15 | * limitations under the License.
16 | *
17 | */
18 |
19 | import React from 'react';
20 | import { ActivityIndicator, StyleSheet, Text, View } from 'react-native';
21 |
22 | const Footer = () => (
23 |
24 |
25 | 数据加载中……
26 |
27 | );
28 |
29 | const styles = StyleSheet.create({
30 | footerContainer: {
31 | flex: 1,
32 | flexDirection: 'row',
33 | justifyContent: 'center',
34 | alignItems: 'center',
35 | padding: 5
36 | },
37 | footerText: {
38 | textAlign: 'center',
39 | fontSize: 16,
40 | marginLeft: 10
41 | }
42 | });
43 |
44 | export default Footer;
45 |
--------------------------------------------------------------------------------
/app/utils/FormatUtil.js:
--------------------------------------------------------------------------------
1 | /**
2 | *
3 | * Copyright 2016-present reading
4 | *
5 | * Licensed under the Apache License, Version 2.0 (the "License");
6 | * you may not use this file except in compliance with the License.
7 | * You may obtain a copy of the License at
8 | *
9 | * http://www.apache.org/licenses/LICENSE-2.0
10 | *
11 | * Unless required by applicable law or agreed to in writing, software
12 | * distributed under the License is distributed on an "AS IS" BASIS,
13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | * See the License for the specific language governing permissions and
15 | * limitations under the License.
16 | *
17 | */
18 | export const formatDateString = (timestamp) => {
19 | if (timestamp === undefined) {
20 | return '';
21 | }
22 | const date = new Date(parseInt(timestamp) * 1000);
23 | const year = date.getFullYear();
24 | const month = parseInt(date.getMonth()) + 1;
25 | const day = date.getDate();
26 | return `${year}-${month}-${day}`;
27 | };
28 |
29 | export const formatStringWithHtml = (originString) => {
30 | if (originString === undefined) {
31 | return '';
32 | }
33 | const newString = originString
34 | .replace(/ /g, ' ')
35 | .replace(/"/g, '"')
36 | .replace(/&/g, '&')
37 | .replace(/</g, '<')
38 | .replace(/>/g, '>');
39 | return newString;
40 | };
41 |
--------------------------------------------------------------------------------
/android/app/src/main/java/com/reading/MainActivity.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2016-present reading
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.reading;
18 |
19 | import android.os.Bundle;
20 |
21 | import com.facebook.react.ReactActivity;
22 |
23 | import org.devio.rn.splashscreen.SplashScreen;
24 |
25 | public class MainActivity extends ReactActivity {
26 |
27 | @Override
28 | protected void onCreate(Bundle savedInstanceState) {
29 | SplashScreen.show(this);
30 | super.onCreate(savedInstanceState);
31 | }
32 |
33 | /**
34 | * Returns the name of the main component registered from JavaScript.
35 | * This is used to schedule rendering of the component.
36 | */
37 | @Override
38 | protected String getMainComponentName() {
39 | return "reading";
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/app/actions/read.js:
--------------------------------------------------------------------------------
1 | /**
2 | *
3 | * Copyright 2016-present reading
4 | *
5 | * Licensed under the Apache License, Version 2.0 (the "License");
6 | * you may not use this file except in compliance with the License.
7 | * You may obtain a copy of the License at
8 | *
9 | * http://www.apache.org/licenses/LICENSE-2.0
10 | *
11 | * Unless required by applicable law or agreed to in writing, software
12 | * distributed under the License is distributed on an "AS IS" BASIS,
13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | * See the License for the specific language governing permissions and
15 | * limitations under the License.
16 | *
17 | */
18 | import * as types from '../constants/ActionTypes';
19 |
20 | export function requestArticleList(
21 | isRefreshing,
22 | loading,
23 | typeId,
24 | isLoadMore,
25 | page = 1
26 | ) {
27 | return {
28 | type: types.REQUEST_ARTICLE_LIST,
29 | isRefreshing,
30 | loading,
31 | isLoadMore,
32 | typeId,
33 | page
34 | };
35 | }
36 |
37 | export function fetchArticleList(isRefreshing, loading, isLoadMore = false) {
38 | return {
39 | type: types.FETCH_ARTICLE_LIST,
40 | isRefreshing,
41 | loading,
42 | isLoadMore
43 | };
44 | }
45 |
46 | export function receiveArticleList(articleList, typeId) {
47 | return {
48 | type: types.RECEIVE_ARTICLE_LIST,
49 | articleList,
50 | typeId
51 | };
52 | }
53 |
--------------------------------------------------------------------------------
/app/components/ImageButton.js:
--------------------------------------------------------------------------------
1 | /**
2 | *
3 | * Copyright 2016-present reading
4 | *
5 | * Licensed under the Apache License, Version 2.0 (the "License");
6 | * you may not use this file except in compliance with the License.
7 | * You may obtain a copy of the License at
8 | *
9 | * http://www.apache.org/licenses/LICENSE-2.0
10 | *
11 | * Unless required by applicable law or agreed to in writing, software
12 | * distributed under the License is distributed on an "AS IS" BASIS,
13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | * See the License for the specific language governing permissions and
15 | * limitations under the License.
16 | *
17 | */
18 | import React from 'react';
19 | import PropTypes from 'prop-types';
20 | import { ViewPropTypes, Image, TouchableOpacity } from 'react-native';
21 |
22 | const propTypes = {
23 | onPress: PropTypes.func,
24 | disabled: PropTypes.bool,
25 | source: PropTypes.object,
26 | style: ViewPropTypes.style,
27 | containerStyle: ViewPropTypes.style
28 | };
29 |
30 | const ImageButton = ({
31 | onPress, disabled, source, style, containerStyle
32 | }) => (
33 |
38 |
39 |
40 | );
41 |
42 | ImageButton.propTypes = propTypes;
43 |
44 | ImageButton.defaultProps = {
45 | onPress() {},
46 | disabled: false
47 | };
48 |
49 | export default ImageButton;
50 |
--------------------------------------------------------------------------------
/app/store/configure-store.js:
--------------------------------------------------------------------------------
1 | /**
2 | *
3 | * Copyright 2016-present reading
4 | *
5 | * Licensed under the Apache License, Version 2.0 (the "License");
6 | * you may not use this file except in compliance with the License.
7 | * You may obtain a copy of the License at
8 | *
9 | * http://www.apache.org/licenses/LICENSE-2.0
10 | *
11 | * Unless required by applicable law or agreed to in writing, software
12 | * distributed under the License is distributed on an "AS IS" BASIS,
13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | * See the License for the specific language governing permissions and
15 | * limitations under the License.
16 | *
17 | */
18 | import { createStore, applyMiddleware } from 'redux';
19 | import createSagaMiddleware, { END } from 'redux-saga';
20 |
21 | import rootReducer from '../reducers/index';
22 |
23 | const middlewares = [];
24 | const { logger } = require('redux-logger');
25 |
26 | // configuring saga middleware
27 | const sagaMiddleware = createSagaMiddleware();
28 |
29 | middlewares.push(sagaMiddleware);
30 | /* global __DEV__ */
31 | if (__DEV__) {
32 | middlewares.push(logger);
33 | }
34 | const createStoreWithMiddleware = applyMiddleware(...middlewares)(createStore);
35 |
36 | export default function configureStore(initialState) {
37 | const store = createStoreWithMiddleware(rootReducer, initialState);
38 | // install saga run
39 | store.runSaga = sagaMiddleware.run;
40 | store.close = () => store.dispatch(END);
41 |
42 | return store;
43 | }
44 |
--------------------------------------------------------------------------------
/app/utils/RequestUtil.js:
--------------------------------------------------------------------------------
1 | /**
2 | *
3 | * Copyright 2015-present reading
4 | *
5 | * Licensed under the Apache License, Version 2.0 (the "License");
6 | * you may not use this file except in compliance with the License.
7 | * You may obtain a copy of the License at
8 | *
9 | * http://www.apache.org/licenses/LICENSE-2.0
10 | *
11 | * Unless required by applicable law or agreed to in writing, software
12 | * distributed under the License is distributed on an "AS IS" BASIS,
13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | * See the License for the specific language governing permissions and
15 | * limitations under the License.
16 | *
17 | */
18 |
19 | import getUrl from './UrlUtil';
20 |
21 | const request = (url, method, body) => {
22 | let isOk;
23 | return new Promise((resolve, reject) => {
24 | fetch(getUrl(url), {
25 | method,
26 | headers: {
27 | 'Content-Type': 'application/json;charset=utf-8'
28 | },
29 | body
30 | })
31 | .then((response) => {
32 | if (response.ok) {
33 | isOk = true;
34 | } else {
35 | isOk = false;
36 | }
37 | return response.json();
38 | })
39 | .then((responseData) => {
40 | if (isOk) {
41 | resolve(responseData);
42 | } else {
43 | reject(responseData);
44 | }
45 | })
46 | .catch((error) => {
47 | reject(error);
48 | });
49 | });
50 | };
51 |
52 | export default {
53 | request
54 | };
55 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributing to Reading
2 |
3 | Reading is an open source project write in React Native.
4 |
5 | >No Profit, No Advertisement, Only Feelings
6 |
7 | ## Development Process
8 |
9 | ### Pull Requests
10 |
11 | Please submit your pull request on the *master* branch.
12 |
13 | *Before* submitting a pull request, please make sure the following is done…
14 |
15 | 1. Fork the repo and create your branch from `master`.
16 | 2. Add the copyright notice to the top of any new files you've added.
17 | 3. **Format your code** (`npm run format`)
18 | 4. **Make sure your code lints** (`npm run lint`).
19 | 5. Squash your commits (`git rebase -i`).
20 |
21 | #### Copyright Notice for files
22 |
23 | Copy and paste this to the top of your new file(s):
24 |
25 | ```JS
26 | /**
27 | *
28 | * Copyright 2016-present reading
29 | *
30 | * Licensed under the Apache License, Version 2.0 (the "License");
31 | * you may not use this file except in compliance with the License.
32 | * You may obtain a copy of the License at
33 | *
34 | * http://www.apache.org/licenses/LICENSE-2.0
35 | *
36 | * Unless required by applicable law or agreed to in writing, software
37 | * distributed under the License is distributed on an "AS IS" BASIS,
38 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
39 | * See the License for the specific language governing permissions and
40 | * limitations under the License.
41 | *
42 | */
43 | ```
44 |
45 | ## License
46 |
47 | By contributing to Reading, you agree that your contributions will be licensed under its Apache License, Version 2.0.
48 |
--------------------------------------------------------------------------------
/android/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
3 |
4 |
5 |
6 |
7 |
13 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
--------------------------------------------------------------------------------
/app/components/Button.js:
--------------------------------------------------------------------------------
1 | /**
2 | *
3 | * Copyright 2016-present reading
4 | *
5 | * Licensed under the Apache License, Version 2.0 (the "License");
6 | * you may not use this file except in compliance with the License.
7 | * You may obtain a copy of the License at
8 | *
9 | * http://www.apache.org/licenses/LICENSE-2.0
10 | *
11 | * Unless required by applicable law or agreed to in writing, software
12 | * distributed under the License is distributed on an "AS IS" BASIS,
13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | * See the License for the specific language governing permissions and
15 | * limitations under the License.
16 | *
17 | */
18 | import React from 'react';
19 | import PropTypes from 'prop-types';
20 | import { ViewPropTypes, Text, TouchableOpacity } from 'react-native';
21 |
22 | const propTypes = {
23 | onPress: PropTypes.func,
24 | disabled: PropTypes.bool,
25 | style: Text.propTypes.style,
26 | containerStyle: ViewPropTypes.style,
27 | text: PropTypes.string,
28 | activeOpacity: PropTypes.number
29 | };
30 |
31 | const Button = ({
32 | onPress,
33 | disabled,
34 | style,
35 | containerStyle,
36 | text,
37 | activeOpacity
38 | }) => (
39 |
45 | {text}
46 |
47 | );
48 |
49 | Button.propTypes = propTypes;
50 |
51 | Button.defaultProps = {
52 | onPress() {},
53 | disabled: false,
54 | activeOpacity: 0.8
55 | };
56 |
57 | export default Button;
58 |
--------------------------------------------------------------------------------
/ios/reading/AppDelegate.m:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (c) 2015-present, Facebook, Inc.
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 | #import
10 |
11 | #import
12 | #import
13 | #import
14 | #import "SplashScreen.h"
15 |
16 | @implementation AppDelegate
17 |
18 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
19 | {
20 | NSURL *jsCodeLocation;
21 |
22 | #ifdef DEBUG
23 | jsCodeLocation = [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:@"index" fallbackResource:nil];
24 | #else
25 | jsCodeLocation = [CodePush bundleURL];
26 | [Bugly startWithAppId:@"b0c9343009"];
27 | #endif
28 |
29 | RCTRootView *rootView = [[RCTRootView alloc] initWithBundleURL:jsCodeLocation
30 | moduleName:@"reading"
31 | initialProperties:nil
32 | launchOptions:launchOptions];
33 | rootView.backgroundColor = [[UIColor alloc] initWithRed:1.0f green:1.0f blue:1.0f alpha:1];
34 |
35 | self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
36 | UIViewController *rootViewController = [UIViewController new];
37 | rootViewController.view = rootView;
38 | self.window.rootViewController = rootViewController;
39 | [self.window makeKeyAndVisible];
40 | [SplashScreen show];
41 | return YES;
42 | }
43 |
44 | @end
45 |
--------------------------------------------------------------------------------
/.flowconfig:
--------------------------------------------------------------------------------
1 | [ignore]
2 | ; We fork some components by platform
3 | .*/*[.]android.js
4 |
5 | ; Ignore "BUCK" generated dirs
6 | /\.buckd/
7 |
8 | ; Ignore unexpected extra "@providesModule"
9 | .*/node_modules/.*/node_modules/fbjs/.*
10 |
11 | ; Ignore duplicate module providers
12 | ; For RN Apps installed via npm, "Libraries" folder is inside
13 | ; "node_modules/react-native" but in the source repo it is in the root
14 | .*/Libraries/react-native/React.js
15 |
16 | ; Ignore polyfills
17 | .*/Libraries/polyfills/.*
18 |
19 | ; Ignore metro
20 | .*/node_modules/metro/.*
21 |
22 | [include]
23 |
24 | [libs]
25 | node_modules/react-native/Libraries/react-native/react-native-interface.js
26 | node_modules/react-native/flow/
27 | node_modules/react-native/flow-github/
28 |
29 | [options]
30 | emoji=true
31 |
32 | module.system=haste
33 |
34 | munge_underscores=true
35 |
36 | module.name_mapper='^[./a-zA-Z0-9$_-]+\.\(bmp\|gif\|jpg\|jpeg\|png\|psd\|svg\|webp\|m4v\|mov\|mp4\|mpeg\|mpg\|webm\|aac\|aiff\|caf\|m4a\|mp3\|wav\|html\|pdf\)$' -> 'RelativeImageStub'
37 |
38 | module.file_ext=.js
39 | module.file_ext=.jsx
40 | module.file_ext=.json
41 | module.file_ext=.native.js
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[a-z,_]*\\)?)\\)
49 | suppress_comment=\\(.\\|\n\\)*\\$FlowIssue\\((\\(\\)? *\\(site=[a-z,_]*react_native[a-z,_]*\\)?)\\)?:? #[0-9]+
50 | suppress_comment=\\(.\\|\n\\)*\\$FlowFixedInNextDeploy
51 | suppress_comment=\\(.\\|\n\\)*\\$FlowExpectedError
52 |
53 | [version]
54 | ^0.67.0
55 |
--------------------------------------------------------------------------------
/app/pages/MainPage/ItemListView.js:
--------------------------------------------------------------------------------
1 | /**
2 | *
3 | * Copyright 2016-present reading
4 | *
5 | * Licensed under the Apache License, Version 2.0 (the "License");
6 | * you may not use this file except in compliance with the License.
7 | * You may obtain a copy of the License at
8 | *
9 | * http://www.apache.org/licenses/LICENSE-2.0
10 | *
11 | * Unless required by applicable law or agreed to in writing, software
12 | * distributed under the License is distributed on an "AS IS" BASIS,
13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | * See the License for the specific language governing permissions and
15 | * limitations under the License.
16 | *
17 | */
18 |
19 | import React from 'react';
20 | import { ListView, RefreshControl, StyleSheet } from 'react-native';
21 |
22 | const ItemListView = ({
23 | dataSource,
24 | typeId,
25 | isRefreshing,
26 | onEndReached,
27 | onRefresh,
28 | renderItem,
29 | renderFooter
30 | }) => (
31 | onEndReached(typeId)}
37 | onEndReachedThreshold={10}
38 | renderFooter={renderFooter}
39 | refreshControl={
40 | onRefresh(typeId)}
44 | title="Loading..."
45 | colors={['#ffaa66cc', '#ff00ddff', '#ffffbb33', '#ffff4444']}
46 | />
47 | }
48 | />
49 | );
50 |
51 | const styles = StyleSheet.create({
52 | listView: {
53 | backgroundColor: '#eeeeec'
54 | },
55 | refreshControlBase: {
56 | backgroundColor: 'transparent'
57 | }
58 | });
59 |
60 | export default ItemListView;
61 |
--------------------------------------------------------------------------------
/ios/reading-tvOS/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | org.reactjs.native.example.$(PRODUCT_NAME:rfc1034identifier)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | APPL
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleSignature
20 | ????
21 | CFBundleVersion
22 | 1
23 | LSRequiresIPhoneOS
24 |
25 | UILaunchStoryboardName
26 | LaunchScreen
27 | UIRequiredDeviceCapabilities
28 |
29 | armv7
30 |
31 | UISupportedInterfaceOrientations
32 |
33 | UIInterfaceOrientationPortrait
34 | UIInterfaceOrientationLandscapeLeft
35 | UIInterfaceOrientationLandscapeRight
36 |
37 | UIViewControllerBasedStatusBarAppearance
38 |
39 | NSLocationWhenInUseUsageDescription
40 |
41 | NSAppTransportSecurity
42 |
43 |
44 | NSExceptionDomains
45 |
46 | localhost
47 |
48 | NSExceptionAllowsInsecureHTTPLoads
49 |
50 |
51 |
52 |
53 |
54 |
55 |
--------------------------------------------------------------------------------
/android/app/BUCK:
--------------------------------------------------------------------------------
1 | # To learn about Buck see [Docs](https://buckbuild.com/).
2 | # To run your application with Buck:
3 | # - install Buck
4 | # - `npm start` - to start the packager
5 | # - `cd android`
6 | # - `keytool -genkey -v -keystore keystores/debug.keystore -storepass android -alias androiddebugkey -keypass android -dname "CN=Android Debug,O=Android,C=US"`
7 | # - `./gradlew :app:copyDownloadableDepsToLibs` - make all Gradle compile dependencies available to Buck
8 | # - `buck install -r android/app` - compile, install and run application
9 | #
10 |
11 | lib_deps = []
12 |
13 | for jarfile in glob(['libs/*.jar']):
14 | name = 'jars__' + jarfile[jarfile.rindex('/') + 1: jarfile.rindex('.jar')]
15 | lib_deps.append(':' + name)
16 | prebuilt_jar(
17 | name = name,
18 | binary_jar = jarfile,
19 | )
20 |
21 | for aarfile in glob(['libs/*.aar']):
22 | name = 'aars__' + aarfile[aarfile.rindex('/') + 1: aarfile.rindex('.aar')]
23 | lib_deps.append(':' + name)
24 | android_prebuilt_aar(
25 | name = name,
26 | aar = aarfile,
27 | )
28 |
29 | android_library(
30 | name = "all-libs",
31 | exported_deps = lib_deps,
32 | )
33 |
34 | android_library(
35 | name = "app-code",
36 | srcs = glob([
37 | "src/main/java/**/*.java",
38 | ]),
39 | deps = [
40 | ":all-libs",
41 | ":build_config",
42 | ":res",
43 | ],
44 | )
45 |
46 | android_build_config(
47 | name = "build_config",
48 | package = "com.reading",
49 | )
50 |
51 | android_resource(
52 | name = "res",
53 | package = "com.reading",
54 | res = "src/main/res",
55 | )
56 |
57 | android_binary(
58 | name = "app",
59 | keystore = "//android/keystores:debug",
60 | manifest = "src/main/AndroidManifest.xml",
61 | package_type = "debug",
62 | deps = [
63 | ":app-code",
64 | ],
65 | )
66 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "reading",
3 | "version": "2.1.0",
4 | "description": "iReading App Write In React-Native",
5 | "license": "Apache-2.0",
6 | "repository": {
7 | "type": "git",
8 | "url": "git@github.com:attentiveness/reading.git"
9 | },
10 | "engines": {
11 | "node": ">=4"
12 | },
13 | "scripts": {
14 | "start": "node node_modules/react-native/local-cli/cli.js start",
15 | "test": "jest",
16 | "lint": "eslint app --fix",
17 | "format": "find app -name '*.js' | xargs prettier --write --single-quote"
18 | },
19 | "dependencies": {
20 | "leancloud-storage": "3.6.7",
21 | "lodash": "^4.17.21",
22 | "moment": "^2.22.2",
23 | "prop-types": "^15.6.1",
24 | "react": "16.3.1",
25 | "react-native": "0.55.4",
26 | "react-native-code-push": "^5.3.2",
27 | "react-native-device-info": "^0.21.5",
28 | "react-native-exceptions-manager": "^0.2.0",
29 | "react-native-scrollable-tab-view": "0.8.0",
30 | "react-native-simple-store": "^1.3.0",
31 | "react-native-splash-screen": "3.0.6",
32 | "react-native-vector-icons": "^4.6.0",
33 | "react-native-wechat": "^1.9.9",
34 | "react-navigation": "1.5.11",
35 | "react-redux": "^5.0.7",
36 | "redux": "^4.0.0",
37 | "redux-logger": "^3.0.6",
38 | "redux-saga": "^0.16.0"
39 | },
40 | "jest": {
41 | "preset": "react-native"
42 | },
43 | "devDependencies": {
44 | "babel-eslint": "^8.2.3",
45 | "babel-jest": "^23.0.1",
46 | "babel-preset-react-native": "4.0.0",
47 | "eslint": "^4.19.1",
48 | "eslint-config-airbnb": "^16.1.0",
49 | "eslint-plugin-import": "^2.11.0",
50 | "eslint-plugin-jsx-a11y": "^6.0.3",
51 | "eslint-plugin-react": "^7.7.0",
52 | "jest": "^23.1.0",
53 | "prettier": "^1.13.4",
54 | "react-test-renderer": "16.3.1",
55 | "redux-devtools": "^3.4.0"
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/app/pages/MainPage/EmptyView.js:
--------------------------------------------------------------------------------
1 | /**
2 | *
3 | * Copyright 2016-present reading
4 | *
5 | * Licensed under the Apache License, Version 2.0 (the "License");
6 | * you may not use this file except in compliance with the License.
7 | * You may obtain a copy of the License at
8 | *
9 | * http://www.apache.org/licenses/LICENSE-2.0
10 | *
11 | * Unless required by applicable law or agreed to in writing, software
12 | * distributed under the License is distributed on an "AS IS" BASIS,
13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | * See the License for the specific language governing permissions and
15 | * limitations under the License.
16 | *
17 | */
18 |
19 | import React from 'react';
20 | import {
21 | RefreshControl,
22 | ScrollView,
23 | StyleSheet,
24 | Text,
25 | View
26 | } from 'react-native';
27 |
28 | const EmptyView = ({ read, typeId, onRefresh }) => (
29 | onRefresh(typeId)}
39 | title="Loading..."
40 | colors={['#ffaa66cc', '#ff00ddff', '#ffffbb33', '#ffff4444']}
41 | />
42 | }
43 | >
44 |
45 | 目前没有数据,请刷新重试……
46 |
47 |
48 | );
49 |
50 | const styles = StyleSheet.create({
51 | base: {
52 | flex: 1
53 | },
54 | no_data: {
55 | flex: 1,
56 | alignItems: 'center',
57 | justifyContent: 'center',
58 | paddingBottom: 100
59 | },
60 | refreshControlBase: {
61 | backgroundColor: 'transparent'
62 | }
63 | });
64 |
65 | export default EmptyView;
66 |
--------------------------------------------------------------------------------
/app/sagas/category.js:
--------------------------------------------------------------------------------
1 | /* eslint no-constant-condition: ["error", { "checkLoops": false }] */
2 | /**
3 | *
4 | * Copyright 2016-present reading
5 | *
6 | * Licensed under the Apache License, Version 2.0 (the "License");
7 | * you may not use this file except in compliance with the License.
8 | * You may obtain a copy of the License at
9 | *
10 | * http://www.apache.org/licenses/LICENSE-2.0
11 | *
12 | * Unless required by applicable law or agreed to in writing, software
13 | * distributed under the License is distributed on an "AS IS" BASIS,
14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | * See the License for the specific language governing permissions and
16 | * limitations under the License.
17 | *
18 | */
19 | import { put, take, call, fork } from 'redux-saga/effects';
20 | import store from 'react-native-simple-store';
21 | import * as types from '../constants/ActionTypes';
22 | import ToastUtil from '../utils/ToastUtil';
23 | import RequestUtil from '../utils/RequestUtil';
24 | import { WEXIN_ARTICLE_TYPE } from '../constants/Urls';
25 | import { fetchTypeList, receiveTypeList } from '../actions/category';
26 |
27 | export function* requestTypeList() {
28 | try {
29 | yield put(fetchTypeList());
30 | const typeList = yield call(RequestUtil.request, WEXIN_ARTICLE_TYPE, 'get');
31 | yield put(receiveTypeList(typeList.showapi_res_body.typeList));
32 | yield call(store.save, 'typeList', typeList.showapi_res_body.typeList);
33 | const errorMessage = typeList.showapi_res_error;
34 | if (errorMessage && errorMessage !== '') {
35 | yield ToastUtil.showShort(errorMessage);
36 | }
37 | } catch (error) {
38 | yield put(receiveTypeList([]));
39 | yield ToastUtil.showShort('网络发生错误,请重试');
40 | }
41 | }
42 |
43 | export function* watchRequestTypeList() {
44 | while (true) {
45 | yield take(types.REQUEST_TYPE_LIST);
46 | yield fork(requestTypeList);
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/app/containers/MainContainer.js:
--------------------------------------------------------------------------------
1 | /**
2 | *
3 | * Copyright 2016-present reading
4 | *
5 | * Licensed under the Apache License, Version 2.0 (the "License");
6 | * you may not use this file except in compliance with the License.
7 | * You may obtain a copy of the License at
8 | *
9 | * http://www.apache.org/licenses/LICENSE-2.0
10 | *
11 | * Unless required by applicable law or agreed to in writing, software
12 | * distributed under the License is distributed on an "AS IS" BASIS,
13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | * See the License for the specific language governing permissions and
15 | * limitations under the License.
16 | *
17 | */
18 | import React from 'react';
19 | import { connect } from 'react-redux';
20 | import CodePush from 'react-native-code-push';
21 | import { bindActionCreators } from 'redux';
22 | import Icon from 'react-native-vector-icons/Ionicons';
23 | import Main from '../pages/MainPage/Main';
24 | import * as readCreators from '../actions/read';
25 |
26 | class MainContainer extends React.Component {
27 | static navigationOptions = {
28 | title: '首页',
29 | tabBarIcon: ({ tintColor }) => (
30 |
31 | )
32 | };
33 |
34 | static componentDidMount() {
35 | CodePush.sync({
36 | deploymentKey: 'RGOUfyINiLicZnld67aD0nrbRvyLV1Ifekvul',
37 | updateDialog: {
38 | optionalIgnoreButtonLabel: '稍后',
39 | optionalInstallButtonLabel: '后台更新',
40 | optionalUpdateMessage: 'iReading有新版本了,是否更新?',
41 | title: '更新提示'
42 | },
43 | installMode: CodePush.InstallMode.ON_NEXT_RESTART
44 | });
45 | }
46 |
47 | render() {
48 | return ;
49 | }
50 | }
51 |
52 | const mapStateToProps = (state) => {
53 | const { read } = state;
54 | return {
55 | read
56 | };
57 | };
58 |
59 | const mapDispatchToProps = (dispatch) => {
60 | const readActions = bindActionCreators(readCreators, dispatch);
61 | return {
62 | readActions
63 | };
64 | };
65 |
66 | export default connect(mapStateToProps, mapDispatchToProps)(MainContainer);
67 |
--------------------------------------------------------------------------------
/app/containers/CategoryContainer.js:
--------------------------------------------------------------------------------
1 | /**
2 | *
3 | * Copyright 2016-present reading
4 | *
5 | * Licensed under the Apache License, Version 2.0 (the "License");
6 | * you may not use this file except in compliance with the License.
7 | * You may obtain a copy of the License at
8 | *
9 | * http://www.apache.org/licenses/LICENSE-2.0
10 | *
11 | * Unless required by applicable law or agreed to in writing, software
12 | * distributed under the License is distributed on an "AS IS" BASIS,
13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | * See the License for the specific language governing permissions and
15 | * limitations under the License.
16 | *
17 | */
18 | import React from 'react';
19 | import { connect } from 'react-redux';
20 | import { bindActionCreators } from 'redux';
21 | import Icon from 'react-native-vector-icons/Ionicons';
22 | import * as categoryCreators from '../actions/category';
23 |
24 | import Category from '../pages/Category/Category';
25 |
26 | class CategoryContainer extends React.Component {
27 | static navigationOptions = ({ navigation }) => ({
28 | title: '分类',
29 | tabBarIcon: ({ tintColor }) => (
30 |
31 | ),
32 | headerRight:
33 | navigation.state.params !== undefined &&
34 | navigation.state.params.isFirst ? null : (
35 | {
41 | navigation.state.params.handleCheck();
42 | }}
43 | />
44 | )
45 | });
46 |
47 | render() {
48 | return ;
49 | }
50 | }
51 |
52 | const mapStateToProps = (state) => {
53 | const { category } = state;
54 | return {
55 | category
56 | };
57 | };
58 |
59 | const mapDispatchToProps = (dispatch) => {
60 | const categoryActions = bindActionCreators(categoryCreators, dispatch);
61 | return {
62 | categoryActions
63 | };
64 | };
65 |
66 | export default connect(mapStateToProps, mapDispatchToProps)(CategoryContainer);
67 |
--------------------------------------------------------------------------------
/ios/reading/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | zh_CN
7 | CFBundleDisplayName
8 |
9 | CFBundleExecutable
10 | $(EXECUTABLE_NAME)
11 | CFBundleIdentifier
12 | $(PRODUCT_BUNDLE_IDENTIFIER)
13 | CFBundleInfoDictionaryVersion
14 | 6.0
15 | CFBundleName
16 | $(PRODUCT_NAME)
17 | CFBundlePackageType
18 | APPL
19 | CFBundleShortVersionString
20 | 2.2.0
21 | CFBundleSignature
22 | ????
23 | CFBundleURLTypes
24 |
25 |
26 | CFBundleTypeRole
27 | Editor
28 | CFBundleURLName
29 | weixin
30 | CFBundleURLSchemes
31 |
32 | wxb24c445773822c79
33 |
34 |
35 |
36 | CFBundleVersion
37 | 1
38 | CodePushDeploymentKey
39 | $(CODEPUSH_KEY)
40 | LSApplicationQueriesSchemes
41 |
42 | wechat
43 | weixin
44 |
45 | LSRequiresIPhoneOS
46 |
47 | NSAppTransportSecurity
48 |
49 | NSAllowsArbitraryLoads
50 |
51 | NSExceptionDomains
52 |
53 | localhost
54 |
55 | NSTemporaryExceptionAllowsInsecureHTTPLoads
56 |
57 |
58 |
59 |
60 | NSLocationWhenInUseUsageDescription
61 |
62 | UIAppFonts
63 |
64 | Ionicons.ttf
65 |
66 | UIRequiredDeviceCapabilities
67 |
68 | armv7
69 |
70 | UISupportedInterfaceOrientations
71 |
72 | UIInterfaceOrientationPortrait
73 |
74 | UIViewControllerBasedStatusBarAppearance
75 |
76 |
77 |
78 |
--------------------------------------------------------------------------------
/app/sagas/read.js:
--------------------------------------------------------------------------------
1 | /* eslint no-constant-condition: ["error", { "checkLoops": false }] */
2 | /**
3 | *
4 | * Copyright 2016-present reading
5 | *
6 | * Licensed under the Apache License, Version 2.0 (the "License");
7 | * you may not use this file except in compliance with the License.
8 | * You may obtain a copy of the License at
9 | *
10 | * http://www.apache.org/licenses/LICENSE-2.0
11 | *
12 | * Unless required by applicable law or agreed to in writing, software
13 | * distributed under the License is distributed on an "AS IS" BASIS,
14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | * See the License for the specific language governing permissions and
16 | * limitations under the License.
17 | *
18 | */
19 | import { put, take, call, fork } from 'redux-saga/effects';
20 |
21 | import * as types from '../constants/ActionTypes';
22 | import ToastUtil from '../utils/ToastUtil';
23 | import RequestUtil from '../utils/RequestUtil';
24 | import { WEXIN_ARTICLE_LIST } from '../constants/Urls';
25 | import { fetchArticleList, receiveArticleList } from '../actions/read';
26 |
27 | export function* requestArticleList(
28 | isRefreshing,
29 | loading,
30 | typeId,
31 | isLoadMore,
32 | page
33 | ) {
34 | try {
35 | yield put(fetchArticleList(isRefreshing, loading, isLoadMore));
36 | const articleList = yield call(
37 | RequestUtil.request,
38 | `${WEXIN_ARTICLE_LIST}?typeId=${typeId}&page=${page}`,
39 | 'get'
40 | );
41 | yield put(receiveArticleList(
42 | articleList.showapi_res_body.pagebean.contentlist,
43 | typeId
44 | ));
45 | const errorMessage = articleList.showapi_res_error;
46 | if (errorMessage && errorMessage !== '') {
47 | yield ToastUtil.showShort(errorMessage);
48 | }
49 | } catch (error) {
50 | yield put(receiveArticleList([], typeId));
51 | ToastUtil.showShort('网络发生错误,请重试');
52 | }
53 | }
54 |
55 | export function* watchRequestArticleList() {
56 | while (true) {
57 | const {
58 | isRefreshing, loading, typeId, isLoadMore, page
59 | } = yield take(types.REQUEST_ARTICLE_LIST);
60 | yield fork(
61 | requestArticleList,
62 | isRefreshing,
63 | loading,
64 | typeId,
65 | isLoadMore,
66 | page
67 | );
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/app/sagas/_spec_/category.spec.js:
--------------------------------------------------------------------------------
1 | /**
2 | *
3 | * Copyright 2016-present reading
4 | *
5 | * Licensed under the Apache License, Version 2.0 (the "License");
6 | * you may not use this file except in compliance with the License.
7 | * You may obtain a copy of the License at
8 | *
9 | * http://www.apache.org/licenses/LICENSE-2.0
10 | *
11 | * Unless required by applicable law or agreed to in writing, software
12 | * distributed under the License is distributed on an "AS IS" BASIS,
13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | * See the License for the specific language governing permissions and
15 | * limitations under the License.
16 | *
17 | */
18 | import { put, call } from 'redux-saga/effects';
19 | import store from 'react-native-simple-store';
20 | import { requestTypeList } from '../category';
21 | import { fetchTypeList, receiveTypeList } from '../../actions/category';
22 | import RequestUtil from '../../utils/RequestUtil';
23 | import { WEXIN_ARTICLE_TYPE } from '../../constants/Urls';
24 |
25 | /* global expect */
26 | describe('category saga tests', () => {
27 | const generator = requestTypeList();
28 | const step = input => generator.next(input).value;
29 | const mockTypeList = {
30 | showapi_res_body: {
31 | typeList: [
32 | {
33 | id: '19',
34 | name: 'Sports'
35 | },
36 | {
37 | id: '2',
38 | name: 'Entertainment'
39 | }
40 | ]
41 | }
42 | };
43 |
44 | it('should put(fetchTypeList())', () => {
45 | const next = step();
46 | expect(next).toEqual(put(fetchTypeList()));
47 | });
48 |
49 | it("should call(request, WEXIN_ARTICLE_TYPE, 'get')", () => {
50 | const next = step();
51 | expect(next).toEqual(call(RequestUtil.request, WEXIN_ARTICLE_TYPE, 'get'));
52 | });
53 |
54 | it('should put(receiveTypeList(typeList.showapi_res_body.typeList))', () => {
55 | const next = step(mockTypeList);
56 | expect(next).toEqual(put(receiveTypeList(mockTypeList.showapi_res_body.typeList)));
57 | });
58 |
59 | it("should call(store.save, 'typeList', typeList.showapi_res_body.typeList)", () => {
60 | const next = step(mockTypeList);
61 | expect(next).toEqual(call(store.save, 'typeList', mockTypeList.showapi_res_body.typeList));
62 | });
63 |
64 | it('should be done', () => {
65 | const next = generator.next();
66 | expect(next.done).toEqual(true);
67 | });
68 | });
69 |
--------------------------------------------------------------------------------
/app/containers/app.js:
--------------------------------------------------------------------------------
1 | /**
2 | *
3 | * Copyright 2015-present reading
4 | *
5 | * Licensed under the Apache License, Version 2.0 (the "License");
6 | * you may not use this file except in compliance with the License.
7 | * You may obtain a copy of the License at
8 | *
9 | * http://www.apache.org/licenses/LICENSE-2.0
10 | *
11 | * Unless required by applicable law or agreed to in writing, software
12 | * distributed under the License is distributed on an "AS IS" BASIS,
13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | * See the License for the specific language governing permissions and
15 | * limitations under the License.
16 | *
17 | */
18 | import { StackNavigator, TabNavigator } from 'react-navigation';
19 | import Splash from '../pages/Splash';
20 | import CategoryContainer from '../containers/CategoryContainer';
21 | import MainContainer from '../containers/MainContainer';
22 | import WebViewPage from '../pages/ItemDetail/WebViewPage';
23 | import Feedback from '../pages/Feedback/Feedback';
24 | import About from '../pages/About/About';
25 |
26 | const TabContainer = TabNavigator(
27 | {
28 | Main: { screen: MainContainer },
29 | Category: { screen: CategoryContainer },
30 | Feedback: { screen: Feedback },
31 | About: { screen: About }
32 | },
33 | {
34 | lazy: true,
35 | tabBarPosition: 'bottom',
36 | tabBarOptions: {
37 | activeTintColor: '#3e9ce9',
38 | inactiveTintColor: '#999999',
39 | showIcon: true,
40 | style: {
41 | backgroundColor: '#fff'
42 | },
43 | indicatorStyle: {
44 | opacity: 0
45 | },
46 | tabStyle: {
47 | padding: 0
48 | }
49 | }
50 | }
51 | );
52 |
53 | const App = StackNavigator(
54 | {
55 | Splash: { screen: Splash },
56 | Category: {
57 | screen: CategoryContainer,
58 | navigationOptions: {
59 | headerLeft: null
60 | }
61 | },
62 | Home: {
63 | screen: TabContainer,
64 | navigationOptions: {
65 | headerLeft: null
66 | }
67 | },
68 | Web: { screen: WebViewPage }
69 | },
70 | {
71 | headerMode: 'screen',
72 | navigationOptions: {
73 | headerStyle: {
74 | backgroundColor: '#3e9ce9'
75 | },
76 | headerTitleStyle: {
77 | color: '#fff',
78 | fontSize: 20
79 | },
80 | headerTintColor: '#fff'
81 | }
82 | }
83 | );
84 |
85 | export default App;
86 |
--------------------------------------------------------------------------------
/app/reducers/read.js:
--------------------------------------------------------------------------------
1 | /**
2 | *
3 | * Copyright 2016-present reading
4 | *
5 | * Licensed under the Apache License, Version 2.0 (the "License");
6 | * you may not use this file except in compliance with the License.
7 | * You may obtain a copy of the License at
8 | *
9 | * http://www.apache.org/licenses/LICENSE-2.0
10 | *
11 | * Unless required by applicable law or agreed to in writing, software
12 | * distributed under the License is distributed on an "AS IS" BASIS,
13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | * See the License for the specific language governing permissions and
15 | * limitations under the License.
16 | *
17 | */
18 | import * as types from '../constants/ActionTypes';
19 |
20 | const initialState = {
21 | isRefreshing: false,
22 | loading: false,
23 | isLoadMore: false,
24 | noMore: false,
25 | articleList: {}
26 | };
27 |
28 | export default function read(state = initialState, action) {
29 | switch (action.type) {
30 | case types.FETCH_ARTICLE_LIST:
31 | return Object.assign({}, state, {
32 | isRefreshing: action.isRefreshing,
33 | loading: action.loading,
34 | isLoadMore: action.isLoadMore
35 | });
36 | case types.RECEIVE_ARTICLE_LIST:
37 | return Object.assign({}, state, {
38 | isRefreshing: false,
39 | isLoadMore: false,
40 | noMore: action.articleList.length === 0,
41 | articleList: state.isLoadMore
42 | ? loadMore(state, action)
43 | : combine(state, action),
44 | loading: state.articleList[action.typeId] === undefined
45 | });
46 | default:
47 | return state;
48 | }
49 | }
50 |
51 | function combine(state, action) {
52 | state.articleList[action.typeId] = action.articleList;
53 | return state.articleList;
54 | }
55 |
56 | function loadMore(state, action) {
57 | state.articleList[action.typeId] = concatFilterDuplicate(
58 | state.articleList[action.typeId],
59 | action.articleList
60 | );
61 | return state.articleList;
62 | }
63 |
64 | /**
65 | * filter duplicate data when loading more.
66 | */
67 | function concatFilterDuplicate(list1, list2) {
68 | const set = new Set(list1.map(item => item.id));
69 | const filterList2 = [];
70 | const length = list2.length;
71 | for (let i = 0; i < length; i++) {
72 | if (!set.has(list2[i].id)) {
73 | filterList2.push(list2[i]);
74 | }
75 | }
76 | return list1.concat(filterList2);
77 | }
78 |
--------------------------------------------------------------------------------
/app/pages/MainPage/ItemCell.js:
--------------------------------------------------------------------------------
1 | /**
2 | *
3 | * Copyright 2016-present reading
4 | *
5 | * Licensed under the Apache License, Version 2.0 (the "License");
6 | * you may not use this file except in compliance with the License.
7 | * You may obtain a copy of the License at
8 | *
9 | * http://www.apache.org/licenses/LICENSE-2.0
10 | *
11 | * Unless required by applicable law or agreed to in writing, software
12 | * distributed under the License is distributed on an "AS IS" BASIS,
13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | * See the License for the specific language governing permissions and
15 | * limitations under the License.
16 | *
17 | */
18 | import React from 'react';
19 | import { Image, Text, TouchableOpacity, View, StyleSheet } from 'react-native';
20 | import moment from 'moment';
21 |
22 | import { formatStringWithHtml } from '../../utils/FormatUtil';
23 |
24 | require('moment/locale/zh-cn');
25 |
26 | const ItemCell = ({ article, onPressHandler }) => (
27 | onPressHandler(article)}>
28 |
29 |
30 |
31 | {formatStringWithHtml(article.title)}
32 |
33 | {article.userName}
34 | {moment(article.date).fromNow()}
35 |
36 |
37 |
38 |
39 | );
40 |
41 | const styles = StyleSheet.create({
42 | containerItem: {
43 | flexDirection: 'row',
44 | justifyContent: 'center',
45 | alignItems: 'center',
46 | backgroundColor: '#fcfcfc',
47 | padding: 10,
48 | borderBottomColor: '#ddd',
49 | borderBottomWidth: 1
50 | },
51 | title: {
52 | fontSize: 18,
53 | textAlign: 'left',
54 | color: 'black'
55 | },
56 | itemImg: {
57 | width: 88,
58 | height: 66,
59 | marginRight: 10
60 | },
61 | itemRightContent: {
62 | flex: 1,
63 | flexDirection: 'column'
64 | },
65 | itemRightBottom: {
66 | flex: 1,
67 | flexDirection: 'row',
68 | alignItems: 'center'
69 | },
70 | userName: {
71 | flex: 1,
72 | fontSize: 14,
73 | color: '#87CEFA',
74 | marginTop: 5,
75 | marginRight: 5
76 | }
77 | });
78 |
79 | export default ItemCell;
80 |
--------------------------------------------------------------------------------
/ios/Bugly.framework/Headers/BuglyLog.h:
--------------------------------------------------------------------------------
1 | //
2 | // BuglyLog.h
3 | //
4 | // Copyright © 2017 tencent.com. All rights reserved.
5 | //
6 |
7 | #import
8 |
9 | // Log level for Bugly Log
10 | typedef NS_ENUM(NSUInteger, BuglyLogLevel) {
11 | BuglyLogLevelSilent = 0,
12 | BuglyLogLevelError = 1,
13 | BuglyLogLevelWarn = 2,
14 | BuglyLogLevelInfo = 3,
15 | BuglyLogLevelDebug = 4,
16 | BuglyLogLevelVerbose = 5,
17 | };
18 | #pragma mark -
19 |
20 | OBJC_EXTERN void BLYLog(BuglyLogLevel level, NSString *format, ...) NS_FORMAT_FUNCTION(2, 3);
21 |
22 | OBJC_EXTERN void BLYLogv(BuglyLogLevel level, NSString *format, va_list args) NS_FORMAT_FUNCTION(2, 0);
23 |
24 | #pragma mark -
25 | #define BUGLY_LOG_MACRO(_level, fmt, ...) [BuglyLog level:_level tag:nil log:fmt, ##__VA_ARGS__]
26 |
27 | #define BLYLogError(fmt, ...) BUGLY_LOG_MACRO(BuglyLogLevelError, fmt, ##__VA_ARGS__)
28 | #define BLYLogWarn(fmt, ...) BUGLY_LOG_MACRO(BuglyLogLevelWarn, fmt, ##__VA_ARGS__)
29 | #define BLYLogInfo(fmt, ...) BUGLY_LOG_MACRO(BuglyLogLevelInfo, fmt, ##__VA_ARGS__)
30 | #define BLYLogDebug(fmt, ...) BUGLY_LOG_MACRO(BuglyLogLevelDebug, fmt, ##__VA_ARGS__)
31 | #define BLYLogVerbose(fmt, ...) BUGLY_LOG_MACRO(BuglyLogLevelVerbose, fmt, ##__VA_ARGS__)
32 |
33 | #pragma mark - Interface
34 | @interface BuglyLog : NSObject
35 |
36 | /**
37 | * @brief 初始化日志模块
38 | *
39 | * @param level 设置默认日志级别,默认BLYLogLevelSilent
40 | *
41 | * @param printConsole 是否打印到控制台,默认NO
42 | */
43 | + (void)initLogger:(BuglyLogLevel) level consolePrint:(BOOL)printConsole;
44 |
45 | /**
46 | * @brief 打印BLYLogLevelInfo日志
47 | *
48 | * @param format 日志内容 总日志大小限制为:字符串长度30k,条数200
49 | */
50 | + (void)log:(NSString *)format, ... NS_FORMAT_FUNCTION(1, 2);
51 |
52 | /**
53 | * @brief 打印日志
54 | *
55 | * @param level 日志级别
56 | * @param message 日志内容 总日志大小限制为:字符串长度30k,条数200
57 | */
58 | + (void)level:(BuglyLogLevel) level logs:(NSString *)message;
59 |
60 | /**
61 | * @brief 打印日志
62 | *
63 | * @param level 日志级别
64 | * @param format 日志内容 总日志大小限制为:字符串长度30k,条数200
65 | */
66 | + (void)level:(BuglyLogLevel) level log:(NSString *)format, ... NS_FORMAT_FUNCTION(2, 3);
67 |
68 | /**
69 | * @brief 打印日志
70 | *
71 | * @param level 日志级别
72 | * @param tag 日志模块分类
73 | * @param format 日志内容 总日志大小限制为:字符串长度30k,条数200
74 | */
75 | + (void)level:(BuglyLogLevel) level tag:(NSString *) tag log:(NSString *)format, ... NS_FORMAT_FUNCTION(3, 4);
76 |
77 | @end
78 |
--------------------------------------------------------------------------------
/app/sagas/_spec_/read.spec.js:
--------------------------------------------------------------------------------
1 | /**
2 | *
3 | * Copyright 2016-present reading
4 | *
5 | * Licensed under the Apache License, Version 2.0 (the "License");
6 | * you may not use this file except in compliance with the License.
7 | * You may obtain a copy of the License at
8 | *
9 | * http://www.apache.org/licenses/LICENSE-2.0
10 | *
11 | * Unless required by applicable law or agreed to in writing, software
12 | * distributed under the License is distributed on an "AS IS" BASIS,
13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | * See the License for the specific language governing permissions and
15 | * limitations under the License.
16 | *
17 | */
18 | import { put, call } from 'redux-saga/effects';
19 |
20 | import { requestArticleList } from '../read';
21 | import RequestUtil from '../../utils/RequestUtil';
22 | import { WEXIN_ARTICLE_LIST } from '../../constants/Urls';
23 | import { fetchArticleList, receiveArticleList } from '../../actions/read';
24 |
25 | /* global expect */
26 | describe('read saga tests', () => {
27 | const {
28 | isRefreshing, loading, typeId, isLoadMore, page
29 | } = {
30 | isRefreshing: false,
31 | loading: false,
32 | typeId: 2,
33 | isLoadMore: false,
34 | page: 1
35 | };
36 | const generator = requestArticleList(
37 | isRefreshing,
38 | loading,
39 | typeId,
40 | isLoadMore,
41 | page
42 | );
43 | const mockArticleList = {
44 | showapi_res_body: {
45 | pagebean: {
46 | contentlist: []
47 | }
48 | }
49 | };
50 | const step = input => generator.next(input).value;
51 |
52 | it(`should put(fetchArticleList(${isRefreshing}, ${loading}, ${isLoadMore}))`, () => {
53 | const next = step();
54 | expect(next).toEqual(put(fetchArticleList(isRefreshing, loading, isLoadMore)));
55 | });
56 |
57 | it(`should call(request, ${WEXIN_ARTICLE_LIST}?typeId=${typeId}&page=${page}, 'get')`, () => {
58 | const next = step();
59 | expect(next).toEqual(call(
60 | RequestUtil.request,
61 | `${WEXIN_ARTICLE_LIST}?typeId=${typeId}&page=${page}`,
62 | 'get'
63 | ));
64 | });
65 |
66 | it(`should put(receiveArticleList(contentlist, ${typeId}))`, () => {
67 | const next = step(mockArticleList);
68 | expect(next).toEqual(put(receiveArticleList(
69 | mockArticleList.showapi_res_body.pagebean.contentlist,
70 | typeId
71 | )));
72 | });
73 |
74 | it('should be done', () => {
75 | const done = generator.next().done;
76 | expect(done).toEqual(true);
77 | });
78 | });
79 |
--------------------------------------------------------------------------------
/app/pages/Splash.js:
--------------------------------------------------------------------------------
1 | /**
2 | *
3 | * Copyright 2016-present reading
4 | *
5 | * Licensed under the Apache License, Version 2.0 (the "License");
6 | * you may not use this file except in compliance with the License.
7 | * You may obtain a copy of the License at
8 | *
9 | * http://www.apache.org/licenses/LICENSE-2.0
10 | *
11 | * Unless required by applicable law or agreed to in writing, software
12 | * distributed under the License is distributed on an "AS IS" BASIS,
13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | * See the License for the specific language governing permissions and
15 | * limitations under the License.
16 | *
17 | */
18 | import React from 'react';
19 | import { Dimensions, Animated } from 'react-native';
20 | import store from 'react-native-simple-store';
21 | import { registerApp } from 'react-native-wechat';
22 | import AV from 'leancloud-storage';
23 | import SplashScreen from 'react-native-splash-screen';
24 | import NavigationUtil from '../utils/NavigationUtil';
25 |
26 | const maxHeight = Dimensions.get('window').height;
27 | const maxWidth = Dimensions.get('window').width;
28 | const splashImg = require('../img/splash.png');
29 |
30 | class Splash extends React.Component {
31 | static navigationOptions = {
32 | header: null
33 | };
34 |
35 | constructor(props) {
36 | super(props);
37 | this.state = {
38 | bounceValue: new Animated.Value(1)
39 | };
40 | registerApp('wxb24c445773822c79');
41 | if (!AV.applicationId) {
42 | AV.init({
43 | appId: 'Tfi1z7dN9sjMwSul8sYaTEvg-gzGzoHsz',
44 | appKey: '57qmeEJonefntNqRe17dAgi4'
45 | });
46 | }
47 | }
48 |
49 | componentDidMount() {
50 | const { navigate } = this.props.navigation;
51 | Animated.timing(this.state.bounceValue, {
52 | toValue: 1.2,
53 | duration: 1000
54 | }).start();
55 | SplashScreen.hide();
56 | this.timer = setTimeout(() => {
57 | store.get('isInit').then((isInit) => {
58 | if (!isInit) {
59 | navigate('Category', { isFirst: true });
60 | } else {
61 | NavigationUtil.reset(this.props.navigation, 'Home');
62 | }
63 | });
64 | }, 1000);
65 | }
66 |
67 | componentWillUnmount() {
68 | clearTimeout(this.timer);
69 | }
70 |
71 | render() {
72 | return (
73 |
81 | );
82 | }
83 | }
84 |
85 | export default Splash;
86 |
--------------------------------------------------------------------------------
/ios/reading/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 | "size" : "29x29",
15 | "idiom" : "iphone",
16 | "filename" : "Icon-Small@2x.png",
17 | "scale" : "2x"
18 | },
19 | {
20 | "size" : "29x29",
21 | "idiom" : "iphone",
22 | "filename" : "Icon-Small@3x.png",
23 | "scale" : "3x"
24 | },
25 | {
26 | "size" : "40x40",
27 | "idiom" : "iphone",
28 | "filename" : "Icon-40@2x.png",
29 | "scale" : "2x"
30 | },
31 | {
32 | "size" : "40x40",
33 | "idiom" : "iphone",
34 | "filename" : "Icon-40@3x.png",
35 | "scale" : "3x"
36 | },
37 | {
38 | "size" : "60x60",
39 | "idiom" : "iphone",
40 | "filename" : "Icon-60@2x.png",
41 | "scale" : "2x"
42 | },
43 | {
44 | "size" : "60x60",
45 | "idiom" : "iphone",
46 | "filename" : "Icon-60@3x.png",
47 | "scale" : "3x"
48 | },
49 | {
50 | "idiom" : "ipad",
51 | "size" : "20x20",
52 | "scale" : "1x"
53 | },
54 | {
55 | "idiom" : "ipad",
56 | "size" : "20x20",
57 | "scale" : "2x"
58 | },
59 | {
60 | "size" : "29x29",
61 | "idiom" : "ipad",
62 | "filename" : "Icon-Small.png",
63 | "scale" : "1x"
64 | },
65 | {
66 | "size" : "29x29",
67 | "idiom" : "ipad",
68 | "filename" : "Icon-Small@2x.png",
69 | "scale" : "2x"
70 | },
71 | {
72 | "size" : "40x40",
73 | "idiom" : "ipad",
74 | "filename" : "Icon-40.png",
75 | "scale" : "1x"
76 | },
77 | {
78 | "size" : "40x40",
79 | "idiom" : "ipad",
80 | "filename" : "Icon-40@2x.png",
81 | "scale" : "2x"
82 | },
83 | {
84 | "size" : "76x76",
85 | "idiom" : "ipad",
86 | "filename" : "Icon-76.png",
87 | "scale" : "1x"
88 | },
89 | {
90 | "size" : "76x76",
91 | "idiom" : "ipad",
92 | "filename" : "Icon-76@2x.png",
93 | "scale" : "2x"
94 | },
95 | {
96 | "size" : "83.5x83.5",
97 | "idiom" : "ipad",
98 | "filename" : "Icon-83.5@2x.png",
99 | "scale" : "2x"
100 | },
101 | {
102 | "idiom" : "ios-marketing",
103 | "size" : "1024x1024",
104 | "scale" : "1x"
105 | }
106 | ],
107 | "info" : {
108 | "version" : 1,
109 | "author" : "xcode"
110 | }
111 | }
--------------------------------------------------------------------------------
/android/gradlew.bat:
--------------------------------------------------------------------------------
1 | @if "%DEBUG%" == "" @echo off
2 | @rem ##########################################################################
3 | @rem
4 | @rem Gradle startup script for Windows
5 | @rem
6 | @rem ##########################################################################
7 |
8 | @rem Set local scope for the variables with windows NT shell
9 | if "%OS%"=="Windows_NT" setlocal
10 |
11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
12 | set DEFAULT_JVM_OPTS=
13 |
14 | set DIRNAME=%~dp0
15 | if "%DIRNAME%" == "" set DIRNAME=.
16 | set APP_BASE_NAME=%~n0
17 | set APP_HOME=%DIRNAME%
18 |
19 | @rem Find java.exe
20 | if defined JAVA_HOME goto findJavaFromJavaHome
21 |
22 | set JAVA_EXE=java.exe
23 | %JAVA_EXE% -version >NUL 2>&1
24 | if "%ERRORLEVEL%" == "0" goto init
25 |
26 | echo.
27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
28 | echo.
29 | echo Please set the JAVA_HOME variable in your environment to match the
30 | echo location of your Java installation.
31 |
32 | goto fail
33 |
34 | :findJavaFromJavaHome
35 | set JAVA_HOME=%JAVA_HOME:"=%
36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
37 |
38 | if exist "%JAVA_EXE%" goto init
39 |
40 | echo.
41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
42 | echo.
43 | echo Please set the JAVA_HOME variable in your environment to match the
44 | echo location of your Java installation.
45 |
46 | goto fail
47 |
48 | :init
49 | @rem Get command-line arguments, handling Windowz variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 | if "%@eval[2+2]" == "4" goto 4NT_args
53 |
54 | :win9xME_args
55 | @rem Slurp the command line arguments.
56 | set CMD_LINE_ARGS=
57 | set _SKIP=2
58 |
59 | :win9xME_args_slurp
60 | if "x%~1" == "x" goto execute
61 |
62 | set CMD_LINE_ARGS=%*
63 | goto execute
64 |
65 | :4NT_args
66 | @rem Get arguments from the 4NT Shell from JP Software
67 | set CMD_LINE_ARGS=%$
68 |
69 | :execute
70 | @rem Setup the command line
71 |
72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
73 |
74 | @rem Execute Gradle
75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
76 |
77 | :end
78 | @rem End local scope for the variables with windows NT shell
79 | if "%ERRORLEVEL%"=="0" goto mainEnd
80 |
81 | :fail
82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
83 | rem the _cmd.exe /c_ return code!
84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
85 | exit /b 1
86 |
87 | :mainEnd
88 | if "%OS%"=="Windows_NT" endlocal
89 |
90 | :omega
91 |
--------------------------------------------------------------------------------
/ios/Bugly.framework/Headers/BuglyConfig.h:
--------------------------------------------------------------------------------
1 | //
2 | // BuglyConfig.h
3 | //
4 | //
5 | // Copyright (c) 2016年 Tencent. All rights reserved.
6 | //
7 |
8 | #pragma once
9 |
10 | #define BLY_UNAVAILABLE(x) __attribute__((unavailable(x)))
11 |
12 | #if __has_feature(nullability)
13 | #define BLY_NONNULL __nonnull
14 | #define BLY_NULLABLE __nullable
15 | #define BLY_START_NONNULL _Pragma("clang assume_nonnull begin")
16 | #define BLY_END_NONNULL _Pragma("clang assume_nonnull end")
17 | #else
18 | #define BLY_NONNULL
19 | #define BLY_NULLABLE
20 | #define BLY_START_NONNULL
21 | #define BLY_END_NONNULL
22 | #endif
23 |
24 | #import
25 | #import "BuglyLog.h"
26 |
27 | BLY_START_NONNULL
28 |
29 | @protocol BuglyDelegate
30 |
31 | @optional
32 | /**
33 | * 发生异常时回调
34 | *
35 | * @param exception 异常信息
36 | *
37 | * @return 返回需上报记录,随异常上报一起上报
38 | */
39 | - (NSString * BLY_NULLABLE)attachmentForException:(NSException * BLY_NULLABLE)exception;
40 |
41 | @end
42 |
43 | @interface BuglyConfig : NSObject
44 |
45 | /**
46 | * SDK Debug信息开关, 默认关闭
47 | */
48 | @property (nonatomic, assign) BOOL debugMode;
49 |
50 | /**
51 | * 设置自定义渠道标识
52 | */
53 | @property (nonatomic, copy) NSString *channel;
54 |
55 | /**
56 | * 设置自定义版本号
57 | */
58 | @property (nonatomic, copy) NSString *version;
59 |
60 | /**
61 | * 设置自定义设备唯一标识
62 | */
63 | @property (nonatomic, copy) NSString *deviceIdentifier;
64 |
65 | /**
66 | * 卡顿监控开关,默认关闭
67 | */
68 | @property (nonatomic) BOOL blockMonitorEnable;
69 |
70 | /**
71 | * 卡顿监控判断间隔,单位为秒
72 | */
73 | @property (nonatomic) NSTimeInterval blockMonitorTimeout;
74 |
75 | /**
76 | * 设置 App Groups Id (如有使用 Bugly iOS Extension SDK,请设置该值)
77 | */
78 | @property (nonatomic, copy) NSString *applicationGroupIdentifier;
79 |
80 | /**
81 | * 进程内还原开关,默认开启
82 | */
83 | @property (nonatomic) BOOL symbolicateInProcessEnable;
84 |
85 | /**
86 | * 非正常退出事件记录开关,默认关闭
87 | */
88 | @property (nonatomic) BOOL unexpectedTerminatingDetectionEnable;
89 |
90 | /**
91 | * 页面信息记录开关,默认开启
92 | */
93 | @property (nonatomic) BOOL viewControllerTrackingEnable;
94 |
95 | /**
96 | * Bugly Delegate
97 | */
98 | @property (nonatomic, assign) id delegate;
99 |
100 | /**
101 | * 控制自定义日志上报,默认值为BuglyLogLevelSilent,即关闭日志记录功能。
102 | * 如果设置为BuglyLogLevelWarn,则在崩溃时会上报Warn、Error接口打印的日志
103 | */
104 | @property (nonatomic, assign) BuglyLogLevel reportLogLevel;
105 |
106 | /**
107 | * 崩溃数据过滤器,如果崩溃堆栈的模块名包含过滤器中设置的关键字,则崩溃数据不会进行上报
108 | * 例如,过滤崩溃堆栈中包含搜狗输入法的数据,可以添加过滤器关键字SogouInputIPhone.dylib等
109 | */
110 | @property (nonatomic, copy) NSArray *excludeModuleFilter;
111 |
112 | /**
113 | * 控制台日志上报开关,默认开启
114 | */
115 | @property (nonatomic, assign) BOOL consolelogEnable;
116 |
117 | @end
118 | BLY_END_NONNULL
119 |
--------------------------------------------------------------------------------
/app/components/Loading.js:
--------------------------------------------------------------------------------
1 | /**
2 | *
3 | * Copyright 2016-present reading
4 | *
5 | * Licensed under the Apache License, Version 2.0 (the "License");
6 | * you may not use this file except in compliance with the License.
7 | * You may obtain a copy of the License at
8 | *
9 | * http://www.apache.org/licenses/LICENSE-2.0
10 | *
11 | * Unless required by applicable law or agreed to in writing, software
12 | * distributed under the License is distributed on an "AS IS" BASIS,
13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | * See the License for the specific language governing permissions and
15 | * limitations under the License.
16 | *
17 | */
18 | import React from 'react';
19 | import PropTypes from 'prop-types';
20 | import {
21 | StyleSheet,
22 | Dimensions,
23 | View,
24 | Text,
25 | Modal,
26 | ActivityIndicator
27 | } from 'react-native';
28 |
29 | const SIZES = ['small', 'large'];
30 |
31 | const propTypes = {
32 | visible: PropTypes.bool,
33 | color: PropTypes.string,
34 | size: PropTypes.oneOf(SIZES),
35 | overlayColor: PropTypes.string,
36 | onRequestClose: PropTypes.func
37 | };
38 |
39 | const Loading = ({
40 | visible, color, size, overlayColor, onRequestClose
41 | }) => (
42 |
43 | {visible ? (
44 |
45 |
46 |
47 |
48 | 数据加载中...
49 |
50 |
51 |
52 | ) : (
53 |
54 | )}
55 |
56 | );
57 |
58 | const styles = StyleSheet.create({
59 | container: {
60 | flex: 1,
61 | backgroundColor: 'transparent',
62 | position: 'absolute',
63 | top: 0,
64 | bottom: 0,
65 | left: 0,
66 | right: 0
67 | },
68 | background: {
69 | position: 'absolute',
70 | top: 0,
71 | bottom: 0,
72 | left: 0,
73 | right: 0,
74 | justifyContent: 'center',
75 | alignItems: 'center'
76 | },
77 | loading: {
78 | alignItems: 'center',
79 | justifyContent: 'center',
80 | width: Dimensions.get('window').width / 2.5,
81 | height: Dimensions.get('window').width / 2.5,
82 | borderRadius: 10,
83 | backgroundColor: 'rgba(0, 0, 0, 0.25)'
84 | },
85 | loadingText: {
86 | marginTop: 10,
87 | textAlign: 'center',
88 | color: '#fcfcfc'
89 | }
90 | });
91 |
92 | Loading.propTypes = propTypes;
93 |
94 | Loading.defaultProps = {
95 | visible: false,
96 | color: 'white',
97 | size: 'large',
98 | overlayColor: 'transparent',
99 | onRequestClose() {}
100 | };
101 |
102 | export default Loading;
103 |
--------------------------------------------------------------------------------
/app/components/GridView.js:
--------------------------------------------------------------------------------
1 | /**
2 | *
3 | * Copyright 2016-present reading
4 | *
5 | * Licensed under the Apache License, Version 2.0 (the "License");
6 | * you may not use this file except in compliance with the License.
7 | * You may obtain a copy of the License at
8 | *
9 | * http://www.apache.org/licenses/LICENSE-2.0
10 | *
11 | * Unless required by applicable law or agreed to in writing, software
12 | * distributed under the License is distributed on an "AS IS" BASIS,
13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | * See the License for the specific language governing permissions and
15 | * limitations under the License.
16 | *
17 | */
18 | import React from 'react';
19 | import PropTypes from 'prop-types';
20 | import { View, StyleSheet, ListView, ViewPropTypes } from 'react-native';
21 |
22 | const propTypes = {
23 | items: PropTypes.array,
24 | renderItem: PropTypes.func,
25 | style: ViewPropTypes.style,
26 | itemsPerRow: PropTypes.number,
27 | onEndReached: PropTypes.func,
28 | scrollEnabled: PropTypes.func,
29 | pageSize: PropTypes.number
30 | };
31 |
32 | const GridView = ({
33 | items,
34 | renderItem,
35 | style,
36 | itemsPerRow,
37 | onEndReached,
38 | scrollEnabled,
39 | pageSize
40 | }) => {
41 | const groupItems = (renderItems, renderItemsPerRow) => {
42 | const itemsGroups = [];
43 | let group = [];
44 | renderItems.forEach((item) => {
45 | if (group.length === renderItemsPerRow) {
46 | itemsGroups.push(group);
47 | group = [item];
48 | } else {
49 | group.push(item);
50 | }
51 | });
52 | if (group.length > 0) {
53 | itemsGroups.push(group);
54 | }
55 | return itemsGroups;
56 | };
57 |
58 | const renderGroup = (group) => {
59 | const itemViews = group.map((item) => {
60 | const i = renderItem(item);
61 | return i;
62 | });
63 | return {itemViews};
64 | };
65 |
66 | const groups = groupItems(items, itemsPerRow);
67 |
68 | const ds = new ListView.DataSource({
69 | rowHasChanged: (r1, r2) => r1 !== r2
70 | });
71 |
72 | return (
73 |
83 | );
84 | };
85 |
86 | const styles = StyleSheet.create({
87 | group: {
88 | flexDirection: 'row',
89 | alignItems: 'center'
90 | }
91 | });
92 |
93 | GridView.propTypes = propTypes;
94 |
95 | GridView.defaultProps = {
96 | items: [],
97 | renderItem: null,
98 | style: undefined,
99 | itemsPerRow: 1,
100 | onEndReached: undefined
101 | };
102 |
103 | export default GridView;
104 |
--------------------------------------------------------------------------------
/ios/Bugly.framework/Headers/Bugly.h:
--------------------------------------------------------------------------------
1 | //
2 | // Bugly.h
3 | // Bugly
4 | //
5 | // Version: 2.4(8)
6 | //
7 | // Copyright (c) 2016年 Bugly. All rights reserved.
8 | //
9 |
10 | #import
11 |
12 | #import "BuglyConfig.h"
13 | #import "BuglyLog.h"
14 |
15 | BLY_START_NONNULL
16 |
17 | @interface Bugly : NSObject
18 |
19 | /**
20 | * 初始化Bugly,使用默认BuglyConfig
21 | *
22 | * @param appId 注册Bugly分配的应用唯一标识
23 | */
24 | + (void)startWithAppId:(NSString * BLY_NULLABLE)appId;
25 |
26 | /**
27 | * 使用指定配置初始化Bugly
28 | *
29 | * @param appId 注册Bugly分配的应用唯一标识
30 | * @param config 传入配置的 BuglyConfig
31 | */
32 | + (void)startWithAppId:(NSString * BLY_NULLABLE)appId
33 | config:(BuglyConfig * BLY_NULLABLE)config;
34 |
35 | /**
36 | * 使用指定配置初始化Bugly
37 | *
38 | * @param appId 注册Bugly分配的应用唯一标识
39 | * @param development 是否开发设备
40 | * @param config 传入配置的 BuglyConfig
41 | */
42 | + (void)startWithAppId:(NSString * BLY_NULLABLE)appId
43 | developmentDevice:(BOOL)development
44 | config:(BuglyConfig * BLY_NULLABLE)config;
45 |
46 | /**
47 | * 设置用户标识
48 | *
49 | * @param userId 用户标识
50 | */
51 | + (void)setUserIdentifier:(NSString *)userId;
52 |
53 | /**
54 | * 更新版本信息
55 | *
56 | * @param version 应用版本信息
57 | */
58 | + (void)updateAppVersion:(NSString *)version;
59 |
60 | /**
61 | * 设置关键数据,随崩溃信息上报
62 | *
63 | * @param value KEY
64 | * @param key VALUE
65 | */
66 | + (void)setUserValue:(NSString *)value
67 | forKey:(NSString *)key;
68 |
69 | /**
70 | * 获取关键数据
71 | *
72 | * @return 关键数据
73 | */
74 | + (NSDictionary * BLY_NULLABLE)allUserValues;
75 |
76 | /**
77 | * 设置标签
78 | *
79 | * @param tag 标签ID,可在网站生成
80 | */
81 | + (void)setTag:(NSUInteger)tag;
82 |
83 | /**
84 | * 获取当前设置标签
85 | *
86 | * @return 当前标签ID
87 | */
88 | + (NSUInteger)currentTag;
89 |
90 | /**
91 | * 获取设备ID
92 | *
93 | * @return 设备ID
94 | */
95 | + (NSString *)buglyDeviceId;
96 |
97 | /**
98 | * 上报自定义Objective-C异常
99 | *
100 | * @param exception 异常信息
101 | */
102 | + (void)reportException:(NSException *)exception;
103 |
104 | /**
105 | * 上报错误
106 | *
107 | * @param error 错误信息
108 | */
109 | + (void)reportError:(NSError *)error;
110 |
111 | /**
112 | * @brief 上报自定义错误
113 | *
114 | * @param category 类型(Cocoa=3,CSharp=4,JS=5,Lua=6)
115 | * @param aName 名称
116 | * @param aReason 错误原因
117 | * @param aStackArray 堆栈
118 | * @param info 附加数据
119 | * @param terminate 上报后是否退出应用进程
120 | */
121 | + (void)reportExceptionWithCategory:(NSUInteger)category name:(NSString *)aName reason:(NSString *)aReason callStack:(NSArray *)aStackArray extraInfo:(NSDictionary *)info terminateApp:(BOOL)terminate;
122 |
123 | /**
124 | * SDK 版本信息
125 | *
126 | * @return SDK版本号
127 | */
128 | + (NSString *)sdkVersion;
129 |
130 | /**
131 | * App 是否发生了连续闪退
132 | * 如果启动SDK 且 5秒内 闪退,且次数达到 3次 则判定为连续闪退
133 | *
134 | * @return 是否连续闪退
135 | */
136 | + (BOOL)isAppCrashedOnStartUpExceedTheLimit;
137 |
138 | + (void)setComponentIdentifier:(NSString *)componentId version:(NSString *)version;
139 |
140 | BLY_END_NONNULL
141 |
142 | @end
143 |
--------------------------------------------------------------------------------
/android/app/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # By default, the flags in this file are appended to flags specified
3 | # in /usr/local/Cellar/android-sdk/24.3.3/tools/proguard/proguard-android.txt
4 | # You can edit the include path and order by changing the proguardFiles
5 | # directive in build.gradle.
6 | #
7 | # For more details, see
8 | # http://developer.android.com/guide/developing/tools/proguard.html
9 |
10 | # Add any project specific keep options here:
11 |
12 | # If your project uses WebView with JS, uncomment the following
13 | # and specify the fully qualified class name to the JavaScript interface
14 | # class:
15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
16 | # public *;
17 | #}
18 |
19 | # Disabling obfuscation is useful if you collect stack traces from production crashes
20 | # (unless you are using a system that supports de-obfuscate the stack traces).
21 | -dontobfuscate
22 |
23 | # React Native
24 |
25 | # Keep our interfaces so they can be used by other ProGuard rules.
26 | # See http://sourceforge.net/p/proguard/bugs/466/
27 | -keep,allowobfuscation @interface com.facebook.proguard.annotations.DoNotStrip
28 | -keep,allowobfuscation @interface com.facebook.proguard.annotations.KeepGettersAndSetters
29 | -keep,allowobfuscation @interface com.facebook.common.internal.DoNotStrip
30 |
31 | # Do not strip any method/class that is annotated with @DoNotStrip
32 | -keep @com.facebook.proguard.annotations.DoNotStrip class *
33 | -keep @com.facebook.common.internal.DoNotStrip class *
34 | -keepclassmembers class * {
35 | @com.facebook.proguard.annotations.DoNotStrip *;
36 | @com.facebook.common.internal.DoNotStrip *;
37 | }
38 |
39 | -keepclassmembers @com.facebook.proguard.annotations.KeepGettersAndSetters class * {
40 | void set*(***);
41 | *** get*();
42 | }
43 |
44 | -keep class * extends com.facebook.react.bridge.JavaScriptModule { *; }
45 | -keep class * extends com.facebook.react.bridge.NativeModule { *; }
46 | -keepclassmembers,includedescriptorclasses class * { native ; }
47 | -keepclassmembers class * { @com.facebook.react.uimanager.UIProp ; }
48 | -keepclassmembers class * { @com.facebook.react.uimanager.annotations.ReactProp ; }
49 | -keepclassmembers class * { @com.facebook.react.uimanager.annotations.ReactPropGroup ; }
50 |
51 | -dontwarn com.facebook.react.**
52 |
53 | # TextLayoutBuilder uses a non-public Android constructor within StaticLayout.
54 | # See libs/proxy/src/main/java/com/facebook/fbui/textlayoutbuilder/proxy for details.
55 | -dontwarn android.text.StaticLayout
56 |
57 | # okhttp
58 |
59 | -keepattributes Signature
60 | -keepattributes *Annotation*
61 | -keep class okhttp3.** { *; }
62 | -keep interface okhttp3.** { *; }
63 | -dontwarn okhttp3.**
64 |
65 | # okio
66 |
67 | -keep class sun.misc.Unsafe { *; }
68 | -dontwarn java.nio.file.*
69 | -dontwarn org.codehaus.mojo.animal_sniffer.IgnoreJRERequirement
70 | -dontwarn okio.**
71 |
72 | -keep class com.facebook.imagepipeline.animated.factory.AnimatedFactoryImpl {
73 | public AnimatedFactoryImpl(com.facebook.imagepipeline.bitmaps.PlatformBitmapFactory, com.facebook.imagepipeline.core.ExecutorSupplier);
74 | }
75 |
76 | -keep class com.tencent.mm.sdk.** {
77 | *;
78 | }
79 |
80 | -dontwarn com.tencent.bugly.**
81 | -keep public class com.tencent.bugly.**{*;}
82 |
83 | -ignorewarnings
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | matrix:
2 | include:
3 | - os: osx
4 | language: objective-c
5 | sudo: false
6 | osx_image: xcode9
7 | env: TEST_TYPE=ios
8 | - os: linux
9 | language: android
10 | sudo: required
11 | jdk: oraclejdk8
12 | env: TEST_TYPE=android
13 | android:
14 | components:
15 | - tools
16 | - tools
17 | # The BuildTools version used by your project
18 | - build-tools-26.0.1
19 | - build-tools-25.0.3
20 | - build-tools-25.0.2
21 | - build-tools-23.0.1
22 | # The SDK version used to compile your project
23 | - android-26
24 | - android-25
25 | - android-23
26 | - extra-android-m2repository
27 | - extra-android-support
28 | - os: osx
29 | language: node_js
30 | sudo: false
31 | node_js: 6
32 | env: TEST_TYPE=js
33 | env:
34 | global:
35 | - PROJECT_MOBILE=reading
36 | addons:
37 | code_climate:
38 | repo_token: $CODE_CLIMATE_TOKEN
39 | before_cache:
40 | - rm -f android/.gradle/caches/modules-2/modules-2.lock
41 | - rm -fr android/.gradle/caches/*/plugin-resolution/
42 | cache:
43 | yarn: true
44 | directories:
45 | - node_modules
46 | - android/.gradle/caches
47 | - android/.gradle/wrapper
48 | before_install:
49 | - if [[ $TRAVIS_PULL_REQUEST == 'false' ]]; then openssl aes-256-cbc -K $encrypted_549897007358_key -iv $encrypted_549897007358_iv
50 | -in .travis/secrets.tar.enc -out secrets.tar -d ; fi
51 | - if [[ $TRAVIS_PULL_REQUEST == 'false' ]]; then tar xvf secrets.tar ; fi
52 | - if [[ $TEST_TYPE == 'android' ]] && [[ $TRAVIS_PULL_REQUEST == 'false' ]]; then mv gradle.properties android ; fi
53 | - if [[ $TEST_TYPE == 'android' ]] && [[ $TRAVIS_PULL_REQUEST == 'false' ]]; then mv reading.keystore android/app ; fi
54 | - if [[ $TEST_TYPE != 'js' ]]; then curl -o- https://raw.githubusercontent.com/creationix/nvm/v0.33.6/install.sh | bash ; fi
55 | - if [[ $TEST_TYPE != 'js' ]]; then source ~/.bashrc ; fi
56 | - if [[ $TEST_TYPE != 'js' ]]; then nvm install 6 ; fi
57 | - if [[ $TEST_TYPE != 'js' ]]; then curl -o- -L https://yarnpkg.com/install.sh | bash -s -- --version 1.3.2 ; fi
58 | - if [[ $TEST_TYPE != 'js' ]]; then export PATH=$HOME/.yarn/bin:$PATH ; fi
59 | install:
60 | - if [[ $TEST_TYPE == 'android' ]] && [[ $TRAVIS_PULL_REQUEST == 'false' ]]; then gem install fir-cli ; fi
61 | - rm -Rf "${TMPDIR}/jest_preprocess_cache"
62 | - yarn config set spin=false
63 | - yarn config set progress=false
64 | - travis_wait yarn install
65 | branches:
66 | only:
67 | - master
68 | script:
69 | - if [[ $TEST_TYPE == 'js' ]]; then yarn lint ; fi
70 | - if [[ $TEST_TYPE == 'js' ]]; then yarn test ; fi
71 | - if [[ $TEST_TYPE == 'ios' ]]; then xcodebuild -project ios/$PROJECT_MOBILE.xcodeproj
72 | -scheme $PROJECT_MOBILE -sdk iphonesimulator11.0 -configuration Debug CODE_SIGN_IDENTITY="" CODE_SIGNING_REQUIRED=NO clean build ; fi
73 | - if [ $TEST_TYPE == 'android' ] && [ $TRAVIS_PULL_REQUEST == 'false' ]; then cd android && ./gradlew clean && ./gradlew resguardRelease --stacktrace ; fi
74 | - if [ $TEST_TYPE == 'android' ] && [ $TRAVIS_PULL_REQUEST != 'false' ]; then cd android && ./gradlew clean && ./gradlew assembleDebug --stacktrace ; fi
75 | after_success:
76 | - if [ $TEST_TYPE == 'android' ] && [ $TRAVIS_PULL_REQUEST == 'false' ]; then fir p
77 | $HOME/build/attentiveness/reading/android/app/build/outputs/apk/AndResGuard_app-armeabi-v7a-release/app-armeabi-v7a-release_aligned_signed.apk
78 | -T $FIR_TOKEN -c "$TRAVIS_TAG" ; fi
79 |
--------------------------------------------------------------------------------
/app/pages/Feedback/Feedback.js:
--------------------------------------------------------------------------------
1 | /**
2 | *
3 | * Copyright 2016-present reading
4 | *
5 | * Licensed under the Apache License, Version 2.0 (the "License");
6 | * you may not use this file except in compliance with the License.
7 | * You may obtain a copy of the License at
8 | *
9 | * http://www.apache.org/licenses/LICENSE-2.0
10 | *
11 | * Unless required by applicable law or agreed to in writing, software
12 | * distributed under the License is distributed on an "AS IS" BASIS,
13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | * See the License for the specific language governing permissions and
15 | * limitations under the License.
16 | *
17 | */
18 | import React from 'react';
19 | import { StyleSheet, TextInput, View, Keyboard } from 'react-native';
20 |
21 | import AV from 'leancloud-storage';
22 | import DeviceInfo from 'react-native-device-info';
23 | import Icon from 'react-native-vector-icons/Ionicons';
24 | import ToastUtil from '../../utils/ToastUtil';
25 |
26 | let feedbackText;
27 |
28 | class Feedback extends React.Component {
29 | static navigationOptions = ({ navigation }) => ({
30 | title: '建议',
31 | tabBarIcon: ({ tintColor }) => (
32 |
33 | ),
34 | headerRight: (
35 | {
41 | navigation.state.params.handleCheck();
42 | }}
43 | />
44 | )
45 | });
46 | componentDidMount() {
47 | feedbackText = '';
48 | this.props.navigation.setParams({ handleCheck: this.onActionSelected });
49 | }
50 |
51 | onActionSelected = () => {
52 | if (feedbackText === undefined || feedbackText.replace(/\s+/g, '') === '') {
53 | ToastUtil.showShort('请填写建议内容哦~');
54 | } else {
55 | const feedback = AV.Object.new('Feedback');
56 | feedback.set('manufacturer', DeviceInfo.getManufacturer());
57 | feedback.set('system', DeviceInfo.getSystemName());
58 | feedback.set('deviceVersion', DeviceInfo.getSystemVersion());
59 | feedback.set('deviceModel', DeviceInfo.getModel());
60 | feedback.set('appVersion', DeviceInfo.getVersion());
61 | feedback.set('feedback', feedbackText);
62 | feedback.save();
63 | ToastUtil.showShort('您的问题已反馈,我们会及时跟进处理');
64 | this.textInput.clear();
65 | Keyboard.dismiss();
66 | }
67 | };
68 |
69 | render() {
70 | return (
71 |
72 | {
74 | this.textInput = ref;
75 | }}
76 | style={styles.textInput}
77 | placeholder="请写下您宝贵的意见或建议,与iReading一起进步!"
78 | placeholderTextColor="#aaaaaa"
79 | underlineColorAndroid="transparent"
80 | numberOfLines={200}
81 | multiline
82 | autoFocus
83 | onChangeText={(text) => {
84 | feedbackText = text;
85 | }}
86 | />
87 |
88 | );
89 | }
90 | }
91 |
92 | const styles = StyleSheet.create({
93 | container: {
94 | flex: 1,
95 | flexDirection: 'column',
96 | backgroundColor: '#fff'
97 | },
98 | textInput: {
99 | flex: 1,
100 | fontSize: 18,
101 | padding: 15,
102 | textAlignVertical: 'top'
103 | }
104 | });
105 |
106 | export default Feedback;
107 |
--------------------------------------------------------------------------------
/app/pages/About/About.js:
--------------------------------------------------------------------------------
1 | /**
2 | *
3 | * Copyright 2016-present reading
4 | *
5 | * Licensed under the Apache License, Version 2.0 (the "License");
6 | * you may not use this file except in compliance with the License.
7 | * You may obtain a copy of the License at
8 | *
9 | * http://www.apache.org/licenses/LICENSE-2.0
10 | *
11 | * Unless required by applicable law or agreed to in writing, software
12 | * distributed under the License is distributed on an "AS IS" BASIS,
13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | * See the License for the specific language governing permissions and
15 | * limitations under the License.
16 | *
17 | */
18 | import React from 'react';
19 | import { StyleSheet, Image, Text, Linking, View } from 'react-native';
20 |
21 | import DeviceInfo from 'react-native-device-info';
22 | import Icon from 'react-native-vector-icons/Ionicons';
23 | import Button from '../../components/Button';
24 |
25 | const SHOW_API = 'https://www.showapi.com';
26 | const READING_REPO = 'https://github.com/attentiveness/reading';
27 |
28 | const aboutLogo = require('../../img/about_logo.png');
29 |
30 | class About extends React.Component {
31 | static navigationOptions = {
32 | title: '关于',
33 | tabBarIcon: ({ tintColor }) => (
34 |
35 | ),
36 | headerRight: (
37 | Linking.openURL(READING_REPO)}
43 | />
44 | )
45 | };
46 |
47 | render() {
48 | return (
49 |
50 |
51 |
52 |
53 | {`v${DeviceInfo.getVersion()}`}
54 | iReading
55 | 让生活更精彩
56 |
57 |
58 |
59 |
60 | 免责声明:所有内容均来自:
61 |
62 |
68 |
69 |
70 |
71 | );
72 | }
73 | }
74 |
75 | const styles = StyleSheet.create({
76 | container: {
77 | flex: 1,
78 | flexDirection: 'column',
79 | backgroundColor: '#fff'
80 | },
81 | content: {
82 | flex: 1,
83 | justifyContent: 'center',
84 | paddingBottom: 10
85 | },
86 | center: {
87 | flex: 1,
88 | alignItems: 'center'
89 | },
90 | logo: {
91 | width: 110,
92 | height: 110,
93 | marginTop: 50
94 | },
95 | version: {
96 | fontSize: 16,
97 | textAlign: 'center',
98 | color: '#aaaaaa',
99 | marginTop: 5
100 | },
101 | title: {
102 | fontSize: 28,
103 | textAlign: 'center',
104 | color: '#313131',
105 | marginTop: 10
106 | },
107 | subtitle: {
108 | fontSize: 18,
109 | textAlign: 'center',
110 | color: '#4e4e4e'
111 | },
112 | disclaimerContent: {
113 | flexDirection: 'column'
114 | },
115 | disclaimer: {
116 | fontSize: 14,
117 | textAlign: 'center'
118 | },
119 | bottomContainer: {
120 | alignItems: 'center'
121 | }
122 | });
123 |
124 | export default About;
125 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # iReading
2 |
3 | 
4 |
5 | [](https://travis-ci.org/attentiveness/reading)
6 | [](https://codeclimate.com/github/attentiveness/reading)
7 | [](https://gitter.im/attentiveness/reading)
8 | [](https://raw.githubusercontent.com/attentiveness/reading/master/LICENSE)
9 | [](https://github.com/attentiveness/reading/releases)
10 | [](https://github.com/attentiveness/reading/pulls)
11 |
12 | iReading App Write In React-Native(Studying and Programing)
13 |
14 | **Support: Android 4.1 (API 16)+ IOS(8.0+)**
15 |
16 | > No Profit, No Advertisement, Only Feelings
17 |
18 | ## Screenshot
19 |
20 | 
21 | 
22 | 
23 | 
24 | 
25 |
26 | ## Download
27 |
28 | ### β Version(master branch)
29 |
30 | *Android:* [Download iReading](http://fir.im/w7gu)
31 |
32 | ### From Android Market(Old)
33 |
34 | *360 Android Market:* [Download iReading](http://zhushou.360.cn/detail/index/soft_id/3217938?recrefer=SE_D_Reading)
35 |
36 | *Wandou Labs:* [Download iReading](http://www.wandoujia.com/apps/com.reading)
37 |
38 | ### From App Store(Old)
39 |
40 | [Download iReading](https://itunes.apple.com/cn/app/ireading/id1135411121?l=zh&ls=1&mt=8)
41 |
42 | ## Application Architecture
43 |
44 | - [Microsoft Code Push](https://github.com/Microsoft/react-native-code-push) for dynamic update.
45 | - [Redux](https://github.com/reactjs/redux) is a predictable state container for reading application, together with [React Native](https://github.com/facebook/react-native).
46 | - [Redux-Saga](https://github.com/yelouafi/redux-saga/) is a library that aims to make side effects in reading application easier and better.
47 | - [react-navigation](https://github.com/react-community/react-navigation) is an extensible yet easy-to-use navigation solution, can also be used across React and React Native projects allowing for a higher degree of shared code.
48 | - [Jest](https://facebook.github.io/jest/) for testing [React Native](https://github.com/facebook/react-native) components and UT.
49 | - [Eslint](https://github.com/eslint/eslint) is a tool for identifying and reporting on patterns found in reading application code.
50 | - [react-native-exceptions-manager](https://github.com/Richard-Cao/react-native-exceptions-manager) for handling crashes in release version.
51 |
52 | ## Development Workflow
53 |
54 | ### Step One
55 |
56 | ```
57 | yarn(or npm) install -g react-native-cli
58 | ```
59 | ### Step Two
60 |
61 | ```
62 | yarn(or npm) install
63 | ```
64 | ### Step Three
65 |
66 | ```
67 | react-native start
68 | ```
69 | ### Run Test
70 |
71 | ```
72 | yarn(or npm) test
73 | ```
74 |
75 | ### Format Code
76 |
77 | ```
78 | yarn(or npm run) format
79 | ```
80 |
81 | ### Run Lint
82 |
83 | ```
84 | yarn(or npm run) lint
85 | ```
86 |
87 | ## Importance
88 |
89 | [ShowAPI](https://www.showapi.com/api/lookPoint/582/2) was used by iReading from free to charge, so my key doesn't work. You could apply for a new key and replace in [UrlUtil.js](https://github.com/attentiveness/reading/blob/master/app/utils/UrlUtil.js), it will work properly.
90 | Reference this issue: [Cannot convert undefined or null to object](https://github.com/attentiveness/reading/issues/127).
91 | I apologize to inconvenience you.
92 |
93 | ## Release Note
94 |
95 | [Reading Release Note](https://github.com/attentiveness/reading/releases)
96 |
97 | ## Contributing
98 |
99 | **For more information about contributing PRs and issues, see our [Contribution Guidelines](https://github.com/attentiveness/reading/blob/master/CONTRIBUTING.md).**
100 |
101 | ## License
102 |
103 | Apache License 2.0
104 |
--------------------------------------------------------------------------------
/ios/reading.xcodeproj/xcshareddata/xcschemes/reading.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
29 |
35 |
36 |
37 |
38 |
39 |
44 |
45 |
46 |
47 |
53 |
54 |
55 |
56 |
57 |
58 |
68 |
70 |
76 |
77 |
78 |
79 |
80 |
81 |
87 |
89 |
95 |
96 |
97 |
98 |
100 |
101 |
104 |
105 |
106 |
--------------------------------------------------------------------------------
/android/app/react.gradle:
--------------------------------------------------------------------------------
1 | import org.apache.tools.ant.taskdefs.condition.Os
2 |
3 | def config = project.hasProperty("react") ? project.react : [];
4 |
5 | def bundleAssetName = config.bundleAssetName ?: "index.android.bundle"
6 | def entryFile = config.entryFile ?: "index.android.js"
7 |
8 | // because elvis operator
9 | def elvisFile(thing) {
10 | return thing ? file(thing) : null;
11 | }
12 |
13 | def reactRoot = elvisFile(config.root) ?: file("../../")
14 | def inputExcludes = config.inputExcludes ?: ["android/**", "ios/**"]
15 |
16 | void runBefore(String dependentTaskName, Task task) {
17 | Task dependentTask = tasks.findByPath(dependentTaskName);
18 | if (dependentTask != null) {
19 | dependentTask.dependsOn task
20 | }
21 | }
22 |
23 | gradle.projectsEvaluated {
24 | // Grab all build types and product flavors
25 | def buildTypes = android.buildTypes.collect { type -> type.name }
26 | def productFlavors = android.productFlavors.collect { flavor -> flavor.name }
27 |
28 | // When no product flavors defined, use empty
29 | if (!productFlavors) productFlavors.add('')
30 |
31 | productFlavors.each { productFlavorName ->
32 | buildTypes.each { buildTypeName ->
33 | // Create variant and target names
34 | def targetName = "${productFlavorName.capitalize()}${buildTypeName.capitalize()}"
35 | def targetPath = productFlavorName ?
36 | "${productFlavorName}/${buildTypeName}" :
37 | "${buildTypeName}"
38 |
39 | // React js bundle directories
40 | def jsBundleDirConfigName = "jsBundleDir${targetName}"
41 | def jsBundleDir = elvisFile(config."$jsBundleDirConfigName") ?:
42 | file("$buildDir/intermediates/assets/${targetPath}")
43 |
44 | def resourcesDirConfigName = "resourcesDir${targetName}"
45 | def resourcesDir = elvisFile(config."${resourcesDirConfigName}") ?:
46 | file("$buildDir/intermediates/res/merged/${targetPath}")
47 | def jsBundleFile = file("$jsBundleDir/$bundleAssetName")
48 |
49 | // Bundle task name for variant
50 | def bundleJsAndAssetsTaskName = "bundle${targetName}JsAndAssets"
51 |
52 | def currentBundleTask = tasks.create(
53 | name: bundleJsAndAssetsTaskName,
54 | type: Exec) {
55 | group = "react"
56 | description = "bundle JS and assets for ${targetName}."
57 |
58 | // Create dirs if they are not there (e.g. the "clean" task just ran)
59 | doFirst {
60 | jsBundleDir.mkdirs()
61 | resourcesDir.mkdirs()
62 | }
63 |
64 | // Set up inputs and outputs so gradle can cache the result
65 | inputs.files fileTree(dir: reactRoot, excludes: inputExcludes)
66 | outputs.dir jsBundleDir
67 | outputs.dir resourcesDir
68 |
69 | // Set up the call to the react-native cli
70 | workingDir reactRoot
71 |
72 | // Set up dev mode
73 | def devEnabled = !targetName.toLowerCase().contains("release")
74 | if (Os.isFamily(Os.FAMILY_WINDOWS)) {
75 | commandLine "cmd", "/c", "node", "node_modules/react-native/local-cli/cli.js", "bundle", "--platform", "android", "--dev", "${devEnabled}",
76 | "--entry-file", entryFile, "--bundle-output", jsBundleFile, "--assets-dest", resourcesDir
77 | } else {
78 | commandLine "node", "node_modules/react-native/local-cli/cli.js", "bundle", "--platform", "android", "--dev", "${devEnabled}",
79 | "--entry-file", entryFile, "--bundle-output", jsBundleFile, "--assets-dest", resourcesDir
80 | }
81 |
82 | enabled config."bundleIn${targetName}" ||
83 | config."bundleIn${buildTypeName.capitalize()}" ?:
84 | targetName.toLowerCase().contains("release")
85 | }
86 |
87 | // Hook bundle${productFlavor}${buildType}JsAndAssets into the android build process
88 | currentBundleTask.dependsOn("merge${targetName}Resources")
89 | currentBundleTask.dependsOn("merge${targetName}Assets")
90 |
91 | runBefore("processArmeabi-v7a${targetName}Resources", currentBundleTask)
92 | runBefore("processX86${targetName}Resources", currentBundleTask)
93 | runBefore("processUniversal${targetName}Resources", currentBundleTask)
94 | runBefore("process${targetName}Resources", currentBundleTask)
95 | }
96 | }
97 | }
98 |
--------------------------------------------------------------------------------
/android/app/src/main/java/com/reading/MainApplication.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2016-present reading
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.reading;
18 |
19 | import android.app.Application;
20 |
21 | import com.facebook.infer.annotation.Assertions;
22 | import com.facebook.react.ReactApplication;
23 | import com.facebook.react.ReactInstanceManager;
24 | import com.facebook.react.ReactInstanceManagerBuilder;
25 | import com.facebook.react.ReactNativeHost;
26 | import com.facebook.react.ReactPackage;
27 | import com.facebook.react.common.LifecycleState;
28 | import com.facebook.react.shell.MainReactPackage;
29 | import com.facebook.soloader.SoLoader;
30 | import com.learnium.RNDeviceInfo.RNDeviceInfo;
31 | import com.microsoft.codepush.react.CodePush;
32 | import com.oblador.vectoricons.VectorIconsPackage;
33 | import com.richardcao.exceptionsmanager.react.ExceptionsManager;
34 | import com.tencent.bugly.crashreport.CrashReport;
35 | import com.theweflex.react.WeChatPackage;
36 |
37 | import org.devio.rn.splashscreen.SplashScreenReactPackage;
38 |
39 | import java.util.ArrayList;
40 | import java.util.Arrays;
41 | import java.util.List;
42 |
43 | import javax.annotation.Nullable;
44 |
45 | public class MainApplication extends Application implements ReactApplication {
46 | @Override
47 | public void onCreate() {
48 | super.onCreate();
49 | SoLoader.init(this, /* native exopackage */ false);
50 | if (!BuildConfig.DEBUG) {
51 | CrashReport.initCrashReport(getApplicationContext(), "900019562", false);
52 | }
53 | }
54 |
55 | private final ReactNativeHost mReactNativeHost = new ReactNativeHost(this) {
56 |
57 | @Override
58 | protected ReactInstanceManager createReactInstanceManager() {
59 | ReactInstanceManagerBuilder builder = ReactInstanceManager.builder()
60 | .setApplication(getApplication())
61 | .setJSMainModulePath(getJSMainModuleName())
62 | .setUseDeveloperSupport(getUseDeveloperSupport())
63 | .setRedBoxHandler(getRedBoxHandler())
64 | .setUIImplementationProvider(getUIImplementationProvider())
65 | .setInitialLifecycleState(LifecycleState.BEFORE_CREATE)
66 | .setNativeModuleCallExceptionHandler(
67 | new ReadingNativeModuleCallExceptionHandler());
68 |
69 | for (ReactPackage reactPackage : getPackages()) {
70 | builder.addPackage(reactPackage);
71 | }
72 |
73 | String jsBundleFile = getJSBundleFile();
74 | if (jsBundleFile != null) {
75 | builder.setJSBundleFile(jsBundleFile);
76 | } else {
77 | builder.setBundleAssetName(Assertions.assertNotNull(getBundleAssetName()));
78 | }
79 | return builder.build();
80 | }
81 |
82 | @Override
83 | public boolean getUseDeveloperSupport() {
84 | return BuildConfig.DEBUG;
85 | }
86 |
87 | @Nullable
88 | @Override
89 | protected String getJSBundleFile() {
90 | return CodePush.getJSBundleFile();
91 | }
92 |
93 | @Override
94 | protected String getJSMainModuleName() {
95 | return "index";
96 | }
97 |
98 | @Override
99 | protected List getPackages() {
100 | List packages = Arrays.asList(
101 | new MainReactPackage(),
102 | new SplashScreenReactPackage(),
103 | new WeChatPackage(),
104 | new CodePush(BuildConfig.CODEPUSH_KEY, MainApplication.this, BuildConfig.DEBUG),
105 | new RNDeviceInfo(),
106 | new VectorIconsPackage());
107 | ArrayList packageList = new ArrayList<>(packages);
108 | if (!BuildConfig.DEBUG) {
109 | packageList.add(new ExceptionsManager());
110 | }
111 | return packageList;
112 | }
113 | };
114 |
115 | @Override
116 | public ReactNativeHost getReactNativeHost() {
117 | return mReactNativeHost;
118 | }
119 | }
120 |
--------------------------------------------------------------------------------
/ios/reading.xcodeproj/xcshareddata/xcschemes/reading-tvOS.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
29 |
35 |
36 |
37 |
43 |
49 |
50 |
51 |
52 |
53 |
58 |
59 |
61 |
67 |
68 |
69 |
70 |
71 |
77 |
78 |
79 |
80 |
81 |
82 |
92 |
94 |
100 |
101 |
102 |
103 |
104 |
105 |
111 |
113 |
119 |
120 |
121 |
122 |
124 |
125 |
128 |
129 |
130 |
--------------------------------------------------------------------------------
/ios/reading/Images.xcassets/LaunchImage.launchimage/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images": [
3 | {
4 | "extent": "full-screen",
5 | "idiom": "iphone",
6 | "subtype": "2436h",
7 | "filename": "Default1125x2436.png",
8 | "minimum-system-version": "11.0",
9 | "orientation": "portrait",
10 | "scale": "3x"
11 | },
12 | {
13 | "extent": "full-screen",
14 | "idiom": "iphone",
15 | "subtype": "2436h",
16 | "filename": "Default2436x1125.png",
17 | "minimum-system-version": "11.0",
18 | "orientation": "landscape",
19 | "scale": "3x"
20 | },
21 | {
22 | "orientation": "landscape",
23 | "idiom": "tv",
24 | "filename": "Default3840x2160.png",
25 | "extent": "full-screen",
26 | "minimum-system-version": "11.0",
27 | "scale": "2x"
28 | },
29 | {
30 | "orientation": "landscape",
31 | "idiom": "tv",
32 | "filename": "Default1920x1080.png",
33 | "extent": "full-screen",
34 | "minimum-system-version": "9.0",
35 | "scale": "1x"
36 | },
37 | {
38 | "extent": "full-screen",
39 | "idiom": "iphone",
40 | "subtype": "736h",
41 | "filename": "Default1242x2208.png",
42 | "minimum-system-version": "8.0",
43 | "orientation": "portrait",
44 | "scale": "3x"
45 | },
46 | {
47 | "extent": "full-screen",
48 | "idiom": "iphone",
49 | "subtype": "736h",
50 | "filename": "Default2208x1242.png",
51 | "minimum-system-version": "8.0",
52 | "orientation": "landscape",
53 | "scale": "3x"
54 | },
55 | {
56 | "extent": "full-screen",
57 | "idiom": "iphone",
58 | "subtype": "667h",
59 | "filename": "Default750x1334.png",
60 | "minimum-system-version": "8.0",
61 | "orientation": "portrait",
62 | "scale": "2x"
63 | },
64 | {
65 | "orientation": "portrait",
66 | "idiom": "iphone",
67 | "filename": "Default640x960.png",
68 | "extent": "full-screen",
69 | "minimum-system-version": "7.0",
70 | "scale": "2x"
71 | },
72 | {
73 | "extent": "full-screen",
74 | "idiom": "iphone",
75 | "subtype": "retina4",
76 | "filename": "Default640x1136.png",
77 | "minimum-system-version": "7.0",
78 | "orientation": "portrait",
79 | "scale": "2x"
80 | },
81 | {
82 | "orientation": "portrait",
83 | "idiom": "ipad",
84 | "filename": "Default768x1024.png",
85 | "extent": "full-screen",
86 | "minimum-system-version": "7.0",
87 | "scale": "1x"
88 | },
89 | {
90 | "orientation": "landscape",
91 | "idiom": "ipad",
92 | "filename": "Default1024x768.png",
93 | "extent": "full-screen",
94 | "minimum-system-version": "7.0",
95 | "scale": "1x"
96 | },
97 | {
98 | "orientation": "portrait",
99 | "idiom": "ipad",
100 | "filename": "Default1536x2048.png",
101 | "extent": "full-screen",
102 | "minimum-system-version": "7.0",
103 | "scale": "2x"
104 | },
105 | {
106 | "orientation": "landscape",
107 | "idiom": "ipad",
108 | "filename": "Default2048x1536.png",
109 | "extent": "full-screen",
110 | "minimum-system-version": "7.0",
111 | "scale": "2x"
112 | },
113 | {
114 | "orientation": "portrait",
115 | "idiom": "iphone",
116 | "filename": "Default320x480.png",
117 | "extent": "full-screen",
118 | "scale": "1x"
119 | },
120 | {
121 | "orientation": "portrait",
122 | "idiom": "iphone",
123 | "filename": "Default640x960.png",
124 | "extent": "full-screen",
125 | "scale": "2x"
126 | },
127 | {
128 | "orientation": "portrait",
129 | "idiom": "iphone",
130 | "filename": "Default640x1136.png",
131 | "extent": "full-screen",
132 | "subtype": "retina4",
133 | "scale": "2x"
134 | },
135 | {
136 | "orientation": "portrait",
137 | "idiom": "ipad",
138 | "filename": "Default768x1024.png",
139 | "extent": "full-screen",
140 | "scale": "1x"
141 | },
142 | {
143 | "orientation": "landscape",
144 | "idiom": "ipad",
145 | "filename": "Default1024x768.png",
146 | "extent": "full-screen",
147 | "scale": "1x"
148 | },
149 | {
150 | "orientation": "portrait",
151 | "idiom": "ipad",
152 | "filename": "Default1536x2048.png",
153 | "extent": "full-screen",
154 | "scale": "2x"
155 | },
156 | {
157 | "orientation": "landscape",
158 | "idiom": "ipad",
159 | "filename": "Default2048x1536.png",
160 | "extent": "full-screen",
161 | "scale": "2x"
162 | }
163 | ],
164 | "info": {
165 | "version": 1,
166 | "author": "fanstudio"
167 | }
168 | }
--------------------------------------------------------------------------------
/android/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | ##############################################################################
4 | ##
5 | ## Gradle start up script for UN*X
6 | ##
7 | ##############################################################################
8 |
9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
10 | DEFAULT_JVM_OPTS=""
11 |
12 | APP_NAME="Gradle"
13 | APP_BASE_NAME=`basename "$0"`
14 |
15 | # Use the maximum available, or set MAX_FD != -1 to use that value.
16 | MAX_FD="maximum"
17 |
18 | warn ( ) {
19 | echo "$*"
20 | }
21 |
22 | die ( ) {
23 | echo
24 | echo "$*"
25 | echo
26 | exit 1
27 | }
28 |
29 | # OS specific support (must be 'true' or 'false').
30 | cygwin=false
31 | msys=false
32 | darwin=false
33 | case "`uname`" in
34 | CYGWIN* )
35 | cygwin=true
36 | ;;
37 | Darwin* )
38 | darwin=true
39 | ;;
40 | MINGW* )
41 | msys=true
42 | ;;
43 | esac
44 |
45 | # For Cygwin, ensure paths are in UNIX format before anything is touched.
46 | if $cygwin ; then
47 | [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
48 | fi
49 |
50 | # Attempt to set APP_HOME
51 | # Resolve links: $0 may be a link
52 | PRG="$0"
53 | # Need this for relative symlinks.
54 | while [ -h "$PRG" ] ; do
55 | ls=`ls -ld "$PRG"`
56 | link=`expr "$ls" : '.*-> \(.*\)$'`
57 | if expr "$link" : '/.*' > /dev/null; then
58 | PRG="$link"
59 | else
60 | PRG=`dirname "$PRG"`"/$link"
61 | fi
62 | done
63 | SAVED="`pwd`"
64 | cd "`dirname \"$PRG\"`/" >&-
65 | APP_HOME="`pwd -P`"
66 | cd "$SAVED" >&-
67 |
68 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
69 |
70 | # Determine the Java command to use to start the JVM.
71 | if [ -n "$JAVA_HOME" ] ; then
72 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
73 | # IBM's JDK on AIX uses strange locations for the executables
74 | JAVACMD="$JAVA_HOME/jre/sh/java"
75 | else
76 | JAVACMD="$JAVA_HOME/bin/java"
77 | fi
78 | if [ ! -x "$JAVACMD" ] ; then
79 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
80 |
81 | Please set the JAVA_HOME variable in your environment to match the
82 | location of your Java installation."
83 | fi
84 | else
85 | JAVACMD="java"
86 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
87 |
88 | Please set the JAVA_HOME variable in your environment to match the
89 | location of your Java installation."
90 | fi
91 |
92 | # Increase the maximum file descriptors if we can.
93 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
94 | MAX_FD_LIMIT=`ulimit -H -n`
95 | if [ $? -eq 0 ] ; then
96 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
97 | MAX_FD="$MAX_FD_LIMIT"
98 | fi
99 | ulimit -n $MAX_FD
100 | if [ $? -ne 0 ] ; then
101 | warn "Could not set maximum file descriptor limit: $MAX_FD"
102 | fi
103 | else
104 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
105 | fi
106 | fi
107 |
108 | # For Darwin, add options to specify how the application appears in the dock
109 | if $darwin; then
110 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
111 | fi
112 |
113 | # For Cygwin, switch paths to Windows format before running java
114 | if $cygwin ; then
115 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
116 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
117 |
118 | # We build the pattern for arguments to be converted via cygpath
119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
120 | SEP=""
121 | for dir in $ROOTDIRSRAW ; do
122 | ROOTDIRS="$ROOTDIRS$SEP$dir"
123 | SEP="|"
124 | done
125 | OURCYGPATTERN="(^($ROOTDIRS))"
126 | # Add a user-defined pattern to the cygpath arguments
127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
129 | fi
130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
131 | i=0
132 | for arg in "$@" ; do
133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
135 |
136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
138 | else
139 | eval `echo args$i`="\"$arg\""
140 | fi
141 | i=$((i+1))
142 | done
143 | case $i in
144 | (0) set -- ;;
145 | (1) set -- "$args0" ;;
146 | (2) set -- "$args0" "$args1" ;;
147 | (3) set -- "$args0" "$args1" "$args2" ;;
148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
154 | esac
155 | fi
156 |
157 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
158 | function splitJvmOpts() {
159 | JVM_OPTS=("$@")
160 | }
161 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
162 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
163 |
164 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
165 |
--------------------------------------------------------------------------------
/app/pages/MainPage/Main.js:
--------------------------------------------------------------------------------
1 | /**
2 | *
3 | * Copyright 2016-present reading
4 | *
5 | * Licensed under the Apache License, Version 2.0 (the "License");
6 | * you may not use this file except in compliance with the License.
7 | * You may obtain a copy of the License at
8 | *
9 | * http://www.apache.org/licenses/LICENSE-2.0
10 | *
11 | * Unless required by applicable law or agreed to in writing, software
12 | * distributed under the License is distributed on an "AS IS" BASIS,
13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | * See the License for the specific language governing permissions and
15 | * limitations under the License.
16 | *
17 | */
18 | import React from 'react';
19 | import PropTypes from 'prop-types';
20 | import {
21 | DeviceEventEmitter,
22 | InteractionManager,
23 | ListView,
24 | StyleSheet,
25 | View
26 | } from 'react-native';
27 | import ScrollableTabView, { ScrollableTabBar } from 'react-native-scrollable-tab-view';
28 | import store from 'react-native-simple-store';
29 | import { SafeAreaView } from 'react-navigation';
30 |
31 | import LoadingView from '../../components/LoadingView';
32 | import ToastUtil from '../../utils/ToastUtil';
33 | import { getArticleList, getTypeName } from '../../utils/ItemsUtil';
34 | import ItemCell from './ItemCell';
35 | import Footer from './Footer';
36 | import EmptyView from './EmptyView';
37 | import ItemListView from './ItemListView';
38 |
39 | const propTypes = {
40 | readActions: PropTypes.object,
41 | read: PropTypes.object.isRequired
42 | };
43 |
44 | const pages = [];
45 | let loadMoreTime = 0;
46 | let currentLoadMoreTypeId;
47 |
48 | class Main extends React.Component {
49 | constructor(props) {
50 | super(props);
51 | this.state = {
52 | dataSource: new ListView.DataSource({
53 | rowHasChanged: (row1, row2) => row1 !== row2
54 | }),
55 | typeIds: [],
56 | typeList: {}
57 | };
58 | }
59 |
60 | componentDidMount() {
61 | const { readActions } = this.props;
62 | DeviceEventEmitter.addListener('changeCategory', (typeIds) => {
63 | typeIds.forEach((typeId) => {
64 | readActions.requestArticleList(false, true, typeId);
65 | pages.push(1);
66 | });
67 | this.setState({
68 | typeIds
69 | });
70 | });
71 | InteractionManager.runAfterInteractions(() => {
72 | store.get('typeIds').then((typeIds) => {
73 | if (!typeIds) {
74 | return;
75 | }
76 | typeIds.forEach((typeId) => {
77 | readActions.requestArticleList(false, true, typeId);
78 | pages.push(1);
79 | });
80 | store.get('typeList').then(typeList =>
81 | this.setState({
82 | typeIds,
83 | typeList
84 | }));
85 | });
86 | });
87 | }
88 |
89 | componentWillReceiveProps(nextProps) {
90 | const { read } = this.props;
91 | if (
92 | read.isLoadMore &&
93 | !nextProps.read.isLoadMore &&
94 | !nextProps.read.isRefreshing
95 | ) {
96 | if (nextProps.read.noMore) {
97 | ToastUtil.showShort('没有更多数据了');
98 | const index = this.state.typeIds.indexOf(currentLoadMoreTypeId);
99 | if (index >= 0) {
100 | pages[index] -= 1;
101 | }
102 | }
103 | }
104 | }
105 |
106 | componentWillUnmount() {
107 | DeviceEventEmitter.removeAllListeners('changeCategory');
108 | }
109 |
110 | onRefresh = (typeId) => {
111 | const { readActions } = this.props;
112 | readActions.requestArticleList(true, false, typeId);
113 | const index = this.state.typeIds.indexOf(typeId);
114 | if (index >= 0) {
115 | pages[index] = 1;
116 | }
117 | };
118 |
119 | onPress = (article) => {
120 | const { navigate } = this.props.navigation;
121 | navigate('Web', { article });
122 | };
123 |
124 | onIconClicked = () => {
125 | this.drawer.openDrawer();
126 | };
127 |
128 | onEndReached = (typeId) => {
129 | currentLoadMoreTypeId = typeId;
130 | const time = Date.parse(new Date()) / 1000;
131 | const index = this.state.typeIds.indexOf(typeId);
132 | if (index < 0) {
133 | return;
134 | }
135 | if (time - loadMoreTime > 1) {
136 | pages[index] += 1;
137 | const { readActions } = this.props;
138 | readActions.requestArticleList(false, false, typeId, true, pages[index]);
139 | loadMoreTime = Date.parse(new Date()) / 1000;
140 | }
141 | };
142 | renderFooter = () => {
143 | const { read } = this.props;
144 | return read.isLoadMore ? : ;
145 | };
146 |
147 | renderItem = article => (
148 |
149 | );
150 |
151 | renderContent = (dataSource, typeId) => {
152 | const { read } = this.props;
153 | if (read.loading) {
154 | return ;
155 | }
156 | const isEmpty =
157 | read.articleList[typeId] === undefined ||
158 | read.articleList[typeId].length === 0;
159 | if (isEmpty) {
160 | return (
161 |
162 | );
163 | }
164 | return (
165 |
174 | );
175 | };
176 |
177 | render() {
178 | const { read } = this.props;
179 | const content = this.state.typeIds.map((typeId) => {
180 | if (this.state.typeList === null) {
181 | return null;
182 | }
183 | const name = getTypeName(this.state.typeList, typeId);
184 | const typeView = (
185 |
186 | {this.renderContent(
187 | this.state.dataSource.cloneWithRows(getArticleList(read.articleList[typeId])),
188 | typeId
189 | )}
190 |
191 | );
192 | return typeView;
193 | });
194 | return (
195 |
196 | (
198 |
202 | )}
203 | tabBarBackgroundColor="#fcfcfc"
204 | tabBarUnderlineStyle={styles.tabBarUnderline}
205 | tabBarActiveTextColor="#3e9ce9"
206 | tabBarInactiveTextColor="#aaaaaa"
207 | >
208 | {content}
209 |
210 |
211 | );
212 | }
213 | }
214 |
215 | const styles = StyleSheet.create({
216 | base: {
217 | flex: 1
218 | },
219 | container: {
220 | flex: 1,
221 | flexDirection: 'column',
222 | backgroundColor: '#fff'
223 | },
224 | drawerContent: {
225 | flexDirection: 'row',
226 | alignItems: 'center',
227 | padding: 15,
228 | borderBottomWidth: 1,
229 | borderBottomColor: '#ddd'
230 | },
231 | drawerTitleContent: {
232 | height: 120,
233 | justifyContent: 'flex-end',
234 | padding: 20,
235 | backgroundColor: '#3e9ce9'
236 | },
237 | drawerIcon: {
238 | width: 30,
239 | height: 30,
240 | marginLeft: 5
241 | },
242 | drawerTitle: {
243 | fontSize: 20,
244 | textAlign: 'left',
245 | color: '#fcfcfc'
246 | },
247 | drawerText: {
248 | fontSize: 18,
249 | marginLeft: 15,
250 | textAlign: 'center',
251 | color: 'black'
252 | },
253 | timeAgo: {
254 | fontSize: 14,
255 | color: '#aaaaaa',
256 | marginTop: 5
257 | },
258 | refreshControlBase: {
259 | backgroundColor: 'transparent'
260 | },
261 | tab: {
262 | paddingBottom: 0
263 | },
264 | tabText: {
265 | fontSize: 16
266 | },
267 | tabBarUnderline: {
268 | backgroundColor: '#3e9ce9',
269 | height: 2
270 | }
271 | });
272 |
273 | Main.propTypes = propTypes;
274 |
275 | export default Main;
276 |
--------------------------------------------------------------------------------
/app/pages/Category/Category.js:
--------------------------------------------------------------------------------
1 | /**
2 | *
3 | * Copyright 2016-present reading
4 | *
5 | * Licensed under the Apache License, Version 2.0 (the "License");
6 | * you may not use this file except in compliance with the License.
7 | * You may obtain a copy of the License at
8 | *
9 | * http://www.apache.org/licenses/LICENSE-2.0
10 | *
11 | * Unless required by applicable law or agreed to in writing, software
12 | * distributed under the License is distributed on an "AS IS" BASIS,
13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | * See the License for the specific language governing permissions and
15 | * limitations under the License.
16 | *
17 | */
18 | import React from 'react';
19 | import PropTypes from 'prop-types';
20 | import {
21 | InteractionManager,
22 | StyleSheet,
23 | Text,
24 | View,
25 | DeviceEventEmitter,
26 | ScrollView,
27 | RefreshControl,
28 | Alert
29 | } from 'react-native';
30 |
31 | import AV from 'leancloud-storage';
32 | import store from 'react-native-simple-store';
33 | import GridView from '../../components/GridView';
34 | import Button from '../../components/Button';
35 | import ToastUtil from '../../utils/ToastUtil';
36 | import NavigationUtil from '../../utils/NavigationUtil';
37 |
38 | let tempTypeIds = [];
39 | let maxCategory = 5; // 默认最多5个类别,远端可配置
40 |
41 | const propTypes = {
42 | categoryActions: PropTypes.object,
43 | category: PropTypes.object.isRequired
44 | };
45 |
46 | class Category extends React.Component {
47 | constructor(props) {
48 | super(props);
49 | this.state = {
50 | typeIds: tempTypeIds
51 | };
52 | }
53 |
54 | componentWillMount() {
55 | const { params } = this.props.navigation.state;
56 | if (params === undefined || !params.isFirst) {
57 | InteractionManager.runAfterInteractions(() => {
58 | store.get('typeIds').then((typeIds) => {
59 | tempTypeIds = typeIds;
60 | this.setState({
61 | typeIds
62 | });
63 | });
64 | });
65 | }
66 | }
67 |
68 | componentDidMount() {
69 | const { categoryActions } = this.props;
70 | categoryActions.requestTypeList();
71 | const query = new AV.Query('Reading_Settings');
72 | query.get('57b86e0ba633bd002a96436b').then((settings) => {
73 | maxCategory = settings.get('max_category');
74 | });
75 | const { params } = this.props.navigation.state;
76 | if (params === undefined || !params.isFirst) {
77 | this.props.navigation.setParams({ handleCheck: this.onActionSelected });
78 | }
79 | }
80 |
81 | onRefresh = () => {
82 | const { categoryActions } = this.props;
83 | categoryActions.requestTypeList();
84 | };
85 |
86 | onPress = (type) => {
87 | const pos = tempTypeIds.indexOf(parseInt(type.id));
88 | if (pos === -1) {
89 | tempTypeIds.push(parseInt(type.id));
90 | } else {
91 | tempTypeIds.splice(pos, 1);
92 | }
93 | this.setState({
94 | typeIds: tempTypeIds
95 | });
96 | };
97 |
98 | onSelectCategory = () => {
99 | if (this.state.typeIds.length === 0) {
100 | Alert.alert('提示', '您确定不选择任何分类吗?', [
101 | { text: '取消', style: 'cancel' },
102 | {
103 | text: '确定',
104 | onPress: () => {
105 | store.save('typeIds', this.state.typeIds);
106 | NavigationUtil.reset(this.props.navigation, 'Home');
107 | }
108 | }
109 | ]);
110 | } else if (this.state.typeIds.length > maxCategory) {
111 | ToastUtil.showShort(`不要超过${maxCategory}个类别哦`);
112 | } else {
113 | store.save('typeIds', this.state.typeIds);
114 | store.save('isInit', true);
115 | NavigationUtil.reset(this.props.navigation, 'Home');
116 | }
117 | };
118 |
119 | onActionSelected = () => {
120 | if (tempTypeIds.length > maxCategory) {
121 | ToastUtil.showShort(`不要超过${maxCategory}个类别哦`);
122 | return;
123 | }
124 | if (tempTypeIds.length < 1) {
125 | ToastUtil.showShort('不要少于1个类别哦');
126 | }
127 | const { navigate } = this.props.navigation;
128 | InteractionManager.runAfterInteractions(() => {
129 | store.get('typeIds').then((typeIds) => {
130 | if (
131 | typeIds.sort().toString() ===
132 | Array.from(tempTypeIds)
133 | .sort()
134 | .toString()
135 | ) {
136 | navigate('Main');
137 | return;
138 | }
139 | store.save('typeIds', this.state.typeIds).then(this.routeMain);
140 | });
141 | });
142 | };
143 |
144 | routeMain = () => {
145 | const { navigate } = this.props.navigation;
146 | DeviceEventEmitter.emit('changeCategory', this.state.typeIds);
147 | navigate('Main');
148 | };
149 |
150 | renderItem = (item) => {
151 | const isSelect =
152 | Array.from(this.state.typeIds).indexOf(parseInt(item.id)) !== -1;
153 | return (
154 |