├── .watchmanconfig ├── ios ├── Podfile ├── Classes │ ├── H5 │ │ ├── GFWebResourceURLProtocol.h │ │ ├── GFWebResourceInterceptorSettings+Internal.h │ │ ├── GFWebResourceInterceptorSettings.m │ │ ├── GFWebResourceInterceptorSettings.h │ │ ├── GFWebResourceCache.h │ │ └── GFWebResourceInterceptor.h │ ├── DXRNUtils.h │ ├── DXRefreshControl.h │ ├── GFDiskCacheManager.h │ ├── DXTopMessageManager.h │ ├── Additions │ │ ├── NSString+GFAdditions.h │ │ └── NSString+GFAdditions.m │ ├── GFDiskCacheManager.m │ ├── DXRNUtils.m │ ├── NavigationCategory │ │ └── DXTextCategoryMenu.h │ ├── UIView+TopBarMessage.h │ ├── DXTopMessageManager.m │ └── DXFileManager.h ├── gitfeed │ ├── Images.xcassets │ │ └── AppIcon.appiconset │ │ │ ├── Icon-40@2x.png │ │ │ ├── Icon-40@3x.png │ │ │ ├── Icon-60@2x.png │ │ │ ├── Icon-60@3x.png │ │ │ ├── Icon-40@2x-1.png │ │ │ ├── Icon-40@3x-1.png │ │ │ ├── Icon-Small@2x.png │ │ │ ├── Icon-Small@3x.png │ │ │ └── Contents.json │ ├── gitfeed.entitlements │ ├── AppDelegate.h │ ├── main.m │ ├── AppDelegate.m │ ├── Info.plist │ └── Base.lproj │ │ └── LaunchScreen.xib ├── Podfile.lock ├── gitfeed.xcworkspace │ └── contents.xcworkspacedata └── gitfeedTests │ ├── Info.plist │ └── gitfeedTests.m ├── .babelrc ├── AppIcons ├── ios │ ├── Icon.png │ ├── Icon-60.png │ ├── Icon-72.png │ ├── Icon@2x.png │ ├── Icon-72@2x.png │ ├── Icon-Small-50.png │ ├── iTunesArtwork.png │ ├── Icon-Small-50@2x.png │ ├── iTunesArtwork@2x.png │ ├── 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-Small.png │ │ ├── Icon-Small@2x.png │ │ ├── Icon-Small@3x.png │ │ └── Contents.json │ └── README.md ├── android │ ├── playstore-icon.png │ ├── drawable-hdpi │ │ └── ic_launcher.png │ ├── drawable-ldpi │ │ └── ic_launcher.png │ ├── drawable-mdpi │ │ └── ic_launcher.png │ ├── drawable-xhdpi │ │ └── ic_launcher.png │ ├── drawable-xxhdpi │ │ └── ic_launcher.png │ └── drawable-xxxhdpi │ │ └── ic_launcher.png └── watchkit │ └── AppIcon.appiconset │ ├── Icon-24@2x.png │ ├── Icon-29@2x.png │ ├── Icon-29@3x.png │ ├── Icon-40@2x.png │ ├── Icon-44@2x.png │ ├── Icon-86@2x.png │ ├── Icon-98@2x.png │ ├── Icon-27.5@2x.png │ └── Contents.json ├── js ├── assets │ ├── chat@2x.png │ ├── chat@3x.png │ ├── ding@2x.png │ ├── ding@3x.png │ ├── pyq@2x.png │ ├── pyq@3x.png │ ├── push-nux.png │ ├── push-nux@2x.png │ ├── push-nux@3x.png │ ├── colorful-windows.jpg │ ├── ic_detail_love@2x.png │ └── ic_detail_unlove@2x.png ├── common │ ├── WLStyles.js │ ├── BlurView │ │ ├── index.js │ │ ├── BlurView.ios.js │ │ └── BlurView.android.js │ ├── Button.ios.js │ ├── markdown │ │ └── issue-html.js │ ├── Button.android.js │ ├── CommonStyles.js │ ├── Pagination.js │ ├── Util.js │ ├── MarkdownView.js │ ├── RoundTagButton.js │ ├── GridView.js │ ├── ReadMore.js │ ├── UserCell.js │ ├── F8StyleSheet.js │ ├── F8Touchable.js │ ├── GridPage.js │ ├── F8Colors.js │ ├── IconCell.js │ ├── TabBarAndroid.js │ ├── SettingsCell.js │ ├── Placeholder.js │ ├── F8Text.js │ ├── DefaultTabBar.js │ ├── PushNUXModal.js │ ├── RepoCell.js │ └── F8Button.js ├── services │ ├── BridgeUtils.js │ ├── Share.js │ ├── xFetch.js │ ├── Login.js │ ├── LookFetch.js │ └── Storage.js ├── actions │ ├── navigation.js │ ├── language.js │ ├── index.js │ ├── installation.js │ ├── search.js │ ├── login.js │ ├── parse.js │ └── notifications.js ├── reducers │ ├── navigation.js │ ├── index.js │ ├── search.js │ ├── login.js │ ├── language.js │ └── notifications.js ├── constants │ ├── Styles.js │ ├── Alerts.js │ ├── Layout.js │ └── Colors.js ├── pages │ ├── WebSharePage.js │ ├── FavPage.js │ ├── RecentPage.js │ ├── FamousPage.js │ ├── NotificationPage.js │ ├── HomePage.js │ ├── Languages.js │ ├── TrendPicker.js │ ├── ShowcasePage.js │ ├── DetailPage.js │ ├── HotTags.js │ ├── SearchHistory.js │ ├── IssueDetailPage.js │ ├── NotificationCell.js │ └── PersonPage.js ├── store │ ├── array.js │ ├── analytics.js │ ├── configureStore.js │ ├── track.js │ └── promise.js ├── _Playgroun.js ├── navigation │ ├── Router.js │ └── RootNavigation.js ├── Playground.js ├── setup.js ├── env.js ├── WLApp.js └── PushNotificationsController.js ├── .buckconfig ├── android ├── app │ ├── src │ │ └── main │ │ │ ├── assets │ │ │ └── fonts │ │ │ │ ├── Entypo.ttf │ │ │ │ ├── Ionicons.ttf │ │ │ │ ├── Octicons.ttf │ │ │ │ ├── Zocial.ttf │ │ │ │ ├── EvilIcons.ttf │ │ │ │ ├── Foundation.ttf │ │ │ │ ├── FontAwesome.ttf │ │ │ │ ├── MaterialIcons.ttf │ │ │ │ └── SimpleLineIcons.ttf │ │ │ ├── res │ │ │ ├── mipmap-hdpi │ │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-mdpi │ │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-xhdpi │ │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-xxhdpi │ │ │ │ └── ic_launcher.png │ │ │ └── values │ │ │ │ ├── strings.xml │ │ │ │ └── styles.xml │ │ │ ├── java │ │ │ └── com │ │ │ │ └── gitfeed │ │ │ │ ├── MainActivity.java │ │ │ │ └── MainApplication.java │ │ │ └── AndroidManifest.xml │ ├── BUCK │ └── proguard-rules.pro ├── keystores │ ├── debug.keystore.properties │ └── BUCK ├── build.gradle ├── settings.gradle ├── gradle.properties └── gradlew.bat ├── index.ios.js ├── index.android.js ├── __tests__ ├── index.ios.js └── index.android.js ├── package.json ├── README.md ├── .flowconfig └── .gitignore /.watchmanconfig: -------------------------------------------------------------------------------- 1 | {} -------------------------------------------------------------------------------- /ios/Podfile: -------------------------------------------------------------------------------- 1 | pod 'UMengAnalytics-NO-IDFA' 2 | -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["react-native-stage-0/decorator-support"] 3 | } -------------------------------------------------------------------------------- /AppIcons/ios/Icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiekw2010/react-native-gitfeed/HEAD/AppIcons/ios/Icon.png -------------------------------------------------------------------------------- /js/assets/chat@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiekw2010/react-native-gitfeed/HEAD/js/assets/chat@2x.png -------------------------------------------------------------------------------- /js/assets/chat@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiekw2010/react-native-gitfeed/HEAD/js/assets/chat@3x.png -------------------------------------------------------------------------------- /js/assets/ding@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiekw2010/react-native-gitfeed/HEAD/js/assets/ding@2x.png -------------------------------------------------------------------------------- /js/assets/ding@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiekw2010/react-native-gitfeed/HEAD/js/assets/ding@3x.png -------------------------------------------------------------------------------- /js/assets/pyq@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiekw2010/react-native-gitfeed/HEAD/js/assets/pyq@2x.png -------------------------------------------------------------------------------- /js/assets/pyq@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiekw2010/react-native-gitfeed/HEAD/js/assets/pyq@3x.png -------------------------------------------------------------------------------- /js/assets/push-nux.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiekw2010/react-native-gitfeed/HEAD/js/assets/push-nux.png -------------------------------------------------------------------------------- /AppIcons/ios/Icon-60.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiekw2010/react-native-gitfeed/HEAD/AppIcons/ios/Icon-60.png -------------------------------------------------------------------------------- /AppIcons/ios/Icon-72.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiekw2010/react-native-gitfeed/HEAD/AppIcons/ios/Icon-72.png -------------------------------------------------------------------------------- /AppIcons/ios/Icon@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiekw2010/react-native-gitfeed/HEAD/AppIcons/ios/Icon@2x.png -------------------------------------------------------------------------------- /js/assets/push-nux@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiekw2010/react-native-gitfeed/HEAD/js/assets/push-nux@2x.png -------------------------------------------------------------------------------- /js/assets/push-nux@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiekw2010/react-native-gitfeed/HEAD/js/assets/push-nux@3x.png -------------------------------------------------------------------------------- /AppIcons/ios/Icon-72@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiekw2010/react-native-gitfeed/HEAD/AppIcons/ios/Icon-72@2x.png -------------------------------------------------------------------------------- /AppIcons/ios/Icon-Small-50.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiekw2010/react-native-gitfeed/HEAD/AppIcons/ios/Icon-Small-50.png -------------------------------------------------------------------------------- /AppIcons/ios/iTunesArtwork.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiekw2010/react-native-gitfeed/HEAD/AppIcons/ios/iTunesArtwork.png -------------------------------------------------------------------------------- /js/assets/colorful-windows.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiekw2010/react-native-gitfeed/HEAD/js/assets/colorful-windows.jpg -------------------------------------------------------------------------------- /js/assets/ic_detail_love@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiekw2010/react-native-gitfeed/HEAD/js/assets/ic_detail_love@2x.png -------------------------------------------------------------------------------- /AppIcons/ios/Icon-Small-50@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiekw2010/react-native-gitfeed/HEAD/AppIcons/ios/Icon-Small-50@2x.png -------------------------------------------------------------------------------- /AppIcons/ios/iTunesArtwork@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiekw2010/react-native-gitfeed/HEAD/AppIcons/ios/iTunesArtwork@2x.png -------------------------------------------------------------------------------- /js/assets/ic_detail_unlove@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiekw2010/react-native-gitfeed/HEAD/js/assets/ic_detail_unlove@2x.png -------------------------------------------------------------------------------- /AppIcons/android/playstore-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiekw2010/react-native-gitfeed/HEAD/AppIcons/android/playstore-icon.png -------------------------------------------------------------------------------- /js/common/WLStyles.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | separator: { 3 | height: 1.0, 4 | backgroundColor: '#EEEEEE' 5 | }, 6 | }; 7 | -------------------------------------------------------------------------------- /ios/Classes/H5/GFWebResourceURLProtocol.h: -------------------------------------------------------------------------------- 1 | @import Foundation; 2 | 3 | @interface GFWebResourceURLProtocol : NSURLProtocol 4 | 5 | @end 6 | -------------------------------------------------------------------------------- /.buckconfig: -------------------------------------------------------------------------------- 1 | 2 | [android] 3 | target = Google Inc.:Google APIs:23 4 | 5 | [maven_repositories] 6 | central = https://repo1.maven.org/maven2 7 | -------------------------------------------------------------------------------- /AppIcons/ios/AppIcon.appiconset/Icon-40.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiekw2010/react-native-gitfeed/HEAD/AppIcons/ios/AppIcon.appiconset/Icon-40.png -------------------------------------------------------------------------------- /AppIcons/ios/AppIcon.appiconset/Icon-76.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiekw2010/react-native-gitfeed/HEAD/AppIcons/ios/AppIcon.appiconset/Icon-76.png -------------------------------------------------------------------------------- /AppIcons/android/drawable-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiekw2010/react-native-gitfeed/HEAD/AppIcons/android/drawable-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /AppIcons/android/drawable-ldpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiekw2010/react-native-gitfeed/HEAD/AppIcons/android/drawable-ldpi/ic_launcher.png -------------------------------------------------------------------------------- /AppIcons/android/drawable-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiekw2010/react-native-gitfeed/HEAD/AppIcons/android/drawable-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /AppIcons/ios/AppIcon.appiconset/Icon-40@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiekw2010/react-native-gitfeed/HEAD/AppIcons/ios/AppIcon.appiconset/Icon-40@2x.png -------------------------------------------------------------------------------- /AppIcons/ios/AppIcon.appiconset/Icon-40@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiekw2010/react-native-gitfeed/HEAD/AppIcons/ios/AppIcon.appiconset/Icon-40@3x.png -------------------------------------------------------------------------------- /AppIcons/ios/AppIcon.appiconset/Icon-60@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiekw2010/react-native-gitfeed/HEAD/AppIcons/ios/AppIcon.appiconset/Icon-60@2x.png -------------------------------------------------------------------------------- /AppIcons/ios/AppIcon.appiconset/Icon-60@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiekw2010/react-native-gitfeed/HEAD/AppIcons/ios/AppIcon.appiconset/Icon-60@3x.png -------------------------------------------------------------------------------- /AppIcons/ios/AppIcon.appiconset/Icon-76@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiekw2010/react-native-gitfeed/HEAD/AppIcons/ios/AppIcon.appiconset/Icon-76@2x.png -------------------------------------------------------------------------------- /AppIcons/ios/AppIcon.appiconset/Icon-Small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiekw2010/react-native-gitfeed/HEAD/AppIcons/ios/AppIcon.appiconset/Icon-Small.png -------------------------------------------------------------------------------- /android/app/src/main/assets/fonts/Entypo.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiekw2010/react-native-gitfeed/HEAD/android/app/src/main/assets/fonts/Entypo.ttf -------------------------------------------------------------------------------- /android/app/src/main/assets/fonts/Ionicons.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiekw2010/react-native-gitfeed/HEAD/android/app/src/main/assets/fonts/Ionicons.ttf -------------------------------------------------------------------------------- /android/app/src/main/assets/fonts/Octicons.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiekw2010/react-native-gitfeed/HEAD/android/app/src/main/assets/fonts/Octicons.ttf -------------------------------------------------------------------------------- /android/app/src/main/assets/fonts/Zocial.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiekw2010/react-native-gitfeed/HEAD/android/app/src/main/assets/fonts/Zocial.ttf -------------------------------------------------------------------------------- /AppIcons/android/drawable-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiekw2010/react-native-gitfeed/HEAD/AppIcons/android/drawable-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /AppIcons/android/drawable-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiekw2010/react-native-gitfeed/HEAD/AppIcons/android/drawable-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/assets/fonts/EvilIcons.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiekw2010/react-native-gitfeed/HEAD/android/app/src/main/assets/fonts/EvilIcons.ttf -------------------------------------------------------------------------------- /android/app/src/main/assets/fonts/Foundation.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiekw2010/react-native-gitfeed/HEAD/android/app/src/main/assets/fonts/Foundation.ttf -------------------------------------------------------------------------------- /AppIcons/android/drawable-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiekw2010/react-native-gitfeed/HEAD/AppIcons/android/drawable-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /AppIcons/ios/AppIcon.appiconset/Icon-Small@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiekw2010/react-native-gitfeed/HEAD/AppIcons/ios/AppIcon.appiconset/Icon-Small@2x.png -------------------------------------------------------------------------------- /AppIcons/ios/AppIcon.appiconset/Icon-Small@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiekw2010/react-native-gitfeed/HEAD/AppIcons/ios/AppIcon.appiconset/Icon-Small@3x.png -------------------------------------------------------------------------------- /AppIcons/watchkit/AppIcon.appiconset/Icon-24@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiekw2010/react-native-gitfeed/HEAD/AppIcons/watchkit/AppIcon.appiconset/Icon-24@2x.png -------------------------------------------------------------------------------- /AppIcons/watchkit/AppIcon.appiconset/Icon-29@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiekw2010/react-native-gitfeed/HEAD/AppIcons/watchkit/AppIcon.appiconset/Icon-29@2x.png -------------------------------------------------------------------------------- /AppIcons/watchkit/AppIcon.appiconset/Icon-29@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiekw2010/react-native-gitfeed/HEAD/AppIcons/watchkit/AppIcon.appiconset/Icon-29@3x.png -------------------------------------------------------------------------------- /AppIcons/watchkit/AppIcon.appiconset/Icon-40@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiekw2010/react-native-gitfeed/HEAD/AppIcons/watchkit/AppIcon.appiconset/Icon-40@2x.png -------------------------------------------------------------------------------- /AppIcons/watchkit/AppIcon.appiconset/Icon-44@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiekw2010/react-native-gitfeed/HEAD/AppIcons/watchkit/AppIcon.appiconset/Icon-44@2x.png -------------------------------------------------------------------------------- /AppIcons/watchkit/AppIcon.appiconset/Icon-86@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiekw2010/react-native-gitfeed/HEAD/AppIcons/watchkit/AppIcon.appiconset/Icon-86@2x.png -------------------------------------------------------------------------------- /AppIcons/watchkit/AppIcon.appiconset/Icon-98@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiekw2010/react-native-gitfeed/HEAD/AppIcons/watchkit/AppIcon.appiconset/Icon-98@2x.png -------------------------------------------------------------------------------- /android/app/src/main/assets/fonts/FontAwesome.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiekw2010/react-native-gitfeed/HEAD/android/app/src/main/assets/fonts/FontAwesome.ttf -------------------------------------------------------------------------------- /android/app/src/main/assets/fonts/MaterialIcons.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiekw2010/react-native-gitfeed/HEAD/android/app/src/main/assets/fonts/MaterialIcons.ttf -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /AppIcons/watchkit/AppIcon.appiconset/Icon-27.5@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiekw2010/react-native-gitfeed/HEAD/AppIcons/watchkit/AppIcon.appiconset/Icon-27.5@2x.png -------------------------------------------------------------------------------- /android/app/src/main/assets/fonts/SimpleLineIcons.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiekw2010/react-native-gitfeed/HEAD/android/app/src/main/assets/fonts/SimpleLineIcons.ttf -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiekw2010/react-native-gitfeed/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/xiekw2010/react-native-gitfeed/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/xiekw2010/react-native-gitfeed/HEAD/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiekw2010/react-native-gitfeed/HEAD/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /ios/gitfeed/Images.xcassets/AppIcon.appiconset/Icon-40@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiekw2010/react-native-gitfeed/HEAD/ios/gitfeed/Images.xcassets/AppIcon.appiconset/Icon-40@2x.png -------------------------------------------------------------------------------- /ios/gitfeed/Images.xcassets/AppIcon.appiconset/Icon-40@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiekw2010/react-native-gitfeed/HEAD/ios/gitfeed/Images.xcassets/AppIcon.appiconset/Icon-40@3x.png -------------------------------------------------------------------------------- /ios/gitfeed/Images.xcassets/AppIcon.appiconset/Icon-60@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiekw2010/react-native-gitfeed/HEAD/ios/gitfeed/Images.xcassets/AppIcon.appiconset/Icon-60@2x.png -------------------------------------------------------------------------------- /ios/gitfeed/Images.xcassets/AppIcon.appiconset/Icon-60@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiekw2010/react-native-gitfeed/HEAD/ios/gitfeed/Images.xcassets/AppIcon.appiconset/Icon-60@3x.png -------------------------------------------------------------------------------- /js/services/BridgeUtils.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by xiekaiwei on 16/8/24. 3 | */ 4 | 5 | import { NativeModules } from 'react-native' 6 | export default NativeModules.DXRNUtils 7 | 8 | -------------------------------------------------------------------------------- /android/keystores/BUCK: -------------------------------------------------------------------------------- 1 | keystore( 2 | name = 'debug', 3 | store = 'debug.keystore', 4 | properties = 'debug.keystore.properties', 5 | visibility = [ 6 | 'PUBLIC', 7 | ], 8 | ) 9 | -------------------------------------------------------------------------------- /ios/gitfeed/Images.xcassets/AppIcon.appiconset/Icon-40@2x-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiekw2010/react-native-gitfeed/HEAD/ios/gitfeed/Images.xcassets/AppIcon.appiconset/Icon-40@2x-1.png -------------------------------------------------------------------------------- /ios/gitfeed/Images.xcassets/AppIcon.appiconset/Icon-40@3x-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiekw2010/react-native-gitfeed/HEAD/ios/gitfeed/Images.xcassets/AppIcon.appiconset/Icon-40@3x-1.png -------------------------------------------------------------------------------- /ios/gitfeed/Images.xcassets/AppIcon.appiconset/Icon-Small@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiekw2010/react-native-gitfeed/HEAD/ios/gitfeed/Images.xcassets/AppIcon.appiconset/Icon-Small@2x.png -------------------------------------------------------------------------------- /ios/gitfeed/Images.xcassets/AppIcon.appiconset/Icon-Small@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiekw2010/react-native-gitfeed/HEAD/ios/gitfeed/Images.xcassets/AppIcon.appiconset/Icon-Small@3x.png -------------------------------------------------------------------------------- /js/actions/navigation.js: -------------------------------------------------------------------------------- 1 | export const ROOT_SWITCH_TAB = 'ROOT_SWITCH_TAB' 2 | 3 | export const rootSwitchTab = (rootTab) => { 4 | return { 5 | type: ROOT_SWITCH_TAB, 6 | rootTab 7 | } 8 | } -------------------------------------------------------------------------------- /index.ios.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | import React from 'react' 4 | import { 5 | AppRegistry 6 | } from 'react-native' 7 | import setup from './js/setup' 8 | 9 | AppRegistry.registerComponent('gitfeed', setup) 10 | -------------------------------------------------------------------------------- /index.android.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | import React from 'react' 4 | import { 5 | AppRegistry 6 | } from 'react-native' 7 | import setup from './js/setup' 8 | 9 | AppRegistry.registerComponent('gitfeed', setup) 10 | -------------------------------------------------------------------------------- /android/app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | gitfeed 7 | 8 | -------------------------------------------------------------------------------- /ios/Podfile.lock: -------------------------------------------------------------------------------- 1 | PODS: 2 | - UMengAnalytics-NO-IDFA (4.1.5) 3 | 4 | DEPENDENCIES: 5 | - UMengAnalytics-NO-IDFA 6 | 7 | SPEC CHECKSUMS: 8 | UMengAnalytics-NO-IDFA: f1b08b540eba6ebad044a574cae6d988cf40cfda 9 | 10 | COCOAPODS: 0.39.0 11 | -------------------------------------------------------------------------------- /js/common/BlurView/index.js: -------------------------------------------------------------------------------- 1 | import * as BView from './BlurView' 2 | 3 | module.exports = BView.default 4 | 5 | if (global) { 6 | global.__exponent = { 7 | Components: { 8 | BlurView: BView.default 9 | } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /android/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ios/Classes/H5/GFWebResourceInterceptorSettings+Internal.h: -------------------------------------------------------------------------------- 1 | #import "GFWebResourceInterceptorSettings.h" 2 | 3 | /** 4 | * Web资源拦截器规则配置类的内部方法。 5 | */ 6 | @interface GFWebResourceInterceptorSettings (Internal) 7 | 8 | // 创建默认拦截设置 9 | + (instancetype)defaultInterceptorSettings; 10 | 11 | @end 12 | -------------------------------------------------------------------------------- /ios/gitfeed/gitfeed.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | aps-environment 6 | development 7 | 8 | 9 | -------------------------------------------------------------------------------- /ios/gitfeed.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /ios/Classes/DXRNUtils.h: -------------------------------------------------------------------------------- 1 | // 2 | // DXRNUtils.h 3 | // RN_CNNode 4 | // 5 | // Created by xiekw on 15/11/18. 6 | // Copyright © 2015年 Facebook. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "RCTBridgeModule.h" 11 | 12 | @interface DXRNUtils : NSObject 13 | 14 | @end 15 | -------------------------------------------------------------------------------- /__tests__/index.ios.js: -------------------------------------------------------------------------------- 1 | import 'react-native'; 2 | import React from 'react'; 3 | import Index from '../index.ios.js'; 4 | 5 | // Note: test renderer must be required after react-native. 6 | import renderer from 'react-test-renderer'; 7 | 8 | it('renders correctly', () => { 9 | const tree = renderer.create( 10 | 11 | ); 12 | }); 13 | -------------------------------------------------------------------------------- /ios/Classes/DXRefreshControl.h: -------------------------------------------------------------------------------- 1 | // 2 | // DXRefreshControl.h 3 | // RN_CNNode 4 | // 5 | // Created by xiekw on 15/10/20. 6 | // Copyright © 2015年 Facebook. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "RCTBridgeModule.h" 11 | 12 | @interface DXRefreshControl : NSObject 13 | 14 | @end 15 | -------------------------------------------------------------------------------- /ios/Classes/GFDiskCacheManager.h: -------------------------------------------------------------------------------- 1 | // 2 | // GFDiskCacheManager.h 3 | // RN_CNNode 4 | // 5 | // Created by xiekw on 16/1/19. 6 | // Copyright © 2016年 Facebook. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "RCTBridgeModule.h" 11 | 12 | @interface GFDiskCacheManager : NSObject 13 | 14 | @end 15 | -------------------------------------------------------------------------------- /js/common/Button.ios.js: -------------------------------------------------------------------------------- 1 | const React = require('react'); 2 | const ReactNative = require('react-native'); 3 | const { 4 | TouchableOpacity, 5 | View, 6 | } = ReactNative; 7 | 8 | const Button = (props) => { 9 | return 10 | {props.children} 11 | ; 12 | }; 13 | 14 | module.exports = Button; 15 | -------------------------------------------------------------------------------- /__tests__/index.android.js: -------------------------------------------------------------------------------- 1 | import 'react-native'; 2 | import React from 'react'; 3 | import Index from '../index.android.js'; 4 | 5 | // Note: test renderer must be required after react-native. 6 | import renderer from 'react-test-renderer'; 7 | 8 | it('renders correctly', () => { 9 | const tree = renderer.create( 10 | 11 | ); 12 | }); 13 | -------------------------------------------------------------------------------- /ios/Classes/DXTopMessageManager.h: -------------------------------------------------------------------------------- 1 | // 2 | // DXTopMessageManager.h 3 | // RN_CNNode 4 | // 5 | // Created by xiekw on 15/10/21. 6 | // Copyright © 2015年 Facebook. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "RCTBridgeModule.h" 11 | 12 | @interface DXTopMessageManager : NSObject 13 | 14 | @end 15 | -------------------------------------------------------------------------------- /js/actions/language.js: -------------------------------------------------------------------------------- 1 | export const CHANGE_LANGUAGE = 'CHANGE_LANGUAGE' 2 | export const CHANGE_RULE = 'CHANGE_RULE' 3 | 4 | export const changeLanguage = language => { 5 | return { 6 | type: CHANGE_LANGUAGE, 7 | currentLanguage: language 8 | } 9 | } 10 | 11 | export const changeRule = rule => { 12 | return { 13 | type: CHANGE_RULE, 14 | currentRule: rule 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /js/reducers/navigation.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | import * as nv from '../actions/navigation' 4 | 5 | const initialState = { 6 | rootTab: { 7 | id: 'HomePage', 8 | } 9 | } 10 | 11 | export const navigation = (state = initialState, action) => { 12 | switch (action.type) { 13 | case nv.ROOT_SWITCH_TAB: 14 | return { ...state, rootTab: action.rootTab } 15 | } 16 | 17 | return state 18 | } 19 | -------------------------------------------------------------------------------- /js/constants/Styles.js: -------------------------------------------------------------------------------- 1 | import { 2 | Dimensions, 3 | StyleSheet 4 | } from 'react-native' 5 | 6 | import Colors from './Colors' 7 | 8 | export default { 9 | separatorBorderStyle: { 10 | borderBottomWidth: StyleSheet.hairlineWidth, 11 | borderBottomColor: Colors.cellBorder, 12 | }, 13 | separatorViewStyle: { 14 | height: StyleSheet.hairlineWidth, 15 | backgroundColor: Colors.cellBorder, 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /js/common/markdown/issue-html.js: -------------------------------------------------------------------------------- 1 | import css from './issue_css' 2 | 3 | export default ` 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 14 | 15 | $body 16 | 17 | ` 18 | 19 | -------------------------------------------------------------------------------- /js/reducers/index.js: -------------------------------------------------------------------------------- 1 | import { NavigationReducer } from '@exponent/ex-navigation' 2 | import { combineReducers } from 'redux' 3 | 4 | const notifications = require('./notifications') 5 | const login = require('./login') 6 | const search = require('./search') 7 | const language = require('./language') 8 | 9 | export default combineReducers({ 10 | navigation: NavigationReducer, 11 | ...notifications, 12 | ...login, 13 | ...search, 14 | ...language 15 | }) -------------------------------------------------------------------------------- /ios/Classes/Additions/NSString+GFAdditions.h: -------------------------------------------------------------------------------- 1 | @import Foundation; 2 | 3 | /** 4 | * 提供`NSString`的通用扩展方法 5 | */ 6 | @interface NSString (GFAdditions) 7 | 8 | /** 9 | * --------------------------------------------------------------------------- 10 | * @name 消息摘要 11 | * --------------------------------------------------------------------------- 12 | */ 13 | 14 | /** 15 | * 生成MD5信息摘要 16 | * 17 | * @return 返回MD5信息摘要 18 | */ 19 | - (NSString *)jm_MD5Digest; 20 | 21 | @end -------------------------------------------------------------------------------- /js/constants/Alerts.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @providesModule Alerts 3 | * @flow 4 | */ 5 | 6 | import { StyleSheet } from 'react-native'; 7 | 8 | export default { 9 | error: StyleSheet.create({ 10 | container: { 11 | backgroundColor: 'red', 12 | }, 13 | text: { 14 | color: 'white', 15 | }, 16 | }), 17 | warning: StyleSheet.create({ 18 | container: { 19 | backgroundColor: '#EAEB5E', 20 | }, 21 | text: { 22 | color: '#666804', 23 | }, 24 | }), 25 | } 26 | -------------------------------------------------------------------------------- /js/common/Button.android.js: -------------------------------------------------------------------------------- 1 | const React = require('react'); 2 | const ReactNative = require('react-native'); 3 | const { 4 | TouchableNativeFeedback, 5 | View, 6 | } = ReactNative; 7 | 8 | const Button = (props) => { 9 | return 14 | {props.children} 15 | ; 16 | }; 17 | 18 | module.exports = Button; 19 | -------------------------------------------------------------------------------- /ios/gitfeed/AppDelegate.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2015-present, Facebook, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | */ 9 | 10 | #import 11 | 12 | @interface AppDelegate : UIResponder 13 | 14 | @property (nonatomic, strong) UIWindow *window; 15 | 16 | @end 17 | -------------------------------------------------------------------------------- /js/services/Share.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by xiekaiwei on 16/8/24. 3 | */ 4 | 5 | import { NativeModules } from 'react-native' 6 | const ShareCenter = NativeModules.MEShareCenter 7 | 8 | /** 9 | * 10 | * @param uri Image URI 11 | * @param scene 12 | */ 13 | //enum WXScene { 14 | // WXSceneSession = 0, /**< 聊天界面 */ 15 | // WXSceneTimeline = 1, /**< 朋友圈 */ 16 | // WXSceneFavorite = 2, /**< 收藏 */ 17 | //}; 18 | exports.shareWXImage = ShareCenter.shareWXImage 19 | exports.shareWebLink = ShareCenter.shareWebLink 20 | -------------------------------------------------------------------------------- /ios/gitfeed/main.m: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2015-present, Facebook, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | */ 9 | 10 | #import 11 | 12 | #import "AppDelegate.h" 13 | 14 | int main(int argc, char * argv[]) { 15 | @autoreleasepool { 16 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /js/actions/index.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | // Note: 这里只能用 CMD 的方式来加载每个 actions 里的对象, 原因参见 http://es6.ruanyifeng.com/#docs/module 4 | 5 | const login = require('./login') 6 | const notifications = require('./notifications') 7 | const installation = require('./installation') 8 | const parse = require('./parse') 9 | const navigation = require('./navigation') 10 | const search = require('./search') 11 | const language = require('./language') 12 | 13 | module.exports = { 14 | ...login, 15 | ...notifications, 16 | ...installation, 17 | ...parse, 18 | ...navigation, 19 | ...search, 20 | ...language 21 | } 22 | -------------------------------------------------------------------------------- /js/common/CommonStyles.js: -------------------------------------------------------------------------------- 1 | const React = require('react-native'); 2 | const Colors = require('./Colors'); 3 | 4 | const { 5 | StyleSheet, 6 | } = React; 7 | 8 | const commonStyles = StyleSheet.create({ 9 | container: { 10 | flex: 1, 11 | justifyContent: 'center', 12 | alignItems: 'center', 13 | }, 14 | 15 | shadowLine: { 16 | shadowColor: '#999999', 17 | shadowOpacity: 0.8, 18 | shadowRadius: 1, 19 | shadowOffset: { 20 | height: 2, 21 | width: 1 22 | }, 23 | }, 24 | 25 | sepLine: { 26 | backgroundColor: Colors.backGray, 27 | height: 0.5, 28 | }, 29 | }); 30 | 31 | module.exports = commonStyles; 32 | -------------------------------------------------------------------------------- /js/pages/WebSharePage.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | import React, { 4 | Component, 5 | PropTypes, 6 | } from 'react' 7 | import { 8 | Platform 9 | } from 'react-native' 10 | import WebView from '../common/WebView' 11 | 12 | const NAV_HEIGHT = 64 13 | class WebSharePage extends Component { 14 | render() { 15 | return ( 16 | 21 | ) 22 | } 23 | } 24 | 25 | WebSharePage.propTypes = { 26 | url: PropTypes.string 27 | } 28 | WebSharePage.defaultProps = { 29 | url: 'http://bonwechat.com' 30 | } 31 | 32 | export default WebSharePage 33 | -------------------------------------------------------------------------------- /js/constants/Layout.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @providesModule Layout 3 | * @flow 4 | */ 5 | 6 | import { 7 | Dimensions, 8 | Platform, 9 | NativeModules, 10 | } from 'react-native'; 11 | 12 | const useDrawerNavigation = Platform.OS === 'android'; 13 | // const useDrawerNavigation = false; 14 | 15 | export default { 16 | navigationLayoutRoute: useDrawerNavigation ? 'drawerNavigationLayout' : 'tabNavigationLayout', 17 | statusBarHeight: 20, 18 | window: { 19 | width: Dimensions.get('window').width, 20 | height: Dimensions.get('window').height, 21 | }, 22 | contentInset: { top: 64, left: 0, right: 0, bottom: 49 }, 23 | navIconSize: { width: 30, marginHorizontal:8, marginVertical: 6} 24 | }; 25 | -------------------------------------------------------------------------------- /android/app/src/main/java/com/gitfeed/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.gitfeed; 2 | 3 | import com.facebook.react.ReactActivity; 4 | import com.dieam.reactnativepushnotification.ReactNativePushNotificationPackage; 5 | import com.learnium.RNDeviceInfo.RNDeviceInfo; 6 | import com.oblador.vectoricons.VectorIconsPackage; 7 | import com.microsoft.codepush.react.CodePush; 8 | 9 | public class MainActivity extends ReactActivity { 10 | 11 | /** 12 | * Returns the name of the main component registered from JavaScript. 13 | * This is used to schedule rendering of the component. 14 | */ 15 | @Override 16 | protected String getMainComponentName() { 17 | return "gitfeed"; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /js/common/Pagination.js: -------------------------------------------------------------------------------- 1 | "use strict" 2 | 3 | function Pagination(pag = 100) { 4 | this.pag = pag 5 | this.step = 0 6 | this.loadingStep = 0 7 | this.hasMore = false 8 | } 9 | 10 | Pagination.prototype.reset = function () { 11 | this.step = 0 12 | this.loadingStep = 0 13 | this.hasMore = true 14 | } 15 | 16 | Pagination.prototype.incrLoadingStep = function () { 17 | this.loadingStep += this.pag 18 | } 19 | 20 | Pagination.prototype.decrLoadingStep = function () { 21 | this.loadingStep -= this.pag 22 | } 23 | 24 | Pagination.prototype.incrStep = function () { 25 | this.step += this.pag 26 | } 27 | 28 | Pagination.prototype.close = function () { 29 | this.hasMore = false 30 | } 31 | 32 | export default Pagination -------------------------------------------------------------------------------- /js/reducers/search.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | import * as sch from '../actions/search' 4 | 5 | const defaultState = { 6 | hotTags: ['react-native', 'redux', 'parse-server'], 7 | history: [], 8 | searchText: '', 9 | } 10 | 11 | export const search = (state = defaultState, action) => { 12 | switch (action.type) { 13 | case sch.GET_HOT_TAGS: { 14 | if (action.tags) { 15 | return { ...state, hotTags: action.tags } 16 | } 17 | } 18 | case sch.GET_SEARCH_HISTORY: 19 | return Object.assign({}, state, { history: action.history }) 20 | case sch.SEARCH_START: 21 | return Object.assign({}, state, { 22 | searchText: action.searchText, 23 | }) 24 | } 25 | 26 | return state 27 | } -------------------------------------------------------------------------------- /android/build.gradle: -------------------------------------------------------------------------------- 1 | // Top-level build file where you can add configuration options common to all sub-projects/modules. 2 | 3 | buildscript { 4 | repositories { 5 | jcenter() 6 | } 7 | dependencies { 8 | classpath 'com.android.tools.build:gradle:1.3.1' 9 | 10 | // NOTE: Do not place your application dependencies here; they belong 11 | // in the individual module build.gradle files 12 | } 13 | } 14 | 15 | allprojects { 16 | repositories { 17 | mavenLocal() 18 | jcenter() 19 | maven { 20 | // All of React Native (JS, Obj-C sources, Android binaries) is installed from npm 21 | url "$rootDir/../node_modules/react-native/android" 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /js/actions/installation.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const Platform = require('Platform'); 4 | const Parse = require('parse/react-native'); 5 | 6 | async function currentInstallation() { 7 | const installationId = await Parse._getInstallationId(); 8 | return new Parse.Installation({ 9 | installationId, 10 | appName: 'gitfeed', 11 | deviceType: Platform.OS, 12 | // TODO: Get this information from the app itself 13 | appIdentifier: Platform.OS === 'ios' ? 'com.bonyiyan.gitfeed' : 'com.bonyiyan.gitfeed', 14 | }); 15 | } 16 | 17 | async function updateInstallation(updates) { 18 | const installation = await currentInstallation(); 19 | await installation.save(updates); 20 | } 21 | 22 | module.exports = { currentInstallation, updateInstallation }; 23 | -------------------------------------------------------------------------------- /js/reducers/login.js: -------------------------------------------------------------------------------- 1 | import * as lg from '../actions/login' 2 | 3 | 4 | /** 5 | * 6 | * @type {{user: {username: null, token: null, pwd: null, avatar: null, userId: null, url: null}}} 7 | */ 8 | const initialLogin = { 9 | user: { 10 | login: null, 11 | token: null, 12 | pwd: null, 13 | avatar: null, 14 | userId: null, 15 | url: null, 16 | }, 17 | loading: false 18 | } 19 | 20 | export const login = (state = initialLogin, action) => { 21 | switch (action.type) { 22 | case lg.USER_LOGIN_SUCCESS: 23 | case lg.USER_ONBOARD: 24 | return { ...state, user: { ...state.user, ...action.user }, loading: false} 25 | case lg.USER_ONBOARD_CHECKING: 26 | return { ...state, loading: true } 27 | } 28 | 29 | return state 30 | } 31 | 32 | -------------------------------------------------------------------------------- /ios/gitfeedTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | org.reactjs.native.example.$(PRODUCT_NAME:rfc1034identifier) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | 24 | 25 | -------------------------------------------------------------------------------- /js/common/BlurView/BlurView.ios.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import React, { 4 | Component, 5 | PropTypes, 6 | } from 'react'; 7 | 8 | import { 9 | View, 10 | } from 'react-native'; 11 | import { BlurView as RNBlurView } from 'react-native-blur' 12 | 13 | export default class BlurView extends Component { 14 | static propTypes = { 15 | tint: PropTypes.oneOf(['light', 'xlight', 'dark']).isRequired, 16 | intensity: PropTypes.number.isRequired, 17 | ...View.propTypes, 18 | }; 19 | 20 | static defaultProps = { 21 | tint: 'light', 22 | intensity: 100, 23 | } 24 | 25 | render() { 26 | const { tint, intensity } = this.props 27 | 28 | return ( 29 | 34 | {this.props.children} 35 | 36 | ); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /ios/Classes/GFDiskCacheManager.m: -------------------------------------------------------------------------------- 1 | // 2 | // GFDiskCacheManager.m 3 | // RN_CNNode 4 | // 5 | // Created by xiekw on 16/1/19. 6 | // Copyright © 2016年 Facebook. All rights reserved. 7 | // 8 | 9 | #import "GFDiskCacheManager.h" 10 | #import "RCTBridge.h" 11 | #import "RCTConvert.h" 12 | #import "GFWebResourceInterceptor.h" 13 | #import "GFWebResourceCache.h" 14 | #import "DXFileManager.h" 15 | 16 | @implementation GFDiskCacheManager 17 | 18 | RCT_EXPORT_MODULE(); 19 | 20 | RCT_EXPORT_METHOD(diskCacheCost:(RCTResponseSenderBlock)callback) { 21 | [[GFWebResourceInterceptor globalWebResourceInterceptor].cache cacheCost:^(NSUInteger fileSize) { 22 | callback(@[@(fileSize)]); 23 | }]; 24 | } 25 | 26 | RCT_EXPORT_METHOD(clearDiskCache:(RCTResponseSenderBlock)callback) { 27 | [[GFWebResourceInterceptor globalWebResourceInterceptor].cache clearCache:^(NSUInteger fileSize) { 28 | callback(@[@(fileSize)]); 29 | }]; 30 | } 31 | 32 | @end 33 | -------------------------------------------------------------------------------- /android/settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'gitfeed' 2 | 3 | include ':app' 4 | include ':react-native-blur' 5 | project(':react-native-blur').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-blur/android') 6 | include ':react-native-push-notification' 7 | project(':react-native-push-notification').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-push-notification/android') 8 | include ':react-native-device-info' 9 | project(':react-native-device-info').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-device-info/android') 10 | include ':react-native-vector-icons' 11 | project(':react-native-vector-icons').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-vector-icons/android') 12 | include ':react-native-code-push' 13 | project(':react-native-code-push').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-code-push/android/app') 14 | -------------------------------------------------------------------------------- /ios/Classes/Additions/NSString+GFAdditions.m: -------------------------------------------------------------------------------- 1 | #import "NSString+GFAdditions.h" 2 | #import 3 | 4 | @implementation NSString (GFAdditions) 5 | 6 | + (NSString *)jm_stringFromDigest:(uint8_t *)digest length:(int)length { 7 | NSMutableString *ms = [[NSMutableString alloc] initWithCapacity:length * 2]; 8 | for (int i = 0; i < length; i++) { 9 | [ms appendFormat: @"%02x", (int)digest[i]]; 10 | } 11 | 12 | return [ms copy]; 13 | } 14 | 15 | - (NSData *)jm_prehashData { 16 | const char *cstr = [self cStringUsingEncoding:NSUTF8StringEncoding]; 17 | return [NSData dataWithBytes:cstr length:self.length]; 18 | } 19 | 20 | - (NSString *)jm_MD5Digest { 21 | NSData *preHashData = [self jm_prehashData]; 22 | uint8_t digest[CC_MD5_DIGEST_LENGTH]; 23 | CC_MD5(preHashData.bytes, (CC_LONG)preHashData.length, digest); 24 | 25 | return [[self class] jm_stringFromDigest:digest length:CC_MD5_DIGEST_LENGTH]; 26 | } 27 | 28 | @end 29 | 30 | -------------------------------------------------------------------------------- /js/common/Util.js: -------------------------------------------------------------------------------- 1 | //export const debounce = (func, wait = 1000, immediate = true) => { 2 | // var timeout; 3 | // return function() { 4 | // var context = this, args = arguments; 5 | // var later = function() { 6 | // timeout = null; 7 | // if (!immediate) func.apply(context, args); 8 | // }; 9 | // var callNow = immediate && !timeout; 10 | // clearTimeout(timeout); 11 | // timeout = setTimeout(later, wait); 12 | // if (callNow) func.apply(context, args); 13 | // }; 14 | //} 15 | 16 | exports.debounce = (func, wait = 1000, immediate = true) => { 17 | var timeout; 18 | return function() { 19 | var context = this, args = arguments; 20 | var later = function() { 21 | timeout = null; 22 | if (!immediate) func.apply(context, args); 23 | }; 24 | var callNow = immediate && !timeout; 25 | clearTimeout(timeout); 26 | timeout = setTimeout(later, wait); 27 | if (callNow) func.apply(context, args); 28 | }; 29 | } -------------------------------------------------------------------------------- /android/gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | 3 | # IDE (e.g. Android Studio) users: 4 | # Gradle settings configured through the IDE *will override* 5 | # any settings specified in this file. 6 | 7 | # For more details on how to configure your build environment visit 8 | # http://www.gradle.org/docs/current/userguide/build_environment.html 9 | 10 | # Specifies the JVM arguments used for the daemon process. 11 | # The setting is particularly useful for tweaking memory settings. 12 | # Default value: -Xmx10248m -XX:MaxPermSize=256m 13 | # org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 14 | 15 | # When configured, Gradle will run in incubating parallel mode. 16 | # This option should only be used with decoupled projects. More details, visit 17 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 18 | # org.gradle.parallel=true 19 | 20 | android.useDeprecatedNdk=true 21 | -------------------------------------------------------------------------------- /ios/Classes/H5/GFWebResourceInterceptorSettings.m: -------------------------------------------------------------------------------- 1 | #import "GFWebResourceInterceptorSettings+Internal.h" 2 | 3 | @implementation GFWebResourceInterceptorSettings 4 | 5 | @synthesize version = _version; 6 | @synthesize enabled = _enabled; 7 | @synthesize extensions = _extensions; 8 | @synthesize whitelist = _whitelist; 9 | @synthesize blacklist = _blacklist; 10 | @synthesize cacheMaxAge = _cacheMaxAge; 11 | 12 | + (instancetype)buildDefaultWebResourceInterceptorSettings { 13 | GFWebResourceInterceptorSettings *settings = [GFWebResourceInterceptorSettings new]; 14 | settings.version = 0; 15 | settings.enabled = YES; 16 | settings.extensions = @"js,css,md"; 17 | settings.whitelist = @[@"github.com"]; 18 | settings.blacklist = @[@"www.google-analytics.com"]; 19 | settings.cacheMaxAge = 24 * 60 * 60 * 3; 20 | 21 | return settings; 22 | } 23 | 24 | + (instancetype)defaultInterceptorSettings { 25 | return [[self class] buildDefaultWebResourceInterceptorSettings]; 26 | } 27 | 28 | @end 29 | -------------------------------------------------------------------------------- /js/pages/FavPage.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | import React, { 4 | Component, 5 | PropTypes, 6 | } from 'react' 7 | import { 8 | StyleSheet, 9 | Text, 10 | View, 11 | } from 'react-native' 12 | import { connect } from 'react-redux' 13 | import { getFavPages } from '../actions/fav' 14 | import GridPage from '../common/GridPage' 15 | 16 | class FavPage extends Component { 17 | constructor(props) { 18 | super(props) 19 | } 20 | 21 | componentDidMount() { 22 | this.props.dispatch(getFavPages()) 23 | } 24 | 25 | render() { 26 | return 31 | } 32 | } 33 | 34 | FavPage.propTypes = { 35 | dataSource: PropTypes.array.isRequired 36 | } 37 | FavPage.defaultProps = { 38 | dataSource: [] 39 | } 40 | 41 | const mapStateToProps = state => { 42 | return { 43 | dataSource: state.favs 44 | } 45 | } 46 | 47 | export default connect(mapStateToProps)(FavPage) -------------------------------------------------------------------------------- /js/pages/RecentPage.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | import React, { 4 | Component, 5 | PropTypes, 6 | } from 'react' 7 | import { 8 | StyleSheet, 9 | Text, 10 | View, 11 | } from 'react-native' 12 | import { connect } from 'react-redux' 13 | import { getRecent } from '../actions/recent' 14 | import GridPage from '../common/GridPage' 15 | 16 | class RecentPage extends Component { 17 | constructor(props) { 18 | super(props) 19 | } 20 | 21 | componentDidMount() { 22 | this.props.dispatch(getRecent()) 23 | } 24 | 25 | render() { 26 | return 32 | } 33 | } 34 | 35 | RecentPage.propTypes = { 36 | dataSource: PropTypes.array.isRequired 37 | } 38 | RecentPage.defaultProps = { 39 | dataSource: [] 40 | } 41 | 42 | const mapStateToProps = state => { 43 | return { 44 | dataSource: state.recents 45 | } 46 | } 47 | 48 | export default connect(mapStateToProps)(RecentPage) 49 | -------------------------------------------------------------------------------- /js/common/BlurView/BlurView.android.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import React, { 4 | Component, 5 | PropTypes, 6 | } from 'react'; 7 | 8 | import { 9 | View, 10 | } from 'react-native'; 11 | 12 | import deprecatedPropType from 'react-native/Libraries/Utilities/deprecatedPropType'; 13 | 14 | export default class BlurView extends Component { 15 | static propTypes = { 16 | tintEffect: deprecatedPropType( 17 | PropTypes.string, 18 | 'Use the `tint` prop instead.' 19 | ), 20 | tint: PropTypes.oneOf(['light', 'default', 'dark']), 21 | ...View.propTypes, 22 | }; 23 | 24 | render() { 25 | let { tint } = this.props; 26 | 27 | let backgroundColor; 28 | if (tint === 'dark') { 29 | backgroundColor = 'rgba(0,0,0,0.5)'; 30 | } else if (tint === 'light') { 31 | backgroundColor = 'rgba(255,255,255,0.7)'; 32 | } else { 33 | backgroundColor = 'rgba(255,255,255,0.4)'; 34 | } 35 | 36 | return ( 37 | 41 | ); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /AppIcons/ios/README.md: -------------------------------------------------------------------------------- 1 | ## iTunesArtwork & iTunesArtwork@2x (App Icon) file extension: 2 | 3 | PNG extension is prepended to these two files - 4 | 5 | While Apple suggested to omit the extension for these files, 6 | the '.png' extension is actually required for iTunesConnect submission. 7 | 8 | This is done for you so you don't have to. 9 | 10 | However, for Ad_hoc or Enterprise distirbution, the extension should be removed 11 | from the files before adding to XCode to avoid error. 12 | 13 | refs: https://developer.apple.com/library/ios/qa/qa1686/_index.html 14 | 15 | ## iTunesArtwork & iTunesArtwork@2x (App Icon) transparency handling: 16 | 17 | As images with alpha channels or transparencies cannot be set as an application's icon on 18 | iTunesConnect, all transparent pixels in your images will be converted into 19 | solid blacks. 20 | 21 | To achieve the best result, you're advised to adjust the transparency settings 22 | in your source files before converting them with makeAppIcon. 23 | 24 | refs: https://developer.apple.com/library/ios/documentation/UserExperience/Conceptual/MobileHIG/AppIcons.html 25 | -------------------------------------------------------------------------------- /js/actions/search.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | import { 4 | getSearchHistory, 5 | insertSearchRecord, 6 | removeSearchRecord 7 | } from '../services/Storage' 8 | 9 | export const GET_HOT_TAGS = 'GET_HOT_TAGS' 10 | export const GET_SEARCH_HISTORY = 'GET_SEARCH_HISTORY' 11 | export const SEARCH_START = 'SEARCH_START' 12 | 13 | export const getHotTags = () => { 14 | // Async get the TAGS 15 | return dispatch => { 16 | dispatch({ 17 | type: GET_HOT_TAGS, 18 | }) 19 | } 20 | } 21 | 22 | export const searchSearchHistory = async() => { 23 | const history = await getSearchHistory() 24 | return { 25 | type: GET_SEARCH_HISTORY, 26 | history 27 | } 28 | } 29 | 30 | export const enqueueSearchHistory = async(his) => { 31 | await insertSearchRecord(his) 32 | return await searchSearchHistory() 33 | } 34 | 35 | export const removeSearchHistory = async(his) => { 36 | await removeSearchRecord(his) 37 | return await searchSearchHistory() 38 | } 39 | 40 | /** 41 | * 42 | * from: 'TAG', 'HISTORY', 'INPUT' 43 | */ 44 | export const startSearch = (text, from = 'INPUT')=> { 45 | return { 46 | type: SEARCH_START, 47 | searchText: text, 48 | from: from 49 | } 50 | } -------------------------------------------------------------------------------- /js/services/xFetch.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by xiekaiwei on 16/8/24. 3 | */ 4 | 5 | const checkIfErrorOccurs = res => { 6 | return { 7 | code: res.status, 8 | res 9 | } 10 | } 11 | 12 | const TIME_OUT = 15000 13 | 14 | async function xFetch(path, headerOptions, ops = { noParse: false }) { 15 | const normalFetch = fetch(path, headerOptions) 16 | if (ops.noParse) { 17 | return timeoutPromise(TIME_OUT, normalFetch) 18 | } 19 | 20 | const res = await timeoutPromise(TIME_OUT, normalFetch.then(checkIfErrorOccurs)) 21 | const response = await res.res.json() 22 | if (res.code < 300) { 23 | return response 24 | } else { 25 | throw new Error(`${res.code} ${response.message}`) 26 | } 27 | } 28 | 29 | export const timeoutPromise = function timeoutPromise(ms, promise) { 30 | return new Promise((resolve, reject) => { 31 | const timeoutId = setTimeout(() => { 32 | reject(new Error("request time out")) 33 | }, ms); 34 | promise.then( 35 | (res) => { 36 | clearTimeout(timeoutId); 37 | resolve(res); 38 | }, 39 | (err) => { 40 | clearTimeout(timeoutId); 41 | reject(err); 42 | } 43 | ); 44 | }) 45 | } 46 | 47 | export default xFetch 48 | -------------------------------------------------------------------------------- /js/common/MarkdownView.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | import React, { 4 | Component, 5 | PropTypes, 6 | } from 'react' 7 | import { 8 | StyleSheet, 9 | View, 10 | Text, 11 | TouchableOpacity, 12 | } from 'react-native' 13 | import Color from './Colors' 14 | import MDWebView from './markdown' 15 | 16 | class MarkdownView extends Component { 17 | constructor() { 18 | super() 19 | this.state = { 20 | md: '' 21 | } 22 | } 23 | 24 | componentDidMount() { 25 | const self = this 26 | fetch('https://github.com/sindresorhus/github-markdown-css/blob/gh-pages/readme.md') 27 | .then(res => res.text()) 28 | .then(md => { 29 | self.setState({ md: md, }) 30 | }) 31 | .catch(err => console.log('err is', err)) 32 | } 33 | 34 | 35 | render() { 36 | const { md } = this.state 37 | 38 | const article = md.slice(md.indexOf('') + 'article>'.length) 39 | 40 | return ( 41 | 42 | {article} 43 | 44 | ); 45 | } 46 | } 47 | 48 | module.exports = MarkdownView 49 | 50 | module.exports.__cards__ = (define) => { 51 | define('Gray', (state = true, update) => 52 | ) 53 | } -------------------------------------------------------------------------------- /android/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 5 | 6 | 7 | 8 | 9 | 12 | 13 | 19 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /js/services/Login.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by xiekaiwei on 16/8/24. 3 | */ 4 | 5 | import Parse from 'parse/react-native' 6 | import DeviceInfo from 'react-native-device-info' 7 | 8 | const DEFAULT_ID = DeviceInfo.getUniqueID() 9 | 10 | export const getCurrentUser = async() => { 11 | let currentUser = await Parse.User.currentAsync() 12 | if (!currentUser) { 13 | const uqId = DEFAULT_ID 14 | const userQuery = new Parse.Query(Parse.User) 15 | userQuery.equalTo('username', uqId) 16 | currentUser = await userQuery.first() 17 | 18 | if (!currentUser) { 19 | currentUser = await signUpDefaultUser() 20 | } else { 21 | currentUser = Parse.User.logIn(DEFAULT_ID, DEFAULT_ID) 22 | } 23 | } 24 | 25 | return currentUser 26 | } 27 | 28 | const signUpDefaultUser = async() => { 29 | const user = new Parse.User() 30 | user.set('username', DEFAULT_ID) 31 | user.set('password', DEFAULT_ID) 32 | user.set('platform', DeviceInfo.getSystemName()) 33 | return await user.signUp() 34 | } 35 | 36 | export const syncUserInfo = async(key, data) => { 37 | if (!key || !data) return 38 | 39 | const currentUser = await getCurrentUser() 40 | currentUser.set(key, data) 41 | return await currentUser.save() 42 | } 43 | 44 | export const getUserInfo = () => { 45 | 46 | } -------------------------------------------------------------------------------- /ios/Classes/H5/GFWebResourceInterceptorSettings.h: -------------------------------------------------------------------------------- 1 | @import Foundation; 2 | 3 | 4 | /** 5 | * Web资源拦截器接口定义 6 | */ 7 | @protocol GFWebResourceInterceptorSettings 8 | @required 9 | 10 | /** 11 | * 配置版本,默认配置版本为`0`。 12 | * 当更新拦截器配置时候,检查之前配置版本与传入配置的版本是否一样, 13 | * 如果配置版本一样或者传入配置版本小于已生效的配置版本,则丢弃传入配置。 14 | * 15 | * @discussion 16 | * 如果配置已变更,采用数字自增方式管理配置版本。 17 | */ 18 | @property (nonatomic, assign) NSInteger version; 19 | 20 | /** 21 | * 控制是否开启Web资源拦截功能,`YES`为开启,`NO`为关闭。默认实现为开启。 22 | */ 23 | @property (nonatomic, assign) BOOL enabled; 24 | 25 | /** 26 | * Web资源拦截支持的后缀名。目前支持`js`和`css`。 27 | */ 28 | @property (nonatomic, strong) NSString *extensions; 29 | 30 | /** 31 | * Web资源拦截域白名单。 32 | * 检查当前请求关联的`Host`和`Referer`是否在白名单内, 33 | * 如果不在白名单内,则不会触发资源拦截。 34 | */ 35 | @property (nonatomic, strong) NSArray *whitelist; 36 | 37 | /** 38 | * Web资源拦截黑名单。 39 | * 检查当前请求关联的`Host`和`Referer`是否在黑名单内, 40 | * 如果在黑名单内,则不会触发资源拦截。 41 | */ 42 | @property (nonatomic, strong) NSArray *blacklist; 43 | 44 | /** 45 | * cache的自动清理时间,默认3天 24 * 60 * 60 * 3 46 | */ 47 | @property (nonatomic, assign) NSUInteger cacheMaxAge; 48 | 49 | @end 50 | 51 | /** 52 | * Web资源拦截配置默认实现类。 53 | */ 54 | @interface GFWebResourceInterceptorSettings : NSObject 55 | 56 | @end -------------------------------------------------------------------------------- /js/store/array.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2016 Facebook, Inc. 3 | * 4 | * You are hereby granted a non-exclusive, worldwide, royalty-free license to 5 | * use, copy, modify, and distribute this software in source code or binary 6 | * form for use in connection with the web services and APIs provided by 7 | * Facebook. 8 | * 9 | * As with any software that integrates with the Facebook platform, your use 10 | * of this software is subject to the Facebook Developer Principles and 11 | * Policies [http://developers.facebook.com/policy/]. This copyright notice 12 | * shall be included in all copies or substantial portions of the software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 17 | * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 19 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 20 | * DEALINGS IN THE SOFTWARE 21 | */ 22 | 23 | 'use strict'; 24 | 25 | module.exports = store => next => action => 26 | Array.isArray(action) 27 | ? action.map(next) 28 | : next(action); 29 | -------------------------------------------------------------------------------- /js/services/LookFetch.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by xiekaiwei on 16/8/24. 3 | */ 4 | 5 | const Crypto = require('crypto-js') 6 | 7 | const BASE_URL = "http://bonwechat.com" 8 | //const BASE_URL = "http://localhost:7001" 9 | 10 | const errorMessages = (res) => `${res.status} ${res.statusText}` 11 | 12 | function check401(res) { 13 | if (res.status === 401) { 14 | return Promise.reject(errorMessages(res)) 15 | } 16 | return res 17 | } 18 | 19 | function check404(res) { 20 | if (res.status === 404) { 21 | return Promise.reject(errorMessages(res)) 22 | } 23 | return res 24 | } 25 | 26 | function jsonParse(res) { 27 | return res.json() 28 | } 29 | 30 | function errorMessageParse(res) { 31 | const { code, msg } = res 32 | if (code > 200) { 33 | return Promise.reject(msg) 34 | } 35 | return res 36 | } 37 | 38 | function xFetch(path, options) { 39 | return fetch(path, options) 40 | .then(check401) 41 | .then(check404) 42 | .then(jsonParse) 43 | .then(errorMessageParse) 44 | } 45 | 46 | function getFetch(path) { 47 | let URL = path 48 | if (!/^http.*/.test(URL)) URL = BASE_URL + path 49 | 50 | return xFetch(URL, { 51 | headers: { 52 | 'WECHATLOOK-HEADER': Crypto.HmacSHA1(URL, 'register.wechatlook_rn.js') 53 | } 54 | }) 55 | } 56 | 57 | export default getFetch 58 | -------------------------------------------------------------------------------- /js/store/analytics.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2016 Facebook, Inc. 3 | * 4 | * You are hereby granted a non-exclusive, worldwide, royalty-free license to 5 | * use, copy, modify, and distribute this software in source code or binary 6 | * form for use in connection with the web services and APIs provided by 7 | * Facebook. 8 | * 9 | * As with any software that integrates with the Facebook platform, your use 10 | * of this software is subject to the Facebook Developer Principles and 11 | * Policies [http://developers.facebook.com/policy/]. This copyright notice 12 | * shall be included in all copies or substantial portions of the software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 17 | * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 19 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 20 | * DEALINGS IN THE SOFTWARE 21 | */ 22 | 23 | 'use strict'; 24 | 25 | const track = require('./track'); 26 | 27 | module.exports = store => next => action => { 28 | track(action); 29 | return next(action); 30 | }; 31 | -------------------------------------------------------------------------------- /ios/gitfeed/Images.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "size" : "20x20", 5 | "idiom" : "iphone", 6 | "filename" : "Icon-40@2x.png", 7 | "scale" : "2x" 8 | }, 9 | { 10 | "size" : "20x20", 11 | "idiom" : "iphone", 12 | "filename" : "Icon-40@3x.png", 13 | "scale" : "3x" 14 | }, 15 | { 16 | "size" : "29x29", 17 | "idiom" : "iphone", 18 | "filename" : "Icon-Small@2x.png", 19 | "scale" : "2x" 20 | }, 21 | { 22 | "size" : "29x29", 23 | "idiom" : "iphone", 24 | "filename" : "Icon-Small@3x.png", 25 | "scale" : "3x" 26 | }, 27 | { 28 | "size" : "40x40", 29 | "idiom" : "iphone", 30 | "filename" : "Icon-40@2x-1.png", 31 | "scale" : "2x" 32 | }, 33 | { 34 | "size" : "40x40", 35 | "idiom" : "iphone", 36 | "filename" : "Icon-40@3x-1.png", 37 | "scale" : "3x" 38 | }, 39 | { 40 | "size" : "60x60", 41 | "idiom" : "iphone", 42 | "filename" : "Icon-60@2x.png", 43 | "scale" : "2x" 44 | }, 45 | { 46 | "size" : "60x60", 47 | "idiom" : "iphone", 48 | "filename" : "Icon-60@3x.png", 49 | "scale" : "3x" 50 | } 51 | ], 52 | "info" : { 53 | "version" : 1, 54 | "author" : "xcode" 55 | } 56 | } -------------------------------------------------------------------------------- /js/pages/FamousPage.js: -------------------------------------------------------------------------------- 1 | import React, { 2 | Component, 3 | PropTypes, 4 | } from 'react' 5 | import { 6 | View, 7 | ActivityIndicatorIOS, 8 | StyleSheet, 9 | ScrollView, 10 | TouchableHighlight, 11 | Image, 12 | TouchableOpacity, 13 | ListView, 14 | Linking, 15 | Dimensions 16 | } from 'react-native' 17 | import Layout from '../constants/Layout' 18 | import RepoCell from '../common/RepoCell' 19 | import GHRefreshListView from '../common/GHRefreshListView' 20 | import { withNavigation } from '@exponent/ex-navigation' 21 | import { UserListView } from '../common/DetailListView' 22 | 23 | @withNavigation 24 | class FamousPage extends Component { 25 | static route = { 26 | navigationBar: { 27 | title: 'Popular guys' 28 | }, 29 | } 30 | 31 | render() { 32 | const path = '/search/users?q=location:USA&sort=followers' 33 | return ( 34 | 35 | v.items} 38 | navigator={this.props.navigator} 39 | /> 40 | 41 | ) 42 | } 43 | } 44 | 45 | FamousPage.propTypes = { 46 | showcase: React.PropTypes.object, 47 | } 48 | FamousPage.defaultProps = {} 49 | 50 | export default FamousPage 51 | 52 | var styles = StyleSheet.create({ 53 | container: { 54 | flex: 1 55 | }, 56 | }) -------------------------------------------------------------------------------- /js/pages/NotificationPage.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | import React, { 4 | Component, 5 | PropTypes, 6 | } from 'react' 7 | import { 8 | Platform, 9 | View, 10 | Linking, 11 | } from 'react-native' 12 | import { getRequest } from '../services/GithubServices' 13 | import AlertStyle from '../constants/Alerts' 14 | 15 | class NotificationPage extends Component { 16 | state = { 17 | notifications: [] 18 | } 19 | 20 | componentDidMount() { 21 | this.getNotifications() 22 | } 23 | 24 | async getNotifications() { 25 | try { 26 | const notifications = await geatRequest('/notifications') 27 | this.setState({ notifications, }) 28 | } catch (err) { 29 | this.handleError(err) 30 | } 31 | } 32 | 33 | handleError(err) { 34 | if (~err.message.indexOf('authentication')) { 35 | const { navigator } = this.props 36 | navigator.showLocalAlert('You need to login first', AlertStyle.warning) 37 | setTimeout(_ => navigator.push('Login', { 38 | didLogin: this.getNotifications.bind(this) 39 | }), 2000) 40 | } 41 | } 42 | 43 | render() { 44 | console.log('this.notifications is', this.state.notifications) 45 | return 46 | } 47 | } 48 | 49 | NotificationPage.propTypes = {} 50 | NotificationPage.defaultProps = {} 51 | 52 | export default NotificationPage 53 | 54 | 55 | -------------------------------------------------------------------------------- /js/actions/login.js: -------------------------------------------------------------------------------- 1 | import { basicLogin, getUserInfo } from '../services/GithubServices' 2 | import { saveUser, getCurrentUser } from '../services/Storage' 3 | 4 | export const USER_LOGIN_SUCCESS = 'USER_LOGIN_SUCCESS' 5 | export const USER_ONBOARD = 'USERG_ONBOARD' 6 | export const USER_ONBOARD_CHECKING = 'USER_ONBOARD_CHECKING' 7 | 8 | export const onboard = async(user) => { 9 | const savedUser = await saveUser(user) 10 | return { 11 | type: USER_ONBOARD, 12 | user: savedUser 13 | } 14 | } 15 | 16 | export const getUser = () => { 17 | return dispatch => { 18 | dispatch({ 19 | type: USER_ONBOARD_CHECKING 20 | }) 21 | getCurrentUser() 22 | .then(user => { 23 | dispatch({ 24 | type: USER_ONBOARD, 25 | user, 26 | }) 27 | }) 28 | } 29 | } 30 | 31 | export const login = async(user) => { 32 | if (user.token) { 33 | await saveUser(user) 34 | return { 35 | type: USER_LOGIN_SUCCESS, 36 | user 37 | } 38 | } 39 | 40 | return null 41 | 42 | // TODO: when user enter his profile then refresh it 43 | const detailUser = await getUserInfo(login) 44 | user = { 45 | ...user, 46 | avatar: detailUser.avatar_url, 47 | userId: detailUser.id, 48 | url: detailUser.url 49 | } 50 | 51 | return { 52 | type: USER_LOGIN_SUCCESS, 53 | user 54 | } 55 | } 56 | 57 | -------------------------------------------------------------------------------- /js/store/configureStore.js: -------------------------------------------------------------------------------- 1 | import { 2 | createNavigationEnabledStore, 3 | NavigationActions 4 | } from '@exponent/ex-navigation' 5 | import { applyMiddleware, createStore } from 'redux' 6 | import thunk from 'redux-thunk' 7 | import promise from './promise' 8 | import array from './array' 9 | import analytics from './analytics' 10 | import reducers from '../reducers' 11 | import createLogger from 'redux-logger' 12 | import Router from '../navigation/Router' 13 | 14 | const createStoreWithNavigation = createNavigationEnabledStore({ 15 | createStore, 16 | navigationStateKey: 'navigation', 17 | }) 18 | 19 | const isDebuggingInChrome = __DEV__ && !!window.navigator.userAgent 20 | const logger = createLogger({ 21 | predicate: (getState, action) => isDebuggingInChrome, 22 | collapsed: true, 23 | duration: true, 24 | }) 25 | 26 | let store = createStoreWithNavigation(reducers, applyMiddleware(thunk, promise, array, analytics)) 27 | if (__DEV__) { 28 | store = createStoreWithNavigation(reducers, applyMiddleware(thunk, promise, array, analytics, logger)) 29 | } 30 | 31 | if (isDebuggingInChrome) { 32 | window.store = store 33 | } 34 | 35 | export default store 36 | export const goToPage = (pageName, params) => { 37 | let navigatorUID = store.getState().navigation.currentNavigatorUID; 38 | store.dispatch(NavigationActions.push(navigatorUID, Router.getRoute(pageName, params))) 39 | } -------------------------------------------------------------------------------- /js/_Playgroun.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | var React = require('React') 4 | var View = require('View') 5 | import RepDetailPage from './pages/RepoDetailPage' 6 | import IssueDetailPage from './pages/IssueDetailPage' 7 | 8 | class Playground extends React.Component { 9 | constructor() { 10 | super() 11 | const content = [] 12 | const define = (name, render) => { 13 | content.push() 14 | } 15 | 16 | // var Module = require('./common/MarkdownView') 17 | // var Module = require('./common/SearchHistory') 18 | // var Module = require('./common/IconCell') 19 | 20 | // Module.__cards__(define) 21 | this.state = { content } 22 | } 23 | 24 | render() { 25 | return ( 26 | // 27 | // 28 | 29 | 30 | ) 31 | } 32 | } 33 | 34 | class Example extends React.Component { 35 | state = { 36 | inner: null 37 | } 38 | 39 | render() { 40 | const content = this.props.render(this.state.inner, (inner) => this.setState({ inner })) 41 | return ( 42 | 43 | {content} 44 | 45 | ) 46 | 47 | } 48 | } 49 | 50 | module.exports = Playground 51 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "gitfeed", 3 | "version": "0.0.1", 4 | "private": true, 5 | "scripts": { 6 | "start": "node node_modules/react-native/local-cli/cli.js start", 7 | "test": "jest" 8 | }, 9 | "dependencies": { 10 | "@exponent/ex-navigation": "^2.4.0", 11 | "@exponent/react-native-fade-in-image": "^1.1.1", 12 | "@exponent/react-native-read-more-text": "^1.0.0", 13 | "babel-preset-react-native-stage-0": "^1.0.1", 14 | "base-64": "^0.1.0", 15 | "crypto-js": "^3.1.8", 16 | "lodash": "^4.17.2", 17 | "moment": "^2.17.1", 18 | "parse": "^1.9.2", 19 | "react": "~15.3.2", 20 | "react-native": "^0.37.x", 21 | "react-native-blur": "^1.2.0", 22 | "react-native-code-push": "^1.16.1-beta", 23 | "react-native-device-info": "^0.9.7", 24 | "react-native-popup-menu": "^0.6.1", 25 | "react-native-push-notification": "^2.2.1", 26 | "react-native-scrollable-tab-view": "^0.7.0", 27 | "react-native-swiper": "^1.5.4", 28 | "react-native-vector-icons": "^3.0.0", 29 | "react-redux": "^4.4.5", 30 | "redux": "^3.6.0", 31 | "redux-logger": "^2.6.1", 32 | "redux-thunk": "^2.1.0" 33 | }, 34 | "jest": { 35 | "preset": "react-native" 36 | }, 37 | "devDependencies": { 38 | "babel-jest": "17.0.2", 39 | "babel-preset-react-native": "1.9.0", 40 | "jest": "17.0.3", 41 | "react-test-renderer": "15.3.1" 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /js/navigation/Router.js: -------------------------------------------------------------------------------- 1 | import { 2 | createRouter, 3 | } from '@exponent/ex-navigation' 4 | 5 | import RepoDetailPage from '../pages/RepoDetailPage' 6 | import HomePage from '../pages/HomePage' 7 | import IssueDetailPage from '../pages/IssueDetailPage' 8 | import SearchPage from '../pages/SearchPage' 9 | import WebView from '../common/WebView' 10 | import LoginPage from '../pages/LoginPage' 11 | import Playground from '../Playground' 12 | import UserDetailPage from '../pages/UserDetailPage' 13 | import NotificationPage from '../pages/NotificationPage' 14 | import PersonPage from '../pages/PersonPage' 15 | import TrendPage from '../pages/TrendPage' 16 | import ShowcasePage from '../pages/ShowcasePage' 17 | import FamousPage from '../pages/FamousPage' 18 | import { RepoListView, UserListView } from '../common/DetailListView' 19 | 20 | export default createRouter(() => ({ 21 | RepoDetail: () => RepoDetailPage, 22 | Home: () => HomePage, 23 | IssueDetail: () => IssueDetailPage, 24 | Search: () => SearchPage, 25 | Web: () => WebView, 26 | Login: () => LoginPage, 27 | Playground: () => Playground, 28 | UserDetail: () => UserDetailPage, 29 | UserList: () => UserListView, // { url: 'https://api.github.com/users/getify/followers?per_page=100' } 30 | RepoList: () => RepoListView, 31 | Notification: () => NotificationPage, 32 | Me: () => PersonPage, 33 | Trend: () => TrendPage, 34 | Showcase: () => ShowcasePage, 35 | Famous: () => FamousPage 36 | })) -------------------------------------------------------------------------------- /android/app/src/main/java/com/gitfeed/MainApplication.java: -------------------------------------------------------------------------------- 1 | package com.gitfeed; 2 | 3 | import android.app.Application; 4 | import android.util.Log; 5 | 6 | import com.facebook.react.ReactApplication; 7 | import com.cmcewen.blurview.BlurViewPackage; 8 | import com.facebook.react.ReactInstanceManager; 9 | import com.facebook.react.ReactNativeHost; 10 | import com.facebook.react.ReactPackage; 11 | import com.facebook.react.shell.MainReactPackage; 12 | import com.facebook.soloader.SoLoader; 13 | 14 | import java.util.Arrays; 15 | import java.util.List; 16 | 17 | public class MainApplication extends Application implements ReactApplication { 18 | 19 | private final ReactNativeHost mReactNativeHost = new ReactNativeHost(this) { 20 | 21 | @Override 22 | protected String getJSBundleFile() { 23 | return CodePush.getJSBundleFile(); 24 | } 25 | 26 | @Override 27 | protected boolean getUseDeveloperSupport() { 28 | return BuildConfig.DEBUG; 29 | } 30 | 31 | @Override 32 | protected List getPackages() { 33 | return Arrays.asList( 34 | new MainReactPackage(), 35 | new BlurViewPackage() 36 | ); 37 | } 38 | }; 39 | 40 | @Override 41 | public ReactNativeHost getReactNativeHost() { 42 | return mReactNativeHost; 43 | } 44 | 45 | @Override 46 | public void onCreate() { 47 | super.onCreate(); 48 | SoLoader.init(this, /* native exopackage */ false); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /js/Playground.js: -------------------------------------------------------------------------------- 1 | import React, { 2 | Component, 3 | PropTypes, 4 | } from 'react' 5 | import { 6 | View, 7 | Text, 8 | StyleSheet, 9 | ScrollView, 10 | TouchableHighlight, 11 | Image, 12 | TouchableOpacity, 13 | } from 'react-native' 14 | import { NavigationStyles } from '@exponent/ex-navigation' 15 | 16 | class Playground extends React.Component { 17 | constructor() { 18 | super() 19 | const content = [] 20 | const define = (name, render) => { 21 | content.push() 22 | } 23 | 24 | // var Module = require('./common/MarkdownView') 25 | // var Module = require('./common/SearchHistory') 26 | // var Module = require('./common/IconCell') 27 | 28 | // Module.__cards__(define) 29 | this.state = { content } 30 | } 31 | 32 | render() { 33 | return ( 34 | // 35 | // 36 | 37 | 38 | ) 39 | } 40 | } 41 | 42 | class Example extends React.Component { 43 | state = { 44 | inner: null 45 | } 46 | 47 | render() { 48 | const content = this.props.render(this.state.inner, (inner) => this.setState({ inner })) 49 | return ( 50 | 51 | {content} 52 | 53 | ) 54 | 55 | } 56 | } 57 | 58 | module.exports = Playground 59 | -------------------------------------------------------------------------------- /js/setup.js: -------------------------------------------------------------------------------- 1 | import React, { 2 | Component, 3 | PropTypes, 4 | AsyncStorage 5 | } from 'react' 6 | import BlurView from './common/BlurView' 7 | import WLApp from './WLApp' 8 | import { Provider } from 'react-redux' 9 | import store from './store/configureStore' 10 | import Parse from 'parse/react-native' 11 | import { serverURL } from './env' 12 | //import Playground from './Playground' 13 | import { 14 | NavigationContext, 15 | NavigationProvider, 16 | } from '@exponent/ex-navigation' 17 | import Router from './navigation/Router' 18 | import { 19 | MenuContext, 20 | } from 'react-native-popup-menu' 21 | 22 | const navigationContext = new NavigationContext({ 23 | router: Router, 24 | store: store, 25 | }) 26 | 27 | const setup = () => { 28 | // AsyncStorage.clear() 29 | 30 | // something for initialize 31 | // Parse.initialize('oss-f8-app-2016') 32 | // Parse.serverURL = `${serverURL}/parse` 33 | 34 | return () => { 35 | return ( 36 | 37 | {/**/} 38 | 39 | 40 | 41 | 42 | 43 | 44 | ) 45 | } 46 | } 47 | 48 | global.LOG = (...args) => { 49 | console.log('/------------------------------\\') 50 | console.log(...args) 51 | console.log('\\------------------------------/') 52 | return args[args.length - 1] 53 | } 54 | 55 | export default setup 56 | -------------------------------------------------------------------------------- /js/common/RoundTagButton.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | import React, { 4 | Component, 5 | PropTypes, 6 | } from 'react' 7 | import { 8 | StyleSheet, 9 | View, 10 | Text, 11 | TouchableOpacity, 12 | } from 'react-native' 13 | import Color from '../constants/Colors' 14 | 15 | class RoundTabButton extends Component { 16 | constructor(props) { 17 | super(props) 18 | } 19 | 20 | render() { 21 | const { color, text, style } = this.props 22 | return ( 23 | 26 | 27 | {text} 28 | 29 | 30 | ) 31 | } 32 | } 33 | 34 | RoundTabButton.propTypes = { 35 | color: PropTypes.string, 36 | text: PropTypes.string.isRequired, 37 | onSelect: PropTypes.func 38 | } 39 | 40 | RoundTabButton.defaultProps = { 41 | color: Color.lightBlack, 42 | text: '金馆长' 43 | } 44 | 45 | module.exports = RoundTabButton 46 | 47 | module.exports.__cards__ = (define) => { 48 | define('Gray', (state = true, update) => 49 | console.log('hellow')} />) 50 | } 51 | 52 | var styles = StyleSheet.create({ 53 | container: { 54 | flexDirection: 'row', 55 | alignItems: 'center', 56 | justifyContent: 'center', 57 | height: 35, 58 | borderRadius: 17, 59 | paddingLeft: 12, 60 | paddingRight: 12, 61 | borderWidth: 1, 62 | }, 63 | text: {} 64 | }) -------------------------------------------------------------------------------- /js/common/GridView.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import React from 'react'; 4 | import { 5 | AppRegistry, 6 | View, 7 | StyleSheet, 8 | ListView, 9 | } from 'react-native'; 10 | 11 | export default React.createClass({ 12 | groupItems: function(items, itemsPerRow) { 13 | var itemsGroups = []; 14 | var group = []; 15 | items.forEach(function(item) { 16 | if (group.length === itemsPerRow) { 17 | itemsGroups.push(group); 18 | group = [item]; 19 | } else { 20 | group.push(item); 21 | } 22 | }); 23 | 24 | if (group.length > 0) { 25 | itemsGroups.push(group); 26 | } 27 | 28 | return itemsGroups; 29 | }, 30 | renderGroup: function(group, sectionID, rowID) { 31 | var that = this; 32 | var items = group.map(function(item, index) { 33 | return that.props.renderItem(item, index, rowID); 34 | }); 35 | return ( 36 | 40 | {items} 41 | 42 | ); 43 | }, 44 | render: function() { 45 | var groups = this.groupItems(this.props.items, this.props.itemsPerRow); 46 | var ds = new ListView.DataSource({rowHasChanged: (r1, r2) => r1 !== r2}); 47 | return (); 52 | }, 53 | }); 54 | 55 | 56 | var styles = StyleSheet.create({ 57 | group: { 58 | flexDirection: 'row', 59 | alignItems: 'center', 60 | justifyContent: 'flex-start', 61 | overflow: 'hidden' 62 | } 63 | }); -------------------------------------------------------------------------------- /js/common/ReadMore.js: -------------------------------------------------------------------------------- 1 | import React, { 2 | Component, 3 | PropTypes, 4 | } from 'react' 5 | import { 6 | StyleSheet, 7 | View, 8 | Image, 9 | TouchableHighlight, 10 | TouchableOpacity 11 | } from 'react-native' 12 | import Colors from '../constants/Colors' 13 | import { Text, LinkText, NormalText, Heading2 } from '../common/F8Text' 14 | import ReadMore from '@exponent/react-native-read-more-text' 15 | 16 | 17 | class RM extends Component { 18 | _renderTruncatedFooter(handlePress) { 19 | return ( 20 | 22 | Read more 23 | 24 | ) 25 | } 26 | 27 | _renderRevealedFooter(handlePress) { 28 | return ( 29 | 31 | Show less 32 | 33 | ) 34 | } 35 | 36 | render() { 37 | const { lines, content } = this.props 38 | return ( 39 | 43 | 50 | {content} 51 | 52 | 53 | ) 54 | } 55 | } 56 | 57 | RM.propTypes = { 58 | lines: PropTypes.number, 59 | } 60 | RM.defaultProps = { 61 | lines: 3 62 | } 63 | 64 | export default RM 65 | -------------------------------------------------------------------------------- /js/env.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2016 Facebook, Inc. 3 | * 4 | * You are hereby granted a non-exclusive, worldwide, royalty-free license to 5 | * use, copy, modify, and distribute this software in source code or binary 6 | * form for use in connection with the web services and APIs provided by 7 | * Facebook. 8 | * 9 | * As with any software that integrates with the Facebook platform, your use 10 | * of this software is subject to the Facebook Developer Principles and 11 | * Policies [http://developers.facebook.com/policy/]. This copyright notice 12 | * shall be included in all copies or substantial portions of the software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 17 | * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 19 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 20 | * DEALINGS IN THE SOFTWARE 21 | * 22 | * @flow 23 | */ 24 | 25 | 'use strict'; 26 | 27 | let env 28 | if (__DEV__) { 29 | env = { 30 | testMenuEnabled: true, 31 | // FIXME: 这里只能填写 ip 地址, 安卓模拟器比较 burden 32 | serverURL: 'http://30.10.108.2:8080', 33 | version: '2.0.0', 34 | fontFamily: undefined, 35 | } 36 | } else { 37 | env = { 38 | testMenuEnabled: true, 39 | serverURL: 'https://bonwechat.com:8080', 40 | version: '2.0.0', 41 | fontFamily: undefined, 42 | } 43 | } 44 | 45 | module.exports = env 46 | -------------------------------------------------------------------------------- /ios/Classes/H5/GFWebResourceCache.h: -------------------------------------------------------------------------------- 1 | @import Foundation; 2 | 3 | /** 4 | * Web资源缓存管理类 5 | */ 6 | @interface GFWebResourceCache : NSObject 7 | 8 | @property (nonatomic, strong, readonly) NSString *cachePath; 9 | 10 | /** 11 | * 检查是否有命中的缓存索引,用于提升检索效率,减少IO操作。 12 | * 13 | * @param key 缓存对应唯一的key 14 | * 15 | * @return 返回`YES`标示本地存在对应的缓存,反之则`NO` 16 | */ 17 | - (BOOL)hasCacheForKey:(NSString *)key; 18 | 19 | /** 20 | * 通过传入的key创建缓存检索索引。 21 | * 22 | * @param key 缓存对应唯一的key 23 | */ 24 | - (void)addCacheIndexForKey:(NSString *)key; 25 | 26 | /** 27 | * 移除该key关联的缓存检索索引。 28 | * 29 | * @param key 缓存对应唯一的key 30 | */ 31 | - (void)removeCacheIndexForKey:(NSString *)key; 32 | 33 | /** 34 | * 清除cache的index table和cache file 35 | * 36 | */ 37 | - (void)clearCache:(void (^)(NSUInteger fileSize))completionBlock; 38 | 39 | /** 40 | * 查看cache了多少大小 41 | * 42 | */ 43 | - (void)cacheCost:(void (^)(NSUInteger fileSize))completionBlock; 44 | 45 | /** 46 | * 通过缓存key获取关联的数据。 47 | * 48 | * @param key 缓存对应唯一的key 49 | * @param url 请求URL 50 | * @param response 响应头 51 | * 52 | * @return 返回本地缓存的数据,如果返回为`nil`,则需要手工移除该key关联的缓存索引 53 | */ 54 | - (NSData *)cacheDataForKey:(NSString *)key url:(NSURL *)url response:(NSURLResponse **)response; 55 | 56 | /** 57 | * 通过关联的key存储缓存数据 58 | * 59 | * @param data 数据 60 | * @param response 响应头 61 | * @param key 缓存对应唯一的key 62 | * @param completionBlock 完成回调 63 | */ 64 | - (void)storeCacheData:(NSData *)data 65 | response:(NSURLResponse *)response 66 | forKey:(NSString *)key 67 | completionBlock:(void (^)(BOOL succeed))completionBlock; 68 | 69 | 70 | @end 71 | -------------------------------------------------------------------------------- /ios/Classes/H5/GFWebResourceInterceptor.h: -------------------------------------------------------------------------------- 1 | @import Foundation; 2 | #import "GFWebResourceInterceptorSettings+Internal.h" 3 | 4 | @class GFWebResourceCache; 5 | 6 | /** 7 | * Web资源拦截器实现类 8 | */ 9 | @interface GFWebResourceInterceptor : NSObject 10 | 11 | /** 12 | * 全局共享的Web资源拦截器 13 | * 14 | * @return 返回全局共享的Web资源拦截器 15 | */ 16 | + (instancetype)globalWebResourceInterceptor; 17 | 18 | /** 19 | * 设置自定义的全局共享Web资源拦截器。当传入`nil`时,则清空全局的Web资源拦截器。 20 | * 21 | * @param interceptor 自定义拦截器或为`nil` 22 | */ 23 | + (void)setGlobalWebResourceInterceptor:(GFWebResourceInterceptor *)interceptor; 24 | 25 | /** 26 | * 拦截器设置 27 | */ 28 | @property (nonatomic, strong, readonly) id settings; 29 | 30 | /** 31 | * 拦截器资源缓存 32 | */ 33 | @property (nonatomic, strong, readonly) GFWebResourceCache *cache; 34 | 35 | /** 36 | * 配置默认Web资源拦截器的设置 37 | */ 38 | - (void)setupDefaultWebResourceInterceptorSettings; 39 | 40 | /** 41 | * 检查传入的主机名是否在白名单内 42 | * 43 | * @param host 主机名 44 | * 45 | * @return 返回`YES`则表示在白名单内,`NO`则表示不在白名单内 46 | */ 47 | - (BOOL)isWhitelistHost:(NSString *)host; 48 | 49 | /** 50 | * 检查传入的资源路径是否在黑名单内 51 | * 52 | * @param path 请求资源路径 53 | * 54 | * @return 返回`YES`则表示在黑名单内,`NO`则表示不在黑名单内 55 | */ 56 | - (BOOL)isBlacklistWithRequestPath:(NSString *)path; 57 | 58 | /** 59 | * 检查是否支持传入的文件后缀名 60 | * 61 | * @param extension 文件后缀名 62 | * 63 | * @return 返回`YES`则表示支持该后缀名,反之则为`NO` 64 | */ 65 | - (BOOL)isSupportedPathExtension:(NSString *)extension; 66 | 67 | /** 68 | * 更新Web资源拦截器设置 69 | * 70 | * @param settings Web资源拦截器设置 71 | */ 72 | - (void)updateInterceptorSettings:(id)settings; 73 | 74 | @end 75 | -------------------------------------------------------------------------------- /js/pages/HomePage.js: -------------------------------------------------------------------------------- 1 | import React, { PropTypes, Component } from 'react' 2 | import { 3 | StyleSheet, 4 | Text, 5 | View, 6 | Navigator, 7 | TouchableOpacity, 8 | Dimensions 9 | } from 'react-native' 10 | import GHRefreshListView from '../common/GHRefreshListView' 11 | import Layout from '../constants/Layout' 12 | import { connect } from 'react-redux' 13 | import { create } from '../common/F8StyleSheet' 14 | import GHCell from '../common/GHEventCell' 15 | import { SearchButton } from '../common/SearchView' 16 | import { goToPage } from '../store/configureStore' 17 | 18 | class HomePage extends Component { 19 | static route = { 20 | navigationBar: { 21 | title(params, props) { 22 | return goToPage('Search')}/> 23 | }, 24 | }, 25 | } 26 | 27 | render() { 28 | const { user } = this.props 29 | 30 | return ( 31 | 38 | ) 39 | } 40 | 41 | renderRow(rowData, sectionID, rowID, highlightRow) { 42 | return ( 43 | 44 | ) 45 | } 46 | } 47 | 48 | HomePage.propTypes = { 49 | navigator: PropTypes.object, 50 | user: PropTypes.object 51 | } 52 | HomePage.defaultProps = { 53 | user: { login: '' } 54 | } 55 | 56 | const select = state => { 57 | return { 58 | user: state.login.user, 59 | } 60 | } 61 | 62 | export default connect(select)(HomePage) 63 | -------------------------------------------------------------------------------- /ios/Classes/DXRNUtils.m: -------------------------------------------------------------------------------- 1 | // 2 | // DXRNUtils.m 3 | // RN_CNNode 4 | // 5 | // Created by xiekw on 15/11/18. 6 | // Copyright © 2015年 Facebook. All rights reserved. 7 | // 8 | 9 | #import "DXRNUtils.h" 10 | #import "MobClick.h" 11 | 12 | static NSString * const kAppId = @"1079873993"; 13 | 14 | @implementation DXRNUtils 15 | 16 | @synthesize bridge = _bridge; 17 | 18 | RCT_EXPORT_MODULE() 19 | 20 | RCT_EXPORT_METHOD(clearCookies:(RCTResponseSenderBlock)callback) { 21 | NSHTTPCookieStorage *cookieStore = [NSHTTPCookieStorage sharedHTTPCookieStorage]; 22 | for (NSHTTPCookie *cookie in [cookieStore cookies]) { 23 | [cookieStore deleteCookie:cookie]; 24 | } 25 | 26 | callback(@[[NSNull null]]); 27 | } 28 | 29 | RCT_EXPORT_METHOD(trackClick:(nonnull NSString *)eventName attributes:(NSDictionary *)atr) { 30 | [MobClick event:eventName attributes:atr]; 31 | } 32 | 33 | RCT_EXPORT_METHOD(appInfo:(RCTResponseSenderBlock)callback) { 34 | NSString *appVersion = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleShortVersionString"]; 35 | NSString *appBuild = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleVersion"]; 36 | NSString *appStoreURL = [[NSString alloc] initWithFormat:@"http://phobos.apple.com/WebObjects/MZStore.woa/wa/viewSoftware?id=%@&mt=8", kAppId]; 37 | NSString *rateURL = [[NSString alloc] initWithFormat:@"itms-apps://ax.itunes.apple.com/WebObjects/MZStore.woa/wa/viewContentsUserReviews?type=Purple+Software&id=%@", kAppId]; 38 | 39 | callback(@[ 40 | @{@"appVersion": appVersion, 41 | @"appBuild": appBuild, 42 | @"appStoreURL": appStoreURL, 43 | @"rateURL": rateURL 44 | } 45 | ]); 46 | } 47 | 48 | @end 49 | -------------------------------------------------------------------------------- /js/pages/Languages.js: -------------------------------------------------------------------------------- 1 | import React, { 2 | Component, 3 | PropTypes, 4 | } from 'react' 5 | import { 6 | StyleSheet, 7 | Text, 8 | View, 9 | TouchableOpacity, 10 | Platform, 11 | ScrollView, 12 | SegmentedControlIOS 13 | } from 'react-native' 14 | import { changeLanguage, changeRule } from '../actions/language' 15 | import { connect } from 'react-redux' 16 | import store from '../store/configureStore' 17 | import PickerFilter from '../common/PickerFilter' 18 | import { defaultState } from '../reducers/language' 19 | 20 | class LanguagePicker extends Component { 21 | onChange(main, sub) { 22 | if (main && sub) { 23 | if (main === 'choose sort') { 24 | store.dispatch(changeRule(sub)) 25 | } 26 | 27 | if (main === 'choose languages') { 28 | store.dispatch(changeLanguage(sub)) 29 | } 30 | } 31 | } 32 | 33 | render() { 34 | const { currentRule, currentLanguage } = this.props 35 | return ( 36 | 51 | ) 52 | } 53 | 54 | } 55 | 56 | LanguagePicker.propTypes = {} 57 | LanguagePicker.defaultProps = {} 58 | 59 | const select = state => { 60 | return { 61 | currentRule: state.language.currentRule, 62 | currentLanguage: state.language.currentLanguage 63 | } 64 | } 65 | 66 | export default connect(select)(LanguagePicker) 67 | -------------------------------------------------------------------------------- /js/common/UserCell.js: -------------------------------------------------------------------------------- 1 | import React, { 2 | Component, 3 | PropTypes, 4 | } from 'react' 5 | import { 6 | StyleSheet, 7 | Text, 8 | View, 9 | TouchableOpacity, 10 | TouchableHighlight, 11 | Image, 12 | Platform, 13 | ScrollView, 14 | SegmentedControlIOS 15 | } from 'react-native' 16 | import Colors from '../constants/Colors' 17 | import CStyles from '../constants/Styles' 18 | import { Heading2 } from './F8Text' 19 | import Touchable from './F8Touchable' 20 | 21 | class UserCell extends Component { 22 | openTargetUser() { 23 | const { user, navigator } = this.props 24 | navigator.push('UserDetail', { user }) 25 | } 26 | 27 | render() { 28 | const user = this.props.user 29 | 30 | return ( 31 | 32 | 33 | 34 | 35 | {user.login} 36 | 37 | 38 | 39 | 40 | ) 41 | } 42 | } 43 | 44 | UserCell.propTypes = { 45 | user: React.PropTypes.object, 46 | } 47 | UserCell.defaultProps = {} 48 | 49 | export default UserCell 50 | 51 | const styles = StyleSheet.create({ 52 | cellContentView: { 53 | flexDirection: 'column', 54 | }, 55 | rowContent: { 56 | flexDirection: 'row', 57 | height: 50, 58 | alignItems: 'center', 59 | }, 60 | avatar: { 61 | width: 40, 62 | height: 40, 63 | marginLeft: 15, 64 | backgroundColor: Colors.imagePlaceholder, 65 | }, 66 | userName: { 67 | marginLeft: 20, 68 | }, 69 | }) -------------------------------------------------------------------------------- /js/pages/TrendPicker.js: -------------------------------------------------------------------------------- 1 | import React, { 2 | Component, 3 | PropTypes, 4 | } from 'react' 5 | import { 6 | StyleSheet, 7 | Text, 8 | View, 9 | TouchableOpacity, 10 | Platform, 11 | ScrollView, 12 | SegmentedControlIOS 13 | } from 'react-native' 14 | import { changeLanguage, changeRule } from '../actions/language' 15 | import { connect } from 'react-redux' 16 | import store from '../store/configureStore' 17 | import PickerFilter from '../common/PickerFilter' 18 | import { defaultState } from '../reducers/language' 19 | 20 | class LanguagePicker extends Component { 21 | onChange(main, sub) { 22 | if (main && sub) { 23 | if (main === 'choose sort') { 24 | store.dispatch(changeRule(sub)) 25 | } 26 | 27 | if (main === 'choose languages') { 28 | store.dispatch(changeLanguage(sub)) 29 | } 30 | } 31 | } 32 | 33 | render() { 34 | const { currentRule, currentLanguage } = this.props 35 | return ( 36 | 51 | ) 52 | } 53 | 54 | } 55 | 56 | LanguagePicker.propTypes = {} 57 | LanguagePicker.defaultProps = {} 58 | 59 | const select = state => { 60 | return { 61 | currentRule: state.language.currentRule, 62 | currentLanguage: state.language.currentLanguage 63 | } 64 | } 65 | 66 | export default connect(select)(LanguagePicker) 67 | -------------------------------------------------------------------------------- /js/pages/ShowcasePage.js: -------------------------------------------------------------------------------- 1 | import React, { 2 | Component, 3 | PropTypes, 4 | } from 'react' 5 | import { 6 | View, 7 | ActivityIndicatorIOS, 8 | StyleSheet, 9 | ScrollView, 10 | TouchableHighlight, 11 | Image, 12 | TouchableOpacity, 13 | ListView, 14 | Linking, 15 | Dimensions 16 | } from 'react-native' 17 | import Layout from '../constants/Layout' 18 | import RepoCell from '../common/RepoCell' 19 | import GHRefreshListView from '../common/GHRefreshListView' 20 | import { withNavigation } from '@exponent/ex-navigation' 21 | 22 | const SHOW_CASE_PATH = 'http://trending.codehub-app.com/v2/showcases' 23 | 24 | @withNavigation 25 | class ShowcasePage extends Component { 26 | static route = { 27 | navigationBar: { 28 | title(props) { 29 | return props.showcase.name 30 | } 31 | }, 32 | } 33 | 34 | renderRow(rowData, sectionID, rowID, highlightRow) { 35 | return 36 | } 37 | 38 | render() { 39 | const path = SHOW_CASE_PATH + '/' + this.props.showcase.slug 40 | return ( 41 | 42 | v.repositories} 48 | contentOffset={{x:0, y:-Layout.contentInset.top}} 49 | /> 50 | 51 | ) 52 | } 53 | } 54 | 55 | ShowcasePage.propTypes = { 56 | showcase: React.PropTypes.object, 57 | } 58 | ShowcasePage.defaultProps = {} 59 | 60 | export default ShowcasePage 61 | 62 | var styles = StyleSheet.create({ 63 | container: { 64 | flex: 1 65 | }, 66 | }) -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Github Feed 2 | --- 3 | Yet another Github client written in react-native. 4 | 5 | ## Includes ? 6 | --- 7 | 8 | 1. Feed like web github home. 9 | 2. Watched repos' notification. 10 | 3. Trends. 11 | 4. Personal. 12 | 13 | ## How to run iOS 14 | 15 | `rm -rf node_modules` 16 | 17 | `npm install` 18 | 19 | if error about 'EACCS' try 20 | 21 | `sudo chown -R $(whoami) "$HOME/.npm"` 22 | 23 | run the project in ios dir 24 | 25 | (***注意*** 如果要contribute cocoapods 建议使用0.35.0版本!) 26 | 27 | in iOS dir 28 | 29 | `pod install` 30 | 31 | ###接入流程 32 | 33 | 1. 在index.ios.js(index.android.js)里写入Production的 deploymentKey,通常在componentDidMount里写入如下: 34 | 35 | ```js 36 | const codePush = require('react-native-code-push'); 37 | 38 | codePush.sync({ 39 | updateDialog: true, 40 | installMode: codePush.InstallMode.IMMEDIATE, 41 | deploymentKey: dpkey, 42 | }); 43 | ``` 44 | 2. 修改xcode的edit schema 使得build app是 release模式的,在appDelegate里写入: 45 | ```js 46 | NSURL *jsCodeLocation; 47 | 48 | #ifdef DEBUG 49 | jsCodeLocation = [NSURL URLWithString:@"http://30.10.111.158:8081/index.ios.bundle?platform=ios&dev=true"]; 50 | 51 | jsCodeLocation = [NSURL URLWithString:[NSString stringWithFormat:@"http://localhost:8081/index.ios.bundle?platform=ios&dev=true"]]; 52 | 53 | #else 54 | jsCodeLocation = [CodePush bundleURL]; 55 | #endif 56 | ``` 57 | 3. 随意乱改js文件 58 | 4. 打包 所有js文件在一个其他位置,可以使用`打包需要更新的资源` 59 | 5. 用code-push发布。 60 | 61 | ## CodePush 62 | 63 | 注意在打包的目标地址先建立对应的目录比如这里的~/Desktop/release,要现在Desktop建立好。 64 | 65 | 打包到桌面本地资源(包括图片): 66 | react-native bundle --platform ios --entry-file index.ios.js --bundle-output ./ios/main.jsbundle --dev false 67 | 68 | 查看更新状态: 69 | code-push deployment ls GitFeed-iOS 70 | 71 | 发布更新: 72 | code-push release GitFeed-iOS ~/Desktop/release 1.0.0 -d Production 73 | -------------------------------------------------------------------------------- /android/app/BUCK: -------------------------------------------------------------------------------- 1 | import re 2 | 3 | # To learn about Buck see [Docs](https://buckbuild.com/). 4 | # To run your application with Buck: 5 | # - install Buck 6 | # - `npm start` - to start the packager 7 | # - `cd android` 8 | # - `keytool -genkey -v -keystore keystores/debug.keystore -storepass android -alias androiddebugkey -keypass android -dname "CN=Android Debug,O=Android,C=US"` 9 | # - `./gradlew :app:copyDownloadableDepsToLibs` - make all Gradle compile dependencies available to Buck 10 | # - `buck install -r android/app` - compile, install and run application 11 | # 12 | 13 | lib_deps = [] 14 | for jarfile in glob(['libs/*.jar']): 15 | name = 'jars__' + re.sub(r'^.*/([^/]+)\.jar$', r'\1', jarfile) 16 | lib_deps.append(':' + name) 17 | prebuilt_jar( 18 | name = name, 19 | binary_jar = jarfile, 20 | ) 21 | 22 | for aarfile in glob(['libs/*.aar']): 23 | name = 'aars__' + re.sub(r'^.*/([^/]+)\.aar$', r'\1', aarfile) 24 | lib_deps.append(':' + name) 25 | android_prebuilt_aar( 26 | name = name, 27 | aar = aarfile, 28 | ) 29 | 30 | android_library( 31 | name = 'all-libs', 32 | exported_deps = lib_deps 33 | ) 34 | 35 | android_library( 36 | name = 'app-code', 37 | srcs = glob([ 38 | 'src/main/java/**/*.java', 39 | ]), 40 | deps = [ 41 | ':all-libs', 42 | ':build_config', 43 | ':res', 44 | ], 45 | ) 46 | 47 | android_build_config( 48 | name = 'build_config', 49 | package = 'com.gitfeed', 50 | ) 51 | 52 | android_resource( 53 | name = 'res', 54 | res = 'src/main/res', 55 | package = 'com.gitfeed', 56 | ) 57 | 58 | android_binary( 59 | name = 'app', 60 | package_type = 'debug', 61 | manifest = 'src/main/AndroidManifest.xml', 62 | keystore = '//android/keystores:debug', 63 | deps = [ 64 | ':app-code', 65 | ], 66 | ) 67 | -------------------------------------------------------------------------------- /js/common/F8StyleSheet.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2016 Facebook, Inc. 3 | * 4 | * You are hereby granted a non-exclusive, worldwide, royalty-free license to 5 | * use, copy, modify, and distribute this software in source code or binary 6 | * form for use in connection with the web services and APIs provided by 7 | * Facebook. 8 | * 9 | * As with any software that integrates with the Facebook platform, your use 10 | * of this software is subject to the Facebook Developer Principles and 11 | * Policies [http://developers.facebook.com/policy/]. This copyright notice 12 | * shall be included in all copies or substantial portions of the software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 17 | * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 19 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 20 | * DEALINGS IN THE SOFTWARE 21 | * 22 | * @providesModule F8StyleSheet 23 | * @flow 24 | */ 25 | 26 | 'use strict'; 27 | 28 | import { StyleSheet, Platform } from 'react-native'; 29 | 30 | export function create(styles) { 31 | const platformStyles = {}; 32 | Object.keys(styles).forEach((name) => { 33 | let { ios, android, ...style } = { ...styles[name] }; 34 | if (ios && Platform.OS === 'ios') { 35 | style = { ...style, ...ios }; 36 | } 37 | if (android && Platform.OS === 'android') { 38 | style = { ...style, ...android }; 39 | } 40 | platformStyles[name] = style; 41 | }); 42 | return StyleSheet.create(platformStyles); 43 | } 44 | -------------------------------------------------------------------------------- /AppIcons/watchkit/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "size" : "24x24", 5 | "idiom" : "watch", 6 | "scale" : "2x", 7 | "filename" : "Icon-24@2x.png", 8 | "role" : "notificationCenter", 9 | "subtype" : "38mm" 10 | }, 11 | { 12 | "size" : "27.5x27.5", 13 | "idiom" : "watch", 14 | "scale" : "2x", 15 | "filename" : "Icon-27.5@2x.png", 16 | "role" : "notificationCenter", 17 | "subtype" : "42mm" 18 | }, 19 | { 20 | "size" : "29x29", 21 | "idiom" : "watch", 22 | "filename" : "Icon-29@2x.png", 23 | "role" : "companionSettings", 24 | "scale" : "2x" 25 | }, 26 | { 27 | "size" : "29x29", 28 | "idiom" : "watch", 29 | "filename" : "Icon-29@3x.png", 30 | "role" : "companionSettings", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "size" : "40x40", 35 | "idiom" : "watch", 36 | "scale" : "2x", 37 | "filename" : "Icon-40@2x.png", 38 | "role" : "appLauncher", 39 | "subtype" : "38mm" 40 | }, 41 | { 42 | "size" : "44x44", 43 | "idiom" : "watch", 44 | "scale" : "2x", 45 | "filename" : "Icon-44@2x.png", 46 | "role" : "longLook", 47 | "subtype" : "42mm" 48 | }, 49 | { 50 | "size" : "86x86", 51 | "idiom" : "watch", 52 | "scale" : "2x", 53 | "filename" : "Icon-86@2x.png", 54 | "role" : "quickLook", 55 | "subtype" : "38mm" 56 | }, 57 | { 58 | "size" : "98x98", 59 | "idiom" : "watch", 60 | "scale" : "2x", 61 | "filename" : "Icon-98@2x.png", 62 | "role" : "quickLook", 63 | "subtype" : "42mm" 64 | } 65 | ], 66 | "info" : { 67 | "version" : 1, 68 | "author" : "makeappicon" 69 | } 70 | } -------------------------------------------------------------------------------- /js/constants/Colors.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @providesModule Colors 3 | * @flow 4 | */ 5 | 6 | const TINT_COLOR = '#586872' 7 | 8 | export default { 9 | tintColor: TINT_COLOR, 10 | textBlack: '#333333', 11 | inactiveText: '#9B9B9B', 12 | textGray: '#767676', 13 | textLink: '#4078C0', 14 | textDark: '#666666', 15 | textLight: '#7F91A7', 16 | cellBorder: '#E2E2E2', 17 | tabIconDefault: '#888', 18 | tabIconSelected: TINT_COLOR, 19 | red: '#f12938', 20 | green: '#80BD01', 21 | backGray: '#E5E5E5', 22 | lightGray: '#cccccc', 23 | gray: '#F0EFF4', 24 | lightBlack: '#919191', 25 | blue: '#0084FF', 26 | imagePlaceholder: '#EFEEEE', 27 | touchableHighlight: '#E5E5E5' 28 | }; 29 | 30 | 31 | //const BRAND_COLOR = '#5C069A'; 32 | //const VERY_LIGHT_GREY = '#f5f5f5'; 33 | //const LIGHT_GREY = '#cccccc'; 34 | //const GREY = '#888'; 35 | //const MID_GREY = '#444'; 36 | //const DARK_GREY = '#222'; 37 | // 38 | //export default { 39 | // navigationBarTintColor: '#fff', 40 | // navigationBarBackgroundColor: BRAND_COLOR, 41 | // tabIconDefault: GREY, 42 | // tabIconSelected: BRAND_COLOR, 43 | // tabBar: '#fefefe', 44 | // drawerIconDefault: GREY, 45 | // drawerTextDefault: MID_GREY, 46 | // drawerIconSelected: BRAND_COLOR, 47 | // 48 | // veryLightGrey: VERY_LIGHT_GREY, 49 | // lightGrey: LIGHT_GREY, 50 | // grey: GREY, 51 | // midGrey: MID_GREY, 52 | // darkGrey: DARK_GREY, 53 | // tintColor: BRAND_COLOR, 54 | // lineGray: '#F0F0F0', 55 | // green: '#80BD01', 56 | // backGray: '#E5E5E5', 57 | // textGray: '#9A9A9A', 58 | // textBlack: '#333333', 59 | // purple: '#9966CC', 60 | // red: '#f12938', 61 | // backWhite: '#F2F2F2', 62 | // textGold: '#BC7233', 63 | // borderColor: '#E2E2E2', 64 | // black: '#586872', 65 | // blue: '#4078c0', 66 | // theme: '#586872', 67 | // lightBlack: '#919191', 68 | // gray: '#F0EFF4' 69 | //}; 70 | -------------------------------------------------------------------------------- /js/store/track.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2016 Facebook, Inc. 3 | * 4 | * You are hereby granted a non-exclusive, worldwide, royalty-free license to 5 | * use, copy, modify, and distribute this software in source code or binary 6 | * form for use in connection with the web services and APIs provided by 7 | * Facebook. 8 | * 9 | * As with any software that integrates with the Facebook platform, your use 10 | * of this software is subject to the Facebook Developer Principles and 11 | * Policies [http://developers.facebook.com/policy/]. This copyright notice 12 | * shall be included in all copies or substantial portions of the software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 17 | * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 19 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 20 | * DEALINGS IN THE SOFTWARE 21 | * 22 | * @flow 23 | */ 24 | 25 | 'use strict' 26 | 27 | import { NativeModules } from 'react-native' 28 | const AppEventsLogger = NativeModules.AppEventsLogger 29 | 30 | function track(action) { 31 | switch (action.type) { 32 | // case 'FAV_LOOK': 33 | // AppEventsLogger.logEvent(action.type, { title: action.title }) 34 | // break 35 | // case 'SEARCH_START': 36 | // AppEventsLogger.logEvent(action.type, { ...action }) 37 | // break 38 | // case 'SHARE_LOOK': 39 | // AppEventsLogger.logEvent(action.type, { 40 | // url: action.imgObj.url, 41 | // scene: JSON.stringify(action.imgObj.scene) 42 | // }) 43 | // break 44 | } 45 | } 46 | 47 | module.exports = track; 48 | -------------------------------------------------------------------------------- /js/common/F8Touchable.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2016 Facebook, Inc. 3 | * 4 | * You are hereby granted a non-exclusive, worldwide, royalty-free license to 5 | * use, copy, modify, and distribute this software in source code or binary 6 | * form for use in connection with the web services and APIs provided by 7 | * Facebook. 8 | * 9 | * As with any software that integrates with the Facebook platform, your use 10 | * of this software is subject to the Facebook Developer Principles and 11 | * Policies [http://developers.facebook.com/policy/]. This copyright notice 12 | * shall be included in all copies or substantial portions of the software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 17 | * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 19 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 20 | * DEALINGS IN THE SOFTWARE 21 | * 22 | * @providesModule F8Touchable 23 | * @flow 24 | */ 25 | 26 | 'use strict'; 27 | 28 | import React from 'react'; 29 | import { 30 | TouchableHighlight, 31 | TouchableNativeFeedback, 32 | Platform, 33 | } from 'react-native'; 34 | import Colors from '../constants/Colors' 35 | 36 | function F8TouchableIOS(props) { 37 | return ( 38 | 43 | ); 44 | } 45 | 46 | // TODO 当用 TouchableNativeFeedback wrap Image 的时候,图片显示不出来 47 | const F8Touchable = Platform.OS === 'android' 48 | ? TouchableNativeFeedback 49 | : F8TouchableIOS; 50 | 51 | module.exports = F8Touchable; 52 | -------------------------------------------------------------------------------- /js/reducers/language.js: -------------------------------------------------------------------------------- 1 | import * as lg from '../actions/language' 2 | 3 | export const defaultState = { 4 | allLanguages: [ 5 | "All Languages", 6 | "ActionScript", 7 | "C", 8 | "C#", 9 | "C++", 10 | "Clojure", 11 | "CoffeeScript", 12 | "CSS", 13 | "Go", 14 | "Haskell", 15 | "HTML", 16 | "Java", 17 | "JavaScript", 18 | "Lua", 19 | "Matlab", 20 | "Objective-C", 21 | "Objective-C++", 22 | "Perl", 23 | "PHP", 24 | "Python", 25 | "R", 26 | "Ruby", 27 | "Scala", 28 | "Shell", 29 | "Swift", 30 | "TeX", 31 | "VimL" 32 | ], 33 | trendingLanguages: [ 34 | "All Languages", 35 | "C#", 36 | "C++", 37 | "Go", 38 | "Java", 39 | "JavaScript", 40 | "Objective-C", 41 | "PHP", 42 | "Python", 43 | "Ruby", 44 | "Scala", 45 | "Shell", 46 | "Swift", 47 | ], 48 | rules: [ 49 | "Best matches", 50 | "Most stars", 51 | "Most forks", 52 | "Recent updated" 53 | ], 54 | currentLanguage: "All Languages", 55 | currentRule: "Best matches" 56 | } 57 | 58 | export const language = (state = defaultState, action) => { 59 | switch (action.type) { 60 | case lg.CHANGE_LANGUAGE: 61 | return { ...state, currentLanguage: action.currentLanguage } 62 | case lg.CHANGE_RULE: 63 | return { ...state, currentRule: action.currentRule } 64 | } 65 | 66 | return state 67 | } 68 | 69 | export const mapRuleToQuery = rule => { 70 | let res = '' 71 | switch (rule) { 72 | case 'Best matches': 73 | res = '' 74 | break 75 | case 'Most stars': 76 | res = 'stars' 77 | break 78 | case 'Most forks': 79 | res = 'forks' 80 | break 81 | case 'Recent updated': 82 | res = 'updated' 83 | break 84 | } 85 | 86 | if (!res.length) return res 87 | return `+sort:${res}` 88 | } -------------------------------------------------------------------------------- /js/store/promise.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2016 Facebook, Inc. 3 | * 4 | * You are hereby granted a non-exclusive, worldwide, royalty-free license to 5 | * use, copy, modify, and distribute this software in source code or binary 6 | * form for use in connection with the web services and APIs provided by 7 | * Facebook. 8 | * 9 | * As with any software that integrates with the Facebook platform, your use 10 | * of this software is subject to the Facebook Developer Principles and 11 | * Policies [http://developers.facebook.com/policy/]. This copyright notice 12 | * shall be included in all copies or substantial portions of the software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 17 | * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 19 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 20 | * DEALINGS IN THE SOFTWARE 21 | */ 22 | 23 | 'use strict'; 24 | 25 | function warn(error) { 26 | console.warn(error.message || error); 27 | throw error; // To let the caller handle the rejection 28 | } 29 | 30 | module.exports = store => next => action => 31 | typeof action.then === 'function' 32 | ? Promise.resolve(action).then(next, warn) 33 | : next(action); 34 | 35 | 36 | // Warning: Naïve implementation! 37 | // That's *not* Redux API. 38 | /* 39 | function applyMiddleware(store, middlewares) { 40 | middlewares = middlewares.slice() 41 | middlewares.reverse() 42 | 43 | let dispatch = store.dispatch 44 | middlewares.forEach(middleware => 45 | dispatch = middleware(store)(dispatch) 46 | ) 47 | 48 | return Object.assign({}, store, { dispatch }) 49 | } 50 | */ -------------------------------------------------------------------------------- /AppIcons/ios/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "29x29", 6 | "scale" : "2x", 7 | "filename" : "Icon-Small@2x.png" 8 | }, 9 | { 10 | "idiom" : "iphone", 11 | "size" : "29x29", 12 | "scale" : "3x", 13 | "filename" : "Icon-Small@3x.png" 14 | }, 15 | { 16 | "idiom" : "iphone", 17 | "size" : "40x40", 18 | "scale" : "2x", 19 | "filename" : "Icon-40@2x.png" 20 | }, 21 | { 22 | "idiom" : "iphone", 23 | "size" : "40x40", 24 | "scale" : "3x", 25 | "filename" : "Icon-40@3x.png" 26 | }, 27 | { 28 | "idiom" : "iphone", 29 | "size" : "60x60", 30 | "scale" : "2x", 31 | "filename" : "Icon-60@2x.png" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "size" : "60x60", 36 | "scale" : "3x", 37 | "filename" : "Icon-60@3x.png" 38 | }, 39 | { 40 | "idiom" : "ipad", 41 | "size" : "29x29", 42 | "scale" : "1x", 43 | "filename" : "Icon-Small.png" 44 | }, 45 | { 46 | "idiom" : "ipad", 47 | "size" : "29x29", 48 | "scale" : "2x", 49 | "filename" : "Icon-Small@2x.png" 50 | }, 51 | { 52 | "idiom" : "ipad", 53 | "size" : "40x40", 54 | "scale" : "1x", 55 | "filename" : "Icon-40.png" 56 | }, 57 | { 58 | "idiom" : "ipad", 59 | "size" : "40x40", 60 | "scale" : "2x", 61 | "filename" : "Icon-40@2x.png" 62 | }, 63 | { 64 | "idiom" : "ipad", 65 | "size" : "76x76", 66 | "scale" : "1x", 67 | "filename" : "Icon-76.png" 68 | }, 69 | { 70 | "idiom" : "ipad", 71 | "size" : "76x76", 72 | "scale" : "2x", 73 | "filename" : "Icon-76@2x.png" 74 | } 75 | ], 76 | "info" : { 77 | "version" : 1, 78 | "author" : "makeappicon" 79 | } 80 | } -------------------------------------------------------------------------------- /ios/gitfeed/AppDelegate.m: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2015-present, Facebook, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | */ 9 | 10 | #import "AppDelegate.h" 11 | #import "CodePush.h" 12 | #import "MobClick.h" 13 | #import "RCTRootView.h" 14 | 15 | @implementation AppDelegate 16 | 17 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions 18 | { 19 | UMConfigInstance.appKey = @"56a8db12e0f55a3cd40005f2"; 20 | [MobClick startWithConfigure:UMConfigInstance]; 21 | 22 | NSURLCache *urlCache = [[NSURLCache alloc] initWithMemoryCapacity:20 * 1024 * 1024 diskCapacity:100 * 1024 * 1024 diskPath:nil]; 23 | [NSURLCache setSharedURLCache:urlCache]; 24 | 25 | NSURL *jsCodeLocation; 26 | 27 | #ifdef DEBUG 28 | jsCodeLocation = [NSURL URLWithString:[NSString stringWithFormat:@"http://localhost:8081/index.ios.bundle?platform=ios&dev=true"]]; 29 | #else 30 | jsCodeLocation = [CodePush bundleURL]; 31 | #endif 32 | 33 | RCTRootView *rootView = [[RCTRootView alloc] initWithBundleURL:jsCodeLocation 34 | moduleName:@"gitfeed" 35 | initialProperties:nil 36 | launchOptions:launchOptions]; 37 | rootView.backgroundColor = [[UIColor alloc] initWithRed:1.0f green:1.0f blue:1.0f alpha:1]; 38 | 39 | self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds]; 40 | UIViewController *rootViewController = [UIViewController new]; 41 | rootViewController.view = rootView; 42 | self.window.rootViewController = rootViewController; 43 | [self.window makeKeyAndVisible]; 44 | return YES; 45 | } 46 | 47 | @end 48 | -------------------------------------------------------------------------------- /js/actions/parse.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2016 Facebook, Inc. 3 | * 4 | * You are hereby granted a non-exclusive, worldwide, royalty-free license to 5 | * use, copy, modify, and distribute this software in source code or binary 6 | * form for use in connection with the web services and APIs provided by 7 | * Facebook. 8 | * 9 | * As with any software that integrates with the Facebook platform, your use 10 | * of this software is subject to the Facebook Developer Principles and 11 | * Policies [http://developers.facebook.com/policy/]. This copyright notice 12 | * shall be included in all copies or substantial portions of the software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 17 | * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 19 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 20 | * DEALINGS IN THE SOFTWARE 21 | * 22 | * @flow 23 | */ 24 | 25 | 'use strict'; 26 | 27 | const Parse = require('parse/react-native'); 28 | const logError = require('logError'); 29 | const InteractionManager = require('InteractionManager'); 30 | const Notification = Parse.Object.extend('Notification'); 31 | 32 | function loadParseQuery(type, query) { 33 | return (dispatch) => { 34 | return query.find({ 35 | success: (list) => { 36 | // We don't want data loading to interfere with smooth animations 37 | InteractionManager.runAfterInteractions(() => { 38 | // Flow can't guarantee {type, list} is a valid action 39 | dispatch(({type, list}: any)); 40 | }); 41 | }, 42 | error: logError, 43 | }); 44 | }; 45 | } 46 | 47 | export const loadNotifications = () => loadParseQuery('LOADED_NOTIFICATIONS', 48 | new Parse.Query(Notification)) 49 | -------------------------------------------------------------------------------- /.flowconfig: -------------------------------------------------------------------------------- 1 | [ignore] 2 | 3 | # We fork some components by platform. 4 | .*/*[.]android.js 5 | 6 | # Ignore templates with `@flow` in header 7 | .*/local-cli/generator.* 8 | 9 | # Ignore malformed json 10 | .*/node_modules/y18n/test/.*\.json 11 | 12 | # Ignore the website subdir 13 | /website/.* 14 | 15 | # Ignore BUCK generated dirs 16 | /\.buckd/ 17 | 18 | # Ignore unexpected extra @providesModule 19 | .*/node_modules/commoner/test/source/widget/share.js 20 | .*/node_modules/.*/node_modules/fbjs/.* 21 | 22 | # Ignore duplicate module providers 23 | # For RN Apps installed via npm, "Libraries" folder is inside node_modules/react-native but in the source repo it is in the root 24 | .*/Libraries/react-native/React.js 25 | .*/Libraries/react-native/ReactNative.js 26 | .*/node_modules/jest-runtime/build/__tests__/.* 27 | 28 | [include] 29 | 30 | [libs] 31 | node_modules/react-native/Libraries/react-native/react-native-interface.js 32 | node_modules/react-native/flow 33 | flow/ 34 | 35 | [options] 36 | module.system=haste 37 | 38 | esproposal.class_static_fields=enable 39 | esproposal.class_instance_fields=enable 40 | 41 | experimental.strict_type_args=true 42 | 43 | munge_underscores=true 44 | 45 | module.name_mapper='^image![a-zA-Z0-9$_-]+$' -> 'GlobalImageStub' 46 | 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' 47 | 48 | suppress_type=$FlowIssue 49 | suppress_type=$FlowFixMe 50 | suppress_type=$FixMe 51 | 52 | suppress_comment=\\(.\\|\n\\)*\\$FlowFixMe\\($\\|[^(]\\|(\\(>=0\\.\\(3[0-3]\\|[1-2][0-9]\\|[0-9]\\).[0-9]\\)? *\\(site=[a-z,_]*react_native[a-z,_]*\\)?)\\) 53 | suppress_comment=\\(.\\|\n\\)*\\$FlowIssue\\((\\(>=0\\.\\(3[0-3]\\|1[0-9]\\|[1-2][0-9]\\).[0-9]\\)? *\\(site=[a-z,_]*react_native[a-z,_]*\\)?)\\)?:? #[0-9]+ 54 | suppress_comment=\\(.\\|\n\\)*\\$FlowFixedInNextDeploy 55 | 56 | unsafe.enable_getters_and_setters=true 57 | 58 | [version] 59 | ^0.33.0 60 | -------------------------------------------------------------------------------- /ios/Classes/NavigationCategory/DXTextCategoryMenu.h: -------------------------------------------------------------------------------- 1 | // 2 | // DXTextCategoryMenu.h 3 | // RN_CNNode 4 | // 5 | // Created by xiekw on 15/10/23. 6 | // Copyright © 2015年 Facebook. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @class DXTextCategoryMenu; 12 | 13 | @protocol DXTextCategoryMenuDelegate 14 | 15 | @optional 16 | - (void)textCategoryMenu:(DXTextCategoryMenu *)menu willSelectedMenuFromIndex:(NSInteger)fromIndex toIndex:(NSInteger)index; 17 | 18 | - (void)textCategoryMenu:(DXTextCategoryMenu *)menu didSelectedMenuFromIndex:(NSInteger)fromIndex toIndex:(NSInteger)index; 19 | 20 | @end 21 | 22 | @interface DXTextCategoryMenu : UIView 23 | 24 | @property (nonatomic, weak) id delegate; 25 | @property (nonatomic, strong) NSArray *options; 26 | 27 | // If the first item stays the position when others scroll 28 | @property (nonatomic, assign) BOOL lockStartPosition; 29 | 30 | // Selected color, default is red 31 | @property (nonatomic, strong) UIColor *selectedColor; 32 | 33 | // UnSelected color, default is lightgray 34 | @property (nonatomic, strong) UIColor *unSelectedColor; 35 | 36 | // The menu contentInset in container. default is {2, 5, 2, 5} 37 | @property (nonatomic, assign) UIEdgeInsets contentInset; 38 | 39 | // Each text menu between space 40 | @property (nonatomic, assign) CGFloat spacingBetweenMenu; 41 | 42 | // The bottom line height, default is 2 43 | @property (nonatomic, assign) CGFloat bottomLineHeight; 44 | 45 | // The bottom line animation flys default is 0.25 46 | @property (nonatomic, assign) CGFloat selectedAnimationDuration; 47 | 48 | // The trigger scrollView autoscroll space between the side, default is 100 49 | @property (nonatomic, assign) CGFloat needCenterMenuOffset; 50 | 51 | // If use the system blur, default is YES 52 | @property (nonatomic, assign, getter=isBlur) BOOL blur; 53 | 54 | // If blur which style to use, defaut is extra light 55 | @property (nonatomic, assign) UIBlurEffectStyle blurEffectStyle; 56 | 57 | - (void)updateSelectedIndex:(NSInteger)index; 58 | 59 | @end 60 | -------------------------------------------------------------------------------- /js/WLApp.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | import React, { 4 | Component, 5 | PropTypes, 6 | } from 'react' 7 | import { 8 | StyleSheet, 9 | AppState, 10 | View, 11 | StatusBar, 12 | ActivityIndicator 13 | } from 'react-native' 14 | import CodePush from 'react-native-code-push' 15 | import { connect } from 'react-redux' 16 | import RootNavigation from './navigation/RootNavigation' 17 | import { create } from './common/F8StyleSheet' 18 | import { getUser, login } from './actions/login' 19 | import { version } from './env' 20 | import OnboardPage from './pages/OnboardPage' 21 | //import PushNotificationsController from './PushNotificationsController' 22 | 23 | class WLApp extends Component { 24 | componentDidMount() { 25 | // TODO: 清理本地所有数据 26 | // storage.clearAll() 27 | 28 | const { dispatch } = this.props 29 | dispatch(getUser()) 30 | 31 | AppState.addEventListener('change', this.handleAppStateChange) 32 | } 33 | 34 | componentWillUnmount() { 35 | AppState.removeEventListener('change', this.handleAppStateChange) 36 | } 37 | 38 | handleAppStateChange(appState) { 39 | if (appState === 'active' && !__DEV__) { 40 | CodePush.sync({ installMode: CodePush.InstallMode.ON_NEXT_RESUME }) 41 | } 42 | } 43 | 44 | render() { 45 | const { isOnboard, loading } = this.props 46 | 47 | if (loading) { 48 | // TODO: A more beautiful loading like weibo avatar page should be 49 | return 50 | 51 | 52 | } 53 | 54 | return isOnboard ? : 55 | } 56 | } 57 | 58 | const styles = create({ 59 | loadingContainer: { 60 | flex: 1, 61 | flexDirection: 'row', 62 | justifyContent: 'center', 63 | alignItems: 'center' 64 | }, 65 | 66 | container: { 67 | flex: 1, 68 | }, 69 | }) 70 | 71 | const select = ({ login }) => { 72 | const { user } = login 73 | return { 74 | user, 75 | isOnboard: !!user.login && !!user.login.length, 76 | loading: login.loading 77 | } 78 | } 79 | 80 | export default connect(select)(WLApp) -------------------------------------------------------------------------------- /js/pages/DetailPage.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | import React, { 4 | Component, 5 | PropTypes, 6 | } from 'react' 7 | import { 8 | StyleSheet, 9 | Text, 10 | View, 11 | Platform 12 | } from 'react-native' 13 | import { addRecent } from '../actions/recent' 14 | import { connect } from 'react-redux' 15 | 16 | import Color from '../common/Colors' 17 | import GridView from '../common/GridView' 18 | import PicCell from '../common/PicCell' 19 | import Dimensions from 'Dimensions' 20 | 21 | const SCREENWIDTH = Dimensions.get('window').width 22 | const PICS_PER_ROW = 3 23 | const PICCELL_WIDTH = SCREENWIDTH / 3 24 | const NAV_HEIGHT = Platform.OS === 'android' ? 44 : 64 25 | 26 | class DetailPage extends Component { 27 | componentDidMount() { 28 | const { dispatch, context } = this.props 29 | dispatch(addRecent(context)) 30 | } 31 | 32 | render() { 33 | return ( 34 | 40 | ) 41 | } 42 | 43 | _renderItem(item, index, rowID) { 44 | const key = index + PICS_PER_ROW * rowID 45 | return ( 46 | ) 53 | } 54 | 55 | _onSelect(item, key) { 56 | this.props.navigator.push({ 57 | id: 'ShareLookPage', 58 | title: this.props.context.title || '天天表情', 59 | context: { 60 | detail: this.props.context, 61 | selectIndex: key 62 | } 63 | }) 64 | } 65 | } 66 | 67 | DetailPage.propTypes = { 68 | context: PropTypes.object 69 | } 70 | DetailPage.defaultProps = {} 71 | 72 | export default connect()(DetailPage) 73 | 74 | const styles = StyleSheet.create({ 75 | gridView: { 76 | marginTop: NAV_HEIGHT, 77 | backgroundColor: Color.gridViewBack 78 | }, 79 | loadingView: { 80 | justifyContent: 'center', 81 | alignItems: 'center' 82 | } 83 | }) 84 | -------------------------------------------------------------------------------- /js/common/GridPage.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | import React, { 4 | Component, 5 | PropTypes, 6 | } from 'react' 7 | import { 8 | StyleSheet, 9 | Text, 10 | View, 11 | } from 'react-native' 12 | 13 | import Color from '../common/Colors' 14 | import GridView from '../common/GridView' 15 | import PicCell from '../common/PicCell' 16 | import Dimensions from 'Dimensions' 17 | 18 | const reactMixin = require('react-mixin') 19 | const TimerMixin = require('react-timer-mixin') 20 | 21 | const SCREEN_WIDTH = Dimensions.get('window').width 22 | const PICS_PER_ROW = 3 23 | const CELL_SIZE = SCREEN_WIDTH / 3 24 | 25 | class GridPage extends Component { 26 | constructor(props) { 27 | super(props) 28 | } 29 | 30 | render() { 31 | return ( 32 | 40 | ) 41 | } 42 | 43 | _renderItem(item, index, rowID) { 44 | const displayIndex = item.displayIndex || 0 45 | return ( 46 | 53 | ) 54 | } 55 | 56 | _onSelect(detail) { 57 | this.requestAnimationFrame(() => { 58 | this.props.navigator.push({ 59 | id: 'ShareLookPage', 60 | title: detail.title, 61 | context: { 62 | detail, 63 | selectIndex: detail.displayIndex || 0 64 | } 65 | }) 66 | }) 67 | } 68 | } 69 | 70 | GridPage.propTypes = { 71 | dataSource: PropTypes.array.isRequired 72 | } 73 | 74 | GridPage.defaultProps = { 75 | dataSource: [] 76 | } 77 | 78 | reactMixin(GridPage.prototype, TimerMixin) 79 | 80 | export default GridPage 81 | 82 | const styles = StyleSheet.create({ 83 | gridView: { 84 | backgroundColor: Color.gridViewBack 85 | }, 86 | loadingView: { 87 | justifyContent: 'center', 88 | alignItems: 'center' 89 | } 90 | }) 91 | -------------------------------------------------------------------------------- /js/pages/HotTags.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | import React, { 4 | Component, 5 | PropTypes, 6 | } from 'react' 7 | import { 8 | StyleSheet, 9 | View, 10 | Text, 11 | TouchableOpacity, 12 | Dimensions 13 | } from 'react-native' 14 | import { connect } from 'react-redux' 15 | import { getHotTags, startSearch } from '../actions/search' 16 | import CStyles from '../constants/Styles' 17 | import Color from '../constants/Colors' 18 | 19 | const RoundTagButton = require('../common/RoundTagButton') 20 | 21 | class HotTags extends Component { 22 | componentDidMount() { 23 | this.props.dispatch && this.props.dispatch(getHotTags()) 24 | } 25 | 26 | render() { 27 | let { tags, style, dispatch } = this.props 28 | tags = tags && tags.map((tag, i) => { 29 | const color = i === 0 ? Color.blue : Color.lightBlack 30 | return ( 31 | dispatch(startSearch(tag, 'TAG'))} 37 | /> 38 | ) 39 | }) 40 | return ( 41 | 42 | Suggested: 43 | 44 | {tags} 45 | 46 | 47 | 48 | ) 49 | } 50 | } 51 | 52 | HotTags.propTypes = { 53 | tags: PropTypes.array 54 | } 55 | HotTags.defaultProps = {} 56 | 57 | const mapStateToProps = state => { 58 | return { 59 | tags: state.search.hotTags 60 | } 61 | } 62 | 63 | exports.HotTags = connect(mapStateToProps)(HotTags) 64 | exports.__cards__ = (define) => { 65 | define('normal', _ => ) 66 | } 67 | 68 | const styles = StyleSheet.create({ 69 | container: { 70 | flexDirection: 'column', 71 | }, 72 | hotSearch: { 73 | margin: 5, 74 | marginBottom: 10, 75 | color: Color.lightBlack 76 | }, 77 | tagContainer: { 78 | flexDirection: 'row', 79 | flexWrap: 'wrap', 80 | padding: 3 81 | }, 82 | tag: { 83 | marginLeft: 10, 84 | marginBottom: 10, 85 | borderColor: Color.lightBlack 86 | }, 87 | }) -------------------------------------------------------------------------------- /js/common/F8Colors.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2016 Facebook, Inc. 3 | * 4 | * You are hereby granted a non-exclusive, worldwide, royalty-free license to 5 | * use, copy, modify, and distribute this software in source code or binary 6 | * form for use in connection with the web services and APIs provided by 7 | * Facebook. 8 | * 9 | * As with any software that integrates with the Facebook platform, your use 10 | * of this software is subject to the Facebook Developer Principles and 11 | * Policies [http://developers.facebook.com/policy/]. This copyright notice 12 | * shall be included in all copies or substantial portions of the software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 17 | * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 19 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 20 | * DEALINGS IN THE SOFTWARE 21 | * 22 | * @providesModule F8Colors 23 | * @flow 24 | */ 25 | 26 | 'use strict'; 27 | 28 | const LOCATION_COLORS = { 29 | 'HERBST': '#00E3AD', 30 | 'HERBST A': '#00E3AD', 31 | 'HERBST B': '#00E3AD', 32 | 'HACKER X': '#4D99EF', 33 | 'HACKER Y': '#CF72B1', 34 | 'COWELL': '#6A6AD5', 35 | 'COWELL C': '#6A6AD5', 36 | 'FOOD TENT': '#FFCD3B', 37 | }; 38 | 39 | function colorForLocation(location: ?string): string { 40 | if (!location) { 41 | return 'black'; 42 | } 43 | 44 | var color = LOCATION_COLORS[location.toUpperCase()]; 45 | if (!color) { 46 | console.warn(`Location '${location}' has no color`); 47 | color = 'black'; 48 | } 49 | return color; 50 | } 51 | 52 | function colorForTopic(count: number, index: number): string { 53 | const hue = Math.round(360 * index / (count + 1)); 54 | return `hsl(${hue}, 74%, 65%)`; 55 | } 56 | 57 | module.exports = { 58 | actionText: '#3FB4CF', 59 | inactiveText: '#9B9B9B', 60 | darkText: '#032250', 61 | lightText: '#7F91A7', 62 | cellBorder: '#EEEEEE', 63 | darkBackground: '#183E63', 64 | colorForLocation, 65 | colorForTopic, 66 | }; 67 | -------------------------------------------------------------------------------- /ios/Classes/UIView+TopBarMessage.h: -------------------------------------------------------------------------------- 1 | // 2 | // UIViewController+TopBarMessage.h 3 | // DXTopBarMessageView 4 | // 5 | // Created by xiekw on 14-3-17. 6 | // Copyright (c) 2014年 xiekw. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface TopWarningView : UIView 12 | 13 | @property (nonatomic, strong) NSString *warningText; 14 | @property (nonatomic, strong) UIImageView *iconIgv; 15 | @property (nonatomic, copy) dispatch_block_t tapHandler; 16 | @property (nonatomic, assign) CGFloat dimissDelay; 17 | @property (nonatomic, strong) UILabel *label; 18 | 19 | - (void)resetViews; 20 | 21 | @end 22 | 23 | @interface UIView (TopBarMessage) 24 | 25 | /** 26 | * Set the global default apperance of the top message, may be the appdelegate class is a good place to setup 27 | * 28 | * @param apperance the top bar view config, the whole version will be @{kDXTopBarBackgroundColor:[UIColor blueColor], kDXTopBarTextColor : [UIColor yellowColor], kDXTopBarIcon : [UIImage imageNamed:@"icon.png"], kDXTopBarTextFont : [UIFont boldSystemFontOfSize:15.0]} 29 | */ 30 | + (void)setTopMessageDefaultApperance:(NSDictionary *)apperance; 31 | 32 | /** 33 | * show the message with config on the top bar, note the config won't change the default top message apperance, this is the setTopMessageDefaultApperance: does. 34 | * 35 | * @param message the text to show 36 | * @param config the top bar view config, the whole version will be @{kDXTopBarBackgroundColor:[UIColor blueColor], kDXTopBarTextColor : [UIColor yellowColor], kDXTopBarIcon : [UIImage imageNamed:@"icon.png"], kDXTopBarTextFont : [UIFont boldSystemFontOfSize:15.0]} 37 | * @param delay time to dismiss 38 | * @param tapHandler the tap handler 39 | */ 40 | - (void)showTopMessage:(NSString *)message topBarConfig:(NSDictionary *)config dismissDelay:(CGFloat)delay withTapBlock:(dispatch_block_t)tapHandler; 41 | 42 | /** 43 | * Default style message something like Instagram does 44 | * 45 | * @param message message to tell 46 | */ 47 | - (void)showTopMessage:(NSString *)message; 48 | 49 | @end 50 | 51 | extern NSString * const kDXTopBarBackgroundColor; 52 | extern NSString * const kDXTopBarTextColor; 53 | extern NSString * const kDXTopBarTextFont; 54 | extern NSString * const kDXTopBarIcon; 55 | extern NSString * const kDXTopBarOffset; 56 | -------------------------------------------------------------------------------- /ios/gitfeed/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleDisplayName 8 | GitFeed 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.0.0 21 | CFBundleSignature 22 | ???? 23 | CFBundleVersion 24 | 100 25 | CodePushDeploymentKey 26 | deployment-key-here 27 | LSRequiresIPhoneOS 28 | 29 | NSAppTransportSecurity 30 | 31 | NSAllowsArbitraryLoads 32 | 33 | NSExceptionDomains 34 | 35 | localhost 36 | 37 | NSExceptionAllowsInsecureHTTPLoads 38 | 39 | 40 | 41 | 42 | NSLocationWhenInUseUsageDescription 43 | 44 | UIAppFonts 45 | 46 | Entypo.ttf 47 | EvilIcons.ttf 48 | FontAwesome.ttf 49 | Foundation.ttf 50 | Ionicons.ttf 51 | MaterialIcons.ttf 52 | Octicons.ttf 53 | SimpleLineIcons.ttf 54 | Zocial.ttf 55 | 56 | UILaunchStoryboardName 57 | LaunchScreen 58 | UIRequiredDeviceCapabilities 59 | 60 | armv7 61 | 62 | UISupportedInterfaceOrientations 63 | 64 | UIInterfaceOrientationPortrait 65 | UIInterfaceOrientationLandscapeLeft 66 | UIInterfaceOrientationLandscapeRight 67 | 68 | UIViewControllerBasedStatusBarAppearance 69 | 70 | 71 | 72 | -------------------------------------------------------------------------------- /js/PushNotificationsController.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | import React, { 4 | Component, 5 | PropTypes, 6 | } from 'react' 7 | import { 8 | Platform, 9 | View, 10 | AppState, 11 | PushNotificationIOS 12 | } from 'react-native' 13 | import { connect } from 'react-redux' 14 | import PushNotification from 'react-native-push-notification' 15 | import { 16 | storeDeviceToken, 17 | receivePushNotification, 18 | updateInstallation, 19 | checkPushNotifications, 20 | loadNotifications 21 | } from './actions' 22 | 23 | class AppBadgeController extends React.Component { 24 | constructor() { 25 | super() 26 | this.handleAppStateChange = this.handleAppStateChange.bind(this) 27 | } 28 | 29 | handleAppStateChange(appState) { 30 | if (appState === 'active') { 31 | this.updateAppBadge() 32 | this.props.dispatch(loadNotifications()) 33 | } 34 | } 35 | 36 | componentDidMount() { 37 | AppState.addEventListener('change', this.handleAppStateChange) 38 | 39 | const { dispatch } = this.props 40 | dispatch(checkPushNotifications()) 41 | dispatch(loadNotifications()) 42 | 43 | PushNotification.configure({ 44 | onRegister: ({ token }) => { 45 | dispatch(storeDeviceToken(token)) 46 | dispatch(checkPushNotifications()) 47 | }, 48 | onNotification: notif => dispatch(receivePushNotification(notif)), 49 | permissions: { 50 | alert: true, 51 | badge: true, 52 | sound: true 53 | }, 54 | requestPermissions: this.props.enabled, 55 | }) 56 | 57 | this.updateAppBadge() 58 | } 59 | 60 | componentWillUnmount() { 61 | AppState.removeEventListener('change', this.handleAppStateChange) 62 | } 63 | 64 | componentDidUpdate(prevProps) { 65 | if (!prevProps.enabled && this.props.enabled) { 66 | PushNotification.requestPermissions() 67 | } 68 | } 69 | 70 | updateAppBadge() { 71 | if (Platform.OS === 'ios') { 72 | PushNotificationIOS.setApplicationIconBadgeNumber(0) 73 | updateInstallation({ badge: 0 }) 74 | } 75 | } 76 | 77 | render() { 78 | return null 79 | } 80 | } 81 | 82 | function select(store) { 83 | return { 84 | enabled: store.notifications.enabled === true, 85 | } 86 | } 87 | 88 | module.exports = connect(select)(AppBadgeController) 89 | -------------------------------------------------------------------------------- /ios/gitfeedTests/gitfeedTests.m: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2015-present, Facebook, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | */ 9 | 10 | #import 11 | #import 12 | 13 | #import "RCTLog.h" 14 | #import "RCTRootView.h" 15 | 16 | #define TIMEOUT_SECONDS 600 17 | #define TEXT_TO_LOOK_FOR @"Welcome to React Native!" 18 | 19 | @interface gitfeedTests : XCTestCase 20 | 21 | @end 22 | 23 | @implementation gitfeedTests 24 | 25 | - (BOOL)findSubviewInView:(UIView *)view matching:(BOOL(^)(UIView *view))test 26 | { 27 | if (test(view)) { 28 | return YES; 29 | } 30 | for (UIView *subview in [view subviews]) { 31 | if ([self findSubviewInView:subview matching:test]) { 32 | return YES; 33 | } 34 | } 35 | return NO; 36 | } 37 | 38 | - (void)testRendersWelcomeScreen 39 | { 40 | UIViewController *vc = [[[[UIApplication sharedApplication] delegate] window] rootViewController]; 41 | NSDate *date = [NSDate dateWithTimeIntervalSinceNow:TIMEOUT_SECONDS]; 42 | BOOL foundElement = NO; 43 | 44 | __block NSString *redboxError = nil; 45 | RCTSetLogFunction(^(RCTLogLevel level, RCTLogSource source, NSString *fileName, NSNumber *lineNumber, NSString *message) { 46 | if (level >= RCTLogLevelError) { 47 | redboxError = message; 48 | } 49 | }); 50 | 51 | while ([date timeIntervalSinceNow] > 0 && !foundElement && !redboxError) { 52 | [[NSRunLoop mainRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.1]]; 53 | [[NSRunLoop mainRunLoop] runMode:NSRunLoopCommonModes beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.1]]; 54 | 55 | foundElement = [self findSubviewInView:vc.view matching:^BOOL(UIView *view) { 56 | if ([view.accessibilityLabel isEqualToString:TEXT_TO_LOOK_FOR]) { 57 | return YES; 58 | } 59 | return NO; 60 | }]; 61 | } 62 | 63 | RCTSetLogFunction(RCTDefaultLogFunction); 64 | 65 | XCTAssertNil(redboxError, @"RedBox error: %@", redboxError); 66 | XCTAssertTrue(foundElement, @"Couldn't find element with text '%@' in %d seconds", TEXT_TO_LOOK_FOR, TIMEOUT_SECONDS); 67 | } 68 | 69 | 70 | @end 71 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled source # 2 | ################### 3 | *.com 4 | *.class 5 | *.dll 6 | *.exe 7 | *.o 8 | *.so 9 | 10 | # Packages # 11 | ############ 12 | # it's better to unpack these files and commit the raw source 13 | # git has its own built in compression methods 14 | *.7z 15 | *.dmg 16 | *.gz 17 | *.iso 18 | *.jar 19 | *.rar 20 | *.tar 21 | *.zip 22 | 23 | # Logs and databases # 24 | ###################### 25 | *.log 26 | *.sql 27 | *.sqlite 28 | 29 | # OS generated files # 30 | ###################### 31 | .DS_Store 32 | .DS_Store? 33 | ._* 34 | .Spotlight-V100 35 | .Trashes 36 | ehthumbs.db 37 | Thumbs.db 38 | 39 | # Xcode 40 | # 41 | #build/ 42 | *.pbxuser 43 | !default.pbxuser 44 | *.mode1v3 45 | !default.mode1v3 46 | *.mode2v3 47 | !default.mode2v3 48 | *.perspectivev3 49 | !default.perspectivev3 50 | xcuserdata 51 | *.xccheckout 52 | *.moved-aside 53 | DerivedData 54 | *.hmap 55 | *.xcuserstate 56 | 57 | # CocoaPods 58 | # 59 | # We recommend against adding the Pods directory to your .gitignore. However 60 | # you should judge for yourself, the pros and cons are mentioned at: 61 | # http://guides.cocoapods.org/using/using-cocoapods.html#should-i-ignore-the-pods-directory-in-source-control 62 | # 63 | Pods/ 64 | 65 | # Nodejs 66 | # 67 | 68 | # Logs 69 | logs 70 | *.log 71 | npm-debug.log* 72 | 73 | # Runtime data 74 | pids 75 | *.pid 76 | *.seed 77 | 78 | # Directory for instrumented libs generated by jscoverage/JSCover 79 | lib-cov 80 | 81 | # Coverage directory used by tools like istanbul 82 | coverage 83 | 84 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 85 | .grunt 86 | 87 | # node-waf configuration 88 | .lock-wscript 89 | 90 | # Compiled binary addons (http://nodejs.org/api/addons.html) 91 | #build/Release 92 | 93 | # Dependency directory 94 | # https://docs.npmjs.com/misc/faq#should-i-check-my-node-modules-folder-into-git 95 | node_modules 96 | 97 | 98 | # tree 99 | tree_out 100 | 101 | # Android 102 | android/.gradle/ 103 | android/.idea/ 104 | android/app/build 105 | android/app.iml 106 | android/gradle 107 | android/build 108 | android/*/build 109 | local.properties 110 | *.keystore 111 | 112 | 113 | # BUCK 114 | buck-out/ 115 | \.buckd/ 116 | android/app/libs 117 | 118 | # iOS 119 | ios/main.jsbundle 120 | ios/main.jsbundle.meta -------------------------------------------------------------------------------- /js/reducers/notifications.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2016 Facebook, Inc. 3 | * 4 | * You are hereby granted a non-exclusive, worldwide, royalty-free license to 5 | * use, copy, modify, and distribute this software in source code or binary 6 | * form for use in connection with the web services and APIs provided by 7 | * Facebook. 8 | * 9 | * As with any software that integrates with the Facebook platform, your use 10 | * of this software is subject to the Facebook Developer Principles and 11 | * Policies [http://developers.facebook.com/policy/]. This copyright notice 12 | * shall be included in all copies or substantial portions of the software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 17 | * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 19 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 20 | * DEALINGS IN THE SOFTWARE 21 | * 22 | * @flow 23 | */ 24 | 25 | 'use strict'; 26 | 27 | var Platform = require('Platform'); 28 | 29 | const initialState = { 30 | enabled: Platform.OS === 'ios' ? null : true, 31 | registered: false, 32 | disabled: false, 33 | server: [] 34 | }; 35 | 36 | export const notifications = (state = initialState, action) => { 37 | switch (action.type) { 38 | case 'LOADED_NOTIFICATIONS': 39 | let list = action.list.map(fromParseObject); 40 | return {...state, server: list}; 41 | 42 | case 'TURNED_ON_PUSH_NOTIFICATIONS': 43 | return { ...state, enabled: true }; 44 | 45 | case 'SKIPPED_PUSH_NOTIFICATIONS': 46 | return { ...state, enabled: false }; 47 | 48 | case 'REGISTERED_PUSH_NOTIFICATIONS': 49 | return { ...state, registered: true }; 50 | 51 | case 'RESET_NUXES': 52 | return { ...state, enabled: initialState.enabled }; 53 | 54 | case 'CHECK_PUSH_NOTIFICATIONS': 55 | return { ...state, disabled: action.disabled } 56 | 57 | default: 58 | return state; 59 | } 60 | } 61 | 62 | const fromParseObject = object => { 63 | return { 64 | id: object.id, 65 | text: object.get('text'), 66 | url: object.get('url'), 67 | time: object.createdAt.getTime(), 68 | } 69 | } -------------------------------------------------------------------------------- /js/common/IconCell.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | import React, { 4 | Component, 5 | PropTypes, 6 | } from 'react' 7 | import { 8 | StyleSheet, 9 | View, 10 | Text, 11 | TouchableOpacity, 12 | } from 'react-native' 13 | import Color from '../constants/Colors' 14 | import Icon from 'react-native-vector-icons/Ionicons' 15 | import CStyles from '../constants/Styles' 16 | 17 | class IconCell extends Component { 18 | render() { 19 | const { text, leftIcon, rightIcon, onPressRightIcon, onPress } = this.props 20 | let rightButton 21 | if (rightIcon) { 22 | rightButton = ( 23 | 26 | 30 | 31 | ) 32 | } 33 | return ( 34 | 35 | 38 | 42 | 43 | {text} 44 | 45 | {rightButton} 46 | 47 | 48 | 49 | ) 50 | } 51 | } 52 | 53 | IconCell.propTypes = { 54 | text: PropTypes.string.isRequired, 55 | leftIcon: PropTypes.string, 56 | rightIcon: PropTypes.string, 57 | onPressRightIcon: PropTypes.func, 58 | onPress: PropTypes.func 59 | } 60 | IconCell.defaultProps = { 61 | text: '使用 Icon cell', 62 | leftIcon: 'ios-time-outline', 63 | } 64 | 65 | module.exports.__cards__ = (define) => { 66 | define('normal', _ => ) 67 | define('right', _ => ) 68 | } 69 | 70 | export default IconCell 71 | 72 | const styles = StyleSheet.create({ 73 | container: { 74 | flexDirection: 'row', 75 | alignItems: 'center', 76 | height: 40, 77 | backgroundColor: 'white' 78 | }, 79 | text: { 80 | fontSize: 15, 81 | color: Color.black 82 | }, 83 | rightIconButton: { 84 | width: 40, 85 | height: 40, 86 | position: 'absolute', 87 | right: 0, 88 | top: 0, 89 | flexDirection: 'row', 90 | alignItems: 'center', 91 | justifyContent: 'center' 92 | } 93 | }) -------------------------------------------------------------------------------- /ios/Classes/DXTopMessageManager.m: -------------------------------------------------------------------------------- 1 | // 2 | // DXTopMessageManager.m 3 | // RN_CNNode 4 | // 5 | // Created by xiekw on 15/10/21. 6 | // Copyright © 2015年 Facebook. All rights reserved. 7 | // 8 | 9 | #import "DXTopMessageManager.h" 10 | #import "RCTBridge.h" 11 | #import "RCTConvert.h" 12 | #import "RCTScrollView.h" 13 | #import "RCTUIManager.h" 14 | #import "RCTEventDispatcher.h" 15 | #import "UIView+react.h" 16 | #import "UIView+TopBarMessage.h" 17 | 18 | @implementation DXTopMessageManager 19 | 20 | @synthesize bridge = _bridge; 21 | 22 | - (dispatch_queue_t)methodQueue { 23 | return _bridge.uiManager.methodQueue; 24 | } 25 | 26 | RCT_EXPORT_MODULE(); 27 | 28 | /** 29 | * Show top message 30 | * 31 | * @param config { 32 | * background: '#111111', 33 | * textColor: '#111111', 34 | * font: {'fontFamily': 'hev', 'fontSize': 12, 'fontWeight': 11, 'fontStyle': 'bold'}, 35 | * icon: 'imagename', 36 | * offset: 64, 37 | * } 38 | * 39 | */ 40 | 41 | RCT_EXPORT_METHOD(showTopMessage:(nonnull NSNumber *)reactTag message:(nonnull NSString *)message config:(NSDictionary *)config callback:(RCTResponseSenderBlock)callback) { 42 | [self.bridge.uiManager addUIBlock:^(RCTUIManager *uiManager, NSDictionary *viewRegistry) { 43 | dispatch_async(dispatch_get_main_queue(), ^{ 44 | UIView *view = viewRegistry[reactTag]; 45 | if (!view) { 46 | NSLog(@"Cannot find view with tag #%@", reactTag); 47 | return; 48 | } 49 | 50 | [view showTopMessage:message topBarConfig:[self mapTopBarConfig:config] dismissDelay:3.0 withTapBlock:^{ 51 | [_bridge.eventDispatcher sendDeviceEventWithName:@"messageTapped" body:reactTag]; 52 | }]; 53 | 54 | callback(@[[NSNull null], reactTag]); 55 | }); 56 | }]; 57 | 58 | } 59 | 60 | - (NSDictionary *)mapTopBarConfig:(NSDictionary *)jsConfig { 61 | NSMutableDictionary *mdic = [NSMutableDictionary dictionaryWithCapacity:jsConfig.count]; 62 | id backColor = jsConfig[@"background"]; 63 | if (backColor) { 64 | mdic[kDXTopBarBackgroundColor] = [RCTConvert UIColor:backColor]; 65 | } 66 | 67 | id textColor = jsConfig[@"textColor"]; 68 | if (textColor) { 69 | mdic[kDXTopBarTextColor] = [RCTConvert UIColor:textColor]; 70 | } 71 | 72 | id font = jsConfig[@"font"]; 73 | if (font) { 74 | } 75 | 76 | id offset = jsConfig[@"offset"]; 77 | mdic[kDXTopBarOffset] = @([offset floatValue]); 78 | 79 | return mdic; 80 | } 81 | 82 | @end 83 | -------------------------------------------------------------------------------- /ios/Classes/DXFileManager.h: -------------------------------------------------------------------------------- 1 | // 2 | // DXFileManager.h 3 | // DXFileManager 4 | // 5 | // Created by xiekw on 15/2/10. 6 | // Copyright (c) 2015年 xiekw. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface DXFileManager : NSObject 12 | 13 | /*usual paths*/ 14 | 15 | + (NSString *)cachesDirectory; 16 | 17 | + (NSString *)documentsDirectory; 18 | 19 | + (NSString *)libraryDirectory; 20 | 21 | + (NSString *)mainBundleDirectory; 22 | 23 | + (NSString *)temporaryDirectory; 24 | 25 | /*create, delete, move, copy*/ 26 | 27 | //create directory 28 | +(BOOL)createDirectoriesForPath:(NSString *)path error:(NSError **)error; 29 | //write file, it will overide the file exist, it the path is the same 30 | +(BOOL)writeFileAtPath:(NSString *)path content:(NSObject *)content; 31 | 32 | //delete directory or file 33 | +(BOOL)removeItemAtPath:(NSString *)path error:(NSError **)error; 34 | //delete file 35 | +(BOOL)removeFilesInDirectoryAtPath:(NSString *)path withExtension:(NSString *)extension; 36 | //The predicate is for the array in this path. 37 | +(BOOL)removeFilesInDirectoryAtPath:(NSString *)path withPredicate:(NSPredicate *)predicate; 38 | 39 | //Move, if the Item is a file and Cover == YES, it will remove the origin and use the new one. 40 | //If the item is a directory, cover makes none sense 41 | +(BOOL)moveItemAtPath:(NSString *)path toPath:(NSString *)toPath ifCover:(BOOL)cover error:(NSError **)error; 42 | 43 | //Use the same rule as moveItem API 44 | + (BOOL)renameItemAtPath:(NSString *)path withName:(NSString *)name error:(NSError **)error; 45 | 46 | //copy 47 | +(BOOL)copyItemAtPath:(NSString *)path toPath:(NSString *)toPath ifCover:(BOOL)cover error:(NSError **)error; 48 | 49 | /*calculate size*/ 50 | 51 | //the size of item at path, if the path is directory, it will recursively calculate the size 52 | + (uint64_t)totalSizeOfItemAtPath:(NSString *)path recursively:(BOOL)recursive; 53 | 54 | /*listing the complete paths for the path, the preicate items is the whole path not the subpath, so maybe you need to use [path pathExtension] to get last extension*/ 55 | + (NSArray *)listingPathsAtPath:(NSString *)path recursively:(BOOL)recursive withPredicate:(NSPredicate *)predicate; 56 | 57 | + (void)getDiskUsage:(void (^)(uint64_t freeSpace, uint64_t totalSpace, uint64_t myAppUsed))block; 58 | 59 | + (dispatch_source_t)monitorFileChangesInDirectory:(NSString *)dirPath changeHandler:(dispatch_block_t)handler; 60 | 61 | + (NSString *)mimeTypeForFileExtension:(NSString *)ext; 62 | 63 | 64 | @end 65 | -------------------------------------------------------------------------------- /js/actions/notifications.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const Platform = require('Platform') 4 | const VibrationIOS = require('VibrationIOS') 5 | const { updateInstallation } = require('./installation') 6 | const PushNotification = require('react-native-push-notification'); 7 | 8 | function normalizeData(s) { 9 | if (s && typeof s === 'object') { 10 | return s 11 | } 12 | try { 13 | return JSON.parse(s) 14 | } catch (e) { 15 | return {} 16 | } 17 | } 18 | 19 | async function storeDeviceToken(deviceToken) { 20 | const pushType = Platform.OS === 'android' ? 'gcm' : undefined 21 | await updateInstallation({ 22 | pushType, 23 | deviceToken, 24 | deviceTokenLastModified: Date.now(), 25 | }) 26 | return { 27 | type: 'REGISTERED_PUSH_NOTIFICATIONS', 28 | } 29 | } 30 | 31 | function checkPushNotifications() { 32 | return dispatch => { 33 | PushNotification.checkPermissions(permission => { 34 | const disabled = permission.alert < 1 35 | dispatch({ 36 | type: 'CHECK_PUSH_NOTIFICATIONS', 37 | disabled 38 | }) 39 | }) 40 | } 41 | } 42 | 43 | function turnOnPushNotifications() { 44 | return { 45 | type: 'TURNED_ON_PUSH_NOTIFICATIONS', 46 | } 47 | } 48 | 49 | function skipPushNotifications() { 50 | return { 51 | type: 'SKIPPED_PUSH_NOTIFICATIONS', 52 | } 53 | } 54 | 55 | function receivePushNotification(notification) { 56 | return (dispatch) => { 57 | let { foreground, message } = notification 58 | const data = normalizeData(notification.data) 59 | 60 | message = message.split(',')[0] 61 | 62 | if (!foreground) { 63 | dispatch(showNotificationView(message)) 64 | } 65 | 66 | if (foreground) { 67 | dispatch(showNotificationView(message)) 68 | 69 | 70 | if (Platform.OS === 'ios') { 71 | VibrationIOS.vibrate() 72 | } 73 | } 74 | 75 | if (data.e /* ephemeral */) { 76 | return 77 | } 78 | 79 | const timestamp = new Date().getTime() 80 | dispatch({ 81 | type: 'RECEIVED_PUSH_NOTIFICATION', 82 | notification: { 83 | text: message, 84 | url: data.url, 85 | time: timestamp, 86 | }, 87 | }) 88 | } 89 | } 90 | 91 | function markAllNotificationsAsSeen() { 92 | return { 93 | type: 'SEEN_ALL_NOTIFICATIONS', 94 | } 95 | } 96 | 97 | module.exports = { 98 | turnOnPushNotifications, 99 | storeDeviceToken, 100 | skipPushNotifications, 101 | receivePushNotification, 102 | markAllNotificationsAsSeen, 103 | checkPushNotifications 104 | } 105 | -------------------------------------------------------------------------------- /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 | # okhttp 54 | 55 | -keepattributes Signature 56 | -keepattributes *Annotation* 57 | -keep class okhttp3.** { *; } 58 | -keep interface okhttp3.** { *; } 59 | -dontwarn okhttp3.** 60 | 61 | # okio 62 | 63 | -keep class sun.misc.Unsafe { *; } 64 | -dontwarn java.nio.file.* 65 | -dontwarn org.codehaus.mojo.animal_sniffer.IgnoreJRERequirement 66 | -dontwarn okio.** 67 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /js/pages/SearchHistory.js: -------------------------------------------------------------------------------- 1 | import React, { 2 | Component, 3 | PropTypes, 4 | } from 'react' 5 | import { 6 | StyleSheet, 7 | View, 8 | Text, 9 | TouchableOpacity, 10 | } from 'react-native' 11 | import Color from '../constants/Colors' 12 | import { connect } from 'react-redux' 13 | import { 14 | searchSearchHistory, 15 | removeSearchHistory, 16 | startSearch 17 | } from '../actions/search' 18 | import IconCell from '../common/IconCell' 19 | 20 | class SearchHistory extends Component { 21 | componentDidMount() { 22 | this.props.getSearchHistory() 23 | } 24 | 25 | render() { 26 | let { history, onRemoveSearchHistory, startSearch } = this.props 27 | 28 | let clearAll 29 | if (history && history.length) { 30 | history = history.map((his, i) => { 31 | return ( 32 | startSearch(his)} 38 | /> 39 | ) 40 | }) 41 | 42 | clearAll = ( 43 | 46 | clear all history 47 | 48 | ) 49 | } 50 | 51 | return ( 52 | 53 | {history} 54 | {clearAll} 55 | 56 | ) 57 | } 58 | } 59 | 60 | SearchHistory.propTypes = { 61 | history: PropTypes.array, 62 | onRemoveSearchHistory: PropTypes.func, 63 | getSearchHistory: PropTypes.func, 64 | startSearch: PropTypes.func 65 | } 66 | SearchHistory.defaultProps = { 67 | history: ['金馆长', '教皇', '丘比龙'] 68 | } 69 | 70 | const mapStateToProps = state => { 71 | return { 72 | history: state.search.history 73 | } 74 | } 75 | 76 | const mapDispatchToProps = dispatch => { 77 | return { 78 | onRemoveSearchHistory: his => dispatch(removeSearchHistory(his)), 79 | getSearchHistory: _ => dispatch(searchSearchHistory()), 80 | startSearch: text => dispatch(startSearch(text, 'HISTORY')) 81 | } 82 | } 83 | 84 | export default connect(mapStateToProps, mapDispatchToProps)(SearchHistory) 85 | module.exports.__cards__ = (define) => { 86 | define('normal', _ => ) 87 | } 88 | 89 | const styles = StyleSheet.create({ 90 | container: { 91 | flexDirection: 'column', 92 | }, 93 | removeAllHis: { 94 | flexDirection: 'row', 95 | justifyContent: 'center', 96 | alignItems: 'center', 97 | padding: 10, 98 | }, 99 | clearAll: { 100 | color: Color.green 101 | } 102 | }) -------------------------------------------------------------------------------- /js/services/Storage.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by xiekaiwei on 16/8/24. 3 | */ 4 | 5 | import { AsyncStorage } from 'react-native' 6 | import Parse from 'parse/react-native' 7 | 8 | const CURRENT_USER_KEY = 'CURRENT_USER_KEY_GITFEED' 9 | const SEARCH_HISTORY_KEY = 'SEARCH_HISTORY_KEY' 10 | 11 | export const saveUser = async user => { 12 | await AsyncStorage.setItem(CURRENT_USER_KEY, JSON.stringify(user)) 13 | return user 14 | } 15 | 16 | export const getCurrentUser = async() => { 17 | const userString = await AsyncStorage.getItem(CURRENT_USER_KEY) 18 | return JSON.parse(userString) 19 | } 20 | 21 | export const getSearchHistory = async() => { 22 | return await _getAllObjects(SEARCH_HISTORY_KEY, obj => !!obj && obj.length > 0, 4) 23 | } 24 | 25 | export const insertSearchRecord = async(item) => { 26 | if (!item || !item.length) return 27 | return await _insertObject(SEARCH_HISTORY_KEY, item, item => item, true) 28 | } 29 | 30 | export const removeSearchRecord = async(item) => { 31 | if (item) { 32 | return await _removeObject(SEARCH_HISTORY_KEY, item, item => item) 33 | } else { 34 | return await AsyncStorage.multiRemove([SEARCH_HISTORY_KEY]) 35 | } 36 | } 37 | 38 | const _getAllKeys = async(key) => { 39 | const allKeys = await AsyncStorage.getItem(key) 40 | return JSON.parse(allKeys) || [] 41 | } 42 | 43 | const _getAllObjects = async(key, prd = obj => !!obj, maxCount = 1000) => { 44 | const allKeys = await _getAllKeys(key) 45 | if (!allKeys) return null 46 | 47 | let allObjs = await AsyncStorage.multiGet(allKeys) 48 | if (!allObjs) return null 49 | 50 | 51 | allObjs = allObjs 52 | .map(kv => JSON.parse(kv[1])) 53 | .reduce((a, b) => a.concat(b), []) 54 | .filter(prd) 55 | if (allKeys.length > maxCount) { 56 | const nextAllKeys = allKeys.slice(0, maxCount) 57 | await AsyncStorage.setItem(key, JSON.stringify(nextAllKeys)) 58 | } 59 | 60 | return allObjs 61 | } 62 | 63 | const _insertObject = async(key, obj, objAsKey = obj => obj.title, distinct = false) => { 64 | const objKey = objAsKey(obj) 65 | if (!objKey) throw new Error('Storage insert this obj does not have key!') 66 | 67 | let allKeys = await _getAllKeys(key) 68 | if (distinct) allKeys = allKeys.filter(k => objKey != k) 69 | 70 | allKeys.unshift(objKey) 71 | 72 | await AsyncStorage.setItem(key, JSON.stringify(allKeys)) 73 | await AsyncStorage.setItem(objKey, JSON.stringify(obj)) 74 | 75 | return obj 76 | } 77 | 78 | const _removeObject = async(key, obj, objAsKey = obj => obj.title) => { 79 | const objKey = objAsKey(obj) 80 | if (!objKey) throw new Error('Storage remove this obj does not have key!') 81 | 82 | let allKeys = await _getAllKeys(key) 83 | allKeys = allKeys.filter(k => k != objKey) 84 | 85 | await AsyncStorage.setItem(key, JSON.stringify(allKeys)) 86 | await AsyncStorage.removeItem(objKey) 87 | 88 | return obj 89 | } -------------------------------------------------------------------------------- /js/common/TabBarAndroid.js: -------------------------------------------------------------------------------- 1 | import React, { 2 | Component, 3 | PropTypes, 4 | } from 'react' 5 | import Icon from 'react-native-vector-icons/Ionicons' 6 | import { 7 | StyleSheet, 8 | Text, 9 | View, 10 | TouchableOpacity, 11 | } from 'react-native' 12 | import Colors from './Colors' 13 | 14 | var FacebookTabBar = React.createClass({ 15 | selectedTabIcons: [], 16 | unselectedTabIcons: [], 17 | 18 | propTypes: { 19 | goToPage: React.PropTypes.func, 20 | activeTab: React.PropTypes.number, 21 | tabs: React.PropTypes.array 22 | }, 23 | 24 | /* 25 | { 26 | pageId: 'HomePage', 27 | name: '流行', 28 | iconSelected: 'ios-flame', 29 | iconUnSelected: 'ios-flame-outline', 30 | }, 31 | */ 32 | renderTabOption(tabItem, page) { 33 | const isTabActive = this.props.activeTab === page 34 | const color = isTabActive ? Colors.green : Colors.black 35 | const { name, iconSelected, iconUnSelected } = tabItem 36 | const icon = isTabActive ? iconSelected : iconUnSelected 37 | 38 | return ( 39 | this.props.goToPage(page)} 42 | style={styles.tab}> 43 | { this.selectedTabIcons[page] = icon }} 45 | style={styles.tabItem} 46 | > 47 | 48 | 49 | {name} 50 | 51 | 52 | 53 | ) 54 | }, 55 | 56 | setAnimationValue({ value }) { 57 | var currentPage = this.props.activeTab 58 | 59 | this.unselectedTabIcons.forEach((icon, i) => { 60 | var iconRef = icon 61 | 62 | if (!icon.setNativeProps && icon !== null) { 63 | iconRef = icon.refs.icon_image 64 | } 65 | 66 | if (value - i >= 0 && value - i <= 1) { 67 | iconRef.setNativeProps({ opacity: value - i }) 68 | } 69 | if (i - value >= 0 && i - value <= 1) { 70 | iconRef.setNativeProps({ opacity: i - value }) 71 | } 72 | }) 73 | }, 74 | 75 | render() { 76 | return ( 77 | 78 | 79 | {this.props.tabs.map((tab, i) => this.renderTabOption(tab, i))} 80 | 81 | 82 | ) 83 | }, 84 | }) 85 | 86 | const styles = StyleSheet.create({ 87 | tab: { 88 | flex: 1, 89 | alignItems: 'center', 90 | justifyContent: 'center', 91 | }, 92 | tabItem: { 93 | flexDirection: 'column', 94 | alignItems: 'center', 95 | }, 96 | tabs: { 97 | height: 49, 98 | flexDirection: 'row', 99 | paddingTop: 5, 100 | borderTopWidth: 0.5, 101 | borderTopColor: Colors.gray, 102 | }, 103 | }) 104 | 105 | export default FacebookTabBar -------------------------------------------------------------------------------- /js/common/SettingsCell.js: -------------------------------------------------------------------------------- 1 | import React, { 2 | Component, 3 | PropTypes, 4 | } from 'react' 5 | import { 6 | View, 7 | Text, 8 | StyleSheet, 9 | TouchableHighlight 10 | } from 'react-native' 11 | import Icon from 'react-native-vector-icons/Ionicons' 12 | import Touchable from '../common/F8Touchable' 13 | 14 | const SettingsCell = props => { 15 | let iconComponent 16 | if (props.iconName) { 17 | iconComponent = 22 | } 23 | 24 | let arrow 25 | if (props.hasArrow) { 26 | arrow = 31 | } 32 | 33 | let rightText 34 | if (props.rightText) { 35 | rightText = 41 | {props.rightText} 42 | 43 | } 44 | 45 | return ( 46 | 49 | 50 | {iconComponent} 51 | 52 | 53 | {props.settingName} 54 | 55 | 56 | 57 | {rightText} 58 | {arrow} 59 | 60 | 61 | 62 | ) 63 | } 64 | 65 | SettingsCell.propTypes = { 66 | onPress: PropTypes.func, 67 | iconName: PropTypes.string, 68 | iconColor: PropTypes.string, 69 | settingName: PropTypes.string, 70 | hasArrow: PropTypes.bool, 71 | rightText: PropTypes.string, 72 | } 73 | SettingsCell.defaultProps = { 74 | iconColor: '#00bfff', 75 | settingName: 'Settings', 76 | onPress: _ => {}, 77 | hasArrow: true, 78 | rightText: '' 79 | } 80 | 81 | export default SettingsCell 82 | 83 | const ICON_SIZE = 30 84 | const styles = StyleSheet.create({ 85 | userTouch: { 86 | marginTop: 20 87 | }, 88 | user: { 89 | padding: 8, 90 | paddingLeft: 10, 91 | paddingRight: 10, 92 | backgroundColor: 'white', 93 | flexDirection: 'row', 94 | alignItems: 'center', 95 | borderTopWidth: 1, 96 | borderBottomWidth: 1, 97 | borderColor: '#EDECF1' 98 | }, 99 | nameInfo: { 100 | flexDirection: 'column', 101 | marginLeft: 0, 102 | justifyContent: 'center', 103 | flex: 1 104 | }, 105 | name: { 106 | color: 'black', 107 | fontSize: 17 108 | }, 109 | right: { 110 | flexDirection: 'row', 111 | marginRight: 10 112 | }, 113 | arrow: { 114 | width: ICON_SIZE, 115 | height: ICON_SIZE, 116 | }, 117 | settings: { 118 | height: 44 119 | } 120 | }) -------------------------------------------------------------------------------- /js/pages/IssueDetailPage.js: -------------------------------------------------------------------------------- 1 | import React, { 2 | Component, 3 | PropTypes, 4 | } from 'react' 5 | import { 6 | StyleSheet, 7 | View, 8 | TouchableOpacity, 9 | Text, 10 | Image, 11 | ActionSheetIOS, 12 | } from 'react-native' 13 | import defaultHtml from '../common/markdown/issue-html' 14 | import WebView from '../common/WebView' 15 | import Layout from '../constants/Layout' 16 | import { LoadingPlaceholder, ErrorPlaceholder } from '../common/Placeholder' 17 | 18 | class IssueDetailPage extends Component { 19 | static route = { 20 | navigationBar: { 21 | title(params) { 22 | if (params.repo) return `${params.repo.title}` 23 | }, 24 | }, 25 | } 26 | 27 | state = { 28 | loading: true, 29 | error: null, 30 | source: { html: '', baseUrl: 'about:blank', } 31 | } 32 | 33 | _issueHtml(originHtml) { 34 | const start = '
' 35 | const end = '
' 36 | const article = originHtml.slice(originHtml.indexOf(start), 37 | originHtml.indexOf(end)) 38 | 39 | return defaultHtml.replace('$body', article) 40 | } 41 | 42 | async _renderHtml(html_url) { 43 | if (html_url) { 44 | this.setState({ loading: true, error: null }) 45 | 46 | try { 47 | let html = await fetch(html_url, { 48 | headers: { 49 | 'User-Agent': 'Mozilla/5.0 (iPhone; CPU iPhone OS 8_0 like Mac OS X) AppleWebKit/600.1.3 (KHTML, like Gecko) Version/8.0 Mobile/12A4345d Safari/600.1.4 AliApp(JU' 50 | } 51 | }) 52 | if (html.status > 200) { 53 | throw new Error(`Issue not found ${html_url}`) 54 | } else { 55 | html = await html.text() 56 | } 57 | 58 | this.setState({ 59 | source: { 60 | html: this._issueHtml(html), 61 | baseUrl: 'about:blank', 62 | }, 63 | loading: false 64 | }) 65 | } catch (error) { 66 | this.setState({ 67 | loading: false, 68 | error 69 | }) 70 | } 71 | } 72 | } 73 | 74 | async componentDidMount() { 75 | const { repo } = this.props.route.params 76 | await this._renderHtml(repo.html_url) 77 | } 78 | 79 | render() { 80 | const { loading, error } = this.state 81 | const { repo } = this.props.route.params 82 | 83 | if (loading) { 84 | return 85 | } 86 | 87 | if (error) { 88 | return this._renderHtml(repo.name)} 92 | /> 93 | } 94 | 95 | return ( 96 | 100 | ) 101 | } 102 | } 103 | 104 | IssueDetailPage.propTypes = { 105 | route: PropTypes.object 106 | } 107 | IssueDetailPage.defaultProps = { 108 | route: { params: {} } 109 | } 110 | 111 | export default IssueDetailPage 112 | 113 | -------------------------------------------------------------------------------- /js/common/Placeholder.js: -------------------------------------------------------------------------------- 1 | import React, { PropTypes, Component } from 'react' 2 | import { 3 | StyleSheet, 4 | View, 5 | TouchableOpacity, 6 | ActivityIndicator 7 | } from 'react-native' 8 | import Colors from '../constants/Colors' 9 | import { Paragraph, NormalText, Heading2, Text } from './F8Text' 10 | 11 | export const ErrorPlaceholder = ({ title, desc, onPress, buttonText }) => { 12 | return ( 13 | 14 | 15 | {title} 16 | 17 | 18 | {desc} 19 | 20 | 21 | 22 | {buttonText} 23 | 24 | 25 | 26 | ) 27 | } 28 | 29 | ErrorPlaceholder.propTypes = { 30 | title: PropTypes.string, 31 | desc: PropTypes.string, 32 | buttonText: PropTypes.string, 33 | onPress: PropTypes.func 34 | } 35 | ErrorPlaceholder.defaultProps = { 36 | title: 'Oops', 37 | desc: 'some thing wrong...', 38 | onPress: _ => {}, 39 | buttonText: 'Reload' 40 | } 41 | 42 | export const LoadingPlaceholder = ({ title, big }) => { 43 | if (big) { 44 | return 45 | 46 | 47 | } else { 48 | return 49 | 50 | 51 | {title} 52 | 53 | 54 | } 55 | } 56 | 57 | LoadingPlaceholder.propTypes = { 58 | title: PropTypes.string, 59 | big: PropTypes.bool 60 | } 61 | LoadingPlaceholder.defaultProps = { 62 | title: 'Loading...', 63 | big: false 64 | } 65 | 66 | export const EmptyPlaceholder = props => { 67 | const image = props.image && 68 | 69 | const title = props.title && 70 | {props.title} 71 | 72 | return ( 73 | 74 | {image} 75 | {title} 76 | 77 | {text} 78 | 79 | {props.children} 80 | 81 | ) 82 | } 83 | 84 | const styles = StyleSheet.create({ 85 | container: { 86 | flex: 1, 87 | justifyContent: 'center', 88 | alignItems: 'center', 89 | backgroundColor: 'white', 90 | }, 91 | reloadText: { 92 | borderColor: Colors.textGray, 93 | borderWidth: 1, 94 | borderRadius: 3, 95 | marginTop: 20, 96 | padding: 5, 97 | paddingLeft: 10, 98 | paddingRight: 10 99 | }, 100 | 101 | loadingText: { 102 | marginTop: 5, 103 | color: Colors.textGray 104 | }, 105 | title: { 106 | textAlign: 'center', 107 | marginBottom: 10, 108 | }, 109 | image: { 110 | marginBottom: 10, 111 | }, 112 | text: { 113 | textAlign: 'center', 114 | marginBottom: 35, 115 | }, 116 | desc: { 117 | padding: 20, 118 | textAlign: 'center', 119 | } 120 | }) 121 | -------------------------------------------------------------------------------- /js/common/F8Text.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2016 Facebook, Inc. 3 | * 4 | * You are hereby granted a non-exclusive, worldwide, royalty-free license to 5 | * use, copy, modify, and distribute this software in source code or binary 6 | * form for use in connection with the web services and APIs provided by 7 | * Facebook. 8 | * 9 | * As with any software that integrates with the Facebook platform, your use 10 | * of this software is subject to the Facebook Developer Principles and 11 | * Policies [http://developers.facebook.com/policy/]. This copyright notice 12 | * shall be included in all copies or substantial portions of the software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 17 | * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 19 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 20 | * DEALINGS IN THE SOFTWARE 21 | * 22 | * @providesModule F8Text 23 | * @flow 24 | */ 25 | 26 | 'use strict'; 27 | 28 | import React from 'react' 29 | import ReactNative, { StyleSheet, Dimensions } from 'react-native' 30 | import Colors from '../constants/Colors' 31 | 32 | export function Text({ style, ...props }: Object):ReactElement { 33 | return ; 34 | } 35 | 36 | export function Heading1({ style, ...props }: Object):ReactElement { 37 | return ; 39 | } 40 | 41 | export function Heading2({ style, ...props }: Object):ReactElement { 42 | return ; 44 | } 45 | 46 | export function Paragraph({ style, ...props }: Object):ReactElement { 47 | return ; 48 | } 49 | 50 | export function LinkText({ style, ...props }: Object):ReactElement { 51 | return ; 53 | } 54 | 55 | export function NormalText({ style, ...props }: Object):ReactElement { 56 | return ; 58 | } 59 | 60 | 61 | const scale = Dimensions.get('window').width / 375; 62 | 63 | function normalize(size:number):number { 64 | return Math.round(scale * size); 65 | } 66 | 67 | const styles = StyleSheet.create({ 68 | font: { 69 | fontFamily: require('../env').fontFamily, 70 | }, 71 | h1: { 72 | fontSize: normalize(24), 73 | lineHeight: normalize(27), 74 | color: Colors.textDark, 75 | fontWeight: 'bold', 76 | letterSpacing: -1, 77 | }, 78 | h2: { 79 | fontSize: normalize(15), 80 | lineHeight: normalize(20), 81 | color: Colors.textDark, 82 | fontWeight: 'bold', 83 | }, 84 | p: { 85 | fontSize: normalize(15), 86 | lineHeight: normalize(23), 87 | color: Colors.textLight, 88 | }, 89 | normal: { 90 | fontSize: normalize(14), 91 | fontWeight: 'normal', 92 | color: Colors.textDark, 93 | } 94 | }); 95 | -------------------------------------------------------------------------------- /js/navigation/RootNavigation.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { 3 | StyleSheet, 4 | Text, 5 | View, 6 | Image 7 | } from 'react-native' 8 | import { 9 | StackNavigation, 10 | TabNavigation, 11 | TabNavigationItem, 12 | } from '@exponent/ex-navigation' 13 | import Icon from 'react-native-vector-icons/Ionicons' 14 | import Colors from '../constants/Colors' 15 | import Layout from '../constants/Layout' 16 | import Router from './Router' 17 | 18 | const defaultRouteConfig = { 19 | navigationBar: { 20 | translucent: true, 21 | translucentTint: 'light', 22 | tintColor: Colors.tintColor 23 | }, 24 | } 25 | 26 | export default class TabNavigationLayout extends React.Component { 27 | render() { 28 | return ( 29 | 33 | 34 | this._renderIcon('Feeds', 'ios-list-box', isSelected)}> 37 | 41 | 42 | 43 | this._renderIcon('Trends', 'ios-flame', isSelected)}> 46 | 49 | 50 | 51 | this._renderIcon('Famous', 'ios-people', isSelected)}> 54 | 58 | 59 | 60 | this._renderIcon('Me', 'ios-person', isSelected)}> 63 | 67 | 68 | 69 | ) 70 | } 71 | 72 | _renderIcon(title:string, iconName:string, isSelected:bool):ReactElement { 73 | const color = isSelected ? Colors.tabIconSelected : Colors.tabIconDefault 74 | const iName = isSelected ? iconName : `${iconName}-outline` 75 | return ( 76 | 77 | 78 | 79 | 80 | {title} 81 | 82 | 83 | ) 84 | } 85 | } 86 | 87 | const styles = StyleSheet.create({ 88 | container: { 89 | flex: 1, 90 | backgroundColor: '#fff', 91 | }, 92 | tabItemContainer: { 93 | alignItems: 'center', 94 | justifyContent: 'center', 95 | }, 96 | tabTitleText: { 97 | fontSize: 11, 98 | }, 99 | image: { 100 | flex: 1, 101 | height: null, 102 | width: null, 103 | resizeMode: 'cover', 104 | }, 105 | 106 | }) -------------------------------------------------------------------------------- /js/common/DefaultTabBar.js: -------------------------------------------------------------------------------- 1 | const React = require('react'); 2 | const ReactNative = require('react-native'); 3 | const { 4 | StyleSheet, 5 | Text, 6 | View, 7 | Animated, 8 | } = ReactNative; 9 | const Button = require('./Button'); 10 | 11 | const DefaultTabBar = React.createClass({ 12 | propTypes: { 13 | goToPage: React.PropTypes.func, 14 | activeTab: React.PropTypes.number, 15 | tabs: React.PropTypes.array, 16 | underlineColor: React.PropTypes.string, 17 | underlineHeight: React.PropTypes.number, 18 | backgroundColor: React.PropTypes.string, 19 | activeTextColor: React.PropTypes.string, 20 | inactiveTextColor: React.PropTypes.string, 21 | textStyle: Text.propTypes.style, 22 | tabStyle: View.propTypes.style, 23 | renderTabName: React.PropTypes.func, 24 | }, 25 | 26 | getDefaultProps() { 27 | return { 28 | activeTextColor: 'navy', 29 | inactiveTextColor: 'black', 30 | underlineColor: 'navy', 31 | backgroundColor: null, 32 | underlineHeight: 4, 33 | renderTabName: this.renderTabName, 34 | }; 35 | }, 36 | 37 | renderTabOption(name, page) { 38 | const isTabActive = this.props.activeTab === page; 39 | 40 | return ; 50 | }, 51 | 52 | renderTabName(name, page, isTabActive) { 53 | const { activeTextColor, inactiveTextColor, textStyle, } = this.props; 54 | const textColor = isTabActive ? activeTextColor : inactiveTextColor; 55 | const fontWeight = isTabActive ? 'bold' : 'normal'; 56 | 57 | return 58 | 59 | {name} 60 | 61 | ; 62 | }, 63 | 64 | render() { 65 | const containerWidth = this.props.containerWidth; 66 | const numberOfTabs = this.props.tabs.length; 67 | const tabUnderlineStyle = { 68 | position: 'absolute', 69 | width: containerWidth / numberOfTabs, 70 | height: this.props.underlineHeight, 71 | backgroundColor: this.props.underlineColor, 72 | bottom: 0, 73 | }; 74 | 75 | const left = this.props.scrollValue.interpolate({ 76 | inputRange: [0, 1, ], outputRange: [0, containerWidth / numberOfTabs, ], 77 | }); 78 | 79 | return ( 80 | 81 | {this.props.tabs.map((tab, i) => this.renderTabOption(tab, i))} 82 | 83 | 84 | ); 85 | }, 86 | }); 87 | 88 | const styles = StyleSheet.create({ 89 | tab: { 90 | flex: 1, 91 | alignItems: 'center', 92 | justifyContent: 'center', 93 | paddingBottom: 3, 94 | }, 95 | tabs: { 96 | height: 40, 97 | flexDirection: 'row', 98 | justifyContent: 'space-around', 99 | borderWidth: 1, 100 | borderTopWidth: 0, 101 | borderLeftWidth: 0, 102 | borderRightWidth: 0, 103 | borderBottomColor: '#ccc', 104 | }, 105 | }); 106 | 107 | module.exports = DefaultTabBar; 108 | -------------------------------------------------------------------------------- /js/pages/NotificationCell.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2016 Facebook, Inc. 3 | * 4 | * You are hereby granted a non-exclusive, worldwide, royalty-free license to 5 | * use, copy, modify, and distribute this software in source code or binary 6 | * form for use in connection with the web services and APIs provided by 7 | * Facebook. 8 | * 9 | * As with any software that integrates with the Facebook platform, your use 10 | * of this software is subject to the Facebook Developer Principles and 11 | * Policies [http://developers.facebook.com/policy/]. This copyright notice 12 | * shall be included in all copies or substantial portions of the software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 17 | * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 19 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 20 | * DEALINGS IN THE SOFTWARE 21 | * 22 | * @flow 23 | */ 24 | 'use strict'; 25 | 26 | var F8Colors = require('F8Colors'); 27 | var React = require('React'); 28 | var StyleSheet = require('StyleSheet'); 29 | var { Text } = require('F8Text'); 30 | var TouchableHighlight = require('TouchableHighlight'); 31 | var View = require('View'); 32 | var moment = require('moment'); 33 | 34 | var { connect } = require('react-redux'); 35 | 36 | class NotificationCell extends React.Component { 37 | render() { 38 | var attachment; 39 | if (this.props.notification.url) { 40 | attachment = {this.props.notification.url}; 41 | } 42 | return ( 43 | 44 | 45 | 46 | {this.props.notification.text} 47 | 48 | {attachment} 49 | 50 | 51 | {moment(this.props.notification.time).fromNow()} 52 | 53 | 54 | 55 | 56 | ); 57 | } 58 | } 59 | 60 | var styles = StyleSheet.create({ 61 | cell: { 62 | padding: 15, 63 | backgroundColor: 'white', 64 | }, 65 | unseen: { 66 | paddingLeft: 12, 67 | borderLeftWidth: 3, 68 | borderLeftColor: '#4D99EF', 69 | }, 70 | text: { 71 | fontSize: 15, 72 | lineHeight: 22, 73 | marginBottom: 10, 74 | }, 75 | session: { 76 | paddingVertical: undefined, 77 | paddingHorizontal: undefined, 78 | paddingLeft: undefined, 79 | padding: 10, 80 | marginBottom: 10, 81 | borderWidth: 1, 82 | borderRadius: 4, 83 | borderColor: F8Colors.cellBorder, 84 | // overflow: 'hidden', 85 | shadowOffset: {width: 1, height: 1}, 86 | shadowColor: '#eee', 87 | shadowOpacity: 1, 88 | }, 89 | footer: { 90 | flexDirection: 'row', 91 | }, 92 | url: { 93 | flex: 1, 94 | color: F8Colors.actionText, 95 | fontSize: 12, 96 | marginBottom: 10, 97 | }, 98 | time: { 99 | color: F8Colors.lightText, 100 | fontSize: 12, 101 | }, 102 | }); 103 | 104 | function select(store, props) { 105 | return { 106 | isSeen: store.notifications.seen[props.notification.id], 107 | }; 108 | } 109 | 110 | module.exports = connect(select)(NotificationCell); 111 | -------------------------------------------------------------------------------- /js/common/PushNUXModal.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2016 Facebook, Inc. 3 | * 4 | * You are hereby granted a non-exclusive, worldwide, royalty-free license to 5 | * use, copy, modify, and distribute this software in source code or binary 6 | * form for use in connection with the web services and APIs provided by 7 | * Facebook. 8 | * 9 | * As with any software that integrates with the Facebook platform, your use 10 | * of this software is subject to the Facebook Developer Principles and 11 | * Policies [http://developers.facebook.com/policy/]. This copyright notice 12 | * shall be included in all copies or substantial portions of the software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 17 | * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 19 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 20 | * DEALINGS IN THE SOFTWARE 21 | * 22 | * @flow 23 | */ 24 | 'use strict'; 25 | 26 | var F8Button = require('F8Button'); 27 | var F8Colors = require('F8Colors'); 28 | var Image = require('Image'); 29 | var React = require('React'); 30 | var StyleSheet = require('StyleSheet'); 31 | var View = require('View'); 32 | var { Heading1, Paragraph } = require('F8Text'); 33 | 34 | class PushNUXModal extends React.Component { 35 | props: { 36 | onTurnOnNotifications: () => void; 37 | onSkipNotifications: () => void; 38 | }; 39 | 40 | render() { 41 | return ( 42 | 43 | 44 | 48 | 49 | 50 | 不要错过啦! 51 | 52 | 53 | 偶尔推送, 但每次的套路却深入人心! 54 | 55 | 61 | 67 | 68 | 69 | 70 | ); 71 | } 72 | } 73 | 74 | 75 | var styles = StyleSheet.create({ 76 | container: { 77 | position: 'absolute', 78 | left: 0, 79 | top: 0, 80 | right: 0, 81 | bottom: 49, 82 | backgroundColor: 'rgba(0, 0, 0, 0.66)', 83 | paddingHorizontal: 20, 84 | justifyContent: 'center', 85 | }, 86 | inner: { 87 | overflow: 'hidden', 88 | backgroundColor: 'white', 89 | borderRadius: 2, 90 | }, 91 | image: { 92 | alignSelf: 'center', 93 | }, 94 | content: { 95 | padding: 20, 96 | paddingBottom: 10, 97 | alignItems: 'center', 98 | }, 99 | text: { 100 | textAlign: 'center', 101 | marginVertical: 20, 102 | }, 103 | page: { 104 | borderTopWidth: 1, 105 | borderTopColor: F8Colors.cellBorder, 106 | paddingTop: undefined, 107 | paddingBottom: 0, 108 | }, 109 | button: { 110 | marginTop: 10, 111 | alignSelf: 'stretch', 112 | }, 113 | }); 114 | 115 | module.exports = PushNUXModal; 116 | -------------------------------------------------------------------------------- /js/pages/PersonPage.js: -------------------------------------------------------------------------------- 1 | import React, { 2 | Component, 3 | PropTypes, 4 | } from 'react' 5 | import { 6 | View, 7 | StyleSheet, 8 | ScrollView, 9 | TouchableHighlight, 10 | Image, 11 | TouchableOpacity, 12 | TextInput, 13 | ActivityIndicator 14 | } from 'react-native' 15 | import { connect } from 'react-redux' 16 | import Layout from '../constants/Layout' 17 | import Touchable from '../common/F8Touchable' 18 | import SettingsCell from '../common/SettingsCell' 19 | import Colors from '../constants/Colors' 20 | import { Text } from '../common/F8Text' 21 | import Icon from 'react-native-vector-icons/Ionicons' 22 | import { withNavigation } from '@exponent/ex-navigation' 23 | 24 | @withNavigation 25 | class PersonPage extends Component { 26 | render() { 27 | const { navigator, user } = this.props 28 | const isLogined = !!user.token 29 | const stateText = isLogined ? 'Logined' : 'Better Press to Login' 30 | const stateColor = isLogined ? Colors.green : 'orange' 31 | const avatarURL = user.avatar || 'a' 32 | const pressLogin = isLogined ? _ => {} : _ => navigator.push('Login') 33 | 34 | return ( 35 | 41 | navigator.push('UserDetail', user)}> 44 | 45 | 49 | 50 | 51 | {user.login} 52 | 53 | 54 | 57 | {stateText} 58 | 59 | 64 | 65 | 66 | 67 | 68 | ) 69 | } 70 | } 71 | 72 | PersonPage.propTypes = {} 73 | PersonPage.defaultProps = {} 74 | 75 | const select = ({ login }) => { 76 | return { 77 | user: login.user 78 | } 79 | } 80 | 81 | export default connect(select)(PersonPage) 82 | 83 | var styles = StyleSheet.create({ 84 | container: { 85 | backgroundColor: '#F0EFF5', 86 | flex: 1, 87 | }, 88 | userTouch: { 89 | marginTop: 20, 90 | }, 91 | user: { 92 | padding: 8, 93 | paddingLeft: 10, 94 | paddingRight: 10, 95 | backgroundColor: 'white', 96 | flexDirection: 'row', 97 | alignItems: 'center', 98 | borderTopWidth: 1, 99 | borderBottomWidth: 1, 100 | borderColor: '#EDECF1', 101 | }, 102 | avatar: { 103 | backgroundColor: Colors.imagePlaceholder, 104 | borderRadius: 2, 105 | width: 48, 106 | height: 48, 107 | flexDirection: 'row', 108 | alignItems: 'center', 109 | borderColor: 'gray', 110 | borderWidth: 0.5, 111 | }, 112 | nameInfo: { 113 | flexDirection: 'column', 114 | marginLeft: 8, 115 | justifyContent: 'center', 116 | flex: 1, 117 | }, 118 | name: { 119 | color: 'black', 120 | fontSize: 17, 121 | }, 122 | arrow: { 123 | width: 20, 124 | height: 20, 125 | }, 126 | settings: { 127 | height: 44, 128 | }, 129 | }); -------------------------------------------------------------------------------- /ios/gitfeed/Base.lproj/LaunchScreen.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 21 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /js/common/RepoCell.js: -------------------------------------------------------------------------------- 1 | import React, { 2 | Component, 3 | PropTypes, 4 | } from 'react' 5 | import { 6 | StyleSheet, 7 | Text, 8 | View, 9 | TouchableOpacity, 10 | TouchableHighlight, 11 | Image, 12 | Platform, 13 | ScrollView, 14 | SegmentedControlIOS 15 | } from 'react-native' 16 | import Icon from 'react-native-vector-icons/Ionicons' 17 | import Colors from '../constants/Colors' 18 | import CStyles from '../constants/Styles' 19 | import ReadMore from './ReadMore' 20 | import Touchable from '../common/F8Touchable' 21 | 22 | const ICON_SIZE = 12 23 | 24 | class ReopCell extends Component { 25 | onPressCell() { 26 | const { repo, navigator } = this.props 27 | navigator.push('RepoDetail', { repo }) 28 | } 29 | 30 | openAuthor() { 31 | const { repo, navigator } = this.props 32 | navigator.push('UserDetail', { user: repo.owner }) 33 | } 34 | 35 | render() { 36 | const { repo, } = this.props 37 | return ( 38 | 39 | 40 | 41 | 42 | 46 | 47 | 48 | 49 | {repo.full_name} 50 | 51 | {repo.language} 52 | 53 | 54 | 58 | 59 | {repo.stargazers_count} 60 | 61 | 62 | 63 | 64 | 68 | 69 | 70 | 71 | 72 | ) 73 | } 74 | } 75 | 76 | ReopCell.propTypes = { 77 | repo: PropTypes.object 78 | } 79 | ReopCell.defaultProps = {} 80 | 81 | export default ReopCell 82 | 83 | const styles = StyleSheet.create({ 84 | /** 85 | * ExploreCell 86 | */ 87 | cellContentView: { 88 | flexDirection: 'column', 89 | flex: 1, 90 | alignItems: 'stretch', 91 | }, 92 | cellUp: { 93 | padding: 10, 94 | height: 40, 95 | flexDirection: 'column', 96 | flexWrap: 'wrap', 97 | alignItems: 'flex-start', 98 | justifyContent: 'flex-start', 99 | marginBottom: 15, 100 | }, 101 | avatar: { 102 | width: 40, 103 | height: 40, 104 | backgroundColor: Colors.imagePlaceholder 105 | }, 106 | username: { 107 | marginLeft: 10, 108 | color: '#4078C0', 109 | fontSize: 15, 110 | maxWidth: 290 111 | }, 112 | textActionContainer: { 113 | margin: 10, 114 | marginTop: 7, 115 | marginBottom: 10, 116 | marginLeft: 10, 117 | }, 118 | createAt: { 119 | marginLeft: 10, 120 | marginTop: 2, 121 | fontSize: 11, 122 | color: '#BFBFBF', 123 | }, 124 | leftAction: { 125 | position: 'absolute', 126 | top: 0, 127 | right: 4, 128 | padding: 3, 129 | flexDirection: 'row', 130 | alignItems: 'center' 131 | }, 132 | rightAction: { 133 | padding: 3, 134 | backgroundColor: "white", 135 | }, 136 | actionText: { 137 | color: Colors.textGray, 138 | fontSize: 12, 139 | fontWeight: 'bold', 140 | alignSelf: 'center', 141 | }, 142 | }) -------------------------------------------------------------------------------- /js/common/F8Button.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2016 Facebook, Inc. 3 | * 4 | * You are hereby granted a non-exclusive, worldwide, royalty-free license to 5 | * use, copy, modify, and distribute this software in source code or binary 6 | * form for use in connection with the web services and APIs provided by 7 | * Facebook. 8 | * 9 | * As with any software that integrates with the Facebook platform, your use 10 | * of this software is subject to the Facebook Developer Principles and 11 | * Policies [http://developers.facebook.com/policy/]. This copyright notice 12 | * shall be included in all copies or substantial portions of the software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 17 | * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 19 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 20 | * DEALINGS IN THE SOFTWARE 21 | * 22 | * @providesModule F8Button 23 | * @flow 24 | */ 25 | 26 | 'use strict'; 27 | 28 | var F8Colors = require('F8Colors'); 29 | var Image = require('Image'); 30 | import LinearGradient from 'react-native-linear-gradient'; 31 | var React = require('React'); 32 | var StyleSheet = require('StyleSheet'); 33 | var { Text } = require('F8Text'); 34 | var TouchableOpacity = require('TouchableOpacity'); 35 | var View = require('View'); 36 | 37 | class F8Button extends React.Component { 38 | props: { 39 | type: 'primary' | 'secondary' | 'bordered'; 40 | icon?: number; 41 | caption: string; 42 | style?: any; 43 | onPress: () => mixed; 44 | }; 45 | 46 | static defaultProps = { 47 | type: 'primary', 48 | }; 49 | 50 | render() { 51 | const caption = this.props.caption.toUpperCase(); 52 | let icon; 53 | if (this.props.icon) { 54 | icon = ; 55 | } 56 | let content; 57 | if (this.props.type === 'primary') { 58 | content = ( 59 | 63 | {icon} 64 | 65 | {caption} 66 | 67 | 68 | ); 69 | } else { 70 | var border = this.props.type === 'bordered' && styles.border; 71 | content = ( 72 | 73 | {icon} 74 | 75 | {caption} 76 | 77 | 78 | ); 79 | } 80 | return ( 81 | 86 | {content} 87 | 88 | ); 89 | } 90 | } 91 | 92 | const HEIGHT = 50; 93 | 94 | var styles = StyleSheet.create({ 95 | container: { 96 | height: HEIGHT, 97 | // borderRadius: HEIGHT / 2, 98 | // borderWidth: 1 / PixelRatio.get(), 99 | }, 100 | button: { 101 | flex: 1, 102 | flexDirection: 'row', 103 | alignItems: 'center', 104 | justifyContent: 'center', 105 | paddingHorizontal: 40, 106 | }, 107 | border: { 108 | borderWidth: 1, 109 | borderColor: F8Colors.lightText, 110 | borderRadius: HEIGHT / 2, 111 | }, 112 | primaryButton: { 113 | borderRadius: HEIGHT / 2, 114 | backgroundColor: 'transparent', 115 | }, 116 | icon: { 117 | marginRight: 12, 118 | }, 119 | caption: { 120 | letterSpacing: 1, 121 | fontSize: 13, 122 | }, 123 | primaryCaption: { 124 | color: 'white', 125 | }, 126 | secondaryCaption: { 127 | color: F8Colors.lightText, 128 | } 129 | }); 130 | 131 | module.exports = F8Button; 132 | --------------------------------------------------------------------------------