diff --git a/.flowconfig b/.flowconfig new file mode 100644 index 0000000..fd62e40 --- /dev/null +++ b/.flowconfig @@ -0,0 +1,70 @@ +[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/fetch.js +.*/node_modules/fbjs/lib/ExecutionEnvironment.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/.* + +[include] + +[libs] +node_modules/react-native/Libraries/react-native/react-native-interface.js + +[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\)$' -> 'RelativeImageStub' + +suppress_type=$FlowIssue +suppress_type=$FlowFixMe +suppress_type=$FixMe + +suppress_comment=\\(.\\|\n\\)*\\$FlowFixMe\\($\\|[^(]\\|(\\(>=0\\.\\(2[0-1]\\|1[0-9]\\|[0-9]\\).[0-9]\\)? *\\(site=[a-z,_]*react_native[a-z,_]*\\)?)\\) +suppress_comment=\\(.\\|\n\\)*\\$FlowIssue\\((\\(>=0\\.\\(2[0-1]\\|1[0-9]\\|[0-9]\\).[0-9]\\)? *\\(site=[a-z,_]*react_native[a-z,_]*\\)?)\\)?:? #[0-9]+ +suppress_comment=\\(.\\|\n\\)*\\$FlowFixedInNextDeploy + +[version] +0.21.0 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..09cc5c7 --- /dev/null +++ b/.gitignore @@ -0,0 +1,37 @@ +# 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 + +# android +android/build +android/app/build diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..7aefbb8 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,8 @@ +#language is noonsnese +language: node_js +node_js: + - "5.6.0" +script: + - npm install + - npm start + - exit 0 \ No newline at end of file diff --git a/.watchmanconfig b/.watchmanconfig new file mode 100644 index 0000000..9e26dfe --- /dev/null +++ b/.watchmanconfig @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/LICENSE.txt b/LICENSE.txt new file mode 100644 index 0000000..1e62156 --- /dev/null +++ b/LICENSE.txt @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2016 KevinOfNeu + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..fd211bf --- /dev/null +++ b/README.md @@ -0,0 +1,41 @@ +xReddit +=======++ +[![Build Status](https://travis-ci.org/KevinOfNeu/xReddit.svg?branch=master)](https://travis-ci.org/KevinOfNeu/xReddit) + +[Reddit](https://www.reddit.com/) for Android, made with[React-Native](https://github.com/facebook/react-native). +Adding support for iOS is on the way, adding support for OSX is also in plan!!! + +![Preview](./art/reddit.gif) + +## Build & Run +### s1 +``` +npm install +``` + +### s2 +``` +react-native run-android +``` + +### s3 +If you came up with 'reload' issues, try run ```npm start``` + +## Future Improvement +* iOS version +* Listing Comments +* OSX version +* more channels +* custom setting + +License +------------- +MIT license. + + +### From Kevin +We would like xReddit to evolve, so we are open to your help, suggestions and feedback. Welcome Star,Fork,PR and Issue! + +Enjoy! + +Made with ♥ by [Kevin](http://kevin.doyeden.com). \ No newline at end of file diff --git a/android/app/build.gradle b/android/app/build.gradle new file mode 100644 index 0000000..7e172ca --- /dev/null +++ b/android/app/build.gradle @@ -0,0 +1,126 @@ +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: "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: "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 "com.xreddit" + minSdkVersion 16 + targetSdkVersion 22 + versionCode 1 + versionName "1.0" + ndk { + abiFilters "armeabi-v7a", "x86" + } + } + 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" + } + } + // 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 +} diff --git a/android/app/proguard-rules.pro b/android/app/proguard-rules.pro new file mode 100644 index 0000000..7d72e46 --- /dev/null +++ b/android/app/proguard-rules.pro @@ -0,0 +1,67 @@ +# 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 com.squareup.okhttp.** { *; } +-keep interface com.squareup.okhttp.** { *; } +-dontwarn com.squareup.okhttp.** + +# okio + +-keep class sun.misc.Unsafe { *; } +-dontwarn java.nio.file.* +-dontwarn org.codehaus.mojo.animal_sniffer.IgnoreJRERequirement +-dontwarn okio.** + +# stetho + +-dontwarn com.facebook.stetho.** diff --git a/android/app/react.gradle b/android/app/react.gradle new file mode 100644 index 0000000..348fb12 --- /dev/null +++ b/android/app/react.gradle @@ -0,0 +1,97 @@ +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", "react-native", "bundle", "--platform", "android", "--dev", "${devEnabled}", + "--entry-file", entryFile, "--bundle-output", jsBundleFile, "--assets-dest", resourcesDir + } else { + commandLine "react-native", "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) + } + } +} diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..7757bae --- /dev/null +++ b/android/app/src/main/AndroidManifest.xml @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + diff --git a/android/app/src/main/java/com/xreddit/MainActivity.java b/android/app/src/main/java/com/xreddit/MainActivity.java new file mode 100644 index 0000000..cc09b93 --- /dev/null +++ b/android/app/src/main/java/com/xreddit/MainActivity.java @@ -0,0 +1,40 @@ +package com.xreddit; + +import com.facebook.react.ReactActivity; +import com.facebook.react.ReactPackage; +import com.facebook.react.shell.MainReactPackage; + +import java.util.Arrays; +import java.util.List; + +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 "xReddit"; + } + + /** + * 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() + ); + } +} diff --git a/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/android/app/src/main/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000..3618fac Binary files /dev/null and b/android/app/src/main/res/mipmap-hdpi/ic_launcher.png differ diff --git a/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/android/app/src/main/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000..3618fac Binary files /dev/null and b/android/app/src/main/res/mipmap-mdpi/ic_launcher.png differ diff --git a/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000..3618fac Binary files /dev/null and b/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/android/app/src/main/res/mipmap-xxhdpi/_ic_launcher.png b/android/app/src/main/res/mipmap-xxhdpi/_ic_launcher.png new file mode 100644 index 0000000..324e72c Binary files /dev/null and b/android/app/src/main/res/mipmap-xxhdpi/_ic_launcher.png differ diff --git a/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000..3618fac Binary files /dev/null and b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/android/app/src/main/res/values/strings.xml b/android/app/src/main/res/values/strings.xml new file mode 100644 index 0000000..ec7824d --- /dev/null +++ b/android/app/src/main/res/values/strings.xml @@ -0,0 +1,3 @@ + + xReddit + diff --git a/android/app/src/main/res/values/styles.xml b/android/app/src/main/res/values/styles.xml new file mode 100644 index 0000000..319eb0c --- /dev/null +++ b/android/app/src/main/res/values/styles.xml @@ -0,0 +1,8 @@ + + + + + + diff --git a/android/build.gradle b/android/build.gradle new file mode 100644 index 0000000..403a007 --- /dev/null +++ b/android/build.gradle @@ -0,0 +1,24 @@ +// 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.3.1' + + // 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" + } + } +} diff --git a/android/gradle.properties b/android/gradle.properties new file mode 100644 index 0000000..1fd964e --- /dev/null +++ b/android/gradle.properties @@ -0,0 +1,20 @@ +# 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 diff --git a/android/gradle/wrapper/gradle-wrapper.jar b/android/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000..b5166da Binary files /dev/null and b/android/gradle/wrapper/gradle-wrapper.jar differ diff --git a/android/gradle/wrapper/gradle-wrapper.properties b/android/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..b9fbfab --- /dev/null +++ b/android/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,5 @@ +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 diff --git a/android/gradlew b/android/gradlew new file mode 100755 index 0000000..91a7e26 --- /dev/null +++ b/android/gradlew @@ -0,0 +1,164 @@ +#!/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 "$@" diff --git a/android/gradlew.bat b/android/gradlew.bat new file mode 100644 index 0000000..aec9973 --- /dev/null +++ b/android/gradlew.bat @@ -0,0 +1,90 @@ +@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 diff --git a/android/settings.gradle b/android/settings.gradle new file mode 100644 index 0000000..3a77ce1 --- /dev/null +++ b/android/settings.gradle @@ -0,0 +1,3 @@ +rootProject.name = 'xReddit' + +include ':app' diff --git a/app/actions/reddit.js b/app/actions/reddit.js new file mode 100644 index 0000000..f70552f --- /dev/null +++ b/app/actions/reddit.js @@ -0,0 +1,44 @@ +'use strict'; + +import * as types from '../constants/ActionTypes'; +import {ToastShort} from '../utils/ToastUtils'; +import {request} from '../utils/HttpServices'; +import * as host from '../constants/Urls'; + +export function fetchReddit (isRefreshing, loading, typeId, isLoadMore, count, after) { + if (count == undefined) { + count = 25; + } + return dispatch => { + dispatch(fetchRedditList(isRefreshing, loading, isLoadMore)); + return request(host.BASE_URL + host.DOMAINS[typeId] + '?count=' + count + '&after=' + after) + .then((redditList) => { + dispatch(receiveRedditList(redditList.data, typeId, redditList.data.after)); + }) + .catch((error) => { + dispatch(receiveRedditList([], typeId)); + ToastShort(error.message); + }) + } +} + +function fetchRedditList (isRefreshing, loading, isLoadMore) { + if (isLoadMore == undefined) { + isLoadMore = false; + } + return { + type: types.FETCH_REDDIT_LIST, + isRefreshing: isRefreshing, + loading: loading, + isLoadMore: isLoadMore + } +} + +function receiveRedditList (redditData, typeId, after) { + return { + type: types.RECEIVE_REDDIT_LIST, + redditData: redditData, + typeId: typeId, + after: after + } +} \ No newline at end of file diff --git a/app/components/Button.js b/app/components/Button.js new file mode 100644 index 0000000..319301d --- /dev/null +++ b/app/components/Button.js @@ -0,0 +1,59 @@ +'use strict'; + +import React from 'react-native'; +const { + Text, + PropTypes, + TouchableOpacity, + } = React; +import StyleSheetPropType from 'StyleSheetPropType'; +import ViewStylePropTypes from 'ViewStylePropTypes'; +import TextStylePropTypes from 'TextStylePropTypes'; + +let ViewStylePropType = StyleSheetPropType(ViewStylePropTypes); +let TextStylePropType = StyleSheetPropType(TextStylePropTypes); + +const propTypes = { + onPress: PropTypes.func, + disabled: PropTypes.bool, + style: TextStylePropType, + containerStyle: ViewStylePropType, + text: PropTypes.string +} + +class Button extends React.Component { + constructor (props) { + super(props); + + this.onPress = this.onPress.bind(this); + } + + onPress () { + if (this.props.disabled) { + return; + } + this.props.onPress(); + } + + render () { + return ( + + + {this.props.text} + + + ); + } +} + +Button.propTypes = propTypes; + +Button.defaultProps = { + onPress: function () { + }, + disabled: false +} + +export default Button; \ No newline at end of file diff --git a/app/components/CustomTabBar.js b/app/components/CustomTabBar.js new file mode 100644 index 0000000..e1bb33f --- /dev/null +++ b/app/components/CustomTabBar.js @@ -0,0 +1,80 @@ +'use strict'; + +import React from 'react-native'; +const { + StyleSheet, + Text, + TouchableOpacity, + View, + Animated + } = React; + +const propTypes = { + goToPage: React.PropTypes.func, + activeTab: React.PropTypes.number, + tabs: React.PropTypes.array, + underlineColor: React.PropTypes.string, + backgroundColor: React.PropTypes.string, + activeTextColor: React.PropTypes.string, + inactiveTextColor: React.PropTypes.string +}; + +class CustomTabBar extends React.Component { + renderTabOption(name, page) { + var isTabActive = this.props.activeTab === page; + var activeTextColor = this.props.activeTextColor || "navy"; + var inactiveTextColor = this.props.inactiveTextColor || "black"; + return ( + this.props.goToPage(page)}> + + + {name} + + + + ); + } + + render() { + var containerWidth = this.props.containerWidth; + var numberOfTabs = this.props.tabs.length; + let tabUnderlineStyle = { + position: 'absolute', + width: containerWidth / numberOfTabs, + height: 2, + backgroundColor: this.props.underlineColor || "navy", + bottom: 0 + }; + var left = this.props.scrollValue.interpolate({ + inputRange: [0, 1], outputRange: [0, containerWidth / numberOfTabs] + }); + return ( + + {this.props.tabs.map((tab, i) => this.renderTabOption(tab, i))} + + + ); + } +} + +let styles = StyleSheet.create({ + tab: { + flex: 1, + alignItems: 'center', + justifyContent: 'center' + }, + tabs: { + height: 50, + flexDirection: 'row', + justifyContent: 'space-around', + borderWidth: 1, + borderTopWidth: 0, + borderLeftWidth: 0, + borderRightWidth: 0, + borderBottomColor: '#ccc' + } +}); + +CustomTabBar.propTypes = propTypes; + +export default CustomTabBar; \ No newline at end of file diff --git a/app/components/CustomToolbar.js b/app/components/CustomToolbar.js new file mode 100644 index 0000000..1eee8b0 --- /dev/null +++ b/app/components/CustomToolbar.js @@ -0,0 +1,88 @@ +'use strict'; + +import React from 'react-native'; +const { + StyleSheet, + PropTypes, + ToolbarAndroid + } = React; +import StyleSheetPropType from 'StyleSheetPropType'; +import ViewStylePropTypes from 'ViewStylePropTypes'; + +import {NaviGoBack} from '../utils/CommonUtils'; + +let ViewStylePropType = StyleSheetPropType(ViewStylePropTypes); + +const propTypes = { + title: PropTypes.string, + actions: PropTypes.array, + navigator: PropTypes.object, + onActionSelected: PropTypes.func, + onIconClicked: PropTypes.func, + navIcon: PropTypes.number, + customView: PropTypes.object +}; + +class CustomToolbar extends React.Component { + constructor (props) { + super(props); + this.onIconClicked = this.onIconClicked.bind(this); + this.onActionSelected = this.onActionSelected.bind(this); + } + + onIconClicked () { + if (this.props.onIconClicked) { + this.props.onIconClicked(); + } else { + const {navigator} = this.props; + if (navigator) { + NaviGoBack(navigator); + } + } + } + + onActionSelected (position) { + this.props.onActionSelected(); + } + + render () { + const {navigator} = this.props; + if (this.props.customView) { + return ( + + {this.props.customView} + + ) + } else { + return ( + + ); + } + } +} + +let styles = StyleSheet.create({ + toolbar: { + backgroundColor: '#FF0000', + height: 58 + } +}); + +CustomToolbar.propTypes = propTypes; + +CustomToolbar.defaultProps = { + onActionSelected: function () { + }, + title: '', + actions: [] +}; + +export default CustomToolbar; \ No newline at end of file diff --git a/app/components/ImageButton.js b/app/components/ImageButton.js new file mode 100644 index 0000000..9a65d36 --- /dev/null +++ b/app/components/ImageButton.js @@ -0,0 +1,68 @@ +'use strict'; + +import React from 'react-native'; +const { + Image, + StyleSheet, + PropTypes, + TouchableOpacity, +} = React; +import StyleSheetPropType from 'StyleSheetPropType'; +import ViewStylePropTypes from 'ViewStylePropTypes'; + +let stylePropType = StyleSheetPropType(ViewStylePropTypes); + +const propTypes = { + onPress: PropTypes.func, + disabled: PropTypes.bool, + uri: PropTypes.string, + require: PropTypes.number, + isStatic: PropTypes.bool, + isUri: PropTypes.bool, + style: stylePropType, + containerStyle: stylePropType +} + +class ImageButton extends React.Component { + constructor(props) { + super(props); + + this.onPress = this.onPress.bind(this); + } + + onPress() { + if (this.props.disabled) { + return; + } + this.props.onPress(); + } + + render() { + var source = this.props.isUri ? { + uri: this.props.uri, + isStatic: this.props.isStatic + } : this.props.require; + return ( + + + + ); + } +} + +ImageButton.propTypes = propTypes; + +ImageButton.defaultProps = { + onPress: function() {}, + disabled: false, + isStatic: false, + uri: '', + isUri: true +} + +export default ImageButton; \ No newline at end of file diff --git a/app/components/Loading.js b/app/components/Loading.js new file mode 100644 index 0000000..ebab3a3 --- /dev/null +++ b/app/components/Loading.js @@ -0,0 +1,171 @@ +'use strict'; + +import React from 'react-native'; +const { + Platform, + StyleSheet, + Dimensions, + View, + Text, + Modal, + ActivityIndicatorIOS, + ProgressBarAndroid + } = React; + +import Portal from 'react-native/Libraries/Portal/Portal.js'; + +const SIZES = ['small', 'normal', 'large']; +let tag; + +const propTypes = { + visible: React.PropTypes.bool, + color: React.PropTypes.string, + size: React.PropTypes.oneOf(SIZES), + overlayColor: React.PropTypes.string +} + +class Loading extends React.Component { + constructor(props) { + super(props); + this.renderLoading = this.renderLoading.bind(this); + this.renderSpinner = this.renderSpinner.bind(this); + } + + componentWillMount() { + if (Platform.OS === 'android') { + tag = Portal.allocateTag(); + } + } + + componentDidUpdate(prevProps) { + if (Platform.OS !== 'android') { + return; + } + if (!prevProps.visible && this.props.visible) { + return Portal.showModal(tag, this.renderSpinner()); + } + Portal.closeModal(tag); + } + + componentWillUnmount() { + if (Platform.OS === 'android') { + tag = null; + } + } + + renderLoading() { + let styleAttr = 'Inverse'; + let size = 'large'; + + switch (this.props.size) { + case 'small': + styleAttr = 'SmallInverse'; + size = 'small'; + break; + case 'large': + styleAttr = 'LargeInverse'; + size = 'large'; + break; + } + if (Platform.OS === 'android') { + return ( + + + 数据加载中... + + ); + } else { + return ( + + ); + } + } + + renderSpinner() { + if (!this.props.visible){ + return ( + + ); + } + let spinner = ( + + + {this.renderLoading()} + + + ); + if (Platform.OS === 'android') { + return spinner; + } + return ( + + {spinner} + + ); + } + + render() { + if (Platform.OS === 'android') { + return + } + return this.renderSpinner(); + } +} + +let styles = StyleSheet.create({ + container: { + flex: 1, + backgroundColor: 'transparent', + position: 'absolute', + top: 0, + bottom: 0, + left: 0, + right: 0 + }, + background: { + position: 'absolute', + top: 0, + bottom: 0, + left: 0, + right: 0, + justifyContent: 'center', + alignItems: 'center' + }, + loading: { + alignItems: 'center', + justifyContent: 'center', + width: Dimensions.get('window').width / 2.5, + height: Dimensions.get('window').width / 2.5, + borderRadius: 10, + backgroundColor: 'rgba(0, 0, 0, 0.25)' + }, + loadingText: { + marginTop: 10, + textAlign: 'center', + color: '#fcfcfc' + } +}) + +Loading.propTypes = propTypes; + +Loading.defaultProps = { + visible: false, + color: 'white', + size: 'large', + overlayColor: 'transparent' +} + +export default Loading; \ No newline at end of file diff --git a/app/components/LoadingView.js b/app/components/LoadingView.js new file mode 100644 index 0000000..d34763f --- /dev/null +++ b/app/components/LoadingView.js @@ -0,0 +1,45 @@ +'use strict'; + +import React from 'react-native'; +const { + ProgressBarAndroid, + ActivityIndicatorIOS, + Platform, + Text, + StyleSheet, + View + } = React; + +class LoadingView extends React.Component { + render () { + if (Platform.OS === 'android') { + return ( + + + 加载中... + + ); + } else { + return ( + + + 加载中... + + ); + } + } +} + +let styles = StyleSheet.create({ + loading: { + flex: 1, + alignItems: 'center', + justifyContent: 'center' + }, + loadingText: { + marginTop: 10, + textAlign: 'center' + } +}); + +export default LoadingView; \ No newline at end of file diff --git a/app/constants/ActionTypes.js b/app/constants/ActionTypes.js new file mode 100644 index 0000000..14c35b3 --- /dev/null +++ b/app/constants/ActionTypes.js @@ -0,0 +1,4 @@ +'use strict'; + +export const FETCH_REDDIT_LIST = 'FETCH_ARTICLE_LIST'; +export const RECEIVE_REDDIT_LIST = 'RECEIVE_ARTICLE_LIST'; diff --git a/app/constants/Alias.js b/app/constants/Alias.js new file mode 100644 index 0000000..cb2399e --- /dev/null +++ b/app/constants/Alias.js @@ -0,0 +1,5 @@ +'use strict'; + +export const CATEGORIES = { + 1: "图片", 2: "电影", 3: "动图", 4: "趣闻" +}; \ No newline at end of file diff --git a/app/constants/Urls.js b/app/constants/Urls.js new file mode 100644 index 0000000..cf3b74d --- /dev/null +++ b/app/constants/Urls.js @@ -0,0 +1,7 @@ +'use strict'; + +export const BASE_URL = 'https://www.reddit.com/r/'; + +export const DOMAINS = { + 1: 'pics/.json', 2: 'movies/.json', 3: 'gifs/.json', 4: 'funny/.json' +}; \ No newline at end of file diff --git a/app/containers/AboutContainer.js b/app/containers/AboutContainer.js new file mode 100644 index 0000000..5b527b5 --- /dev/null +++ b/app/containers/AboutContainer.js @@ -0,0 +1,26 @@ +'use strict'; + +import React from 'react-native'; +const { + Component + } = React; +import {connect} from 'react-redux'; + +import About from '../pages/About'; + +class AboutContainer extends Component { + render () { + return ( + + ); + } +} + +function mapStateToProps (state) { + const {reddit} = state; + return { + reddit + } +} + +export default connect(mapStateToProps)(AboutContainer); \ No newline at end of file diff --git a/app/containers/MainContainer.js b/app/containers/MainContainer.js new file mode 100644 index 0000000..d872c6b --- /dev/null +++ b/app/containers/MainContainer.js @@ -0,0 +1,22 @@ +'use strict'; +import React from 'react-native'; +import {connect} from 'react-redux'; +import Main from '../pages/Main'; + +class MainContainer extends React.Component { + + render () { + return ( +
+ ); + } +} + +function mapStateToProps (state) { + const {reddit} = state; + return { + reddit + } +} + +export default connect(mapStateToProps)(MainContainer); \ No newline at end of file diff --git a/app/containers/WebViewContainer.js b/app/containers/WebViewContainer.js new file mode 100644 index 0000000..aba9b68 --- /dev/null +++ b/app/containers/WebViewContainer.js @@ -0,0 +1,26 @@ +'use strict'; + +import React from 'react-native'; +const { + Component + } = React; +import {connect} from 'react-redux'; + +import WebViewPage from '../pages/WebViewPage'; + +class WebViewContainer extends Component { + render () { + return ( + + ); + } +} + +function mapStateToProps (state) { + const {reddit} = state; + return { + reddit + } +} + +export default connect(mapStateToProps)(WebViewContainer); diff --git a/app/containers/app.js b/app/containers/app.js new file mode 100644 index 0000000..ea51ae6 --- /dev/null +++ b/app/containers/app.js @@ -0,0 +1,78 @@ +import React from 'react-native'; +import LoadingViewAndroid from '../components/LoadingView'; +import {ToastShort} from '../utils/ToastUtils'; + +import Splash from '../pages/Splash'; +import {NaviGoBack} from '../utils/CommonUtils'; + +const { + StyleSheet, + Navigator, + StatusBar, + BackAndroid, + View + } = React; + +var _navigator, isRemoved = false; + +class App extends React.Component { + constructor (props) { + super(props); + this.renderScene = this.renderScene.bind(this); + this.goBack = this.goBack.bind(this); + BackAndroid.addEventListener('hardwareBackPress', this.goBack); + } + + goBack () { + return NaviGoBack(_navigator); + } + + renderScene (route, navigator) { + let Component = route.component; + _navigator = navigator; + if (route.name === 'WebViewPage') { + BackAndroid.removeEventListener('hardwareBackPress', this.goBack); + isRemoved = true; + } else { + if (isRemoved) { + BackAndroid.addEventListener('hardwareBackPress', this.goBack); + } + } + return ( + + ); + } + + configureScene (route, routeStack) { + return Navigator.SceneConfigs.PushFromRight; + } + + render () { + return ( + + + + + ); + } +} + +let styles = StyleSheet.create({ + navigator: { + flex: 1 + } +}); + +export default App; diff --git a/app/img/about.png b/app/img/about.png new file mode 100644 index 0000000..0fe84dd Binary files /dev/null and b/app/img/about.png differ diff --git a/app/img/about_logo.png b/app/img/about_logo.png new file mode 100644 index 0000000..3618fac Binary files /dev/null and b/app/img/about_logo.png differ diff --git a/app/img/drawerlayout.png b/app/img/drawerlayout.png new file mode 100644 index 0000000..c4ac809 Binary files /dev/null and b/app/img/drawerlayout.png differ diff --git a/app/img/icon_left.png b/app/img/icon_left.png new file mode 100644 index 0000000..871f11a Binary files /dev/null and b/app/img/icon_left.png differ diff --git a/app/img/info.png b/app/img/info.png new file mode 100644 index 0000000..ee64257 Binary files /dev/null and b/app/img/info.png differ diff --git a/app/img/menu.png b/app/img/menu.png new file mode 100644 index 0000000..35dfdbc Binary files /dev/null and b/app/img/menu.png differ diff --git a/app/img/reddit_bg.png b/app/img/reddit_bg.png new file mode 100644 index 0000000..40a8268 Binary files /dev/null and b/app/img/reddit_bg.png differ diff --git a/app/img/reddit_icon.png b/app/img/reddit_icon.png new file mode 100644 index 0000000..3618fac Binary files /dev/null and b/app/img/reddit_icon.png differ diff --git a/app/img/reddit_red.png b/app/img/reddit_red.png new file mode 100644 index 0000000..7cf1bd4 Binary files /dev/null and b/app/img/reddit_red.png differ diff --git a/app/img/reddit_splash.jpg b/app/img/reddit_splash.jpg new file mode 100644 index 0000000..85aa696 Binary files /dev/null and b/app/img/reddit_splash.jpg differ diff --git a/app/img/reddit_splash.png b/app/img/reddit_splash.png new file mode 100644 index 0000000..6a6824b Binary files /dev/null and b/app/img/reddit_splash.png differ diff --git a/app/img/settings.png b/app/img/settings.png new file mode 100644 index 0000000..6409e4b Binary files /dev/null and b/app/img/settings.png differ diff --git a/app/pages/About.js b/app/pages/About.js new file mode 100644 index 0000000..a2ed307 --- /dev/null +++ b/app/pages/About.js @@ -0,0 +1,62 @@ +import React from 'react-native'; +const { + StyleSheet, + Image, + Text, + Linking, + View + } = React; + +import CustomToolbar from '../components/CustomToolbar'; +import Button from '../components/Button'; + +class About extends React.Component { + constructor (props) { + super(props); + } + + onPress (url) { + Linking.openURL(url); + } + + render () { + const {navigator} = this.props; + return ( + + + + + + + {'xReddit'} + + + {'v0.0.1'} + + + + + ); + } +} + +let styles = StyleSheet.create({ + container: { + flex: 1, + flexDirection: 'column' + }, + content: { + flex: 1, + backgroundColor: '#fcfcfc', + justifyContent: 'center', + paddingBottom: 10 + } +}); + +export default About; \ No newline at end of file diff --git a/app/pages/Main.js b/app/pages/Main.js new file mode 100644 index 0000000..4364c9c --- /dev/null +++ b/app/pages/Main.js @@ -0,0 +1,370 @@ +'use strict'; + +import React from 'react-native'; +const { + StyleSheet, + ListView, + RefreshControl, + ScrollView, + Text, + TouchableOpacity, + PropTypes, + InteractionManager, + ProgressBarAndroid, + Image, + DrawerLayoutAndroid, + Dimensions, + View + } = React; +import LoadingView from '../components/LoadingView'; +import {fetchReddit} from '../actions/reddit'; +import CustomTabBar from '../components/CustomTabBar'; +import CustomToolbar from '../components/CustomToolbar'; +import ScrollableTabView from 'react-native-scrollable-tab-view'; +import WebViewContainer from '../containers/WebViewContainer'; +import AboutContainer from '../containers/AboutContainer'; +import {ToastShort} from '../utils/ToastUtils'; +import * as Urls from '../constants/Urls'; +import * as Alias from '../constants/Alias'; + +const propTypes = { + dispatch: PropTypes.func.isRequired, + reddit: PropTypes.object.isRequired +}; + +var canLoadMore; +var _typeIds = new Array(); +var loadMoreTime = 0; + +class Main extends React.Component { + constructor (props) { + super(props); + this.state = { + dataSource: new ListView.DataSource({ + rowHasChanged: (row1, row2) => row1 !== row2, + }) + }; + this.renderItem = this.renderItem.bind(this); + this.renderFooter = this.renderFooter.bind(this); + this.renderNavigationView = this.renderNavigationView.bind(this); + this.onIconClicked = this.onIconClicked.bind(this); + this.onScroll = this.onScroll.bind(this); + canLoadMore = false; + } + + componentDidMount () { + const {dispatch} = this.props; + InteractionManager.runAfterInteractions(() => { + _typeIds = [1, 2, 3, 4]; + _typeIds.forEach((typeId) => { + dispatch(fetchReddit(false, true, typeId)); + }); + }); + + } + + componentWillReceiveProps (nextProps) { + const {reddit} = this.props; + if (reddit.isLoadMore && !nextProps.reddit.isLoadMore && !nextProps.reddit.isRefreshing) { + if (nextProps.reddit.noMore) { + ToastShort('没有更多数据了'); + } + } + } + + onRefresh (typeId) { + const {dispatch} = this.props; + canLoadMore = false; + dispatch(fetchReddit(true, false, typeId)); + } + + onPress (reddit) { + const {navigator} = this.props; + console.error('click:', reddit); + InteractionManager.runAfterInteractions(() => { + navigator.push({ + component: WebViewContainer, + name: 'WebViewPage', + reddit: reddit + }); + }); + } + + onPressDrawerItem (index) { + const {navigator} = this.props; + this.refs.drawer.closeDrawer(); + switch (index) { + case 2: + InteractionManager.runAfterInteractions(() => { + navigator.push({ + component: AboutContainer, + name: 'About' + }); + }); + break; + default: + break; + } + } + + onIconClicked () { + this.refs.drawer.openDrawer(); + } + + onScroll () { + if (!canLoadMore) { + canLoadMore = true; + } + } + + onEndReached (typeId) { + let time = Date.parse(new Date()) / 1000; + const {reddit} = this.props; + if (canLoadMore && time - loadMoreTime > 1) { + const {dispatch} = this.props; + dispatch(fetchReddit(false, false, typeId, true, 25, reddit.redditAfter[typeId])); + canLoadMore = false; + loadMoreTime = Date.parse(new Date()) / 1000; + } + } + + renderFooter () { + const {reddit} = this.props; + if (reddit.isLoadMore) { + return ( + + + + 加载中… + + + ); + } + } + + renderItem (reddit, sectionID, rowID) { + const thumbnail = reddit.data.thumbnail.lastIndexOf("http") >= 0 ? reddit.data.thumbnail : 'https://www.redditstatic.com/reddit404b.png'; + console.log('render', reddit.data.thumbnail.lastIndexOf("http")); + return ( + + + + + + {reddit.data.title} + + + + + 来自: + + + {reddit.data.author} + + + + + + 赞: + + + {reddit.data.ups} + + + + + + + + ); + } + + renderContent (dataSource, typeId) { + const {reddit} = this.props; + if (reddit.loading) { + return ; + } + let isEmpty = reddit.redditList[typeId] == undefined || reddit.redditList[typeId].length == 0; + if (isEmpty) { + return ( + + } + > + + + 正在与网络撕扯... + + + + ); + } + return ( + + } + /> + ); + } + + renderNavigationView () { + return ( + + + + xReddit + + + + + + 首页 + + + + + + 关于 + + + + ); + } + + render () { + const {reddit, navigator} = this.props; + var lists = []; + _typeIds.forEach((typeId) => { + lists.push( + + {this.renderContent(this.state.dataSource.cloneWithRows(reddit.redditList[typeId] == undefined ? [] : reddit.redditList[typeId]), typeId)} + ); + }); + return ( + + + + } + tabBarBackgroundColor="#fcfcfc" + tabBarUnderlineColor="#FF0000" + tabBarActiveTextColor="#FF0000" + tabBarInactiveTextColor="#aaaaaa" + > + {lists} + + + + ); + } +} + +let styles = StyleSheet.create({ + container: { + flex: 1, + flexDirection: 'column' + }, + containerItem: { + flex: 1, + flexDirection: 'row', + justifyContent: 'center', + alignItems: 'center', + backgroundColor: '#fcfcfc', + padding: 10, + borderBottomColor: '#ddd', + borderBottomWidth: 1 + }, + title: { + flex: 3, + fontSize: 18, + textAlign: 'left', + color: 'black' + }, + listView: { + backgroundColor: '#eeeeec' + }, + no_data: { + flex: 1, + alignItems: 'center', + justifyContent: 'center', + paddingBottom: 100 + }, + drawerContent: { + flexDirection: 'row', + alignItems: 'center', + padding: 15, + borderBottomWidth: 1, + borderBottomColor: '#ddd' + }, + drawerIcon: { + width: 30, + height: 30, + marginLeft: 5 + }, + drawerText: { + fontSize: 18, + marginLeft: 15, + textAlign: 'center', + color: 'black' + } +}); + +Main.propTypes = propTypes; + +export default Main; \ No newline at end of file diff --git a/app/pages/Splash.js b/app/pages/Splash.js new file mode 100644 index 0000000..941688b --- /dev/null +++ b/app/pages/Splash.js @@ -0,0 +1,39 @@ +import React from 'react-native'; +const { + Dimensions, + Image, + InteractionManager + } = React; + +import MainContainer from '../containers/MainContainer'; + +var {height, width} = Dimensions.get('window'); + +class Splash extends React.Component { + constructor (props) { + super(props); + } + + componentDidMount () { + const {navigator} = this.props; + setTimeout(() => { + InteractionManager.runAfterInteractions(() => { + navigator.resetTo({ + component: MainContainer, + name: 'Main' + }); + }); + }, 1500); + } + + render () { + return ( + + ); + } +} + +export default Splash; \ No newline at end of file diff --git a/app/pages/WebViewPage.js b/app/pages/WebViewPage.js new file mode 100644 index 0000000..4bc5498 --- /dev/null +++ b/app/pages/WebViewPage.js @@ -0,0 +1,136 @@ +'use strict'; + +import React from 'react-native'; +const { + StyleSheet, + PropTypes, + WebView, + BackAndroid, + Dimensions, + Text, + Image, + Platform, + TouchableOpacity, + View + } = React; + +import CustomToolbar from '../components/CustomToolbar'; +import {ToastShort} from '../utils/ToastUtils'; +import LoadingView from '../components/LoadingView'; +import {NaviGoBack} from '../utils/CommonUtils'; +import Portal from 'react-native/Libraries/Portal/Portal.js'; + +let tag; +var canGoBack = false; + +class WebViewPage extends React.Component { + constructor (props) { + super(props); + this.onActionSelected = this.onActionSelected.bind(this); + this.onNavigationStateChange = this.onNavigationStateChange.bind(this); + this.goBack = this.goBack.bind(this); + } + + componentWillMount () { + if (Platform.OS === 'android') { + tag = Portal.allocateTag(); + } + } + + componentDidMount () { + BackAndroid.addEventListener('hardwareBackPress', this.goBack); + } + + componentWillUnmount () { + BackAndroid.removeEventListener('hardwareBackPress', this.goBack); + } + + onActionSelected () { + + } + + onNavigationStateChange (navState) { + canGoBack = navState.canGoBack; + } + + goBack () { + if (Portal.getOpenModals().length != 0) { + Portal.closeModal(tag); + return true; + } else if (canGoBack) { + this.refs.webview.goBack(); + return true; + } + return NaviGoBack(this.props.navigator); + } + + renderLoading () { + return ; + } + + render () { + const {navigator, route} = this.props; + return ( + + + + + ); + } +} + +let styles = StyleSheet.create({ + container: { + flex: 1, + flexDirection: 'column' + }, + spinner: { + flex: 1, + flexDirection: 'column', + justifyContent: 'center', + alignItems: 'center', + backgroundColor: 'rgba(0, 0, 0, 0.65)' + }, + spinnerContent: { + justifyContent: 'center', + width: Dimensions.get('window').width * (7 / 10), + height: Dimensions.get('window').width * (7 / 10) * 0.68, + backgroundColor: '#fcfcfc', + padding: 20, + borderRadius: 5 + }, + spinnerTitle: { + fontSize: 18, + color: '#313131', + textAlign: 'center' + }, + shareContent: { + flex: 1, + flexDirection: 'column', + justifyContent: 'center', + alignItems: 'center' + }, + shareIcon: { + width: 40, + height: 40 + } +}); + +export default WebViewPage; \ No newline at end of file diff --git a/app/reducers/index.js b/app/reducers/index.js new file mode 100644 index 0000000..a3eeb62 --- /dev/null +++ b/app/reducers/index.js @@ -0,0 +1,10 @@ +'use strict'; + +import {combineReducers} from 'redux'; +import reddit from './reddit'; + +const rootReducer = combineReducers({ + reddit +}); + +export default rootReducer; \ No newline at end of file diff --git a/app/reducers/reddit.js b/app/reducers/reddit.js new file mode 100644 index 0000000..1e32675 --- /dev/null +++ b/app/reducers/reddit.js @@ -0,0 +1,49 @@ +'use strict'; + +import * as types from '../constants/ActionTypes'; + +const initialState = { + isRefreshing: false, + loading: false, + isLoadMore: false, + noMore: false, + redditList: {}, + redditAfter: {1: '', 2: '', 3: '', 4: ''} +}; + +export default function reddit (state = initialState, action) { + switch (action.type) { + case types.FETCH_REDDIT_LIST: + return Object.assign({}, state, { + isRefreshing: action.isRefreshing, + loading: action.loading, + isLoadMore: action.isLoadMore + }); + case types.RECEIVE_REDDIT_LIST: + return Object.assign({}, state, { + isRefreshing: false, + isLoadMore: false, + noMore: action.redditData.children.length == 0, + redditList: state.isLoadMore ? loadMore(state, action) : combine(state, action), + loading: state.redditList[action.typeId] == undefined + }); + default: + return state; + } +} + +function combine (state, action) { + state.redditList[action.typeId] = action.redditData.children; + state.redditAfter[action.typeId] = action.after; + console.info('after=', state.after); + return state.redditList; +} + +function loadMore (state, action) { + state.redditList[action.typeId] = state.redditList[action.typeId].concat(action.redditData.children); + state.redditAfter[action.typeId] = action.after; + state.after = action.after; + console.error('after=', action.redditData.after); + console.error(state.redditList); + return state.redditList; +} \ No newline at end of file diff --git a/app/root.js b/app/root.js new file mode 100644 index 0000000..acbe2b3 --- /dev/null +++ b/app/root.js @@ -0,0 +1,19 @@ +import React from 'react-native' +import {Provider} from 'react-redux' +import configureStore from './store/configure-store' + +import App from './containers/app'; + +const store = configureStore(); + +class Root extends React.Component { + render () { + return ( + + + + ) + } +} + +export default Root; \ No newline at end of file diff --git a/app/store/configure-store.js b/app/store/configure-store.js new file mode 100644 index 0000000..e17c48b --- /dev/null +++ b/app/store/configure-store.js @@ -0,0 +1,12 @@ +'use strict'; +import {createStore, applyMiddleware} from 'redux'; +import thunkMiddleware from 'redux-thunk'; +import rootReducer from '../reducers/index'; + +const createStoreWithMiddleware = applyMiddleware(thunkMiddleware)(createStore); + + +export default function configureStore (initState) { + const store = createStoreWithMiddleware(rootReducer, initState); + return store; +} \ No newline at end of file diff --git a/app/utils/CommonUtils.js b/app/utils/CommonUtils.js new file mode 100644 index 0000000..4155ac8 --- /dev/null +++ b/app/utils/CommonUtils.js @@ -0,0 +1,16 @@ +'use strict'; + +export function NaviGoBack (navigator) { + if (navigator && navigator.getCurrentRoutes().length > 1) { + navigator.pop(); + return true; + } + return false; +} + +export function isEmptyObject (obj) { + for (var name in obj) { + return false; + } + return true; +} \ No newline at end of file diff --git a/app/utils/HttpServices.js b/app/utils/HttpServices.js new file mode 100644 index 0000000..cc4e776 --- /dev/null +++ b/app/utils/HttpServices.js @@ -0,0 +1,6 @@ +'use strict'; + +export function request (url) { + console.info("url=", url); + return fetch(url).then(response=>response.json()); +} \ No newline at end of file diff --git a/app/utils/Storage.js b/app/utils/Storage.js new file mode 100644 index 0000000..81bc48a --- /dev/null +++ b/app/utils/Storage.js @@ -0,0 +1,31 @@ +'use strict'; + +import React from 'react-native'; +const { + AsyncStorage + } = React; + +class DeviceStorage { + static get(key) { + return AsyncStorage.getItem(key).then(function(value) { + return JSON.parse(value); + }); + } + + static save(key, value) { + return AsyncStorage.setItem(key, JSON.stringify(value)); + } + + static update(key, value) { + return deviceStorage.get(key).then((item) => { + value = typeof value === 'string' ? value : Object.assign({}, item, value); + return AsyncStorage.setItem(key, JSON.stringify(value)); + }); + } + + static delete(key) { + return AsyncStorage.removeItem(key); + } +} + +export default DeviceStorage; \ No newline at end of file diff --git a/app/utils/ToastUtils.js b/app/utils/ToastUtils.js new file mode 100644 index 0000000..e1f2b3b --- /dev/null +++ b/app/utils/ToastUtils.js @@ -0,0 +1,14 @@ +'use strict'; + +import React from 'react-native'; +const { + ToastAndroid + } = React; + +export function ToastShort (content) { + ToastAndroid.show(new String(content), ToastAndroid.SHORT); +} + +export function ToastLong (content) { + ToastAndroid.show(new String(content), ToastAndroid.LONG); +} \ No newline at end of file diff --git a/art/reddit.gif b/art/reddit.gif new file mode 100644 index 0000000..3e349e7 Binary files /dev/null and b/art/reddit.gif differ diff --git a/index.android.js b/index.android.js new file mode 100644 index 0000000..5ddd2a1 --- /dev/null +++ b/index.android.js @@ -0,0 +1,7 @@ +import React from 'react-native'; +import Root from './app/root'; + +const { + AppRegistry + } = React; +AppRegistry.registerComponent('xReddit', ()=>Root); diff --git a/index.ios.js b/index.ios.js new file mode 100644 index 0000000..b219712 --- /dev/null +++ b/index.ios.js @@ -0,0 +1,52 @@ +/** + * Sample React Native App + * https://github.com/facebook/react-native + */ + +import React, { + AppRegistry, + Component, + StyleSheet, + Text, + View +} from 'react-native'; + +class xReddit extends Component { + render() { + return ( + + + Welcome to React Native! + + + To get started, edit index.ios.js + + + Press Cmd+R to reload,{'\n'} + Cmd+D or shake for dev menu + + + ); + } +} + +const styles = StyleSheet.create({ + container: { + flex: 1, + justifyContent: 'center', + alignItems: 'center', + backgroundColor: '#F5FCFF', + }, + welcome: { + fontSize: 20, + textAlign: 'center', + margin: 10, + }, + instructions: { + textAlign: 'center', + color: '#333333', + marginBottom: 5, + }, +}); + +AppRegistry.registerComponent('xReddit', () => xReddit); diff --git a/ios/xReddit.xcodeproj/project.pbxproj b/ios/xReddit.xcodeproj/project.pbxproj new file mode 100644 index 0000000..a13815c --- /dev/null +++ b/ios/xReddit.xcodeproj/project.pbxproj @@ -0,0 +1,763 @@ +// !$*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 /* xRedditTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 00E356F21AD99517003FC87E /* xRedditTests.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 */; }; +/* 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 = xReddit; + }; + 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; + }; +/* 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 /* xRedditTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = xRedditTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 00E356F11AD99517003FC87E /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 00E356F21AD99517003FC87E /* xRedditTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = xRedditTests.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 /* xReddit.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = xReddit.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 13B07FAF1A68108700A75B9A /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = AppDelegate.h; path = xReddit/AppDelegate.h; sourceTree = ""; }; + 13B07FB01A68108700A75B9A /* AppDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = AppDelegate.m; path = xReddit/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 = xReddit/Images.xcassets; sourceTree = ""; }; + 13B07FB61A68108700A75B9A /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = Info.plist; path = xReddit/Info.plist; sourceTree = ""; }; + 13B07FB71A68108700A75B9A /* main.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = main.m; path = xReddit/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 = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 00E356EB1AD99517003FC87E /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 13B07F8C1A680F5B00A75B9A /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 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 /* xRedditTests */ = { + isa = PBXGroup; + children = ( + 00E356F21AD99517003FC87E /* xRedditTests.m */, + 00E356F01AD99517003FC87E /* Supporting Files */, + ); + path = xRedditTests; + 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 /* xReddit */ = { + 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 = xReddit; + 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 = ( + 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 /* xReddit */, + 832341AE1AAA6A7D00B99B32 /* Libraries */, + 00E356EF1AD99517003FC87E /* xRedditTests */, + 83CBBA001A601CBA00E9B192 /* Products */, + ); + indentWidth = 2; + sourceTree = ""; + tabWidth = 2; + }; + 83CBBA001A601CBA00E9B192 /* Products */ = { + isa = PBXGroup; + children = ( + 13B07F961A680F5B00A75B9A /* xReddit.app */, + 00E356EE1AD99517003FC87E /* xRedditTests.xctest */, + ); + name = Products; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 00E356ED1AD99517003FC87E /* xRedditTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 00E357021AD99517003FC87E /* Build configuration list for PBXNativeTarget "xRedditTests" */; + buildPhases = ( + 00E356EA1AD99517003FC87E /* Sources */, + 00E356EB1AD99517003FC87E /* Frameworks */, + 00E356EC1AD99517003FC87E /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 00E356F51AD99517003FC87E /* PBXTargetDependency */, + ); + name = xRedditTests; + productName = xRedditTests; + productReference = 00E356EE1AD99517003FC87E /* xRedditTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; + 13B07F861A680F5B00A75B9A /* xReddit */ = { + isa = PBXNativeTarget; + buildConfigurationList = 13B07F931A680F5B00A75B9A /* Build configuration list for PBXNativeTarget "xReddit" */; + buildPhases = ( + 13B07F871A680F5B00A75B9A /* Sources */, + 13B07F8C1A680F5B00A75B9A /* Frameworks */, + 13B07F8E1A680F5B00A75B9A /* Resources */, + 00DD1BFF1BD5951E006B06BC /* Bundle React Native code and images */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = xReddit; + productName = "Hello World"; + productReference = 13B07F961A680F5B00A75B9A /* xReddit.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; + }; + }; + }; + buildConfigurationList = 83CBB9FA1A601CBA00E9B192 /* Build configuration list for PBXProject "xReddit" */; + 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 */; + }, + ); + projectRoot = ""; + targets = ( + 13B07F861A680F5B00A75B9A /* xReddit */, + 00E356ED1AD99517003FC87E /* xRedditTests */, + ); + }; +/* 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; + }; +/* 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 /* xRedditTests.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 /* xReddit */; + targetProxy = 00E356F41AD99517003FC87E /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin PBXVariantGroup section */ + 13B07FB11A68108700A75B9A /* LaunchScreen.xib */ = { + isa = PBXVariantGroup; + children = ( + 13B07FB21A68108700A75B9A /* Base */, + ); + name = LaunchScreen.xib; + path = xReddit; + 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 = xRedditTests/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)/xReddit.app/xReddit"; + }; + 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 = xRedditTests/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)/xReddit.app/xReddit"; + }; + name = Release; + }; + 13B07F941A680F5B00A75B9A /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + DEAD_CODE_STRIPPING = NO; + HEADER_SEARCH_PATHS = ( + "$(inherited)", + /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include, + "$(SRCROOT)/../node_modules/react-native/React/**", + ); + INFOPLIST_FILE = "xReddit/Info.plist"; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + OTHER_LDFLAGS = "-ObjC"; + PRODUCT_NAME = xReddit; + }; + name = Debug; + }; + 13B07F951A680F5B00A75B9A /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + HEADER_SEARCH_PATHS = ( + "$(inherited)", + /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include, + "$(SRCROOT)/../node_modules/react-native/React/**", + ); + INFOPLIST_FILE = "xReddit/Info.plist"; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + OTHER_LDFLAGS = "-ObjC"; + PRODUCT_NAME = xReddit; + }; + 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 = 7.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 = 7.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 "xRedditTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 00E356F61AD99517003FC87E /* Debug */, + 00E356F71AD99517003FC87E /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 13B07F931A680F5B00A75B9A /* Build configuration list for PBXNativeTarget "xReddit" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 13B07F941A680F5B00A75B9A /* Debug */, + 13B07F951A680F5B00A75B9A /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 83CBB9FA1A601CBA00E9B192 /* Build configuration list for PBXProject "xReddit" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 83CBBA201A601CBA00E9B192 /* Debug */, + 83CBBA211A601CBA00E9B192 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 83CBB9F71A601CBA00E9B192 /* Project object */; +} diff --git a/ios/xReddit.xcodeproj/xcshareddata/xcschemes/xReddit.xcscheme b/ios/xReddit.xcodeproj/xcshareddata/xcschemes/xReddit.xcscheme new file mode 100644 index 0000000..4c07d06 --- /dev/null +++ b/ios/xReddit.xcodeproj/xcshareddata/xcschemes/xReddit.xcscheme @@ -0,0 +1,112 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ios/xReddit/AppDelegate.h b/ios/xReddit/AppDelegate.h new file mode 100644 index 0000000..a9654d5 --- /dev/null +++ b/ios/xReddit/AppDelegate.h @@ -0,0 +1,16 @@ +/** + * 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 diff --git a/ios/xReddit/AppDelegate.m b/ios/xReddit/AppDelegate.m new file mode 100644 index 0000000..6a9bafc --- /dev/null +++ b/ios/xReddit/AppDelegate.m @@ -0,0 +1,59 @@ +/** + * 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" + +@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:@"xReddit" + initialProperties:nil + launchOptions:launchOptions]; + + self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds]; + UIViewController *rootViewController = [UIViewController new]; + rootViewController.view = rootView; + self.window.rootViewController = rootViewController; + [self.window makeKeyAndVisible]; + return YES; +} + +@end diff --git a/ios/xReddit/Base.lproj/LaunchScreen.xib b/ios/xReddit/Base.lproj/LaunchScreen.xib new file mode 100644 index 0000000..8fe5cde --- /dev/null +++ b/ios/xReddit/Base.lproj/LaunchScreen.xib @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ios/xReddit/Images.xcassets/AppIcon.appiconset/Contents.json b/ios/xReddit/Images.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000..118c98f --- /dev/null +++ b/ios/xReddit/Images.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,38 @@ +{ + "images" : [ + { + "idiom" : "iphone", + "size" : "29x29", + "scale" : "2x" + }, + { + "idiom" : "iphone", + "size" : "29x29", + "scale" : "3x" + }, + { + "idiom" : "iphone", + "size" : "40x40", + "scale" : "2x" + }, + { + "idiom" : "iphone", + "size" : "40x40", + "scale" : "3x" + }, + { + "idiom" : "iphone", + "size" : "60x60", + "scale" : "2x" + }, + { + "idiom" : "iphone", + "size" : "60x60", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/ios/xReddit/Info.plist b/ios/xReddit/Info.plist new file mode 100644 index 0000000..91963b2 --- /dev/null +++ b/ios/xReddit/Info.plist @@ -0,0 +1,48 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + org.reactjs.native.example.$(PRODUCT_NAME:rfc1034identifier) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + APPL + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 1 + LSRequiresIPhoneOS + + UILaunchStoryboardName + LaunchScreen + UIRequiredDeviceCapabilities + + armv7 + + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UIViewControllerBasedStatusBarAppearance + + NSLocationWhenInUseUsageDescription + + NSAppTransportSecurity + + + NSAllowsArbitraryLoads + + + + diff --git a/ios/xReddit/main.m b/ios/xReddit/main.m new file mode 100644 index 0000000..3d767fc --- /dev/null +++ b/ios/xReddit/main.m @@ -0,0 +1,18 @@ +/** + * 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])); + } +} diff --git a/ios/xRedditTests/Info.plist b/ios/xRedditTests/Info.plist new file mode 100644 index 0000000..886825c --- /dev/null +++ b/ios/xRedditTests/Info.plist @@ -0,0 +1,24 @@ + + + + + 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 + + diff --git a/ios/xRedditTests/xRedditTests.m b/ios/xRedditTests/xRedditTests.m new file mode 100644 index 0000000..f5277ce --- /dev/null +++ b/ios/xRedditTests/xRedditTests.m @@ -0,0 +1,70 @@ +/** + * 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 240 +#define TEXT_TO_LOOK_FOR @"Welcome to React Native!" + +@interface xRedditTests : XCTestCase + +@end + +@implementation xRedditTests + +- (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 diff --git a/package.json b/package.json new file mode 100644 index 0000000..14f3d32 --- /dev/null +++ b/package.json @@ -0,0 +1,20 @@ +{ + "name": "xReddit", + "version": "0.0.1", + "private": true, + "scripts": { + "start": "node node_modules/react-native/local-cli/cli.js start" + }, + "dependencies": { + "react": "^0.14.8", + "react-native": "^0.22.2", + "immutable": "^3.7.5", + "react-redux": "^4.0.6", + "redux": "^3.0.5", + "redux-thunk": "^1.0.3", + "react-native-scrollable-tab-view": "^0.4.0" + }, + "devDependencies": { + "redux-devtools": "^3.2.0" + } +}