├── .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 | 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 |