Repository: cheeaun/hackerweb-native
Branch: master
Commit: c823767694a6
Files: 61
Total size: 135.0 KB
Directory structure:
gitextract_aon_j7yw/
├── .buckconfig
├── .flowconfig
├── .gitignore
├── .npmignore
├── .watchmanconfig
├── README.md
├── actions/
│ ├── LinkActions.js
│ └── StoryActions.js
├── alt.js
├── android/
│ ├── android.iml
│ ├── app/
│ │ ├── BUCK
│ │ ├── build.gradle
│ │ ├── proguard-rules.pro
│ │ ├── react.gradle
│ │ └── src/
│ │ └── main/
│ │ ├── AndroidManifest.xml
│ │ ├── java/
│ │ │ └── cheeaun/
│ │ │ └── hackerweb/
│ │ │ └── MainActivity.java
│ │ └── res/
│ │ ├── values/
│ │ │ ├── strings.xml
│ │ │ └── styles.xml
│ │ └── values-v23/
│ │ └── styles.xml
│ ├── build.gradle
│ ├── gradle/
│ │ └── wrapper/
│ │ ├── gradle-wrapper.jar
│ │ └── gradle-wrapper.properties
│ ├── gradle.properties
│ ├── gradlew
│ ├── gradlew.bat
│ └── settings.gradle
├── colors.js
├── components/
│ ├── Button.android.js
│ ├── Button.ios.js
│ ├── Comment.js
│ ├── CommentRow.js
│ ├── CommentsThread.js
│ ├── HTMLView.js
│ ├── LoadingIndicator.js
│ ├── ProgressBar.js
│ └── StoryRow.js
├── index.android.js
├── index.ios.js
├── ios/
│ ├── HackerWeb/
│ │ ├── AppDelegate.h
│ │ ├── AppDelegate.m
│ │ ├── Base.lproj/
│ │ │ └── LaunchScreen.xib
│ │ ├── Images.xcassets/
│ │ │ ├── AppIcon.appiconset/
│ │ │ │ └── Contents.json
│ │ │ ├── Contents.json
│ │ │ └── app-icon.imageset/
│ │ │ └── Contents.json
│ │ ├── Info.plist
│ │ └── main.m
│ ├── HackerWeb.xcodeproj/
│ │ ├── project.pbxproj
│ │ └── xcshareddata/
│ │ └── xcschemes/
│ │ └── HackerWeb.xcscheme
│ └── HackerWebTests/
│ ├── HackerWebTests.m
│ └── Info.plist
├── package.json
├── stores/
│ ├── LinkStore.js
│ └── StoryStore.js
├── utils/
│ ├── domainify.js
│ ├── showActivity.android.js
│ ├── showActivity.ios.js
│ ├── showBrowser.android.js
│ └── showBrowser.ios.js
└── views/
├── AboutView.js
├── CommentsView.js
└── StoriesView.js
================================================
FILE CONTENTS
================================================
================================================
FILE: .buckconfig
================================================
[android]
target = Google Inc.:Google APIs:23
[maven_repositories]
central = https://repo1.maven.org/maven2
================================================
FILE: .flowconfig
================================================
[ignore]
# We fork some components by platform.
.*/*.web.js
.*/*.android.js
# Some modules have their own node_modules with overlap
.*/node_modules/node-haste/.*
# Ugh
.*/node_modules/babel.*
.*/node_modules/babylon.*
.*/node_modules/invariant.*
# Ignore react and fbjs where there are overlaps, but don't ignore
# anything that react-native relies on
.*/node_modules/fbjs/lib/Map.js
.*/node_modules/fbjs/lib/ErrorUtils.js
# Flow has a built-in definition for the 'react' module which we prefer to use
# over the currently-untyped source
.*/node_modules/react/react.js
.*/node_modules/react/lib/React.js
.*/node_modules/react/lib/ReactDOM.js
.*/__mocks__/.*
.*/__tests__/.*
.*/commoner/test/source/widget/share.js
# Ignore commoner tests
.*/node_modules/commoner/test/.*
# See https://github.com/facebook/flow/issues/442
.*/react-tools/node_modules/commoner/lib/reader.js
# Ignore jest
.*/node_modules/jest-cli/.*
# Ignore Website
.*/website/.*
# Ignore generators
.*/local-cli/generator.*
# Ignore BUCK generated folders
.*\.buckd/
# Ignore RNPM
.*/local-cli/rnpm/.*
.*/node_modules/is-my-json-valid/test/.*\.json
.*/node_modules/iconv-lite/encodings/tables/.*\.json
.*/node_modules/y18n/test/.*\.json
.*/node_modules/spdx-license-ids/spdx-license-ids.json
.*/node_modules/spdx-exceptions/index.json
.*/node_modules/resolve/test/subdirs/node_modules/a/b/c/x.json
.*/node_modules/resolve/lib/core.json
.*/node_modules/jsonparse/samplejson/.*\.json
.*/node_modules/json5/test/.*\.json
.*/node_modules/ua-parser-js/test/.*\.json
.*/node_modules/builtin-modules/builtin-modules.json
.*/node_modules/binary-extensions/binary-extensions.json
.*/node_modules/url-regex/tlds.json
.*/node_modules/joi/.*\.json
.*/node_modules/isemail/.*\.json
.*/node_modules/tr46/.*\.json
[include]
[libs]
node_modules/react-native/Libraries/react-native/react-native-interface.js
node_modules/react-native/flow
flow/
[options]
module.system=haste
esproposal.class_static_fields=enable
esproposal.class_instance_fields=enable
munge_underscores=true
module.name_mapper='^image![a-zA-Z0-9$_-]+$' -> 'GlobalImageStub'
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'
suppress_type=$FlowIssue
suppress_type=$FlowFixMe
suppress_type=$FixMe
suppress_comment=\\(.\\|\n\\)*\\$FlowFixMe\\($\\|[^(]\\|(\\(>=0\\.\\(2[0-5]\\|1[0-9]\\|[0-9]\\).[0-9]\\)? *\\(site=[a-z,_]*react_native[a-z,_]*\\)?)\\)
suppress_comment=\\(.\\|\n\\)*\\$FlowIssue\\((\\(>=0\\.\\(2[0-5]\\|1[0-9]\\|[0-9]\\).[0-9]\\)? *\\(site=[a-z,_]*react_native[a-z,_]*\\)?)\\)?:? #[0-9]+
suppress_comment=\\(.\\|\n\\)*\\$FlowFixedInNextDeploy
[version]
^0.25.0
================================================
FILE: .gitignore
================================================
# OSX
#
.DS_Store
# Xcode
#
build/
*.pbxuser
!default.pbxuser
*.mode1v3
!default.mode1v3
*.mode2v3
!default.mode2v3
*.perspectivev3
!default.perspectivev3
xcuserdata
*.xccheckout
*.moved-aside
DerivedData
*.hmap
*.ipa
*.xcuserstate
project.xcworkspace
# Android/IJ
#
.idea
.gradle
local.properties
# node.js
#
node_modules/
npm-debug.log
# BUCK
buck-out/
\.buckd/
android/app/libs
android/keystores/debug.keystore
# CUSTOM
*.keystore
================================================
FILE: .npmignore
================================================
# OSX
#
.DS_Store
# Xcode
#
build/
*.pbxuser
!default.pbxuser
*.mode1v3
!default.mode1v3
*.mode2v3
!default.mode2v3
*.perspectivev3
!default.perspectivev3
xcuserdata
*.xccheckout
*.moved-aside
DerivedData
*.hmap
*.ipa
*.xcuserstate
# node.js
#
node_modules/
npm-debug.log
================================================
FILE: .watchmanconfig
================================================
{}
================================================
FILE: README.md
================================================
HackerWeb
===
A simply readable Hacker News app for iOS and Android.
Read about the story here: [Building HackerWeb for iOS](http://cheeaun.com/blog/2016/03/building-hackerweb-ios/).
Preview
---
### iOS

### Android

Development
---
### Requirements
- [React Native](https://facebook.github.io/react-native/)
- [React Native Package Manager](https://github.com/rnpm/rnpm)
- [Node.js](https://nodejs.org/)
- [Xcode](https://developer.apple.com/xcode/)
- [iOS](https://www.apple.com/ios/) **9**
- Android SDK API **23** (minimum support: 21)
### Getting started
- `npm install` - Install all dependencies
- `rnpm link` - Linking all native dependencies
### Implementations
- [Alt](http://alt.js.org/) - the Flux thing
- [htmlparser2](https://github.com/fb55/htmlparser2) - for parsing the comments HTML
- [url-parse](https://github.com/unshiftio/url-parse) - for extracting domains out of story URLs
- **iOS**
- [react-native-safari-view](https://github.com/naoufal/react-native-safari-view) - the reason why iOS 9 is the minimum requirement
- **Android**
- [react-native-chrome-custom-tabs](https://github.com/dstaley/react-native-chrome-custom-tabs) - same as SafariView for iOS, but for Chrome
- [react-native-android-share](https://github.com/haydenth/react-native-android-share) - for sharing
### Components
- `LoadingIndicator` - inspired by [react-native-activity-indicator-ios](https://github.com/pwmckenna/react-native-activity-indicator-ios)
- `HTMLView` - inspired by [react-native-htmlview](https://github.com/jsdf/react-native-htmlview), [react-native-htmltext](https://github.com/siuying/react-native-htmltext) and [react-native-html-render](https://github.com/soliury/react-native-html-render)
- [react-native-cache-store](https://github.com/cheeaun/react-native-cache-store) - for local caching
Similar apps
---
- [HackerNews-React-Native](https://github.com/iSimar/HackerNews-React-Native)
- [ReactNativeHackerNews](https://github.com/jsdf/ReactNativeHackerNews)
License
---
[MIT](http://cheeaun.mit-license.org/).
================================================
FILE: actions/LinkActions.js
================================================
'use strict';
import alt from '../alt';
import CacheStore from 'react-native-cache-store';
class LinkActions {
getLinks(){
return (dispatch) => {
CacheStore.get('links')
.then((links) => dispatch(links || []))
.catch(() => {});
};
}
addLink(link){
return (dispatch) => {
CacheStore.get('links')
.then((links) => {
links = links || [];
if (links.indexOf(link) < 0){
dispatch(link);
links.unshift(link);
CacheStore.set('links', links.slice(0, 100));
}
})
.catch(() => {});
};
}
}
export default alt.createActions(LinkActions);
================================================
FILE: actions/StoryActions.js
================================================
'use strict';
import alt from '../alt';
import CacheStore from 'react-native-cache-store';
const API_HOST = 'https://api.hackerwebapp.com/';
const FETCH_TIMEOUT = 20000; // 20 seconds
function fetchTimeout(){
return new Promise((resolve, reject) => {
setTimeout(() => reject(new Error('Response timeout.')), FETCH_TIMEOUT);
})
};
const MAX_RETRIES = 3;
function betterFetch(url, times){
times = times || 0;
return new Promise((resolve, reject) => {
fetch(url)
.then((response) => response.json())
.then(resolve)
.catch((times >= MAX_RETRIES) ? reject : (e) => {
setTimeout(() => {
betterFetch(url, times+1).then(resolve).catch(reject);
}, 500);
});
});
};
class StoryActions {
updateStories(stories){
return stories;
}
updateMoreStories(stories){
return stories;
}
flush(){
CacheStore.flush(); // Clears everything
}
fetchStories(){
return (dispatch) => {
dispatch();
var request = () => {
Promise.race([
betterFetch(API_HOST + 'news'),
fetchTimeout()
])
.then((stories) => {
if (!stories || !stories.length) throw new Error('Stories payload is empty');
this.updateStories(stories);
CacheStore.set('stories', stories, 10); // 10 minutes
})
.catch(this.storiesFailed);
// Meanwhile...
betterFetch(API_HOST + 'news2')
.then((stories) => {
if (!stories || !stories.length) return;
this.hasMoreStories();
CacheStore.set('stories2', stories, 10); // 10 minutes
})
.catch(() => {});
};
CacheStore.get('stories').then((stories) => {
if (stories){
this.updateStories(stories);
} else {
request();
}
}).catch(request);
CacheStore.get('stories2').then((stories) => {
if (stories) this.hasMoreStories();
}).catch(() => {});
};
}
hasMoreStories(){
return true;
}
fetchMoreStories(){
CacheStore.get('stories2').then((stories) => {
if (stories) this.updateMoreStories(stories);
}).catch(() => {});
}
fetchStoriesIfExpired(){
CacheStore.isExpired('stories').then(this.fetchStories).catch(() => {});
}
storiesFailed(error){
return error;
}
updateStory(story){
return story;
}
fetchStory(id) {
return (dispatch) => {
dispatch(id);
const key = `story-${id}`;
var request = () => {
Promise.race([
betterFetch(API_HOST + 'item/' + id),
fetchTimeout()
])
.then((story) => {
if (!story) throw new Error('Story payload is empty');
this.updateStory(story);
CacheStore.set(key, story, 5); // 5 minutes
})
.catch(this.storyFailed);
};
CacheStore.get(key).then((story) => {
if (story){
this.updateStory(story);
} else {
request();
}
}).catch(request);
};
}
storyFailed(error){
return error;
}
}
export default alt.createActions(StoryActions);
================================================
FILE: alt.js
================================================
import Alt from 'alt';
export default new Alt();
================================================
FILE: android/android.iml
================================================
================================================
FILE: android/app/BUCK
================================================
import re
# To learn about Buck see [Docs](https://buckbuild.com/).
# To run your application with Buck:
# - install Buck
# - `npm start` - to start the packager
# - `cd android`
# - `keytool -genkey -v -keystore keystores/debug.keystore -storepass android -alias androiddebugkey -keypass android -dname "CN=Android Debug,O=Android,C=US`
# - `./gradlew :app:copyDownloadableDepsToLibs` - make all Gradle compile dependencies available to Buck
# - `buck install -r android/app` - compile, install and run application
#
lib_deps = []
for jarfile in glob(['libs/*.jar']):
name = 'jars__' + re.sub(r'^.*/([^/]+)\.jar$', r'\1', jarfile)
lib_deps.append(':' + name)
prebuilt_jar(
name = name,
binary_jar = jarfile,
)
for aarfile in glob(['libs/*.aar']):
name = 'aars__' + re.sub(r'^.*/([^/]+)\.aar$', r'\1', aarfile)
lib_deps.append(':' + name)
android_prebuilt_aar(
name = name,
aar = aarfile,
)
android_library(
name = 'all-libs',
exported_deps = lib_deps
)
android_library(
name = 'app-code',
srcs = glob([
'src/main/java/**/*.java',
]),
deps = [
':all-libs',
':build_config',
':res',
],
)
android_build_config(
name = 'build_config',
package = 'cheeaun.hackerweb',
)
android_resource(
name = 'res',
res = 'src/main/res',
package = 'cheeaun.hackerweb',
)
android_binary(
name = 'app',
package_type = 'debug',
manifest = 'src/main/AndroidManifest.xml',
keystore = '//android/keystores:debug',
deps = [
':app-code',
],
)
================================================
FILE: android/app/build.gradle
================================================
apply plugin: "com.android.application"
import com.android.build.OutputFile
/**
* The react.gradle file registers a task for each build variant (e.g. bundleDebugJsAndAssets
* and bundleReleaseJsAndAssets).
* These basically call `react-native bundle` with the correct arguments during the Android build
* cycle. By default, bundleDebugJsAndAssets is skipped, as in debug/dev mode we prefer to load the
* bundle directly from the development server. Below you can see all the possible configurations
* and their defaults. If you decide to add a configuration block, make sure to add it before the
* `apply from: "../../node_modules/react-native/react.gradle"` line.
*
* project.ext.react = [
* // the name of the generated asset file containing your JS bundle
* bundleAssetName: "index.android.bundle",
*
* // the entry file for bundle generation
* entryFile: "index.android.js",
*
* // whether to bundle JS and assets in debug mode
* bundleInDebug: false,
*
* // whether to bundle JS and assets in release mode
* bundleInRelease: true,
*
* // whether to bundle JS and assets in another build variant (if configured).
* // See http://tools.android.com/tech-docs/new-build-system/user-guide#TOC-Build-Variants
* // The configuration property can be in the following formats
* // 'bundleIn${productFlavor}${buildType}'
* // 'bundleIn${buildType}'
* // bundleInFreeDebug: true,
* // bundleInPaidRelease: true,
* // bundleInBeta: true,
*
* // the root of your project, i.e. where "package.json" lives
* root: "../../",
*
* // where to put the JS bundle asset in debug mode
* jsBundleDirDebug: "$buildDir/intermediates/assets/debug",
*
* // where to put the JS bundle asset in release mode
* jsBundleDirRelease: "$buildDir/intermediates/assets/release",
*
* // where to put drawable resources / React Native assets, e.g. the ones you use via
* // require('./image.png')), in debug mode
* resourcesDirDebug: "$buildDir/intermediates/res/merged/debug",
*
* // where to put drawable resources / React Native assets, e.g. the ones you use via
* // require('./image.png')), in release mode
* resourcesDirRelease: "$buildDir/intermediates/res/merged/release",
*
* // by default the gradle tasks are skipped if none of the JS files or assets change; this means
* // that we don't look at files in android/ or ios/ to determine whether the tasks are up to
* // date; if you have any other folders that you want to ignore for performance reasons (gradle
* // indexes the entire tree), add them here. Alternatively, if you have JS files in android/
* // for example, you might want to remove it from here.
* inputExcludes: ["android/**", "ios/**"]
* ]
*/
apply from: "../../node_modules/react-native/react.gradle"
/**
* Set this to true to create two separate APKs instead of one:
* - An APK that only works on ARM devices
* - An APK that only works on x86 devices
* The advantage is the size of the APK is reduced by about 4MB.
* Upload all the APKs to the Play Store and people will download
* the correct one based on the CPU architecture of their device.
*/
def enableSeparateBuildPerCPUArchitecture = false
/**
* Run Proguard to shrink the Java bytecode in release builds.
*/
def enableProguardInReleaseBuilds = false
android {
compileSdkVersion 23
buildToolsVersion "23.0.1"
defaultConfig {
applicationId "cheeaun.hackerweb"
minSdkVersion 21
targetSdkVersion 23
versionCode 3
versionName "1.2"
ndk {
abiFilters "armeabi-v7a", "x86"
}
}
signingConfigs {
release {
storeFile file(MYAPP_RELEASE_STORE_FILE)
storePassword MYAPP_RELEASE_STORE_PASSWORD
keyAlias MYAPP_RELEASE_KEY_ALIAS
keyPassword MYAPP_RELEASE_KEY_PASSWORD
}
}
splits {
abi {
reset()
enable enableSeparateBuildPerCPUArchitecture
universalApk false // If true, also generate a universal APK
include "armeabi-v7a", "x86"
}
}
buildTypes {
release {
minifyEnabled enableProguardInReleaseBuilds
proguardFiles getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro"
signingConfig signingConfigs.release
}
}
// applicationVariants are e.g. debug, release
applicationVariants.all { variant ->
variant.outputs.each { output ->
// For each separate APK per architecture, set a unique version code as described here:
// http://tools.android.com/tech-docs/new-build-system/user-guide/apk-splits
def versionCodes = ["armeabi-v7a":1, "x86":2]
def abi = output.getFilter(OutputFile.ABI)
if (abi != null) { // null for the universal-debug, universal-release variants
output.versionCodeOverride =
versionCodes.get(abi) * 1048576 + defaultConfig.versionCode
}
}
}
}
dependencies {
compile fileTree(dir: "libs", include: ["*.jar"])
compile "com.android.support:appcompat-v7:23.0.1"
compile "com.facebook.react:react-native:+" // From node_modules
compile project(':ReactNativeChromeCustomTabs')
compile project(':RNAndroidShare')
}
// Run this once to be able to run the application with BUCK
// puts all compile dependencies into folder libs for BUCK to use
task copyDownloadableDepsToLibs(type: Copy) {
from configurations.compile
into 'libs'
}
================================================
FILE: android/app/proguard-rules.pro
================================================
# Add project specific ProGuard rules here.
# By default, the flags in this file are appended to flags specified
# in /usr/local/Cellar/android-sdk/24.3.3/tools/proguard/proguard-android.txt
# You can edit the include path and order by changing the proguardFiles
# directive in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# Add any project specific keep options here:
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
# Disabling obfuscation is useful if you collect stack traces from production crashes
# (unless you are using a system that supports de-obfuscate the stack traces).
-dontobfuscate
# React Native
# Keep our interfaces so they can be used by other ProGuard rules.
# See http://sourceforge.net/p/proguard/bugs/466/
-keep,allowobfuscation @interface com.facebook.proguard.annotations.DoNotStrip
-keep,allowobfuscation @interface com.facebook.proguard.annotations.KeepGettersAndSetters
# Do not strip any method/class that is annotated with @DoNotStrip
-keep @com.facebook.proguard.annotations.DoNotStrip class *
-keepclassmembers class * {
@com.facebook.proguard.annotations.DoNotStrip *;
}
-keepclassmembers @com.facebook.proguard.annotations.KeepGettersAndSetters class * {
void set*(***);
*** get*();
}
-keep class * extends com.facebook.react.bridge.JavaScriptModule { *; }
-keep class * extends com.facebook.react.bridge.NativeModule { *; }
-keepclassmembers,includedescriptorclasses class * { native ; }
-keepclassmembers class * { @com.facebook.react.uimanager.UIProp ; }
-keepclassmembers class * { @com.facebook.react.uimanager.annotations.ReactProp ; }
-keepclassmembers class * { @com.facebook.react.uimanager.annotations.ReactPropGroup ; }
-dontwarn com.facebook.react.**
# okhttp
-keepattributes Signature
-keepattributes *Annotation*
-keep class okhttp3.** { *; }
-keep interface okhttp3.** { *; }
-dontwarn okhttp3.**
# okio
-keep class sun.misc.Unsafe { *; }
-dontwarn java.nio.file.*
-dontwarn org.codehaus.mojo.animal_sniffer.IgnoreJRERequirement
-dontwarn okio.**
================================================
FILE: android/app/react.gradle
================================================
import org.apache.tools.ant.taskdefs.condition.Os
def config = project.hasProperty("react") ? project.react : [];
def bundleAssetName = config.bundleAssetName ?: "index.android.bundle"
def entryFile = config.entryFile ?: "index.android.js"
// because elvis operator
def elvisFile(thing) {
return thing ? file(thing) : null;
}
def reactRoot = elvisFile(config.root) ?: file("../../")
def inputExcludes = config.inputExcludes ?: ["android/**", "ios/**"]
void runBefore(String dependentTaskName, Task task) {
Task dependentTask = tasks.findByPath(dependentTaskName);
if (dependentTask != null) {
dependentTask.dependsOn task
}
}
gradle.projectsEvaluated {
// Grab all build types and product flavors
def buildTypes = android.buildTypes.collect { type -> type.name }
def productFlavors = android.productFlavors.collect { flavor -> flavor.name }
// When no product flavors defined, use empty
if (!productFlavors) productFlavors.add('')
productFlavors.each { productFlavorName ->
buildTypes.each { buildTypeName ->
// Create variant and target names
def targetName = "${productFlavorName.capitalize()}${buildTypeName.capitalize()}"
def targetPath = productFlavorName ?
"${productFlavorName}/${buildTypeName}" :
"${buildTypeName}"
// React js bundle directories
def jsBundleDirConfigName = "jsBundleDir${targetName}"
def jsBundleDir = elvisFile(config."$jsBundleDirConfigName") ?:
file("$buildDir/intermediates/assets/${targetPath}")
def resourcesDirConfigName = "resourcesDir${targetName}"
def resourcesDir = elvisFile(config."${resourcesDirConfigName}") ?:
file("$buildDir/intermediates/res/merged/${targetPath}")
def jsBundleFile = file("$jsBundleDir/$bundleAssetName")
// Bundle task name for variant
def bundleJsAndAssetsTaskName = "bundle${targetName}JsAndAssets"
def currentBundleTask = tasks.create(
name: bundleJsAndAssetsTaskName,
type: Exec) {
group = "react"
description = "bundle JS and assets for ${targetName}."
// Create dirs if they are not there (e.g. the "clean" task just ran)
doFirst {
jsBundleDir.mkdirs()
resourcesDir.mkdirs()
}
// Set up inputs and outputs so gradle can cache the result
inputs.files fileTree(dir: reactRoot, excludes: inputExcludes)
outputs.dir jsBundleDir
outputs.dir resourcesDir
// Set up the call to the react-native cli
workingDir reactRoot
// Set up dev mode
def devEnabled = !targetName.toLowerCase().contains("release")
if (Os.isFamily(Os.FAMILY_WINDOWS)) {
commandLine "cmd", "/c", "node", "node_modules/react-native/local-cli/cli.js", "bundle", "--platform", "android", "--dev", "${devEnabled}",
"--entry-file", entryFile, "--bundle-output", jsBundleFile, "--assets-dest", resourcesDir
} else {
commandLine "node", "node_modules/react-native/local-cli/cli.js", "bundle", "--platform", "android", "--dev", "${devEnabled}",
"--entry-file", entryFile, "--bundle-output", jsBundleFile, "--assets-dest", resourcesDir
}
enabled config."bundleIn${targetName}" ||
config."bundleIn${buildTypeName.capitalize()}" ?:
targetName.toLowerCase().contains("release")
}
// Hook bundle${productFlavor}${buildType}JsAndAssets into the android build process
currentBundleTask.dependsOn("merge${targetName}Resources")
currentBundleTask.dependsOn("merge${targetName}Assets")
runBefore("processArmeabi-v7a${targetName}Resources", currentBundleTask)
runBefore("processX86${targetName}Resources", currentBundleTask)
runBefore("processUniversal${targetName}Resources", currentBundleTask)
runBefore("process${targetName}Resources", currentBundleTask)
}
}
}
================================================
FILE: android/app/src/main/AndroidManifest.xml
================================================
================================================
FILE: android/app/src/main/java/cheeaun/hackerweb/MainActivity.java
================================================
package cheeaun.hackerweb;
import com.facebook.react.ReactActivity;
import com.facebook.react.ReactPackage;
import com.facebook.react.shell.MainReactPackage;
import java.util.Arrays;
import java.util.List;
import com.dstaley.ReactNativeChromeCustomTabs.ChromeCustomTabsPackage;
import com.blueprintalpha.rnandroidshare.RNAndroidSharePackage;
public class MainActivity extends ReactActivity {
/**
* Returns the name of the main component registered from JavaScript.
* This is used to schedule rendering of the component.
*/
@Override
protected String getMainComponentName() {
return "HackerWeb";
}
/**
* Returns whether dev mode should be enabled.
* This enables e.g. the dev menu.
*/
@Override
protected boolean getUseDeveloperSupport() {
return BuildConfig.DEBUG;
}
/**
* A list of packages used by the app. If the app uses additional views
* or modules besides the default ones, add more packages here.
*/
@Override
protected List getPackages() {
return Arrays.asList(
new MainReactPackage(),
new ChromeCustomTabsPackage(this),
new RNAndroidSharePackage(this)
);
}
}
================================================
FILE: android/app/src/main/res/values/strings.xml
================================================
HackerWeb
================================================
FILE: android/app/src/main/res/values/styles.xml
================================================
================================================
FILE: android/app/src/main/res/values-v23/styles.xml
================================================
================================================
FILE: android/build.gradle
================================================
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
repositories {
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:1.5.0'
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
}
}
allprojects {
repositories {
mavenLocal()
jcenter()
maven {
// All of React Native (JS, Obj-C sources, Android binaries) is installed from npm
url "$projectDir/../../node_modules/react-native/android"
}
}
}
================================================
FILE: android/gradle/wrapper/gradle-wrapper.properties
================================================
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-2.4-all.zip
================================================
FILE: android/gradle.properties
================================================
# Project-wide Gradle settings.
# IDE (e.g. Android Studio) users:
# Gradle settings configured through the IDE *will override*
# any settings specified in this file.
# For more details on how to configure your build environment visit
# http://www.gradle.org/docs/current/userguide/build_environment.html
# Specifies the JVM arguments used for the daemon process.
# The setting is particularly useful for tweaking memory settings.
# Default value: -Xmx10248m -XX:MaxPermSize=256m
# org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
# When configured, Gradle will run in incubating parallel mode.
# This option should only be used with decoupled projects. More details, visit
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
# org.gradle.parallel=true
android.useDeprecatedNdk=true
================================================
FILE: android/gradlew
================================================
#!/usr/bin/env bash
##############################################################################
##
## Gradle start up script for UN*X
##
##############################################################################
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS=""
APP_NAME="Gradle"
APP_BASE_NAME=`basename "$0"`
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum"
warn ( ) {
echo "$*"
}
die ( ) {
echo
echo "$*"
echo
exit 1
}
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
case "`uname`" in
CYGWIN* )
cygwin=true
;;
Darwin* )
darwin=true
;;
MINGW* )
msys=true
;;
esac
# For Cygwin, ensure paths are in UNIX format before anything is touched.
if $cygwin ; then
[ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
fi
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
PRG="$0"
# Need this for relative symlinks.
while [ -h "$PRG" ] ; do
ls=`ls -ld "$PRG"`
link=`expr "$ls" : '.*-> \(.*\)$'`
if expr "$link" : '/.*' > /dev/null; then
PRG="$link"
else
PRG=`dirname "$PRG"`"/$link"
fi
done
SAVED="`pwd`"
cd "`dirname \"$PRG\"`/" >&-
APP_HOME="`pwd -P`"
cd "$SAVED" >&-
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD="$JAVA_HOME/jre/sh/java"
else
JAVACMD="$JAVA_HOME/bin/java"
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD="java"
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
# Increase the maximum file descriptors if we can.
if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
MAX_FD_LIMIT=`ulimit -H -n`
if [ $? -eq 0 ] ; then
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
MAX_FD="$MAX_FD_LIMIT"
fi
ulimit -n $MAX_FD
if [ $? -ne 0 ] ; then
warn "Could not set maximum file descriptor limit: $MAX_FD"
fi
else
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
fi
fi
# For Darwin, add options to specify how the application appears in the dock
if $darwin; then
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
fi
# For Cygwin, switch paths to Windows format before running java
if $cygwin ; then
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
# We build the pattern for arguments to be converted via cygpath
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
SEP=""
for dir in $ROOTDIRSRAW ; do
ROOTDIRS="$ROOTDIRS$SEP$dir"
SEP="|"
done
OURCYGPATTERN="(^($ROOTDIRS))"
# Add a user-defined pattern to the cygpath arguments
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
fi
# Now convert the arguments - kludge to limit ourselves to /bin/sh
i=0
for arg in "$@" ; do
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
else
eval `echo args$i`="\"$arg\""
fi
i=$((i+1))
done
case $i in
(0) set -- ;;
(1) set -- "$args0" ;;
(2) set -- "$args0" "$args1" ;;
(3) set -- "$args0" "$args1" "$args2" ;;
(4) set -- "$args0" "$args1" "$args2" "$args3" ;;
(5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
(6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
(7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
(8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
(9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
esac
fi
# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
function splitJvmOpts() {
JVM_OPTS=("$@")
}
eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
================================================
FILE: android/gradlew.bat
================================================
@if "%DEBUG%" == "" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS=
set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto init
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto init
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:init
@rem Get command-line arguments, handling Windowz variants
if not "%OS%" == "Windows_NT" goto win9xME_args
if "%@eval[2+2]" == "4" goto 4NT_args
:win9xME_args
@rem Slurp the command line arguments.
set CMD_LINE_ARGS=
set _SKIP=2
:win9xME_args_slurp
if "x%~1" == "x" goto execute
set CMD_LINE_ARGS=%*
goto execute
:4NT_args
@rem Get arguments from the 4NT Shell from JP Software
set CMD_LINE_ARGS=%$
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
:end
@rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega
================================================
FILE: android/settings.gradle
================================================
rootProject.name = 'HackerWeb'
include ':app'
include ':ReactNativeChromeCustomTabs', ':app'
project(':ReactNativeChromeCustomTabs').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-chrome-custom-tabs/android')
include ':RNAndroidShare', ':app'
project(':RNAndroidShare').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-android-share')
================================================
FILE: colors.js
================================================
import { Platform } from 'react-native';
const isIOS = Platform.OS === 'ios';
export default {
linkColor: '#007aff',
viewBackgroundColor: isIOS ? '#efeff4' : '#fafafa',
userColor: isIOS ? '#bf223f' : '#b71c1c',
opColor: '#fff',
opBackgroundColor: isIOS ? '#bf223f' : '#b71c1c',
insignificantColor: 'rgba(0,0,0,.54)',
sectionInsignificantColor: '#6d6d72',
separatorColor: isIOS ? '#c8c7cc' : '#ebebeb',
blockCodeBackgroundColor: '#eee',
progressBarBackgroundColor: '#eee',
progressBarColor: '#007aff',
sectionBackgroundColor: '#fff',
domainColor: isIOS ? '#003d80' : '#0D47A1',
defaultButtonThemeColor: isIOS ? '#848484' : '#f5f5f5',
primaryTextColor: isIOS ? null : 'rgba(0,0,0,.87)',
toolbarBackgroundColor: isIOS ? null: '#f5f5f5',
disabledColor: 'rgba(0,0,0,.38)',
}
================================================
FILE: components/Button.android.js
================================================
'use strict';
import React from 'react';
import {
StyleSheet,
View,
Text,
TouchableNativeFeedback,
} from 'react-native';
import colors from '../colors';
const styles = StyleSheet.create({
button: {
borderRadius: 2,
backgroundColor: colors.defaultButtonThemeColor,
paddingVertical: 11,
paddingHorizontal: 16,
},
text: {
textAlign: 'center',
},
});
export default (props) => {
return (
{props.children.map((c) => c.toUpperCase ? c.toUpperCase() : c)}
);
}
================================================
FILE: components/Button.ios.js
================================================
'use strict';
import React, { Component } from 'react';
import {
StyleSheet,
Text,
View,
TouchableWithoutFeedback,
} from 'react-native';
import colors from '../colors';
const styles = StyleSheet.create({
button: {
backgroundColor: '#ffffff',
borderRadius: 6,
borderColor: colors.defaultButtonThemeColor,
borderWidth: StyleSheet.hairlineWidth,
paddingVertical: 5,
paddingHorizontal: 10,
},
text: {
color: colors.defaultButtonThemeColor,
textAlign: 'center',
},
pressedButton: {
backgroundColor: colors.defaultButtonThemeColor,
},
pressedText: {
color: '#ffffff',
},
});
export default class Button extends Component {
constructor(props){
super(props);
this.state = {
pressed: false,
};
}
_onPressIn(){
this.setState({
pressed: true,
});
}
_onPressOut(){
this.setState({
pressed: false,
});
}
render(){
const {pressed} = this.state;
const {onPress, buttonStyles, pressedButtonStyles, textStyles, pressedTextStyles, children} = this.props;
return (
{children}
);
}
}
================================================
FILE: components/Comment.js
================================================
'use strict';
import React from 'react';
import {
StyleSheet,
View,
Text,
Image,
Platform,
} from 'react-native';
const isIOS = Platform.OS === 'ios';
import HTMLView from '../components/HTMLView';
import showBrowser from '../utils/showBrowser';
import showActivity from '../utils/showActivity';
import colors from '../colors';
const styles = StyleSheet.create({
comment: {
padding: isIOS ? 15 : 16,
flex: 1,
flexDirection: 'row',
},
subComment: {
paddingTop: 0,
marginTop: -10
},
commentInner: {
flex: 1,
flexDirection: 'column',
},
commentMetadata: {
flex: 1,
flexDirection: 'row',
marginBottom: 8,
},
commentUserWrap: {
flex: 1,
flexWrap: 'wrap',
flexDirection: 'row',
},
commentUser: {
fontWeight: '500',
color: colors.userColor,
},
opUserContainer: {
backgroundColor: colors.opBackgroundColor,
paddingVertical: 2,
paddingHorizontal: 4,
borderRadius: 2,
marginLeft: 5,
},
opUser: {
fontSize: 10,
fontWeight: 'bold',
color: colors.opColor,
},
commentDeleted: {
flex: 1,
},
commentTime: {
color: colors.insignificantColor,
},
commentArrowIcon: {
width: 8,
height: 9,
marginRight: 6,
marginTop: 4,
opacity: isIOS ? 1 : .54,
},
});
export default (props) => {
const {data, op} = props;
const {level, user} = data;
const innerComment = data.deleted ? (
[deleted]
{data.time_ago}
) : (
{user}
{op == user && OP}
{data.time_ago}
);
return (
0 && styles.subComment]}>
{level > 0 && }
{innerComment}
);
}
================================================
FILE: components/CommentRow.js
================================================
'use strict';
import React, { Component } from 'react';
import {
StyleSheet,
View,
LayoutAnimation,
} from 'react-native';
import CommentsThread from '../components/CommentsThread';
import Comment from '../components/Comment';
import Button from '../components/Button';
import colors from '../colors';
const styles = StyleSheet.create({
comment: {
backgroundColor: colors.sectionBackgroundColor,
},
repliesButton: {
marginTop: -7,
marginRight: 15,
marginBottom: 15,
marginLeft: 15,
},
});
export default class CommentRow extends Component {
constructor(props){
super(props);
this.state = {
expanded: false,
};
}
_toggleComments(){
LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut);
this.setState({
expanded: !this.state.expanded,
});
}
render(){
const {op, comment, hasManyComments} = this.props;
const {comments} = comment;
const commentsThread = ;
const hasComments = comments && comments.length;
const hasOnlyOneComment = comments.length == 1 && !comments[0].comments.length;
if (hasManyComments && hasComments && !hasOnlyOneComment){
let commentsCount = comment.comments.length;
(function dive(comments){
comments.forEach(function(c){
var len = c.comments.length;
commentsCount += len;
if (len) dive(c.comments);
});
})(comment.comments);
let repliesButton = commentsCount > 0 && ;
return (
{repliesButton}
{this.state.expanded && commentsThread}
);
} else {
return (
{commentsThread}
);
}
}
}
================================================
FILE: components/CommentsThread.js
================================================
'use strict';
import React from 'react';
import {
StyleSheet,
View,
} from 'react-native';
import Comment from './Comment';
const styles = StyleSheet.create({
indentedThread: {
marginLeft: 14,
},
});
const CommentsThread = (props) => {
const {data, op} = props;
if (!data || !data.length) return ;
return (
{data.map((comment) => {
return (
1 && styles.indentedThread}>
);
})}
);
}
export default CommentsThread;
================================================
FILE: components/HTMLView.js
================================================
'use strict';
import React, { Component } from 'react';
import {
StyleSheet,
Text,
View,
ScrollView,
Linking,
Platform,
} from 'react-native';
const isIOS = Platform.OS === 'ios';
import PureRenderMixin from 'react-addons-pure-render-mixin';
import htmlparser from 'htmlparser2';
import colors from '../colors';
const nodeStyles = StyleSheet.create({
p: {
color: colors.primaryTextColor,
marginBottom: 8,
lineHeight: isIOS ? null : 20,
},
pre: {
paddingVertical: 10,
backgroundColor: colors.blockCodeBackgroundColor,
borderRadius: 3,
marginBottom: 8,
},
code: {
color: colors.primaryTextColor,
fontFamily: isIOS ? 'Menlo' : 'monospace',
fontSize: 12,
},
a: {
color: colors.linkColor,
},
i: {
fontStyle: 'italic',
},
});
function dom2elements(nodes, opts, parentName){
if (!nodes || !nodes.length) return;
const {onLinkPress, onLinkLongPress} = opts;
return nodes.map((node) => {
const {name, type, children} = node;
const key = (name || type) + '-' + Math.random();
const style = nodeStyles[name];
if (type == 'tag'){
var elements = dom2elements(children, opts, name);
if (!elements) return null;
if (name == 'pre'){
return (
{elements}
);
}
if (name == 'a'){
const {href} = node.attribs;
// Steps to make sure children inside is ACTUALLY text
const child = children && children.length == 1 && children[0];
const text = child && child.type == 'text' && child.data;
return {text || elements};
}
return {elements};
} else if (type == 'text'){
const {data} = node;
let text;
if (parentName == 'code'){
// Trim EOL newline
text = data.replace(/\n$/, '');
} else {
// Trim ALL newlines, because HTML
text = data.replace(/[\n\s\t]+/g, ' ');
}
return {text};
}
});
};
function processDOM(html, opts, callback){
if (typeof opts == 'function'){
callback = opts;
opts = {};
}
const handler = new htmlparser.DomHandler((err, dom) => {
const elements = dom2elements(dom, opts);
callback(elements);
});
const parser = new htmlparser.Parser(handler, {
recognizeSelfClosing: true,
lowerCaseAttributeNames: true,
lowerCaseTags: true,
decodeEntities: true,
});
// Clean up HTML first
if (!html.match(/^/i)) html = '
' + html;
// Stop
from being wrapped by
html = html.replace(/
\s*
/ig, '
');
if (!html.match(/<\/pre>\s*/i)){
html = html.replace(/<\/pre>([^<])/ig, '
$1');
}
parser.write(html);
parser.end();
}
export default class HTMLView extends Component {
constructor(props){
super(props);
this.state = {
elements: null,
};
this.shouldComponentUpdate = PureRenderMixin.shouldComponentUpdate.bind(this);
}
componentDidMount(){
const {html, onLinkPress, onLinkLongPress} = this.props;
if (!html) return null;
processDOM(html, {
onLinkPress: onLinkPress || Linking.openURL,
onLinkLongPress: onLinkLongPress || function(){},
}, (elements) => {
this.setState({
elements: elements,
});
});
}
render(){
return (
{this.state.elements}
);
}
}
================================================
FILE: components/LoadingIndicator.js
================================================
'use strict';
import React, { Component } from 'react';
import {
StyleSheet,
Text,
View,
ActivityIndicatorIOS,
ProgressBarAndroid,
Platform,
} from 'react-native';
const isIOS = Platform.OS === 'ios';
import colors from '../colors';
const styles = StyleSheet.create({
container: {
flexDirection: 'row',
alignItems: 'center',
},
spinner: isIOS ? {
width: 30,
height: 30,
} : {
width: 24,
height: 24,
},
text: {
opacity: .6,
},
});
export default class LoadingIndicator extends Component {
constructor(props){
super(props);
this.state = {
opacity: 0,
};
}
componentDidMount(){
this._timer = setTimeout(() => {
this.setState({
opacity: 1,
});
}, 900); // less than 1 second
}
componentWillUnmount(){
clearTimeout(this._timer);
}
render(){
const {opacity} = this.state;
if (isIOS){
return (
Loading…
);
}
return (
);
}
}
================================================
FILE: components/ProgressBar.js
================================================
'use strict';
import React, { Component } from 'react';
import {
StyleSheet,
View,
Animated,
} from 'react-native';
import colors from '../colors';
const styles = StyleSheet.create({
bar: {
backgroundColor: colors.progressBarBackgroundColor,
height: 3,
borderRadius: 1.5,
overflow: 'hidden',
},
progress: {
backgroundColor: colors.progressBarColor,
height: 3,
}
});
export default class ProgressBar extends Component {
constructor(props){
super(props);
this.state = {
progressWidth: new Animated.Value(0),
};
this._onBarLayout = this._onBarLayout.bind(this);
}
_onBarLayout(e){
const {value, max} = this.props;
const width = e.nativeEvent.layout.width;
Animated.spring(this.state.progressWidth, {
toValue: value/max*width,
duration: 300,
}).start();
}
render(){
return (
);
}
}
================================================
FILE: components/StoryRow.js
================================================
'use strict';
import React, { Component } from 'react';
import {
StyleSheet,
View,
Text,
TouchableOpacity,
TouchableHighlight,
TouchableNativeFeedback,
Image,
Platform,
} from 'react-native';
const isIOS = Platform.OS === 'ios';
const CrossTouchableHighlight = isIOS ? TouchableHighlight : TouchableNativeFeedback;
const CrossTouchableOpacity = isIOS ? TouchableOpacity : TouchableNativeFeedback;
import LinkStore from '../stores/LinkStore';
import LinkActions from '../actions/LinkActions';
import domainify from '../utils/domainify';
import colors from '../colors';
const styles = StyleSheet.create({
story: {
backgroundColor: colors.sectionBackgroundColor,
flexDirection: 'row',
alignItems: 'stretch',
},
storyPosition: {
paddingTop: isIOS ? 10 : 16,
paddingLeft: 15,
},
storyPositionNumber: {
width: 22,
textAlign: 'center',
color: colors.insignificantColor,
fontSize: 17,
},
storyInfo: {
padding: 10,
paddingVertical: isIOS ? 10 : 16,
flex: 1,
},
storyComments: {
padding: isIOS ? 10 : 16,
},
storyDisclosure: {
paddingVertical: 15,
paddingRight: 15,
paddingLeft: 5,
},
storyTitle: {
color: colors.primaryTextColor,
fontSize: 17,
},
storyTitleVisited: {
color: colors.insignificantColor,
},
storyDomain: {
fontSize: isIOS ? 13 : 14,
color: colors.domainColor,
},
storyMetadataWrap: {
flexWrap: 'wrap',
flexDirection: 'row',
},
storyMetadata: {
fontSize: isIOS ? 13 : 14,
color: colors.insignificantColor,
},
commentIcon: {
width: isIOS ? 20 : 24,
height: isIOS ? 19 : 24,
marginHorizontal: isIOS ? 2 : 0,
marginTop: 3,
marginBottom: 2,
opacity: isIOS ? 1 : .54,
},
disclosureIcon: {
width: 8,
height: 13,
marginLeft: 2,
marginTop: 1,
},
});
export default class StoryRow extends Component {
constructor(props){
super(props);
const {data} = props;
const {links} = LinkStore.getState();
this.state = {
visited: links.indexOf(data.url) >= 0,
};
this._onLinkChange = this._onLinkChange.bind(this);
}
componentDidMount(){
LinkStore.listen(this._onLinkChange);
LinkActions.getLinks();
}
componentWillUnmount(){
LinkStore.unlisten(this._onLinkChange);
}
_onLinkChange(state){
this.setState({
visited: state.links.indexOf(this.props.data.url) >= 0,
});
}
render(){
const {data, position, onCommentPress, ...touchableProps} = this.props;
const {url, type, comments_count, points, time_ago} = data;
const externalLink = !/^item/i.test(url);
const {visited} = this.state;
// Turns out that longPress is not common at all in native iOS apps
// But I actually like this feature on the browser, thus I'm keeping
// this but delay it slightly longer than default 500
const delayLongPress = 1000;
return (
{position}
{data.title}
{externalLink && {domainify(url)}}
{(() => {
if (type == 'job'){
return {time_ago};
} else {
const commentsText = comments_count>0 && · {comments_count} comment{comments_count != 1 && 's'};
return (
{points} point{points != 1 && 's'} by {data.user}
{time_ago}{commentsText}
);
}
})()}
{type != 'job' && (() => {
if (externalLink){
return (
);
} else if (isIOS){
return (
);
}
})()}
);
}
}
================================================
FILE: index.android.js
================================================
'use strict';
import React, { Component } from 'react';
import {
AppRegistry,
StyleSheet,
Navigator,
AppState,
Text,
View,
ToolbarAndroid,
BackAndroid,
Linking,
} from 'react-native';
import StoryActions from './actions/StoryActions';
import StoriesView from './views/StoriesView';
import CommentsView from './views/CommentsView';
import AboutView from './views/AboutView';
import colors from './colors';
const styles = StyleSheet.create({
container: {
flex: 1,
},
wrapper: {
backgroundColor: colors.viewBackgroundColor,
},
toolbar: {
backgroundColor: colors.toolbarBackgroundColor,
height: 56,
elevation: 2,
},
});
let _navigator;
BackAndroid.addEventListener('hardwareBackPress', () => {
if (_navigator.getCurrentRoutes().length == 1){
return false;
}
_navigator.pop();
return true;
});
class HackerWeb extends Component {
constructor(props){
super(props);
this.state = {
currentAppState: AppState.currentState,
};
this._handleAppStateChange = this._handleAppStateChange.bind(this);
}
componentDidMount(){
AppState.addEventListener('change', this._handleAppStateChange);
Linking.getInitialURL().then((url) => {
if (!url) return;
const id = (url.match(/item\?id=([a-z\d]+)/i) || [,null])[1];
if (!id) return;
_navigator.push({
id: 'Comments',
component: CommentsView,
wrapperStyle: styles.wrapper,
passProps: {
data: {id},
}
});
}).catch(() => {});
}
componentWillUnmount(){
AppState.removeEventListener('change', this._handleAppStateChange);
}
_handleAppStateChange(currentAppState){
if (currentAppState == 'active' && this.state.currentAppState != currentAppState){
StoryActions.fetchStoriesIfExpired();
}
this.setState({
currentAppState,
});
}
_navigatorRenderScene(route, navigator){
_navigator = navigator;
switch (route.id){
case 'Stories':
return
{
switch (position){
case 0:
StoryActions.fetchStories();
break;
case 1:
navigator.push({
id: 'About',
});
break;
}
}}
/>
;
case 'Comments':
const { component, title, passProps, rightButtonIcon, onRightButtonPress } = route;
const TheComponent = component;
return
;
case 'About':
return
;
}
}
render(){
return (
Navigator.SceneConfigs.FloatFromBottomAndroid}
/>
);
}
}
AppRegistry.registerComponent('HackerWeb', () => HackerWeb);
================================================
FILE: index.ios.js
================================================
'use strict';
import React, { Component } from 'react';
import {
AppStateIOS,
AppRegistry,
StyleSheet,
Modal,
NavigatorIOS,
View,
Linking,
} from 'react-native';
import StoryActions from './actions/StoryActions';
import StoriesView from './views/StoriesView';
import AboutView from './views/AboutView';
import CommentsView from './views/CommentsView';
import colors from './colors';
const styles = StyleSheet.create({
container: {
flex: 1,
},
wrapper: {
backgroundColor: colors.viewBackgroundColor,
},
});
class App extends Component {
constructor(props){
super(props);
this.state = {
currentAppState: AppStateIOS.currentState,
isAboutVisible: false,
};
this._reloadCount = 0;
this._reloadCountTimeout = null;
this._handleAppStateChange = this._handleAppStateChange.bind(this);
this._handleOpenURL = this._handleOpenURL.bind(this);
}
componentDidMount(){
AppStateIOS.addEventListener('change', this._handleAppStateChange);
Linking.addEventListener('url', this._handleOpenURL);
}
componentWillUnmount(){
AppStateIOS.removeEventListener('change', this._handleAppStateChange);
Linking.removeEventListener('url', this._handleOpenURL);
}
_handleAppStateChange(currentAppState){
if (currentAppState == 'active' && this.state.currentAppState != currentAppState){
StoryActions.fetchStoriesIfExpired();
}
this.setState({
currentAppState,
});
}
_handleOpenURL(e){
const {url} = e;
if (!url) return;
const id = (url.match(/item\?id=([a-z\d]+)/i) || [,null])[1];
if (!id) return;
this.refs.nav.push({
component: CommentsView,
wrapperStyle: styles.wrapper,
passProps: {
data: {id}
},
});
}
_showAbout(){
this.setState({
isAboutVisible: true,
});
}
_hideAbout(){
this.setState({
isAboutVisible: false,
});
}
render(){
const {isAboutVisible} = this.state;
return (
{
// For the compulsive-type of people who likes to press Reload multiple times
this._reloadCount++;
clearTimeout(this._reloadCountTimeout);
if (this._reloadCount >= 3){
StoryActions.flush();
this._reloadCount = 0;
} else {
this._reloadCountTimeout = setTimeout(() => this._reloadCount = 0, 3000);
}
StoryActions.fetchStories();
},
}}/>
);
}
}
AppRegistry.registerComponent('HackerWeb', () => App);
================================================
FILE: ios/HackerWeb/AppDelegate.h
================================================
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
#import
@interface AppDelegate : UIResponder
@property (nonatomic, strong) UIWindow *window;
@end
================================================
FILE: ios/HackerWeb/AppDelegate.m
================================================
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
#import "AppDelegate.h"
#import "RCTRootView.h"
#import "RCTLinkingManager.h"
@implementation AppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
NSURL *jsCodeLocation;
/**
* Loading JavaScript code - uncomment the one you want.
*
* OPTION 1
* Load from development server. Start the server from the repository root:
*
* $ npm start
*
* To run on device, change `localhost` to the IP address of your computer
* (you can get this by typing `ifconfig` into the terminal and selecting the
* `inet` value under `en0:`) and make sure your computer and iOS device are
* on the same Wi-Fi network.
*/
jsCodeLocation = [NSURL URLWithString:@"http://localhost:8081/index.ios.bundle?platform=ios&dev=true"];
/**
* OPTION 2
* Load from pre-bundled file on disk. The static bundle is automatically
* generated by the "Bundle React Native code and images" build step when
* running the project on an actual device or running the project on the
* simulator in the "Release" build configuration.
*/
// jsCodeLocation = [[NSBundle mainBundle] URLForResource:@"main" withExtension:@"jsbundle"];
RCTRootView *rootView = [[RCTRootView alloc] initWithBundleURL:jsCodeLocation
moduleName:@"HackerWeb"
initialProperties:nil
launchOptions:launchOptions];
rootView.backgroundColor = [[UIColor alloc] initWithRed:1.0f green:1.0f blue:1.0f alpha:1];
self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
UIViewController *rootViewController = [UIViewController new];
rootViewController.view = rootView;
self.window.rootViewController = rootViewController;
[self.window makeKeyAndVisible];
return YES;
}
- (BOOL)application:(UIApplication *)application openURL:(NSURL *)url
sourceApplication:(NSString *)sourceApplication annotation:(id)annotation
{
return [RCTLinkingManager application:application openURL:url
sourceApplication:sourceApplication annotation:annotation];
}
@end
================================================
FILE: ios/HackerWeb/Base.lproj/LaunchScreen.xib
================================================
================================================
FILE: ios/HackerWeb/Images.xcassets/AppIcon.appiconset/Contents.json
================================================
{
"images" : [
{
"size" : "29x29",
"idiom" : "iphone",
"filename" : "58.png",
"scale" : "2x"
},
{
"size" : "29x29",
"idiom" : "iphone",
"filename" : "97.png",
"scale" : "3x"
},
{
"size" : "40x40",
"idiom" : "iphone",
"filename" : "80.png",
"scale" : "2x"
},
{
"size" : "40x40",
"idiom" : "iphone",
"filename" : "120.png",
"scale" : "3x"
},
{
"size" : "60x60",
"idiom" : "iphone",
"filename" : "120-1.png",
"scale" : "2x"
},
{
"size" : "60x60",
"idiom" : "iphone",
"filename" : "180.png",
"scale" : "3x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}
================================================
FILE: ios/HackerWeb/Images.xcassets/Contents.json
================================================
{
"info" : {
"version" : 1,
"author" : "xcode"
}
}
================================================
FILE: ios/HackerWeb/Images.xcassets/app-icon.imageset/Contents.json
================================================
{
"images" : [
{
"idiom" : "universal",
"filename" : "app-icon.png",
"scale" : "1x"
},
{
"idiom" : "universal",
"filename" : "app-icon@2x.png",
"scale" : "2x"
},
{
"idiom" : "universal",
"filename" : "app-icon@3x.png",
"scale" : "3x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}
================================================
FILE: ios/HackerWeb/Info.plist
================================================
CFBundleDevelopmentRegion
en
CFBundleExecutable
$(EXECUTABLE_NAME)
CFBundleIdentifier
$(PRODUCT_BUNDLE_IDENTIFIER)
CFBundleInfoDictionaryVersion
6.0
CFBundleName
$(PRODUCT_NAME)
CFBundlePackageType
APPL
CFBundleShortVersionString
1.1
CFBundleSignature
????
CFBundleURLTypes
CFBundleTypeRole
Editor
CFBundleURLName
$(PRODUCT_BUNDLE_IDENTIFIER)
CFBundleURLSchemes
hackerweb
CFBundleVersion
5
LSRequiresIPhoneOS
NSAppTransportSecurity
NSAllowsArbitraryLoads
NSLocationWhenInUseUsageDescription
UILaunchStoryboardName
LaunchScreen
UIRequiredDeviceCapabilities
armv7
UISupportedInterfaceOrientations
UIInterfaceOrientationPortrait
UIViewControllerBasedStatusBarAppearance
NSLocationWhenInUseUsageDescription
NSAppTransportSecurity
NSAllowsArbitraryLoads
================================================
FILE: ios/HackerWeb/main.m
================================================
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
#import
#import "AppDelegate.h"
int main(int argc, char * argv[]) {
@autoreleasepool {
return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
}
}
================================================
FILE: ios/HackerWeb.xcodeproj/project.pbxproj
================================================
// !$*UTF8*$!
{
archiveVersion = 1;
classes = {
};
objectVersion = 46;
objects = {
/* Begin PBXBuildFile section */
00C302E51ABCBA2D00DB3ED1 /* libRCTActionSheet.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 00C302AC1ABCB8CE00DB3ED1 /* libRCTActionSheet.a */; };
00C302E71ABCBA2D00DB3ED1 /* libRCTGeolocation.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 00C302BA1ABCB90400DB3ED1 /* libRCTGeolocation.a */; };
00C302E81ABCBA2D00DB3ED1 /* libRCTImage.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 00C302C01ABCB91800DB3ED1 /* libRCTImage.a */; };
00C302E91ABCBA2D00DB3ED1 /* libRCTNetwork.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 00C302DC1ABCB9D200DB3ED1 /* libRCTNetwork.a */; };
00C302EA1ABCBA2D00DB3ED1 /* libRCTVibration.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 00C302E41ABCB9EE00DB3ED1 /* libRCTVibration.a */; };
00E356F31AD99517003FC87E /* HackerWebTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 00E356F21AD99517003FC87E /* HackerWebTests.m */; };
133E29F31AD74F7200F7D852 /* libRCTLinking.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 78C398B91ACF4ADC00677621 /* libRCTLinking.a */; };
139105C61AF99C1200B5F7CC /* libRCTSettings.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 139105C11AF99BAD00B5F7CC /* libRCTSettings.a */; };
139FDEF61B0652A700C62182 /* libRCTWebSocket.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 139FDEF41B06529B00C62182 /* libRCTWebSocket.a */; };
13B07FBC1A68108700A75B9A /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 13B07FB01A68108700A75B9A /* AppDelegate.m */; };
13B07FBD1A68108700A75B9A /* LaunchScreen.xib in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB11A68108700A75B9A /* LaunchScreen.xib */; };
13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB51A68108700A75B9A /* Images.xcassets */; };
13B07FC11A68108700A75B9A /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 13B07FB71A68108700A75B9A /* main.m */; };
146834051AC3E58100842450 /* libReact.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 146834041AC3E56700842450 /* libReact.a */; };
832341BD1AAA6AB300B99B32 /* libRCTText.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 832341B51AAA6A8300B99B32 /* libRCTText.a */; };
9B47A38C1C5E057600228361 /* libSafariViewManager.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 9B47A3891C5E056200228361 /* libSafariViewManager.a */; };
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
00C302AB1ABCB8CE00DB3ED1 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 00C302A71ABCB8CE00DB3ED1 /* RCTActionSheet.xcodeproj */;
proxyType = 2;
remoteGlobalIDString = 134814201AA4EA6300B7C361;
remoteInfo = RCTActionSheet;
};
00C302B91ABCB90400DB3ED1 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 00C302B51ABCB90400DB3ED1 /* RCTGeolocation.xcodeproj */;
proxyType = 2;
remoteGlobalIDString = 134814201AA4EA6300B7C361;
remoteInfo = RCTGeolocation;
};
00C302BF1ABCB91800DB3ED1 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 00C302BB1ABCB91800DB3ED1 /* RCTImage.xcodeproj */;
proxyType = 2;
remoteGlobalIDString = 58B5115D1A9E6B3D00147676;
remoteInfo = RCTImage;
};
00C302DB1ABCB9D200DB3ED1 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 00C302D31ABCB9D200DB3ED1 /* RCTNetwork.xcodeproj */;
proxyType = 2;
remoteGlobalIDString = 58B511DB1A9E6C8500147676;
remoteInfo = RCTNetwork;
};
00C302E31ABCB9EE00DB3ED1 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 00C302DF1ABCB9EE00DB3ED1 /* RCTVibration.xcodeproj */;
proxyType = 2;
remoteGlobalIDString = 832C81801AAF6DEF007FA2F7;
remoteInfo = RCTVibration;
};
00E356F41AD99517003FC87E /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 83CBB9F71A601CBA00E9B192 /* Project object */;
proxyType = 1;
remoteGlobalIDString = 13B07F861A680F5B00A75B9A;
remoteInfo = HackerWeb;
};
139105C01AF99BAD00B5F7CC /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 139105B61AF99BAD00B5F7CC /* RCTSettings.xcodeproj */;
proxyType = 2;
remoteGlobalIDString = 134814201AA4EA6300B7C361;
remoteInfo = RCTSettings;
};
139FDEF31B06529B00C62182 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 139FDEE61B06529A00C62182 /* RCTWebSocket.xcodeproj */;
proxyType = 2;
remoteGlobalIDString = 3C86DF461ADF2C930047B81A;
remoteInfo = RCTWebSocket;
};
146834031AC3E56700842450 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 146833FF1AC3E56700842450 /* React.xcodeproj */;
proxyType = 2;
remoteGlobalIDString = 83CBBA2E1A601D0E00E9B192;
remoteInfo = React;
};
78C398B81ACF4ADC00677621 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 78C398B01ACF4ADC00677621 /* RCTLinking.xcodeproj */;
proxyType = 2;
remoteGlobalIDString = 134814201AA4EA6300B7C361;
remoteInfo = RCTLinking;
};
832341B41AAA6A8300B99B32 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 832341B01AAA6A8300B99B32 /* RCTText.xcodeproj */;
proxyType = 2;
remoteGlobalIDString = 58B5119B1A9E6C1200147676;
remoteInfo = RCTText;
};
9B47A3881C5E056200228361 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 9B47A3841C5E056200228361 /* SafariViewManager.xcodeproj */;
proxyType = 2;
remoteGlobalIDString = 134814201AA4EA6300B7C361;
remoteInfo = SafariViewManager;
};
/* End PBXContainerItemProxy section */
/* Begin PBXFileReference section */
008F07F21AC5B25A0029DE68 /* main.jsbundle */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; name = main.jsbundle; path = main.jsbundle; sourceTree = ""; };
00C302A71ABCB8CE00DB3ED1 /* RCTActionSheet.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTActionSheet.xcodeproj; path = ../node_modules/react-native/Libraries/ActionSheetIOS/RCTActionSheet.xcodeproj; sourceTree = ""; };
00C302B51ABCB90400DB3ED1 /* RCTGeolocation.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTGeolocation.xcodeproj; path = ../node_modules/react-native/Libraries/Geolocation/RCTGeolocation.xcodeproj; sourceTree = ""; };
00C302BB1ABCB91800DB3ED1 /* RCTImage.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTImage.xcodeproj; path = ../node_modules/react-native/Libraries/Image/RCTImage.xcodeproj; sourceTree = ""; };
00C302D31ABCB9D200DB3ED1 /* RCTNetwork.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTNetwork.xcodeproj; path = ../node_modules/react-native/Libraries/Network/RCTNetwork.xcodeproj; sourceTree = ""; };
00C302DF1ABCB9EE00DB3ED1 /* RCTVibration.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTVibration.xcodeproj; path = ../node_modules/react-native/Libraries/Vibration/RCTVibration.xcodeproj; sourceTree = ""; };
00E356EE1AD99517003FC87E /* HackerWebTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = HackerWebTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
00E356F11AD99517003FC87E /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
00E356F21AD99517003FC87E /* HackerWebTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = HackerWebTests.m; sourceTree = ""; };
139105B61AF99BAD00B5F7CC /* RCTSettings.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTSettings.xcodeproj; path = ../node_modules/react-native/Libraries/Settings/RCTSettings.xcodeproj; sourceTree = ""; };
139FDEE61B06529A00C62182 /* RCTWebSocket.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTWebSocket.xcodeproj; path = ../node_modules/react-native/Libraries/WebSocket/RCTWebSocket.xcodeproj; sourceTree = ""; };
13B07F961A680F5B00A75B9A /* HackerWeb.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = HackerWeb.app; sourceTree = BUILT_PRODUCTS_DIR; };
13B07FAF1A68108700A75B9A /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = AppDelegate.h; path = HackerWeb/AppDelegate.h; sourceTree = ""; };
13B07FB01A68108700A75B9A /* AppDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = AppDelegate.m; path = HackerWeb/AppDelegate.m; sourceTree = ""; };
13B07FB21A68108700A75B9A /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/LaunchScreen.xib; sourceTree = ""; };
13B07FB51A68108700A75B9A /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Images.xcassets; path = HackerWeb/Images.xcassets; sourceTree = ""; };
13B07FB61A68108700A75B9A /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = Info.plist; path = HackerWeb/Info.plist; sourceTree = ""; };
13B07FB71A68108700A75B9A /* main.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = main.m; path = HackerWeb/main.m; sourceTree = ""; };
146833FF1AC3E56700842450 /* React.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = React.xcodeproj; path = ../node_modules/react-native/React/React.xcodeproj; sourceTree = ""; };
78C398B01ACF4ADC00677621 /* RCTLinking.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTLinking.xcodeproj; path = ../node_modules/react-native/Libraries/LinkingIOS/RCTLinking.xcodeproj; sourceTree = ""; };
832341B01AAA6A8300B99B32 /* RCTText.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTText.xcodeproj; path = ../node_modules/react-native/Libraries/Text/RCTText.xcodeproj; sourceTree = ""; };
9B47A3841C5E056200228361 /* SafariViewManager.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = SafariViewManager.xcodeproj; path = "../node_modules/react-native-safari-view/SafariViewManager.xcodeproj"; sourceTree = ""; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
00E356EB1AD99517003FC87E /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
13B07F8C1A680F5B00A75B9A /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
9B47A38C1C5E057600228361 /* libSafariViewManager.a in Frameworks */,
146834051AC3E58100842450 /* libReact.a in Frameworks */,
00C302E51ABCBA2D00DB3ED1 /* libRCTActionSheet.a in Frameworks */,
00C302E71ABCBA2D00DB3ED1 /* libRCTGeolocation.a in Frameworks */,
00C302E81ABCBA2D00DB3ED1 /* libRCTImage.a in Frameworks */,
133E29F31AD74F7200F7D852 /* libRCTLinking.a in Frameworks */,
00C302E91ABCBA2D00DB3ED1 /* libRCTNetwork.a in Frameworks */,
139105C61AF99C1200B5F7CC /* libRCTSettings.a in Frameworks */,
832341BD1AAA6AB300B99B32 /* libRCTText.a in Frameworks */,
00C302EA1ABCBA2D00DB3ED1 /* libRCTVibration.a in Frameworks */,
139FDEF61B0652A700C62182 /* libRCTWebSocket.a in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
00C302A81ABCB8CE00DB3ED1 /* Products */ = {
isa = PBXGroup;
children = (
00C302AC1ABCB8CE00DB3ED1 /* libRCTActionSheet.a */,
);
name = Products;
sourceTree = "";
};
00C302B61ABCB90400DB3ED1 /* Products */ = {
isa = PBXGroup;
children = (
00C302BA1ABCB90400DB3ED1 /* libRCTGeolocation.a */,
);
name = Products;
sourceTree = "";
};
00C302BC1ABCB91800DB3ED1 /* Products */ = {
isa = PBXGroup;
children = (
00C302C01ABCB91800DB3ED1 /* libRCTImage.a */,
);
name = Products;
sourceTree = "";
};
00C302D41ABCB9D200DB3ED1 /* Products */ = {
isa = PBXGroup;
children = (
00C302DC1ABCB9D200DB3ED1 /* libRCTNetwork.a */,
);
name = Products;
sourceTree = "";
};
00C302E01ABCB9EE00DB3ED1 /* Products */ = {
isa = PBXGroup;
children = (
00C302E41ABCB9EE00DB3ED1 /* libRCTVibration.a */,
);
name = Products;
sourceTree = "";
};
00E356EF1AD99517003FC87E /* HackerWebTests */ = {
isa = PBXGroup;
children = (
00E356F21AD99517003FC87E /* HackerWebTests.m */,
00E356F01AD99517003FC87E /* Supporting Files */,
);
path = HackerWebTests;
sourceTree = "";
};
00E356F01AD99517003FC87E /* Supporting Files */ = {
isa = PBXGroup;
children = (
00E356F11AD99517003FC87E /* Info.plist */,
);
name = "Supporting Files";
sourceTree = "";
};
139105B71AF99BAD00B5F7CC /* Products */ = {
isa = PBXGroup;
children = (
139105C11AF99BAD00B5F7CC /* libRCTSettings.a */,
);
name = Products;
sourceTree = "";
};
139FDEE71B06529A00C62182 /* Products */ = {
isa = PBXGroup;
children = (
139FDEF41B06529B00C62182 /* libRCTWebSocket.a */,
);
name = Products;
sourceTree = "";
};
13B07FAE1A68108700A75B9A /* HackerWeb */ = {
isa = PBXGroup;
children = (
008F07F21AC5B25A0029DE68 /* main.jsbundle */,
13B07FAF1A68108700A75B9A /* AppDelegate.h */,
13B07FB01A68108700A75B9A /* AppDelegate.m */,
13B07FB51A68108700A75B9A /* Images.xcassets */,
13B07FB61A68108700A75B9A /* Info.plist */,
13B07FB11A68108700A75B9A /* LaunchScreen.xib */,
13B07FB71A68108700A75B9A /* main.m */,
);
name = HackerWeb;
sourceTree = "";
};
146834001AC3E56700842450 /* Products */ = {
isa = PBXGroup;
children = (
146834041AC3E56700842450 /* libReact.a */,
);
name = Products;
sourceTree = "";
};
78C398B11ACF4ADC00677621 /* Products */ = {
isa = PBXGroup;
children = (
78C398B91ACF4ADC00677621 /* libRCTLinking.a */,
);
name = Products;
sourceTree = "";
};
832341AE1AAA6A7D00B99B32 /* Libraries */ = {
isa = PBXGroup;
children = (
9B47A3841C5E056200228361 /* SafariViewManager.xcodeproj */,
146833FF1AC3E56700842450 /* React.xcodeproj */,
00C302A71ABCB8CE00DB3ED1 /* RCTActionSheet.xcodeproj */,
00C302B51ABCB90400DB3ED1 /* RCTGeolocation.xcodeproj */,
00C302BB1ABCB91800DB3ED1 /* RCTImage.xcodeproj */,
78C398B01ACF4ADC00677621 /* RCTLinking.xcodeproj */,
00C302D31ABCB9D200DB3ED1 /* RCTNetwork.xcodeproj */,
139105B61AF99BAD00B5F7CC /* RCTSettings.xcodeproj */,
832341B01AAA6A8300B99B32 /* RCTText.xcodeproj */,
00C302DF1ABCB9EE00DB3ED1 /* RCTVibration.xcodeproj */,
139FDEE61B06529A00C62182 /* RCTWebSocket.xcodeproj */,
);
name = Libraries;
sourceTree = "";
};
832341B11AAA6A8300B99B32 /* Products */ = {
isa = PBXGroup;
children = (
832341B51AAA6A8300B99B32 /* libRCTText.a */,
);
name = Products;
sourceTree = "";
};
83CBB9F61A601CBA00E9B192 = {
isa = PBXGroup;
children = (
13B07FAE1A68108700A75B9A /* HackerWeb */,
832341AE1AAA6A7D00B99B32 /* Libraries */,
00E356EF1AD99517003FC87E /* HackerWebTests */,
83CBBA001A601CBA00E9B192 /* Products */,
);
indentWidth = 2;
sourceTree = "";
tabWidth = 2;
};
83CBBA001A601CBA00E9B192 /* Products */ = {
isa = PBXGroup;
children = (
13B07F961A680F5B00A75B9A /* HackerWeb.app */,
00E356EE1AD99517003FC87E /* HackerWebTests.xctest */,
);
name = Products;
sourceTree = "";
};
9B47A3851C5E056200228361 /* Products */ = {
isa = PBXGroup;
children = (
9B47A3891C5E056200228361 /* libSafariViewManager.a */,
);
name = Products;
sourceTree = "";
};
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
00E356ED1AD99517003FC87E /* HackerWebTests */ = {
isa = PBXNativeTarget;
buildConfigurationList = 00E357021AD99517003FC87E /* Build configuration list for PBXNativeTarget "HackerWebTests" */;
buildPhases = (
00E356EA1AD99517003FC87E /* Sources */,
00E356EB1AD99517003FC87E /* Frameworks */,
00E356EC1AD99517003FC87E /* Resources */,
);
buildRules = (
);
dependencies = (
00E356F51AD99517003FC87E /* PBXTargetDependency */,
);
name = HackerWebTests;
productName = HackerWebTests;
productReference = 00E356EE1AD99517003FC87E /* HackerWebTests.xctest */;
productType = "com.apple.product-type.bundle.unit-test";
};
13B07F861A680F5B00A75B9A /* HackerWeb */ = {
isa = PBXNativeTarget;
buildConfigurationList = 13B07F931A680F5B00A75B9A /* Build configuration list for PBXNativeTarget "HackerWeb" */;
buildPhases = (
13B07F871A680F5B00A75B9A /* Sources */,
13B07F8C1A680F5B00A75B9A /* Frameworks */,
13B07F8E1A680F5B00A75B9A /* Resources */,
00DD1BFF1BD5951E006B06BC /* Bundle React Native code and images */,
);
buildRules = (
);
dependencies = (
);
name = HackerWeb;
productName = "Hello World";
productReference = 13B07F961A680F5B00A75B9A /* HackerWeb.app */;
productType = "com.apple.product-type.application";
};
/* End PBXNativeTarget section */
/* Begin PBXProject section */
83CBB9F71A601CBA00E9B192 /* Project object */ = {
isa = PBXProject;
attributes = {
LastUpgradeCheck = 0610;
ORGANIZATIONNAME = Facebook;
TargetAttributes = {
00E356ED1AD99517003FC87E = {
CreatedOnToolsVersion = 6.2;
TestTargetID = 13B07F861A680F5B00A75B9A;
};
13B07F861A680F5B00A75B9A = {
DevelopmentTeam = 7WDVUPXAPH;
};
};
};
buildConfigurationList = 83CBB9FA1A601CBA00E9B192 /* Build configuration list for PBXProject "HackerWeb" */;
compatibilityVersion = "Xcode 3.2";
developmentRegion = English;
hasScannedForEncodings = 0;
knownRegions = (
en,
Base,
);
mainGroup = 83CBB9F61A601CBA00E9B192;
productRefGroup = 83CBBA001A601CBA00E9B192 /* Products */;
projectDirPath = "";
projectReferences = (
{
ProductGroup = 00C302A81ABCB8CE00DB3ED1 /* Products */;
ProjectRef = 00C302A71ABCB8CE00DB3ED1 /* RCTActionSheet.xcodeproj */;
},
{
ProductGroup = 00C302B61ABCB90400DB3ED1 /* Products */;
ProjectRef = 00C302B51ABCB90400DB3ED1 /* RCTGeolocation.xcodeproj */;
},
{
ProductGroup = 00C302BC1ABCB91800DB3ED1 /* Products */;
ProjectRef = 00C302BB1ABCB91800DB3ED1 /* RCTImage.xcodeproj */;
},
{
ProductGroup = 78C398B11ACF4ADC00677621 /* Products */;
ProjectRef = 78C398B01ACF4ADC00677621 /* RCTLinking.xcodeproj */;
},
{
ProductGroup = 00C302D41ABCB9D200DB3ED1 /* Products */;
ProjectRef = 00C302D31ABCB9D200DB3ED1 /* RCTNetwork.xcodeproj */;
},
{
ProductGroup = 139105B71AF99BAD00B5F7CC /* Products */;
ProjectRef = 139105B61AF99BAD00B5F7CC /* RCTSettings.xcodeproj */;
},
{
ProductGroup = 832341B11AAA6A8300B99B32 /* Products */;
ProjectRef = 832341B01AAA6A8300B99B32 /* RCTText.xcodeproj */;
},
{
ProductGroup = 00C302E01ABCB9EE00DB3ED1 /* Products */;
ProjectRef = 00C302DF1ABCB9EE00DB3ED1 /* RCTVibration.xcodeproj */;
},
{
ProductGroup = 139FDEE71B06529A00C62182 /* Products */;
ProjectRef = 139FDEE61B06529A00C62182 /* RCTWebSocket.xcodeproj */;
},
{
ProductGroup = 146834001AC3E56700842450 /* Products */;
ProjectRef = 146833FF1AC3E56700842450 /* React.xcodeproj */;
},
{
ProductGroup = 9B47A3851C5E056200228361 /* Products */;
ProjectRef = 9B47A3841C5E056200228361 /* SafariViewManager.xcodeproj */;
},
);
projectRoot = "";
targets = (
13B07F861A680F5B00A75B9A /* HackerWeb */,
00E356ED1AD99517003FC87E /* HackerWebTests */,
);
};
/* End PBXProject section */
/* Begin PBXReferenceProxy section */
00C302AC1ABCB8CE00DB3ED1 /* libRCTActionSheet.a */ = {
isa = PBXReferenceProxy;
fileType = archive.ar;
path = libRCTActionSheet.a;
remoteRef = 00C302AB1ABCB8CE00DB3ED1 /* PBXContainerItemProxy */;
sourceTree = BUILT_PRODUCTS_DIR;
};
00C302BA1ABCB90400DB3ED1 /* libRCTGeolocation.a */ = {
isa = PBXReferenceProxy;
fileType = archive.ar;
path = libRCTGeolocation.a;
remoteRef = 00C302B91ABCB90400DB3ED1 /* PBXContainerItemProxy */;
sourceTree = BUILT_PRODUCTS_DIR;
};
00C302C01ABCB91800DB3ED1 /* libRCTImage.a */ = {
isa = PBXReferenceProxy;
fileType = archive.ar;
path = libRCTImage.a;
remoteRef = 00C302BF1ABCB91800DB3ED1 /* PBXContainerItemProxy */;
sourceTree = BUILT_PRODUCTS_DIR;
};
00C302DC1ABCB9D200DB3ED1 /* libRCTNetwork.a */ = {
isa = PBXReferenceProxy;
fileType = archive.ar;
path = libRCTNetwork.a;
remoteRef = 00C302DB1ABCB9D200DB3ED1 /* PBXContainerItemProxy */;
sourceTree = BUILT_PRODUCTS_DIR;
};
00C302E41ABCB9EE00DB3ED1 /* libRCTVibration.a */ = {
isa = PBXReferenceProxy;
fileType = archive.ar;
path = libRCTVibration.a;
remoteRef = 00C302E31ABCB9EE00DB3ED1 /* PBXContainerItemProxy */;
sourceTree = BUILT_PRODUCTS_DIR;
};
139105C11AF99BAD00B5F7CC /* libRCTSettings.a */ = {
isa = PBXReferenceProxy;
fileType = archive.ar;
path = libRCTSettings.a;
remoteRef = 139105C01AF99BAD00B5F7CC /* PBXContainerItemProxy */;
sourceTree = BUILT_PRODUCTS_DIR;
};
139FDEF41B06529B00C62182 /* libRCTWebSocket.a */ = {
isa = PBXReferenceProxy;
fileType = archive.ar;
path = libRCTWebSocket.a;
remoteRef = 139FDEF31B06529B00C62182 /* PBXContainerItemProxy */;
sourceTree = BUILT_PRODUCTS_DIR;
};
146834041AC3E56700842450 /* libReact.a */ = {
isa = PBXReferenceProxy;
fileType = archive.ar;
path = libReact.a;
remoteRef = 146834031AC3E56700842450 /* PBXContainerItemProxy */;
sourceTree = BUILT_PRODUCTS_DIR;
};
78C398B91ACF4ADC00677621 /* libRCTLinking.a */ = {
isa = PBXReferenceProxy;
fileType = archive.ar;
path = libRCTLinking.a;
remoteRef = 78C398B81ACF4ADC00677621 /* PBXContainerItemProxy */;
sourceTree = BUILT_PRODUCTS_DIR;
};
832341B51AAA6A8300B99B32 /* libRCTText.a */ = {
isa = PBXReferenceProxy;
fileType = archive.ar;
path = libRCTText.a;
remoteRef = 832341B41AAA6A8300B99B32 /* PBXContainerItemProxy */;
sourceTree = BUILT_PRODUCTS_DIR;
};
9B47A3891C5E056200228361 /* libSafariViewManager.a */ = {
isa = PBXReferenceProxy;
fileType = archive.ar;
path = libSafariViewManager.a;
remoteRef = 9B47A3881C5E056200228361 /* PBXContainerItemProxy */;
sourceTree = BUILT_PRODUCTS_DIR;
};
/* End PBXReferenceProxy section */
/* Begin PBXResourcesBuildPhase section */
00E356EC1AD99517003FC87E /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
13B07F8E1A680F5B00A75B9A /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */,
13B07FBD1A68108700A75B9A /* LaunchScreen.xib in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXResourcesBuildPhase section */
/* Begin PBXShellScriptBuildPhase section */
00DD1BFF1BD5951E006B06BC /* Bundle React Native code and images */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputPaths = (
);
name = "Bundle React Native code and images";
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "export NODE_BINARY=node\n../node_modules/react-native/packager/react-native-xcode.sh";
showEnvVarsInLog = 1;
};
/* End PBXShellScriptBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
00E356EA1AD99517003FC87E /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
00E356F31AD99517003FC87E /* HackerWebTests.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
13B07F871A680F5B00A75B9A /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
13B07FBC1A68108700A75B9A /* AppDelegate.m in Sources */,
13B07FC11A68108700A75B9A /* main.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXSourcesBuildPhase section */
/* Begin PBXTargetDependency section */
00E356F51AD99517003FC87E /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
target = 13B07F861A680F5B00A75B9A /* HackerWeb */;
targetProxy = 00E356F41AD99517003FC87E /* PBXContainerItemProxy */;
};
/* End PBXTargetDependency section */
/* Begin PBXVariantGroup section */
13B07FB11A68108700A75B9A /* LaunchScreen.xib */ = {
isa = PBXVariantGroup;
children = (
13B07FB21A68108700A75B9A /* Base */,
);
name = LaunchScreen.xib;
path = HackerWeb;
sourceTree = "";
};
/* End PBXVariantGroup section */
/* Begin XCBuildConfiguration section */
00E356F61AD99517003FC87E /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
FRAMEWORK_SEARCH_PATHS = (
"$(SDKROOT)/Developer/Library/Frameworks",
"$(inherited)",
);
GCC_PREPROCESSOR_DEFINITIONS = (
"DEBUG=1",
"$(inherited)",
);
INFOPLIST_FILE = HackerWebTests/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 8.2;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
PRODUCT_NAME = "$(TARGET_NAME)";
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/HackerWeb.app/HackerWeb";
};
name = Debug;
};
00E356F71AD99517003FC87E /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
COPY_PHASE_STRIP = NO;
FRAMEWORK_SEARCH_PATHS = (
"$(SDKROOT)/Developer/Library/Frameworks",
"$(inherited)",
);
INFOPLIST_FILE = HackerWebTests/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 8.2;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
PRODUCT_NAME = "$(TARGET_NAME)";
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/HackerWeb.app/HackerWeb";
};
name = Release;
};
13B07F941A680F5B00A75B9A /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CODE_SIGN_IDENTITY = "iPhone Developer";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
DEAD_CODE_STRIPPING = NO;
HEADER_SEARCH_PATHS = (
"$(inherited)",
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include,
"$(SRCROOT)/../node_modules/react-native/React/**",
"$(SRCROOT)/../node_modules/react-native/Libraries/**",
);
INFOPLIST_FILE = "HackerWeb/Info.plist";
IPHONEOS_DEPLOYMENT_TARGET = 9.0;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
OTHER_LDFLAGS = (
"-ObjC",
"-lc++",
);
PRODUCT_BUNDLE_IDENTIFIER = cheeaun.hackerweb;
PRODUCT_NAME = HackerWeb;
PROVISIONING_PROFILE = "";
};
name = Debug;
};
13B07F951A680F5B00A75B9A /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CODE_SIGN_IDENTITY = "iPhone Developer";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
DEAD_CODE_STRIPPING = NO;
HEADER_SEARCH_PATHS = (
"$(inherited)",
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include,
"$(SRCROOT)/../node_modules/react-native/React/**",
"$(SRCROOT)/../node_modules/react-native/Libraries/**",
);
INFOPLIST_FILE = "HackerWeb/Info.plist";
IPHONEOS_DEPLOYMENT_TARGET = 9.0;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
OTHER_LDFLAGS = (
"-ObjC",
"-lc++",
);
PRODUCT_BUNDLE_IDENTIFIER = cheeaun.hackerweb;
PRODUCT_NAME = HackerWeb;
PROVISIONING_PROFILE = "";
};
name = Release;
};
83CBBA201A601CBA00E9B192 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
COPY_PHASE_STRIP = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_DYNAMIC_NO_PIC = NO;
GCC_OPTIMIZATION_LEVEL = 0;
GCC_PREPROCESSOR_DEFINITIONS = (
"DEBUG=1",
"$(inherited)",
);
GCC_SYMBOLS_PRIVATE_EXTERN = NO;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
HEADER_SEARCH_PATHS = (
"$(inherited)",
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include,
"$(SRCROOT)/../node_modules/react-native/React/**",
);
IPHONEOS_DEPLOYMENT_TARGET = 9.0;
MTL_ENABLE_DEBUG_INFO = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = iphoneos;
};
name = Debug;
};
83CBBA211A601CBA00E9B192 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
COPY_PHASE_STRIP = YES;
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
HEADER_SEARCH_PATHS = (
"$(inherited)",
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include,
"$(SRCROOT)/../node_modules/react-native/React/**",
);
IPHONEOS_DEPLOYMENT_TARGET = 9.0;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos;
VALIDATE_PRODUCT = YES;
};
name = Release;
};
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
00E357021AD99517003FC87E /* Build configuration list for PBXNativeTarget "HackerWebTests" */ = {
isa = XCConfigurationList;
buildConfigurations = (
00E356F61AD99517003FC87E /* Debug */,
00E356F71AD99517003FC87E /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
13B07F931A680F5B00A75B9A /* Build configuration list for PBXNativeTarget "HackerWeb" */ = {
isa = XCConfigurationList;
buildConfigurations = (
13B07F941A680F5B00A75B9A /* Debug */,
13B07F951A680F5B00A75B9A /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
83CBB9FA1A601CBA00E9B192 /* Build configuration list for PBXProject "HackerWeb" */ = {
isa = XCConfigurationList;
buildConfigurations = (
83CBBA201A601CBA00E9B192 /* Debug */,
83CBBA211A601CBA00E9B192 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
/* End XCConfigurationList section */
};
rootObject = 83CBB9F71A601CBA00E9B192 /* Project object */;
}
================================================
FILE: ios/HackerWeb.xcodeproj/xcshareddata/xcschemes/HackerWeb.xcscheme
================================================
================================================
FILE: ios/HackerWebTests/HackerWebTests.m
================================================
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
#import
#import
#import "RCTLog.h"
#import "RCTRootView.h"
#define TIMEOUT_SECONDS 600
#define TEXT_TO_LOOK_FOR @"Welcome to React Native!"
@interface HackerWebTests : XCTestCase
@end
@implementation HackerWebTests
- (BOOL)findSubviewInView:(UIView *)view matching:(BOOL(^)(UIView *view))test
{
if (test(view)) {
return YES;
}
for (UIView *subview in [view subviews]) {
if ([self findSubviewInView:subview matching:test]) {
return YES;
}
}
return NO;
}
- (void)testRendersWelcomeScreen
{
UIViewController *vc = [[[[UIApplication sharedApplication] delegate] window] rootViewController];
NSDate *date = [NSDate dateWithTimeIntervalSinceNow:TIMEOUT_SECONDS];
BOOL foundElement = NO;
__block NSString *redboxError = nil;
RCTSetLogFunction(^(RCTLogLevel level, RCTLogSource source, NSString *fileName, NSNumber *lineNumber, NSString *message) {
if (level >= RCTLogLevelError) {
redboxError = message;
}
});
while ([date timeIntervalSinceNow] > 0 && !foundElement && !redboxError) {
[[NSRunLoop mainRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.1]];
[[NSRunLoop mainRunLoop] runMode:NSRunLoopCommonModes beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.1]];
foundElement = [self findSubviewInView:vc.view matching:^BOOL(UIView *view) {
if ([view.accessibilityLabel isEqualToString:TEXT_TO_LOOK_FOR]) {
return YES;
}
return NO;
}];
}
RCTSetLogFunction(RCTDefaultLogFunction);
XCTAssertNil(redboxError, @"RedBox error: %@", redboxError);
XCTAssertTrue(foundElement, @"Couldn't find element with text '%@' in %d seconds", TEXT_TO_LOOK_FOR, TIMEOUT_SECONDS);
}
@end
================================================
FILE: ios/HackerWebTests/Info.plist
================================================
CFBundleDevelopmentRegion
en
CFBundleExecutable
$(EXECUTABLE_NAME)
CFBundleIdentifier
org.reactjs.native.example.$(PRODUCT_NAME:rfc1034identifier)
CFBundleInfoDictionaryVersion
6.0
CFBundleName
$(PRODUCT_NAME)
CFBundlePackageType
BNDL
CFBundleShortVersionString
1.0
CFBundleSignature
????
CFBundleVersion
1
================================================
FILE: package.json
================================================
{
"name": "HackerWeb",
"version": "0.0.1",
"private": true,
"scripts": {
"start": "node_modules/react-native/packager/packager.sh"
},
"dependencies": {
"alt": "~0.18.4",
"buffer": "~4.6.0",
"events": "~1.1.0",
"htmlparser2": "~3.9.0",
"react": "~15.1.0",
"react-addons-pure-render-mixin": "~15.1.0",
"react-native": "~0.27.2",
"react-native-android-share": "https://github.com/haydenth/react-native-android-share.git",
"react-native-cache-store": "~1.0.2",
"react-native-chrome-custom-tabs": "~0.0.2",
"react-native-safari-view": "~0.4.1",
"stream": "~0.0.2",
"url-parse": "~1.1.1"
}
}
================================================
FILE: stores/LinkStore.js
================================================
'use strict';
import alt from '../alt';
import LinkActions from '../actions/LinkActions';
class LinkStore {
constructor(){
this.links = [];
this.bindListeners({
handleGetLinks: LinkActions.GET_LINKS,
handleAddLink: LinkActions.ADD_LINK,
});
}
handleGetLinks(links){
this.links = links;
}
handleAddLink(link){
this.links.push(link);
}
}
export default alt.createStore(LinkStore, 'LinkStore');
================================================
FILE: stores/StoryStore.js
================================================
'use strict';
import alt from '../alt';
import StoryActions from '../actions/StoryActions';
class StoryStore {
constructor(){
this.stories = [];
this.storiesLoading = false;
this.storiesError = false;
this.story = null;
this.storyLoading = false;
this.storyError = false;
this.hasMoreStories = false;
this.bindListeners({
handleFetchStories: StoryActions.FETCH_STORIES,
handleFetchMoreStories: StoryActions.FETCH_MORE_STORIES,
handleUpdateStories: StoryActions.UPDATE_STORIES,
handleUpdateMoreStories: StoryActions.UPDATE_MORE_STORIES,
handleHasMoreStories: StoryActions.HAS_MORE_STORIES,
handleStoriesFailed: StoryActions.STORIES_FAILED,
handleFetchStory: StoryActions.FETCH_STORY,
handleUpdateStory: StoryActions.UPDATE_STORY,
handleStoryFailed: StoryActions.STORY_FAILED,
});
}
handleFetchStories(){
this.storiesLoading = true;
this.storiesError = false;
this.hasMoreStories = false;
}
handleFetchMoreStories(){
}
handleUpdateStories(stories){
this.stories = stories;
this.storiesLoading = false;
this.storiesError = false;
}
handleHasMoreStories(){
this.hasMoreStories = true;
}
handleUpdateMoreStories(stories){
this.stories = this.stories.concat(stories);
}
handleStoriesFailed(error){
this.storiesLoading = false;
this.storiesError = error;
}
handleFetchStory(id){
if (this.story && this.story.id != id){
this.story = null;
this.storyLoading = true;
}
if (!this.story) this.storyLoading = true;
this.storyError = false;
}
handleUpdateStory(story){
this.story = story;
this.storyLoading = false;
this.storyError = false;
// Update story in stories
const stories = this.stories;
for (let i=0, l=stories.length; i {
if (!url) return;
var link = urlparse(url);
var domain = link.hostname.replace(/^www\./, '');
var pathname = link.pathname.replace(/^\//, '').split('/')[0];
var pathnameLen = pathname.length;
var firstPath = domain.length <= 25 && pathnameLen > 3 && pathnameLen <= 15 && /^[^0-9][^.]+$/.test(pathname) ? ('/' + pathname) : '';
return domain + firstPath;
};
================================================
FILE: utils/showActivity.android.js
================================================
'use strict';
import AndroidShare from 'react-native-android-share';
export default (url, message) => {
if (!url) return;
AndroidShare.openChooserWithOptions({
subject: message || url,
text: (message ? (message + ' ') : '') + url,
}, 'Share via');
}
================================================
FILE: utils/showActivity.ios.js
================================================
'use strict';
import { ActionSheetIOS } from 'react-native';
export default (url, message) => {
if (!url) return;
ActionSheetIOS.showShareActionSheetWithOptions({
url: url,
message: message || '',
}, () => {}, () => {});
}
================================================
FILE: utils/showBrowser.android.js
================================================
'use strict';
import ChromeCustomTabsClient from 'react-native-chrome-custom-tabs';
import LinkActions from '../actions/LinkActions';
export default (url) => {
if (!url) return;
ChromeCustomTabsClient.launchCustomTab(url);
// Log link visit to History
setTimeout(() => LinkActions.addLink(url), 1000);
}
================================================
FILE: utils/showBrowser.ios.js
================================================
'use strict';
import { Linking, AlertIOS } from 'react-native';
import SafariView from 'react-native-safari-view';
import LinkActions from '../actions/LinkActions';
export default (url) => {
if (!url) return;
SafariView.isAvailable()
.then(() => {
SafariView.show({
url: url,
});
// Log link visit to History
setTimeout(() => LinkActions.addLink(url), 1000);
})
.catch(() => {
Linking.canOpenURL(url, (supported) => {
if (!supported){
AlertIOS.alert('Can\'t handle URL: ' + url);
} else {
Linking.openURL(url);
// Log link visit to History
setTimeout(() => LinkActions.addLink(url), 1000);
}
});
});
}
================================================
FILE: views/AboutView.js
================================================
'use strict';
import React from 'react';
import {
StyleSheet,
View,
Text,
Image,
ScrollView,
TouchableOpacity,
TouchableNativeFeedback,
Linking,
Platform,
} from 'react-native';
const isIOS = Platform.OS === 'ios';
const CrossTouchable = isIOS ? TouchableOpacity : TouchableNativeFeedback;
// import SafariView from 'react-native-safari-view';
import colors from '../colors';
const hairlineWidth = isIOS ? StyleSheet.hairlineWidth : 1;
const styles = StyleSheet.create({
aboutContainer: {
marginTop: isIOS ? 34 : 0,
paddingVertical: isIOS ? 10 : 32,
paddingHorizontal: isIOS ? 15 : 16,
borderTopWidth: isIOS ? hairlineWidth : 0,
borderTopColor: colors.separatorColor,
borderBottomWidth: hairlineWidth,
borderBottomColor: colors.separatorColor,
backgroundColor: colors.sectionBackgroundColor,
flexDirection: 'row',
},
appIcon: {
width: 60,
height: 60,
marginRight: 10,
borderColor: colors.separatorColor,
borderWidth: hairlineWidth,
borderRadius: isIOS ? 14 : 2,
},
aboutTextContainer: {
flex: 1,
},
aboutHeading: {
color: colors.primaryTextColor,
fontWeight: '500',
fontSize: 17,
},
aboutDescription: {
fontSize: 15,
color: colors.insignificantColor,
},
listContainer: {
marginTop: 34,
borderTopWidth: hairlineWidth,
borderTopColor: colors.separatorColor,
borderBottomWidth: hairlineWidth,
borderBottomColor: colors.separatorColor,
backgroundColor: colors.sectionBackgroundColor,
},
listItem: {
paddingVertical: isIOS ? 13 : 16,
paddingHorizontal: isIOS ? 15 : 16,
},
listItemSeparator: {
marginLeft: 15,
marginTop: -1,
height: hairlineWidth,
backgroundColor: colors.separatorColor,
},
link: {
color: colors.linkColor,
fontSize: isIOS ? 17 : 16,
},
disclaimer: {
paddingVertical: 27,
paddingHorizontal: 15,
},
disclaimerText: {
color: colors.sectionInsignificantColor,
}
});
var linkPress = function(url){
Linking.openURL(url);
/* BUG: Once is open, SafariView can't work anymore.
if (/^mailto:/.test(url)){
// Note: won't work in Simulator because there's no Mail there
Linking.openURL(url);
} else {
SafariView.show({
url: url
});
}
*/
};
function linksContainer(links){
return
{links.map((link, i) => {
return (
{link.text}
{i < links.length-1 && }
);
})}
};
export default (props) => {
return (
HackerWeb
A simply readable Hacker News app.
{linksContainer([
isIOS ? {
text: '🌟 Rate HackerWeb on the App Store',
url: 'http://itunes.apple.com/WebObjects/MZStore.woa/wa/viewContentsUserReviews?id=1084209377&pageNumber=0&sortOrdering=2&type=Purple+Software&mt=8'
} : {
text: '🌟 Rate HackerWeb on Google Play',
url: 'https://play.google.com/store/apps/details?id=cheeaun.hackerweb'
},
{ text: '☕️ Buy me a cup of coffee', url: 'https://donorbox.org/support-cheeaun' },
])}
{linksContainer([
{ text: 'HackerWeb homepage', url: 'https://hackerwebapp.com/' },
{ text: 'Hacker News homepage', url: 'https://news.ycombinator.com/' },
{ text: 'Hacker News FAQ', url: 'https://news.ycombinator.com/newsfaq.html' },
{ text: 'HackerWeb on GitHub', url: 'https://github.com/cheeaun/hackerweb-native' },
{ text: 'Follow @cheeaun', url: 'https://twitter.com/cheeaun' },
{ text: 'Send Feedback', url: 'mailto:cheeaun+hackerweb@gmail.com?subject=HackerWeb feedback' },
])}
Built by Lim Chee Aun.
Not affiliated with Hacker News or YCombinator.
);
}
================================================
FILE: views/CommentsView.js
================================================
'use strict';
import React, { Component } from 'react';
import {
StyleSheet,
ActivityIndicatorIOS,
View,
Text,
TouchableHighlight,
TouchableOpacity,
TouchableWithoutFeedback,
Image,
ActionSheetIOS,
ListView,
LayoutAnimation,
Platform,
} from 'react-native';
const isIOS = Platform.OS === 'ios';
const isAndroid = Platform.OS === 'android';
import ChromeCustomTabsClient from 'react-native-chrome-custom-tabs';
import StoryStore from '../stores/StoryStore';
import StoryActions from '../actions/StoryActions';
import CommentRow from '../components/CommentRow';
import LoadingIndicator from '../components/LoadingIndicator';
import HTMLView from '../components/HTMLView';
import ProgressBar from '../components/ProgressBar';
import showBrowser from '../utils/showBrowser';
import showActivity from '../utils/showActivity';
import domainify from '../utils/domainify';
import colors from '../colors';
const hairlineWidth = isIOS ? StyleSheet.hairlineWidth : 1;
const styles = StyleSheet.create({
viewCommentsBlank: {
flex: 1,
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'center',
padding: 50,
backgroundColor: colors.sectionBackgroundColor,
},
footer: {
borderTopColor: colors.separatorColor,
borderTopWidth: hairlineWidth,
height: 30,
},
errorContainer: {
alignItems: 'center',
},
errorText: {
opacity: .6,
textAlign: 'center',
},
retryText: {
color: colors.linkColor,
textAlign: 'center',
},
noCommentsText: {
opacity: .6,
},
externalArrowIcon: {
width: isIOS ? 12 : 10,
height: isIOS ? 9 : 10,
marginLeft: 2,
marginRight: 4,
opacity: isIOS ? 1 : .54,
},
storyLink: {
backgroundColor: colors.viewBackgroundColor,
},
storyTitle: {
color: colors.primaryTextColor,
fontSize: 17,
},
storySection: {
padding: 15,
},
storyDomain: {
fontSize: isIOS ? 13 : 14,
color: colors.domainColor,
},
storyMetadataWrap: {
flexWrap: 'wrap',
flexDirection: 'row',
marginBottom: 2,
},
storyMetadata: {
fontSize: isIOS ? 13 : 14,
color: colors.insignificantColor,
},
storyContent: {
backgroundColor: colors.sectionBackgroundColor,
padding: isIOS ? 15 : 16,
borderTopColor: colors.separatorColor,
borderTopWidth: hairlineWidth,
borderBottomColor: colors.separatorColor,
borderBottomWidth: hairlineWidth,
marginBottom: 30,
},
separator: {
backgroundColor: colors.separatorColor,
height: hairlineWidth,
},
externalLink: {
flex: 1,
flexDirection: 'row',
alignItems: 'center',
backgroundColor: colors.viewBackgroundColor,
},
pollContainer: {
flexDirection: 'row',
marginTop: 9,
marginBottom: 2,
alignItems: 'flex-end',
},
pollItem: {
color: colors.primaryTextColor,
flex: 1,
},
pollPoints: {
color: colors.insignificantColor,
},
touchableLink: {
borderRadius: 3,
transform: [
{translateX: -2},
{translateY: -2}
]
},
touchableLinkInner: {
padding: 2
},
});
export default class CommentsView extends Component {
constructor(props){
super(props);
var { story, storyLoading, storyError } = StoryStore.getState();
this.state = {
data: story,
dataSource: new ListView.DataSource({rowHasChanged: (row1, row2) => row1 !== row2}),
loading: storyLoading,
error: storyError,
pollDisplay: 'points',
};
this._onChange = this._onChange.bind(this);
this._fetchStory = this._fetchStory.bind(this);
}
componentDidMount(){
StoryStore.listen(this._onChange);
this._fetchStory();
}
componentWillUnmount(){
StoryStore.unlisten(this._onChange);
}
_fetchStory(){
StoryActions.fetchStory(this.props.data.id);
}
_onChange(state){
const {story} = state;
const comments = (story && story.comments) || [];
LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut);
this.setState({
data: story,
dataSource: this.state.dataSource.cloneWithRows(comments),
loading: state.storyLoading,
error: state.storyError,
});
if (isAndroid && story && story.url){
const externalLink = !/^item/i.test(story.url);
const url = externalLink ? story.url : `https://news.ycombinator.com/item?id=${story.id}`;
ChromeCustomTabsClient.mayLaunchUrl(url);
}
/* Note:
Title update doesn't work yet due to https://github.com/facebook/react-native/issues/476
Hopefully this works https://github.com/bjornco/react-native/commit/5fcb2a8673a2c17f4fdb03327008397a10a9c53a
*/
if (state.story && state.story.title){
var route = this.props.navigator.navigationContext.currentRoute;
if (!route) return;
if (route.title != state.story.title){
const {story} = state;
route.title = story.title;
if (!route.rightButtonIcon) route.rightButtonIcon = require('../images/share-icon.png');
route.onRightButtonPress = showActivity.bind(null, `https://news.ycombinator.com/item?id=${story.id}`, story.title);
this.props.navigator.replace(route);
}
}
}
_togglePollDisplay(){
LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut);
this.setState({
pollDisplay: this.state.pollDisplay == 'points' ? 'percentage' : 'points',
});
}
_renderHeader(){
let {data, loading, error, pollDisplay} = this.state;
data = data || this.props.data;
const commentsText = · {data.comments_count} comment{data.comments_count != 1 && 's'};
const url = data.url;
const externalLink = !/^item/i.test(url);
let domainText = null;
let storyHeader = null;
if (externalLink){
domainText = {domainify(data.url)};
storyHeader = (
{data.title}
{domainText}
);
} else {
storyHeader = {data.title};
}
const hnShortURL = `news.ycombinator.com/item?id=${data.id}`;
const hnURL = `https://${hnShortURL}`;
let contentSection;
if (data.content){
let pollElements;
if (data.poll && data.poll.length){
let maxPoints = Math.max.apply(null, data.poll.map((p) => p.points ));
let totalPoints = data.poll.reduce((a, b) => a + b.points, 0);
pollElements = data.poll.map((p) => {
let {points} = p;
let pointsText;
if (pollDisplay == 'points'){
pointsText = `${points} point${points != 1 ? 's' : ''}`;
} else {
pointsText = `${(points/totalPoints*100).toFixed(1)}%`;
}
return (
{p.item}
{pointsText}
);
});
}
contentSection = (
{pollElements}
);
}
let commentsSection;
if (data && data.comments && data.comments.length){
commentsSection = null;
} else if (loading){
commentsSection = (
);
} else if (error){
commentsSection = (
Couldn't load comments.
Try again
);
} else {
commentsSection = (
No comments.
);
}
return (
{storyHeader}
{data.points} points by {data.user}
{data.time_ago} {data.comments_count>0 && commentsText}
{hnShortURL}
{contentSection}
{commentsSection}
);
}
render(){
const {data, dataSource} = this.state;
const comments = (data && data.comments) || [];
const hasManyComments = JSON.stringify(comments).length > 20*1000;
const op = data && data.user;
return (
}
renderSeparator={(sectionID, rowID) => }
renderFooter={() => }
/>
);
}
}
================================================
FILE: views/StoriesView.js
================================================
'use strict';
import React, { Component } from 'react';
import {
StyleSheet,
View,
Text,
ListView,
TouchableOpacity,
TouchableNativeFeedback,
ActionSheetIOS,
LayoutAnimation,
Platform,
} from 'react-native';
const isIOS = Platform.OS === 'ios';
const CrossTouchable = isIOS ? TouchableOpacity : TouchableNativeFeedback;
import StoryStore from '../stores/StoryStore';
import StoryActions from '../actions/StoryActions';
import LinkActions from '../actions/LinkActions';
import StoryRow from '../components/StoryRow';
import LoadingIndicator from '../components/LoadingIndicator';
import CommentsView from './CommentsView';
import showBrowser from '../utils/showBrowser';
import showActivity from '../utils/showActivity';
import colors from '../colors';
const hairlineWidth = isIOS ? StyleSheet.hairlineWidth : 1;
const styles = StyleSheet.create({
container: {
flex: 1,
},
navbarSpacing: {
marginTop: 64,
},
viewLoading: {
flex: 1,
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'center',
},
viewError: {
flex: 1,
alignItems: 'center',
justifyContent: 'center',
},
errorText: {
opacity: .6,
},
itemSeparator: {
height: hairlineWidth,
backgroundColor: colors.separatorColor,
marginLeft: isIOS ? 15 : 0,
marginTop: -hairlineWidth,
},
itemHighligtedSeparator: {
opacity: 0,
},
wrapper: {
backgroundColor: colors.viewBackgroundColor,
},
moreLink: {
padding: 19,
textAlign: 'center',
color: colors.linkColor,
fontSize: 17,
textAlignVertical: 'center',
},
});
export default class StoriesView extends Component {
constructor(props){
super(props);
const { stories, storiesLoading, storiesError, hasMoreStories } = StoryStore.getState();
this.state = {
stories,
loading: storiesLoading,
error: storiesError,
dataSource: new ListView.DataSource({rowHasChanged: (row1, row2) => row1 !== row2}),
hasMoreStories,
};
this._onChange = this._onChange.bind(this);
}
componentDidMount(){
StoryStore.listen(this._onChange);
StoryActions.fetchStories();
}
componentWillUnmount(){
StoryStore.unlisten(this._onChange);
}
_onChange(state){
const {stories, storiesLoading, storiesError, hasMoreStories} = state;
LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut);
this.setState({
stories,
dataSource: this.state.dataSource.cloneWithRows([].concat(stories)),
loading: storiesLoading,
error: storiesError,
hasMoreStories,
});
}
_navigateToComments(data){
this.props.navigator.push({
id: 'Comments',
title: data.title,
component: CommentsView,
wrapperStyle: styles.wrapper,
rightButtonIcon: require('../images/share-icon.png'),
onRightButtonPress: showActivity.bind(null, `https://news.ycombinator.com/item?id=${data.id}`, data.title),
passProps: {
data: data,
}
});
// Log link visit to History
const {url} = data;
if (/^item/i.test(url)) LinkActions.addLink(url);
}
_renderRow(row, sectionID, rowID, highlightRow){
const position = parseInt(rowID, 10) + 1;
const {url} = row;
const externalLink = !/^item/i.test(url);
const navToComments = this._navigateToComments.bind(this, row);
const linkPress = externalLink ? showBrowser.bind(null, url) : navToComments;
return (
highlightRow(sectionID, rowID)} onHideUnderlay={() => highlightRow(null, null)}/>
);
}
_renderSeparator(sectionID, rowID, adjacentRowHighlighted){
return ;
}
_fetchMoreStories(){
// Delay the fetching a bit to create that sense of something's happening in the background
if (this._fetchingMore) return;
this._fetchingMore = true;
setTimeout(() => {
StoryActions.fetchMoreStories();
this._fetchingMore = false;
}, 300);
}
_renderFooter(){
const {hasMoreStories, stories} = this.state;
if (hasMoreStories && stories && stories.length <= 30){
return (
More…
);
}
return null;
}
render(){
const {loading, error, dataSource} = this.state;
if (loading){
return (
);
}
if (error){
return (
Couldn't load stories.
{error && error.message ? error.message : '😭'}
);
}
return (
{/* Temporary FIX: Prevent the taps on NavigatorIOS buttons "leaking" into the ListView rows' taps */}
{isIOS && false} style={{width: 100, height: 10, position: 'absolute', top: styles.navbarSpacing-2}} />}
{isIOS && false} style={{width: 60, height: 10, position: 'absolute', top: styles.navbarSpacing-2, right: 0}} />}
);
}
}