├── .flowconfig ├── .gitignore ├── .watchmanconfig ├── LICENSE.txt ├── README.md ├── android ├── app │ ├── build.gradle │ ├── proguard-rules.pro │ ├── react.gradle │ └── src │ │ └── main │ │ ├── AndroidManifest.xml │ │ ├── java │ │ └── com │ │ │ └── oscgit │ │ │ └── MainActivity.java │ │ └── res │ │ ├── mipmap-hdpi │ │ └── ic_launcher.png │ │ ├── mipmap-mdpi │ │ └── ic_launcher.png │ │ ├── mipmap-xhdpi │ │ └── ic_launcher.png │ │ ├── mipmap-xxhdpi │ │ └── ic_launcher.png │ │ └── values │ │ ├── strings.xml │ │ └── styles.xml ├── build.gradle ├── gradle.properties ├── gradlew ├── gradlew.bat └── settings.gradle ├── common ├── Colors.js ├── CommonComponents.js ├── CommonStyles.js ├── ErrorPlacehoderComponent.js ├── LanguageComponent.js ├── RefreshListView.js └── SettingsCell.js ├── components ├── CreateIssueComponent.js ├── EventCell.js ├── FamousComponent.js ├── FeedbackComponent.js ├── LoginComponent.js ├── MyProfileComponent.js ├── OSCRefreshListView.js ├── PersonalComponent.js ├── PersonalEventComponent.js ├── PersonalProjectComponent.js ├── PersonalStarComponent.js ├── PersonalWatchComponent.js ├── ProjectCategoryComponent.js ├── ProjectComponent.js ├── RootTab.js ├── Routes.js ├── SearchComponent.js ├── SettingsComponent.js ├── ShakeComponent.js ├── WebComponent.js └── repo │ ├── RepoCell2.js │ └── RepoDetailComponent.js ├── config.js ├── entity └── User.js ├── icons ├── android │ ├── mipmap-hdpi │ │ └── ic_launcher.png │ ├── mipmap-ldpi │ │ └── ic_launcher.png │ ├── mipmap-mdpi │ │ └── ic_launcher.png │ ├── mipmap-xhdpi │ │ └── ic_launcher.png │ ├── mipmap-xxhdpi │ │ └── ic_launcher.png │ ├── mipmap-xxxhdpi │ │ └── ic_launcher.png │ └── playstore-icon.png ├── ios │ ├── AppIcon.appiconset │ │ ├── Contents.json │ │ ├── Icon-40.png │ │ ├── Icon-40@2x.png │ │ ├── Icon-40@3x.png │ │ ├── Icon-60@2x.png │ │ ├── Icon-60@3x.png │ │ ├── Icon-76.png │ │ ├── Icon-76@2x.png │ │ ├── Icon-83.5@2x.png │ │ ├── Icon-Small.png │ │ ├── Icon-Small@2x.png │ │ └── Icon-Small@3x.png │ ├── Icon-60.png │ ├── Icon-72.png │ ├── Icon-72@2x.png │ ├── Icon-Small-50.png │ ├── Icon-Small-50@2x.png │ ├── Icon.png │ ├── Icon@2x.png │ ├── README.md │ ├── iTunesArtwork.png │ └── iTunesArtwork@2x.png └── watchkit │ └── AppIcon.appiconset │ ├── Contents.json │ ├── Icon-24@2x.png │ ├── Icon-27.5@2x.png │ ├── Icon-29@2x.png │ ├── Icon-29@3x.png │ ├── Icon-40@2x.png │ ├── Icon-44@2x.png │ ├── Icon-86@2x.png │ └── Icon-98@2x.png ├── index.android.js ├── index.ios.js ├── ios ├── OSCGit.xcodeproj │ ├── project.pbxproj │ └── xcshareddata │ │ └── xcschemes │ │ └── OSCGit.xcscheme ├── OSCGit.xcworkspace │ └── contents.xcworkspacedata ├── OSCGit │ ├── AppDelegate.h │ ├── AppDelegate.m │ ├── Base.lproj │ │ └── LaunchScreen.xib │ ├── Classes │ │ ├── Additions │ │ │ ├── NSString+GFAdditions.h │ │ │ └── NSString+GFAdditions.m │ │ ├── DXTopMessageManager.h │ │ ├── DXTopMessageManager.m │ │ ├── GFDiskCacheManager.h │ │ ├── GFDiskCacheManager.m │ │ ├── H5 │ │ │ ├── GFWebResourceCache.h │ │ │ ├── GFWebResourceCache.m │ │ │ ├── GFWebResourceInterceptor.h │ │ │ ├── GFWebResourceInterceptor.m │ │ │ ├── GFWebResourceInterceptorSettings+Internal.h │ │ │ ├── GFWebResourceInterceptorSettings.h │ │ │ ├── GFWebResourceInterceptorSettings.m │ │ │ ├── GFWebResourceURLProtocol.h │ │ │ └── GFWebResourceURLProtocol.m │ │ ├── UIView+TopBarMessage.h │ │ ├── UIView+TopBarMessage.m │ │ ├── Utils.h │ │ └── Utils.m │ ├── Images.xcassets │ │ ├── AppIcon.appiconset │ │ │ ├── Contents.json │ │ │ ├── Icon-40.png │ │ │ ├── Icon-40@2x.png │ │ │ ├── Icon-40@3x.png │ │ │ ├── Icon-60@2x.png │ │ │ ├── Icon-60@3x.png │ │ │ ├── Icon-76.png │ │ │ ├── Icon-76@2x.png │ │ │ ├── Icon-83.5@2x.png │ │ │ ├── Icon-Small.png │ │ │ ├── Icon-Small@2x.png │ │ │ └── Icon-Small@3x.png │ │ └── Contents.json │ ├── Info.plist │ └── main.m ├── OSCGitTests │ ├── Info.plist │ └── OSCGitTests.m ├── Podfile ├── Podfile.lock └── bundle │ ├── index.ios.jsbundle │ └── source.map ├── package.json ├── screen ├── famous.jpg ├── famous_choose.jpg ├── feedback.jpg ├── login.jpg ├── my_profile.jpg ├── personal.jpg ├── project.jpg ├── project_commits.jpg ├── project_detail.jpg ├── project_detail_code.jpg ├── project_detail_readme.jpg ├── project_share.jpg ├── search.jpg ├── settings.jpg └── shake.jpg ├── service └── OSCService.js └── utils ├── DXRNUtils.js ├── GFDiskCache.js ├── Log.js └── Utils.js /.flowconfig: -------------------------------------------------------------------------------- 1 | [ignore] 2 | 3 | # We fork some components by platform. 4 | .*/*.web.js 5 | .*/*.android.js 6 | 7 | # Some modules have their own node_modules with overlap 8 | .*/node_modules/node-haste/.* 9 | 10 | # Ugh 11 | .*/node_modules/babel.* 12 | .*/node_modules/babylon.* 13 | .*/node_modules/invariant.* 14 | 15 | # Ignore react and fbjs where there are overlaps, but don't ignore 16 | # anything that react-native relies on 17 | .*/node_modules/fbjs/lib/Map.js 18 | .*/node_modules/fbjs/lib/Promise.js 19 | .*/node_modules/fbjs/lib/fetch.js 20 | .*/node_modules/fbjs/lib/ExecutionEnvironment.js 21 | .*/node_modules/fbjs/lib/isEmpty.js 22 | .*/node_modules/fbjs/lib/crc32.js 23 | .*/node_modules/fbjs/lib/ErrorUtils.js 24 | 25 | # Flow has a built-in definition for the 'react' module which we prefer to use 26 | # over the currently-untyped source 27 | .*/node_modules/react/react.js 28 | .*/node_modules/react/lib/React.js 29 | .*/node_modules/react/lib/ReactDOM.js 30 | 31 | # Ignore commoner tests 32 | .*/node_modules/commoner/test/.* 33 | 34 | # See https://github.com/facebook/flow/issues/442 35 | .*/react-tools/node_modules/commoner/lib/reader.js 36 | 37 | # Ignore jest 38 | .*/node_modules/jest-cli/.* 39 | 40 | # Ignore Website 41 | .*/website/.* 42 | 43 | [include] 44 | 45 | [libs] 46 | node_modules/react-native/Libraries/react-native/react-native-interface.js 47 | 48 | [options] 49 | module.system=haste 50 | 51 | munge_underscores=true 52 | 53 | module.name_mapper='^image![a-zA-Z0-9$_-]+$' -> 'GlobalImageStub' 54 | 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' 55 | 56 | suppress_type=$FlowIssue 57 | suppress_type=$FlowFixMe 58 | suppress_type=$FixMe 59 | 60 | suppress_comment=\\(.\\|\n\\)*\\$FlowFixMe\\($\\|[^(]\\|(\\(>=0\\.\\(2[0-1]\\|1[0-9]\\|[0-9]\\).[0-9]\\)? *\\(site=[a-z,_]*react_native[a-z,_]*\\)?)\\) 61 | suppress_comment=\\(.\\|\n\\)*\\$FlowIssue\\((\\(>=0\\.\\(2[0-1]\\|1[0-9]\\|[0-9]\\).[0-9]\\)? *\\(site=[a-z,_]*react_native[a-z,_]*\\)?)\\)?:? #[0-9]+ 62 | suppress_comment=\\(.\\|\n\\)*\\$FlowFixedInNextDeploy 63 | 64 | [version] 65 | 0.21.0 66 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled source # 2 | ################### 3 | *.com 4 | *.class 5 | *.dll 6 | *.exe 7 | *.o 8 | *.so 9 | 10 | # Packages # 11 | ############ 12 | # it's better to unpack these files and commit the raw source 13 | # git has its own built in compression methods 14 | *.7z 15 | *.dmg 16 | *.gz 17 | *.iso 18 | *.jar 19 | *.rar 20 | *.tar 21 | *.zip 22 | 23 | # Logs and databases # 24 | ###################### 25 | *.log 26 | *.sql 27 | *.sqlite 28 | 29 | # OS generated files # 30 | ###################### 31 | .DS_Store 32 | .DS_Store? 33 | ._* 34 | .Spotlight-V100 35 | .Trashes 36 | ehthumbs.db 37 | Thumbs.db 38 | 39 | # Xcode 40 | # 41 | #build/ 42 | *.pbxuser 43 | !default.pbxuser 44 | *.mode1v3 45 | !default.mode1v3 46 | *.mode2v3 47 | !default.mode2v3 48 | *.perspectivev3 49 | !default.perspectivev3 50 | xcuserdata 51 | *.xccheckout 52 | *.moved-aside 53 | DerivedData 54 | *.hmap 55 | *.xcuserstate 56 | 57 | # CocoaPods 58 | # 59 | # We recommend against adding the Pods directory to your .gitignore. However 60 | # you should judge for yourself, the pros and cons are mentioned at: 61 | # http://guides.cocoapods.org/using/using-cocoapods.html#should-i-ignore-the-pods-directory-in-source-control 62 | # 63 | Pods/ 64 | 65 | # Nodejs 66 | # 67 | 68 | # Logs 69 | logs 70 | *.log 71 | npm-debug.log* 72 | 73 | # Runtime data 74 | pids 75 | *.pid 76 | *.seed 77 | 78 | # Directory for instrumented libs generated by jscoverage/JSCover 79 | lib-cov 80 | 81 | # Coverage directory used by tools like istanbul 82 | coverage 83 | 84 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 85 | .grunt 86 | 87 | # node-waf configuration 88 | .lock-wscript 89 | 90 | # Compiled binary addons (http://nodejs.org/api/addons.html) 91 | #build/Release 92 | 93 | # Dependency directory 94 | # https://docs.npmjs.com/misc/faq#should-i-check-my-node-modules-folder-into-git 95 | node_modules 96 | 97 | 98 | # tree 99 | tree_out 100 | 101 | # Android 102 | android/.gradle/ 103 | android/.idea/ 104 | android/app/build 105 | android/app.iml 106 | android/gradle 107 | android/build 108 | android/*/build 109 | local.properties 110 | *.keystore 111 | 112 | # WebStorm 113 | .idea 114 | -------------------------------------------------------------------------------- /.watchmanconfig: -------------------------------------------------------------------------------- 1 | {} -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ReactNative Git@OSC 2 | 3 | [![js-standard-style](https://img.shields.io/badge/code%20style-standard-brightgreen.svg?style=flat)](https://github.com/feross/standard) 4 | 5 | 使用ReactNative仿造的Git@OSC! (感谢 [xiekw2010](https://github.com/xiekw2010/react-native-gitfeed)) 6 | 7 | ... 8 | ... 9 | ... 10 | ... 11 | 12 | **[项目已经迁移到 https://git.oschina.net/rplees/react-native-gitosc.git](https://git.oschina.net/rplees/react-native-gitosc.git)** 13 | 14 | ### License 15 | [GPL](./LICENSE.txt). Copyright (c) [rplees](https://github.com/rplees). 16 | -------------------------------------------------------------------------------- /android/app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: "com.android.application" 2 | 3 | import com.android.build.OutputFile 4 | 5 | /** 6 | * The react.gradle file registers a task for each build variant (e.g. bundleDebugJsAndAssets 7 | * and bundleReleaseJsAndAssets). 8 | * These basically call `react-native bundle` with the correct arguments during the Android build 9 | * cycle. By default, bundleDebugJsAndAssets is skipped, as in debug/dev mode we prefer to load the 10 | * bundle directly from the development server. Below you can see all the possible configurations 11 | * and their defaults. If you decide to add a configuration block, make sure to add it before the 12 | * `apply from: "react.gradle"` line. 13 | * 14 | * project.ext.react = [ 15 | * // the name of the generated asset file containing your JS bundle 16 | * bundleAssetName: "index.android.bundle", 17 | * 18 | * // the entry file for bundle generation 19 | * entryFile: "index.android.js", 20 | * 21 | * // whether to bundle JS and assets in debug mode 22 | * bundleInDebug: false, 23 | * 24 | * // whether to bundle JS and assets in release mode 25 | * bundleInRelease: true, 26 | * 27 | * // whether to bundle JS and assets in another build variant (if configured). 28 | * // See http://tools.android.com/tech-docs/new-build-system/user-guide#TOC-Build-Variants 29 | * // The configuration property can be in the following formats 30 | * // 'bundleIn${productFlavor}${buildType}' 31 | * // 'bundleIn${buildType}' 32 | * // bundleInFreeDebug: true, 33 | * // bundleInPaidRelease: true, 34 | * // bundleInBeta: true, 35 | * 36 | * // the root of your project, i.e. where "package.json" lives 37 | * root: "../../", 38 | * 39 | * // where to put the JS bundle asset in debug mode 40 | * jsBundleDirDebug: "$buildDir/intermediates/assets/debug", 41 | * 42 | * // where to put the JS bundle asset in release mode 43 | * jsBundleDirRelease: "$buildDir/intermediates/assets/release", 44 | * 45 | * // where to put drawable resources / React Native assets, e.g. the ones you use via 46 | * // require('./image.png')), in debug mode 47 | * resourcesDirDebug: "$buildDir/intermediates/res/merged/debug", 48 | * 49 | * // where to put drawable resources / React Native assets, e.g. the ones you use via 50 | * // require('./image.png')), in release mode 51 | * resourcesDirRelease: "$buildDir/intermediates/res/merged/release", 52 | * 53 | * // by default the gradle tasks are skipped if none of the JS files or assets change; this means 54 | * // that we don't look at files in android/ or ios/ to determine whether the tasks are up to 55 | * // date; if you have any other folders that you want to ignore for performance reasons (gradle 56 | * // indexes the entire tree), add them here. Alternatively, if you have JS files in android/ 57 | * // for example, you might want to remove it from here. 58 | * inputExcludes: ["android/**", "ios/**"] 59 | * ] 60 | */ 61 | 62 | apply from: "react.gradle" 63 | 64 | /** 65 | * Set this to true to create two separate APKs instead of one: 66 | * - An APK that only works on ARM devices 67 | * - An APK that only works on x86 devices 68 | * The advantage is the size of the APK is reduced by about 4MB. 69 | * Upload all the APKs to the Play Store and people will download 70 | * the correct one based on the CPU architecture of their device. 71 | */ 72 | def enableSeparateBuildPerCPUArchitecture = false 73 | 74 | /** 75 | * Run Proguard to shrink the Java bytecode in release builds. 76 | */ 77 | def enableProguardInReleaseBuilds = false 78 | 79 | android { 80 | compileSdkVersion 23 81 | buildToolsVersion "23.0.1" 82 | 83 | defaultConfig { 84 | applicationId "com.oscgit" 85 | minSdkVersion 16 86 | targetSdkVersion 22 87 | versionCode 1 88 | versionName "1.0" 89 | ndk { 90 | abiFilters "armeabi-v7a", "x86" 91 | } 92 | } 93 | splits { 94 | abi { 95 | reset() 96 | enable enableSeparateBuildPerCPUArchitecture 97 | universalApk false // If true, also generate a universal APK 98 | include "armeabi-v7a", "x86" 99 | } 100 | } 101 | buildTypes { 102 | release { 103 | minifyEnabled enableProguardInReleaseBuilds 104 | proguardFiles getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro" 105 | } 106 | } 107 | // applicationVariants are e.g. debug, release 108 | applicationVariants.all { variant -> 109 | variant.outputs.each { output -> 110 | // For each separate APK per architecture, set a unique version code as described here: 111 | // http://tools.android.com/tech-docs/new-build-system/user-guide/apk-splits 112 | def versionCodes = ["armeabi-v7a":1, "x86":2] 113 | def abi = output.getFilter(OutputFile.ABI) 114 | if (abi != null) { // null for the universal-debug, universal-release variants 115 | output.versionCodeOverride = 116 | versionCodes.get(abi) * 1048576 + defaultConfig.versionCode 117 | } 118 | } 119 | } 120 | } 121 | 122 | dependencies { 123 | compile fileTree(dir: "libs", include: ["*.jar"]) 124 | compile "com.android.support:appcompat-v7:23.0.1" 125 | compile "com.facebook.react:react-native:+" // From node_modules 126 | } 127 | -------------------------------------------------------------------------------- /android/app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in /usr/local/Cellar/android-sdk/24.3.3/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | 19 | # Disabling obfuscation is useful if you collect stack traces from production crashes 20 | # (unless you are using a system that supports de-obfuscate the stack traces). 21 | -dontobfuscate 22 | 23 | # React Native 24 | 25 | # Keep our interfaces so they can be used by other ProGuard rules. 26 | # See http://sourceforge.net/p/proguard/bugs/466/ 27 | -keep,allowobfuscation @interface com.facebook.proguard.annotations.DoNotStrip 28 | -keep,allowobfuscation @interface com.facebook.proguard.annotations.KeepGettersAndSetters 29 | 30 | # Do not strip any method/class that is annotated with @DoNotStrip 31 | -keep @com.facebook.proguard.annotations.DoNotStrip class * 32 | -keepclassmembers class * { 33 | @com.facebook.proguard.annotations.DoNotStrip *; 34 | } 35 | 36 | -keepclassmembers @com.facebook.proguard.annotations.KeepGettersAndSetters class * { 37 | void set*(***); 38 | *** get*(); 39 | } 40 | 41 | -keep class * extends com.facebook.react.bridge.JavaScriptModule { *; } 42 | -keep class * extends com.facebook.react.bridge.NativeModule { *; } 43 | -keepclassmembers,includedescriptorclasses class * { native ; } 44 | -keepclassmembers class * { @com.facebook.react.uimanager.UIProp ; } 45 | -keepclassmembers class * { @com.facebook.react.uimanager.annotations.ReactProp ; } 46 | -keepclassmembers class * { @com.facebook.react.uimanager.annotations.ReactPropGroup ; } 47 | 48 | -dontwarn com.facebook.react.** 49 | 50 | # okhttp 51 | 52 | -keepattributes Signature 53 | -keepattributes *Annotation* 54 | -keep class com.squareup.okhttp.** { *; } 55 | -keep interface com.squareup.okhttp.** { *; } 56 | -dontwarn com.squareup.okhttp.** 57 | 58 | # okio 59 | 60 | -keep class sun.misc.Unsafe { *; } 61 | -dontwarn java.nio.file.* 62 | -dontwarn org.codehaus.mojo.animal_sniffer.IgnoreJRERequirement 63 | -dontwarn okio.** 64 | 65 | # stetho 66 | 67 | -dontwarn com.facebook.stetho.** 68 | -------------------------------------------------------------------------------- /android/app/react.gradle: -------------------------------------------------------------------------------- 1 | import org.apache.tools.ant.taskdefs.condition.Os 2 | 3 | def config = project.hasProperty("react") ? project.react : []; 4 | 5 | def bundleAssetName = config.bundleAssetName ?: "index.android.bundle" 6 | def entryFile = config.entryFile ?: "index.android.js" 7 | 8 | // because elvis operator 9 | def elvisFile(thing) { 10 | return thing ? file(thing) : null; 11 | } 12 | 13 | def reactRoot = elvisFile(config.root) ?: file("../../") 14 | def inputExcludes = config.inputExcludes ?: ["android/**", "ios/**"] 15 | 16 | void runBefore(String dependentTaskName, Task task) { 17 | Task dependentTask = tasks.findByPath(dependentTaskName); 18 | if (dependentTask != null) { 19 | dependentTask.dependsOn task 20 | } 21 | } 22 | 23 | gradle.projectsEvaluated { 24 | // Grab all build types and product flavors 25 | def buildTypes = android.buildTypes.collect { type -> type.name } 26 | def productFlavors = android.productFlavors.collect { flavor -> flavor.name } 27 | 28 | // When no product flavors defined, use empty 29 | if (!productFlavors) productFlavors.add('') 30 | 31 | productFlavors.each { productFlavorName -> 32 | buildTypes.each { buildTypeName -> 33 | // Create variant and target names 34 | def targetName = "${productFlavorName.capitalize()}${buildTypeName.capitalize()}" 35 | def targetPath = productFlavorName ? 36 | "${productFlavorName}/${buildTypeName}" : 37 | "${buildTypeName}" 38 | 39 | // React js bundle directories 40 | def jsBundleDirConfigName = "jsBundleDir${targetName}" 41 | def jsBundleDir = elvisFile(config."$jsBundleDirConfigName") ?: 42 | file("$buildDir/intermediates/assets/${targetPath}") 43 | 44 | def resourcesDirConfigName = "jsBundleDir${targetName}" 45 | def resourcesDir = elvisFile(config."${resourcesDirConfigName}") ?: 46 | file("$buildDir/intermediates/res/merged/${targetPath}") 47 | def jsBundleFile = file("$jsBundleDir/$bundleAssetName") 48 | 49 | // Bundle task name for variant 50 | def bundleJsAndAssetsTaskName = "bundle${targetName}JsAndAssets" 51 | 52 | def currentBundleTask = tasks.create( 53 | name: bundleJsAndAssetsTaskName, 54 | type: Exec) { 55 | group = "react" 56 | description = "bundle JS and assets for ${targetName}." 57 | 58 | // Create dirs if they are not there (e.g. the "clean" task just ran) 59 | doFirst { 60 | jsBundleDir.mkdirs() 61 | resourcesDir.mkdirs() 62 | } 63 | 64 | // Set up inputs and outputs so gradle can cache the result 65 | inputs.files fileTree(dir: reactRoot, excludes: inputExcludes) 66 | outputs.dir jsBundleDir 67 | outputs.dir resourcesDir 68 | 69 | // Set up the call to the react-native cli 70 | workingDir reactRoot 71 | 72 | // Set up dev mode 73 | def devEnabled = !targetName.toLowerCase().contains("release") 74 | if (Os.isFamily(Os.FAMILY_WINDOWS)) { 75 | commandLine "cmd", "/c", "react-native", "bundle", "--platform", "android", "--dev", "${devEnabled}", 76 | "--entry-file", entryFile, "--bundle-output", jsBundleFile, "--assets-dest", resourcesDir 77 | } else { 78 | commandLine "react-native", "bundle", "--platform", "android", "--dev", "${devEnabled}", 79 | "--entry-file", entryFile, "--bundle-output", jsBundleFile, "--assets-dest", resourcesDir 80 | } 81 | 82 | enabled config."bundleIn${targetName}" || 83 | config."bundleIn${buildTypeName.capitalize()}" ?: 84 | targetName.toLowerCase().contains("release") 85 | } 86 | 87 | // Hook bundle${productFlavor}${buildType}JsAndAssets into the android build process 88 | currentBundleTask.dependsOn("merge${targetName}Resources") 89 | currentBundleTask.dependsOn("merge${targetName}Assets") 90 | 91 | runBefore("processArmeabi-v7a${targetName}Resources", currentBundleTask) 92 | runBefore("processX86${targetName}Resources", currentBundleTask) 93 | runBefore("processUniversal${targetName}Resources", currentBundleTask) 94 | runBefore("process${targetName}Resources", currentBundleTask) 95 | } 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /android/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 11 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /android/app/src/main/java/com/oscgit/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.oscgit; 2 | 3 | import com.facebook.react.ReactActivity; 4 | import com.facebook.react.ReactPackage; 5 | import com.facebook.react.shell.MainReactPackage; 6 | 7 | import java.util.Arrays; 8 | import java.util.List; 9 | 10 | public class MainActivity extends ReactActivity { 11 | 12 | /** 13 | * Returns the name of the main component registered from JavaScript. 14 | * This is used to schedule rendering of the component. 15 | */ 16 | @Override 17 | protected String getMainComponentName() { 18 | return "OSCGit"; 19 | } 20 | 21 | /** 22 | * Returns whether dev mode should be enabled. 23 | * This enables e.g. the dev menu. 24 | */ 25 | @Override 26 | protected boolean getUseDeveloperSupport() { 27 | return BuildConfig.DEBUG; 28 | } 29 | 30 | /** 31 | * A list of packages used by the app. If the app uses additional views 32 | * or modules besides the default ones, add more packages here. 33 | */ 34 | @Override 35 | protected List getPackages() { 36 | return Arrays.asList( 37 | new MainReactPackage() 38 | ); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rplees/react-native-gitosc/HEAD/android/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rplees/react-native-gitosc/HEAD/android/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rplees/react-native-gitosc/HEAD/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rplees/react-native-gitosc/HEAD/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | OSCGit 3 | 4 | -------------------------------------------------------------------------------- /android/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /android/build.gradle: -------------------------------------------------------------------------------- 1 | // Top-level build file where you can add configuration options common to all sub-projects/modules. 2 | 3 | buildscript { 4 | repositories { 5 | jcenter() 6 | } 7 | dependencies { 8 | classpath 'com.android.tools.build:gradle:1.3.1' 9 | 10 | // NOTE: Do not place your application dependencies here; they belong 11 | // in the individual module build.gradle files 12 | } 13 | } 14 | 15 | allprojects { 16 | repositories { 17 | mavenLocal() 18 | jcenter() 19 | maven { 20 | // All of React Native (JS, Obj-C sources, Android binaries) is installed from npm 21 | url "$projectDir/../../node_modules/react-native/android" 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /android/gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | 3 | # IDE (e.g. Android Studio) users: 4 | # Gradle settings configured through the IDE *will override* 5 | # any settings specified in this file. 6 | 7 | # For more details on how to configure your build environment visit 8 | # http://www.gradle.org/docs/current/userguide/build_environment.html 9 | 10 | # Specifies the JVM arguments used for the daemon process. 11 | # The setting is particularly useful for tweaking memory settings. 12 | # Default value: -Xmx10248m -XX:MaxPermSize=256m 13 | # org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 14 | 15 | # When configured, Gradle will run in incubating parallel mode. 16 | # This option should only be used with decoupled projects. More details, visit 17 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 18 | # org.gradle.parallel=true 19 | 20 | android.useDeprecatedNdk=true 21 | -------------------------------------------------------------------------------- /android/gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 10 | DEFAULT_JVM_OPTS="" 11 | 12 | APP_NAME="Gradle" 13 | APP_BASE_NAME=`basename "$0"` 14 | 15 | # Use the maximum available, or set MAX_FD != -1 to use that value. 16 | MAX_FD="maximum" 17 | 18 | warn ( ) { 19 | echo "$*" 20 | } 21 | 22 | die ( ) { 23 | echo 24 | echo "$*" 25 | echo 26 | exit 1 27 | } 28 | 29 | # OS specific support (must be 'true' or 'false'). 30 | cygwin=false 31 | msys=false 32 | darwin=false 33 | case "`uname`" in 34 | CYGWIN* ) 35 | cygwin=true 36 | ;; 37 | Darwin* ) 38 | darwin=true 39 | ;; 40 | MINGW* ) 41 | msys=true 42 | ;; 43 | esac 44 | 45 | # For Cygwin, ensure paths are in UNIX format before anything is touched. 46 | if $cygwin ; then 47 | [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"` 48 | fi 49 | 50 | # Attempt to set APP_HOME 51 | # Resolve links: $0 may be a link 52 | PRG="$0" 53 | # Need this for relative symlinks. 54 | while [ -h "$PRG" ] ; do 55 | ls=`ls -ld "$PRG"` 56 | link=`expr "$ls" : '.*-> \(.*\)$'` 57 | if expr "$link" : '/.*' > /dev/null; then 58 | PRG="$link" 59 | else 60 | PRG=`dirname "$PRG"`"/$link" 61 | fi 62 | done 63 | SAVED="`pwd`" 64 | cd "`dirname \"$PRG\"`/" >&- 65 | APP_HOME="`pwd -P`" 66 | cd "$SAVED" >&- 67 | 68 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 69 | 70 | # Determine the Java command to use to start the JVM. 71 | if [ -n "$JAVA_HOME" ] ; then 72 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 73 | # IBM's JDK on AIX uses strange locations for the executables 74 | JAVACMD="$JAVA_HOME/jre/sh/java" 75 | else 76 | JAVACMD="$JAVA_HOME/bin/java" 77 | fi 78 | if [ ! -x "$JAVACMD" ] ; then 79 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 80 | 81 | Please set the JAVA_HOME variable in your environment to match the 82 | location of your Java installation." 83 | fi 84 | else 85 | JAVACMD="java" 86 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 87 | 88 | Please set the JAVA_HOME variable in your environment to match the 89 | location of your Java installation." 90 | fi 91 | 92 | # Increase the maximum file descriptors if we can. 93 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then 94 | MAX_FD_LIMIT=`ulimit -H -n` 95 | if [ $? -eq 0 ] ; then 96 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 97 | MAX_FD="$MAX_FD_LIMIT" 98 | fi 99 | ulimit -n $MAX_FD 100 | if [ $? -ne 0 ] ; then 101 | warn "Could not set maximum file descriptor limit: $MAX_FD" 102 | fi 103 | else 104 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 105 | fi 106 | fi 107 | 108 | # For Darwin, add options to specify how the application appears in the dock 109 | if $darwin; then 110 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 111 | fi 112 | 113 | # For Cygwin, switch paths to Windows format before running java 114 | if $cygwin ; then 115 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 116 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules 158 | function splitJvmOpts() { 159 | JVM_OPTS=("$@") 160 | } 161 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS 162 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" 163 | 164 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" 165 | -------------------------------------------------------------------------------- /android/gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 12 | set DEFAULT_JVM_OPTS= 13 | 14 | set DIRNAME=%~dp0 15 | if "%DIRNAME%" == "" set DIRNAME=. 16 | set APP_BASE_NAME=%~n0 17 | set APP_HOME=%DIRNAME% 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windowz variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | if "%@eval[2+2]" == "4" goto 4NT_args 53 | 54 | :win9xME_args 55 | @rem Slurp the command line arguments. 56 | set CMD_LINE_ARGS= 57 | set _SKIP=2 58 | 59 | :win9xME_args_slurp 60 | if "x%~1" == "x" goto execute 61 | 62 | set CMD_LINE_ARGS=%* 63 | goto execute 64 | 65 | :4NT_args 66 | @rem Get arguments from the 4NT Shell from JP Software 67 | set CMD_LINE_ARGS=%$ 68 | 69 | :execute 70 | @rem Setup the command line 71 | 72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if "%ERRORLEVEL%"=="0" goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 85 | exit /b 1 86 | 87 | :mainEnd 88 | if "%OS%"=="Windows_NT" endlocal 89 | 90 | :omega 91 | -------------------------------------------------------------------------------- /android/settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'OSCGit' 2 | 3 | include ':app' 4 | -------------------------------------------------------------------------------- /common/Colors.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by rplees on 3/8/16. 3 | */ 4 | const Colors = { 5 | lightGray:"#F0F0F0", 6 | lineGray: '#F0F0F0', 7 | green: '#80BD01', 8 | backGray: '#E5E5E5', 9 | textGray: '#9A9A9A', 10 | textBlack: '#333333', 11 | purple: '#9966CC', 12 | red: '#f61d4b', 13 | backWhite: '#F2F2F2', 14 | textGold: '#BC7233', 15 | borderColor: '#E2E2E2', 16 | black: '#586872', 17 | blue: '#4078c0', 18 | white: "white", 19 | } 20 | 21 | module.exports = Colors; -------------------------------------------------------------------------------- /common/CommonComponents.js: -------------------------------------------------------------------------------- 1 | const React = require('react-native'); 2 | const Colors = require('./Colors'); 3 | const CommonStyles = require('./CommonStyles'); 4 | const Platform = require('Platform'); 5 | const ErrorPlacehoderComponent = require('./ErrorPlacehoderComponent'); 6 | 7 | const { 8 | View, 9 | ActivityIndicatorIOS, 10 | ProgressBarAndroid, 11 | } = React; 12 | 13 | class CommonComponents { 14 | static renderLoadingView() { 15 | if (Platform.OS === 'android') { 16 | return ( 17 | 18 | 19 | 20 | ) 21 | } else if (Platform.OS === 'ios') { 22 | return ( 23 | 24 | 25 | 26 | ); 27 | } 28 | } 29 | 30 | static errorPlaceholder(title, 31 | desc, 32 | onPress) { 33 | return ( 34 | 35 | ) 36 | } 37 | 38 | static renderSepLine() { 39 | return ( 40 | 41 | ) 42 | } 43 | 44 | } 45 | 46 | module.exports = CommonComponents; 47 | -------------------------------------------------------------------------------- /common/CommonStyles.js: -------------------------------------------------------------------------------- 1 | const React = require('react-native'); 2 | const Colors = require('./Colors'); 3 | 4 | const { 5 | StyleSheet, 6 | } = React; 7 | 8 | const commonStyles = StyleSheet.create({ 9 | container: { 10 | flex: 1, 11 | justifyContent: 'center', 12 | alignItems: 'center', 13 | }, 14 | 15 | shadowLine: { 16 | shadowColor: '#999999', 17 | shadowOpacity: 0.8, 18 | shadowRadius: 1, 19 | shadowOffset: { 20 | height: 2, 21 | width: 1 22 | }, 23 | }, 24 | 25 | sepLine: { 26 | backgroundColor: Colors.backGray, 27 | height: 0.5, 28 | }, 29 | 30 | textInput: { 31 | fontSize: 15, 32 | borderWidth: 1, 33 | height: 38, 34 | marginTop: 5, 35 | marginBottom: 10, 36 | borderRadius: 4, 37 | padding: 3, 38 | borderColor: Colors.blue, 39 | }, 40 | btn: { 41 | borderWidth: 1, 42 | height: 38, 43 | marginLeft: 20, 44 | marginRight: 20, 45 | justifyContent: "center", 46 | borderColor: Colors.blue, 47 | backgroundColor: Colors.blue, 48 | borderRadius: 6, 49 | marginTop:40, 50 | } 51 | }); 52 | 53 | module.exports = commonStyles; 54 | -------------------------------------------------------------------------------- /common/ErrorPlacehoderComponent.js: -------------------------------------------------------------------------------- 1 | const React = require('react-native'); 2 | const Colors = require('./Colors'); 3 | 4 | const { 5 | StyleSheet, 6 | View, 7 | Text, 8 | TouchableOpacity, 9 | } = React; 10 | 11 | const ErrorPlaceholder = React.createClass({ 12 | propTypes: { 13 | title: React.PropTypes.string, 14 | desc: React.PropTypes.string, 15 | onPress: React.PropTypes.func, 16 | }, 17 | 18 | render() { 19 | return ( 20 | 21 | 22 | {this.props.title} 23 | 24 | 25 | {this.props.desc} 26 | 27 | 28 | 29 | Reload 30 | 31 | 32 | 33 | ); 34 | }, 35 | }); 36 | 37 | var styles = StyleSheet.create({ 38 | container: { 39 | flex: 1, 40 | }, 41 | errorContainer: { 42 | flex: 1, 43 | justifyContent: 'center', 44 | alignItems: 'center', 45 | backgroundColor: 'rgba(255,255,255,0.8)', 46 | }, 47 | errorText: { 48 | fontSize: 14, 49 | textAlign: 'center', 50 | marginBottom: 2, 51 | }, 52 | errorTextTitle: { 53 | fontSize: 15, 54 | fontWeight: '500', 55 | marginBottom: 10, 56 | }, 57 | reloadText: { 58 | borderColor: Colors.backGray, 59 | borderWidth: 1, 60 | borderRadius: 3, 61 | marginTop: 20, 62 | padding: 2, 63 | }, 64 | }); 65 | 66 | module.exports = ErrorPlaceholder; 67 | -------------------------------------------------------------------------------- /common/LanguageComponent.js: -------------------------------------------------------------------------------- 1 | const React = require('react-native'); 2 | const CommonComponents = require('../common/CommonComponents'); 3 | const Colors = require('../common/Colors'); 4 | const DXRNUtils = require('../utils/DXRNUtils'); 5 | const Platform = require('Platform'); 6 | var _ = require('lodash'); 7 | 8 | const { 9 | View, 10 | Text, 11 | TouchableHighlight, 12 | StyleSheet, 13 | TouchableOpacity, 14 | Picker, 15 | } = React; 16 | 17 | const LISTVIEWREF = 'listview'; 18 | const CONTAINERREF = 'container'; 19 | 20 | const LanguageComponent = React.createClass({ 21 | propTypes: { 22 | toggleOn: React.PropTypes.bool, 23 | languageList: React.PropTypes.array, 24 | onSelectLanguage: React.PropTypes.func, 25 | currentLanguage: React.PropTypes.object, 26 | }, 27 | 28 | getDefaultProps() { 29 | return { 30 | languageList: [], 31 | toggleOn: false, 32 | currentLanguage: null, 33 | } 34 | }, 35 | 36 | getInitialState() { 37 | return { 38 | toggleOn: this.props.toggleOn, 39 | currentLanguage: this.props.currentLanguage, 40 | } 41 | }, 42 | 43 | onSelectLanguage(value) { 44 | DXRNUtils.trackClick('clickLan', {name: 'Explore 打开语言选择'}); 45 | if (this.state.currentLanguage.value == value) { 46 | this.setState({ 47 | toggleOn: false, 48 | }); 49 | 50 | return; 51 | } 52 | var f = _.filter(this.props.languageList, (o) => o.value == value)[0]; 53 | 54 | this.setState({ 55 | toggleOn: false, 56 | currentLanguage: f, 57 | }); 58 | this.props.onSelectLanguage(f); 59 | }, 60 | 61 | render() { 62 | const languageList = this.props.languageList; 63 | const selectedLanguage = this.state.currentLanguage || languageList[0]; 64 | 65 | if (Platform.OS == 'ios') { 66 | if (!this.state.toggleOn) { 67 | return ( 68 | this.setState({ 71 | toggleOn: true, 72 | })}> 73 | 74 | {selectedLanguage.label} 75 | 76 | 77 | ); 78 | } else { 79 | const pickerHeight = require('NativeModules').UIManager.RCTPicker.Constants.height; 80 | return ( 81 | 82 | 87 | {this.props.languageList.map((obj, index) => { 88 | return ( 89 | 90 | ); 91 | })} 92 | 93 | this.setState({ 96 | toggleOn: false, 97 | })}> 98 | 99 | Cancel 100 | 101 | 102 | 103 | ); 104 | } 105 | } else if (Platform.OS == 'android') { 106 | return ( 107 | 113 | {this.props.languageList.map((obj, index) => { 114 | return ( 115 | 116 | ); 117 | })} 118 | 119 | ) 120 | } 121 | }, 122 | }); 123 | 124 | const ICON_SIZE = 20; 125 | const styles = StyleSheet.create({ 126 | cellContentView: { 127 | flexDirection: 'row', 128 | height: 44, 129 | alignItems: 'center', 130 | borderColor: Colors.borderColor, 131 | borderBottomWidth: 0.5, 132 | }, 133 | userName: { 134 | color: 'black', 135 | fontWeight: 'bold', 136 | fontSize: 17, 137 | marginLeft: 20, 138 | flex: 1, 139 | }, 140 | cellLeftRepoIcon: { 141 | width: ICON_SIZE, 142 | height: ICON_SIZE, 143 | marginRight: 8, 144 | }, 145 | lan: { 146 | color: Colors.blue, 147 | fontSize: 16, 148 | fontWeight: 'bold', 149 | }, 150 | chooseLan: { 151 | flexDirection: 'column', 152 | alignItems: 'center', 153 | justifyContent: 'center', 154 | height: 40, 155 | borderBottomWidth: 0.5, 156 | borderColor: Colors.backGray, 157 | }, 158 | }); 159 | 160 | module.exports = LanguageComponent; -------------------------------------------------------------------------------- /common/RefreshListView.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Component Name: RefreshableListView 3 | * Author: Simar Singh (github/iSimar) 4 | * Description: This component is used to render a listview that can be 5 | * pulled down to refresh 6 | * 7 | * Dependencies: 8 | * -> react-native-gifted-listview 0.0.7 (https://github.com/FaridSafi/react-native-gifted-listview) 9 | * 10 | * Properties: 11 | * -> renderRow 12 | * render function for rows or cells in the listview 13 | * -> onRefresh 14 | * used for filling the listview on ethier pull to refresh or pagination (load more), 15 | * it is called with 2 arugments page number and callback. see react-native-gifted-listview docs. 16 | * -> backgroundColor (optional) 17 | * default = '#FFFFFF', background color of the listview 18 | * -> loadMoreText (optional) 19 | * default = '+', text used at the end of the listview - pagination 20 | * -> renderHeader (optional) 21 | * rendering not sticky header of the listview 22 | * 23 | * Example: 24 | * this.renderListViewRow(row)} 25 | * renderHeader={this.renderListViewHeader} 26 | * onRefresh={(page, callback)=>this.listViewOnRefresh(page, callback)} 27 | * backgroundColor={'#F6F6EF'} 28 | * loadMoreText={'Load More...'}/> 29 | * 30 | */ 31 | var React = require('react-native'); 32 | 33 | var { 34 | StyleSheet, 35 | Text, 36 | View, 37 | TouchableOpacity, 38 | Platform 39 | } = React; 40 | 41 | var GiftedListView = require('react-native-gifted-listview'); 42 | 43 | module.exports = React.createClass({ 44 | getInitialState: function(){ 45 | return { 46 | renderRow: this.props.renderRow, 47 | backgroundColor: this.props.backgroundColor ? this.props.backgroundColor : '#FFFFFF', 48 | loadMoreText: this.props.loadMoreText ? this.props.loadMoreText : 'Load More...', 49 | renderHeader: this.props.renderHeader ? this.props.renderHeader : null, 50 | }; 51 | }, 52 | onRefresh: function(page=1, callback, options){ 53 | this.props.onRefresh(page, callback); 54 | }, 55 | renderRow: function(row){ 56 | return this.state.renderRow(row); 57 | }, 58 | forceRefresh() { 59 | //this._listview._postRefresh([], {external: true}); 60 | this._listview._refresh(); 61 | }, 62 | render: function(){ 63 | return( 64 | 65 | this._listview = c} 67 | rowView={this.renderRow} 68 | onFetch={this.onRefresh} 69 | paginationAllLoadedView={this.renderPaginationAllLoadedView} 70 | paginationWaitingView={this.renderPaginationWaitingView} 71 | headerView={this.renderHeaderView} 72 | PullToRefreshViewAndroidProps={{ 73 | colors: ['#F6F6EF'], 74 | progressBackgroundColor: '#FF6600', 75 | }} 76 | customStyles={{ 77 | refreshableView: { 78 | backgroundColor: this.state.backgroundColor, 79 | justifyContent: 'flex-end', 80 | paddingBottom: 12, 81 | }, 82 | paginationView: { 83 | backgroundColor: this.state.backgroundColor, 84 | height: 60 85 | } 86 | }}/> 87 | 88 | ); 89 | }, 90 | renderPaginationAllLoadedView: function(){ 91 | return( 92 | 93 | ); 94 | }, 95 | renderPaginationWaitingView: function(paginateCallback) { 96 | return ( 97 | 99 | 100 | {this.state.loadMoreText} 101 | 102 | 103 | ); 104 | }, 105 | renderHeaderView: function(){ 106 | if(this.state.renderHeader){ 107 | return this.props.renderHeader(); 108 | } 109 | return (null); 110 | } 111 | }); 112 | 113 | var styles = StyleSheet.create({ 114 | container: { 115 | flex: 1 116 | }, 117 | rowContainer: { 118 | paddingRight: 15, 119 | paddingLeft: 10, 120 | flexDirection: 'row' 121 | }, 122 | paginationView: { 123 | justifyContent: 'center', 124 | alignItems: 'center', 125 | paddingTop: 20, 126 | paddingBottom: 20 127 | }, 128 | loadMoreText: { 129 | fontSize: 15, 130 | color: 'gray', 131 | } 132 | }); -------------------------------------------------------------------------------- /common/SettingsCell.js: -------------------------------------------------------------------------------- 1 | const React = require('react-native'); 2 | const CommonComponents = require('../common/CommonComponents'); 3 | const Icon = require('react-native-vector-icons/FontAwesome'); 4 | const Colors = require('../common/Colors'); 5 | 6 | const { 7 | View, 8 | Text, 9 | StyleSheet, 10 | TouchableHighlight, 11 | } = React; 12 | 13 | const ICON_SIZE = 20; 14 | 15 | const SettingsComponent = React.createClass({ 16 | propTypes: { 17 | onPress: React.PropTypes.func, 18 | iconName: React.PropTypes.string, 19 | iconColor: React.PropTypes.string, 20 | settingName: React.PropTypes.string, 21 | }, 22 | 23 | getDefaultProps() { 24 | return { 25 | iconName: 'ios-cog', 26 | iconColor: Colors.blue, 27 | settingName: 'Settings', 28 | } 29 | }, 30 | 31 | render() { 32 | return ( 33 | 39 | 40 | 45 | 46 | 47 | {this.props.settingName} 48 | 49 | 50 | 55 | 56 | 57 | ); 58 | } 59 | }); 60 | 61 | var styles = StyleSheet.create({ 62 | userTouch: { 63 | //marginTop: 20, 64 | }, 65 | user: { 66 | padding: 8, 67 | paddingLeft: 10, 68 | paddingRight: 5, 69 | backgroundColor: 'white', 70 | flexDirection: 'row', 71 | alignItems: 'center', 72 | borderTopWidth: 1, 73 | borderColor: '#EDECF1', 74 | }, 75 | nameInfo: { 76 | flexDirection: 'column', 77 | marginLeft: 0, 78 | justifyContent: 'center', 79 | flex: 1, 80 | }, 81 | name: { 82 | color: 'black', 83 | fontSize: 14, 84 | }, 85 | arrow: { 86 | width: ICON_SIZE, 87 | height: ICON_SIZE, 88 | marginRight: 10, 89 | }, 90 | settings: { 91 | height: 44, 92 | }, 93 | }); 94 | 95 | module.exports = SettingsComponent; 96 | -------------------------------------------------------------------------------- /components/CreateIssueComponent.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by rplees on 3/8/16. 3 | */ 4 | const React = require('react-native'); 5 | const Platform = require('Platform'); 6 | const Colors = require('../common/Colors'); 7 | const StringUtils = require('../utils/Utils').StringUtils; 8 | const OSCService = require('../service/OSCService'); 9 | const CommonStyles = require('../common/CommonStyles'); 10 | 11 | const { 12 | View, 13 | Text, 14 | TouchableHighlight, 15 | TextInput, 16 | ScrollView, 17 | Alert, 18 | } = React; 19 | 20 | const CreateIssueComponent = React.createClass({ 21 | PropTypes: {}, 22 | getInitialState() { 23 | return {title:"", description: ""} 24 | }, 25 | doSubmit() { 26 | if(StringUtils.isNotBlank(this.state.description) && StringUtils.isNotBlank(this.state.title)) { 27 | var repo = this.props.repo; 28 | let assignee_id = repo.owner.id; 29 | OSCService.pubCreateIssue(repo.id,this.state.title,this.state.description,assignee_id, "") 30 | .then((json) => { 31 | Alert.alert( 32 | "成功", 33 | '操作成功:', 34 | [ 35 | {text: '返回', onPress: () => { 36 | this.props.navigator.pop(); 37 | }}, 38 | ] 39 | ); 40 | }).catch(err => { 41 | Alert.alert( 42 | "Oops", 43 | '操作失败:' + err 44 | ); 45 | }); 46 | } else { 47 | Alert.alert( 48 | "Oops", 49 | '输入完整的信息.' 50 | ); 51 | } 52 | }, 53 | onTextChange(description){ 54 | this.setState({description:description}); 55 | }, 56 | onTitleChange(title){ 57 | this.setState({title:title}); 58 | }, 59 | 60 | render() { 61 | let paddingTop = 64; 62 | if (Platform.OS == 'android') { 63 | paddingTop = 0; 64 | } 65 | return ( 66 | 67 | 68 | 69 | {"标题"} 70 | 71 | 72 | 79 | 80 | 81 | 82 | {"请写下你对OSChina的意见."} 83 | 84 | 85 | 99 | 100 | 101 | 105 | 发表意见 106 | 107 | 108 | 109 | ); 110 | } 111 | }); 112 | module.exports = CreateIssueComponent; -------------------------------------------------------------------------------- /components/EventCell.js: -------------------------------------------------------------------------------- 1 | const React = require('react-native'); 2 | const CommonComponents = require('../common/CommonComponents'); 3 | const Colors = require('../common/Colors'); 4 | const DateUtils = require('../utils/Utils').DateUtils; 5 | const constant = require('../config').constant; 6 | 7 | const { 8 | View, 9 | Text, 10 | Alert, 11 | StyleSheet, 12 | TouchableHighlight, 13 | Image, 14 | TouchableOpacity, 15 | } = React; 16 | 17 | /** 动态的类型*/ 18 | const EVENT_TYPE_CREATED = 0x1;// 创建了issue 19 | const EVENT_TYPE_UPDATED = 0x2;// 更新项目 20 | const EVENT_TYPE_CLOSED = 0x3;// 关闭项目 21 | const EVENT_TYPE_REOPENED = 0x4;// 重新打开了项目 22 | const EVENT_TYPE_PUSHED = 0x5;// push 23 | const EVENT_TYPE_COMMENTED = 0x6;// 评论 24 | const EVENT_TYPE_MERGED = 0x7;// 合并 25 | const EVENT_TYPE_JOINED = 0x8; //# User joined project 26 | const EVENT_TYPE_LEFT = 0x9; //# User left project 27 | const EVENT_TYPE_FORKED = 0xb;// fork了项目 28 | 29 | const EventCell = React.createClass({ 30 | propTypes: { 31 | event: React.PropTypes.object.isRequired, 32 | }, 33 | 34 | onPressCell() { 35 | this.props.navigator.push({id: constant.scene.repo_detail.key, obj: this.props.event.project}); 36 | }, 37 | 38 | openAuthor() { 39 | let event = this.props.event; 40 | if (event) { 41 | this.props.navigator.push({id: constant.scene.personal.key, obj: event.author}); 42 | } 43 | }, 44 | 45 | _push(text, texts, f = false, b = false) { 46 | let style = {color:Colors.black}; 47 | if(f) { 48 | style.color = Colors.blue; 49 | } 50 | if(b) { 51 | style.fontWeight = "bold"; 52 | } 53 | texts.push({style: style, text: text}); 54 | }, 55 | 56 | __getEventsTitle(event){ 57 | let title = ""; 58 | if(event.events.issue) { 59 | title = " #" + event.events.issue.iid; 60 | } 61 | 62 | if(event.events.pull_request) { 63 | title = " #" + event.events.pull_request.iid; 64 | } 65 | 66 | return title; 67 | }, 68 | 69 | createEventTitle() { 70 | var event = this.props.event; 71 | let fullProjectName = event.project.path_with_namespace; 72 | let texts = []; 73 | let eventTitle = ""; 74 | this._push(event.author.name, texts, true, true); 75 | 76 | switch (event.action) { 77 | case EVENT_TYPE_CREATED://创建了issue 78 | this._push(" 在项目 ", texts); 79 | this._push(fullProjectName, texts, true); 80 | this._push(" 创建了 ", texts); 81 | this._push(event.target_type + this.__getEventsTitle(event), texts, true); 82 | break; 83 | case EVENT_TYPE_UPDATED:// 更新项目 84 | this._push(" 更新了项目 ", texts); 85 | this._push(fullProjectName, texts, true); 86 | break; 87 | case EVENT_TYPE_CLOSED:// 关闭项目 88 | this._push(" 关闭了项目 ", texts); 89 | this._push(fullProjectName, texts, true); 90 | this._push(" 的 ", texts); 91 | this._push(event.target_type + this.__getEventsTitle(event), texts, true); 92 | break; 93 | case EVENT_TYPE_REOPENED:// 重新打开了项目 94 | this._push(" 重新打开了项目 ", texts); 95 | this._push(fullProjectName, texts, true); 96 | this._push(" 的 ", texts); 97 | this._push(event.target_type + this.__getEventsTitle(event), texts, true); 98 | break; 99 | case EVENT_TYPE_PUSHED:// push 100 | //eventTitle = event.getData().getRef().substring(event.getData().getRef().lastIndexOf("/") + 1); 101 | eventTitle = event.data.ref.substring(event.data.ref.lastIndexOf("/") + 1); 102 | this._push(" 推送到了项目 ", texts); 103 | this._push(fullProjectName, texts, true); 104 | this._push(" 的 ", texts); 105 | this._push(eventTitle, texts, true); 106 | this._push(" 分支 ", texts); 107 | break; 108 | case EVENT_TYPE_COMMENTED:// 评论 109 | if(event.events.issue) { 110 | eventTitle = "Issue"; 111 | } else if(event.events.pull_request) { 112 | eventTitle = "PullRequest"; 113 | } 114 | 115 | eventTitle += this.__getEventsTitle(event); 116 | this._push(" 评论了项目 ", texts); 117 | this._push(fullProjectName, texts, true); 118 | this._push(" 的 ", texts); 119 | this._push(eventTitle, texts, true); 120 | break; 121 | case EVENT_TYPE_MERGED:// 合并 122 | this._push("接受了项目 ", texts); 123 | this._push(fullProjectName, texts, true); 124 | this._push(" 的 ", texts); 125 | this._push(event.target_type + this.__getEventsTitle(event), texts, true); 126 | break; 127 | case EVENT_TYPE_JOINED:// # User joined project 128 | this._push("加入了项目 ", texts); 129 | this._push(fullProjectName, texts, true); 130 | break; 131 | case EVENT_TYPE_LEFT:// # User left project 132 | this._push("离开了项目 ", texts); 133 | this._push(fullProjectName, texts, true); 134 | break; 135 | case EVENT_TYPE_FORKED:// fork了项目 136 | this._push("Fork了项目 ", texts); 137 | this._push(fullProjectName, texts, true); 138 | break; 139 | default: 140 | this._push("更新了动态:", texts); 141 | break; 142 | } 143 | 144 | var cp = texts.map((v, i) => 145 | {v.text} 146 | ); 147 | 148 | return {cp}; 149 | }, 150 | createEventComment() { 151 | var event = this.props.event; 152 | let commentText; 153 | 154 | let commitsView; 155 | if(event.data && event.data.commits) { 156 | let _c = event.data.commits.map((commit, i) => 157 | {commit.id} 158 | {commit.author.name} - {commit.message} 159 | 160 | ); 161 | commitsView = {_c} 162 | } 163 | 164 | if(event.events.note && event.events.note.note) {// 评论的内容 165 | commentText = event.events.note.note; 166 | } 167 | 168 | if(event.events.issue && !event.events.note) {// issue的title 169 | commentText = event.events.issue.title; 170 | } 171 | 172 | if(event.events.pull_request && !event.events.note) {// pr的title 173 | commentText = event.events.pull_request.title; 174 | } 175 | 176 | return [commitsView, {commentText}]; 177 | }, 178 | render() { 179 | const event = this.props.event; 180 | return ( 181 | 182 | 183 | 184 | 185 | 186 | 190 | 191 | 192 | 193 | 194 | 195 | {this.createEventTitle()} 196 | 197 | 198 | 199 | {this.createEventComment()} 200 | 201 | 202 | 203 | {DateUtils.formatDiff(event.created_at)} 204 | 205 | 206 | 207 | 208 | {CommonComponents.renderSepLine()} 209 | 210 | ) 211 | }, 212 | }); 213 | 214 | var styles = StyleSheet.create({ 215 | avatar: { 216 | width: 40, 217 | height: 40, 218 | borderRadius: 8, 219 | backgroundColor: Colors.backGray 220 | }, 221 | text: { 222 | color: Colors.blue, 223 | fontSize: 12, 224 | alignSelf: 'center', 225 | }, 226 | text_desc: { 227 | color: Colors.black, 228 | fontSize: 12, 229 | }, 230 | }); 231 | 232 | module.exports = EventCell; 233 | -------------------------------------------------------------------------------- /components/FamousComponent.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by rplees on 3/8/16. 3 | */ 4 | const React = require('react-native'); 5 | const Platform = require('Platform'); 6 | const Colors = require('../common/Colors'); 7 | const GFDiskCache = require('../utils/GFDiskCache'); 8 | const OSCService = require('../service/OSCService'); 9 | const CommonComponents = require('../common/CommonComponents'); 10 | const LanguageComponent = require('../common/LanguageComponent'); 11 | const RepoCell2 = require('../components/repo/RepoCell2'); 12 | const OSCRefreshListView = require('../components/OSCRefreshListView'); 13 | var _ = require('lodash'); 14 | 15 | const { 16 | View, 17 | } = React; 18 | 19 | const FamousComponent = React.createClass({ 20 | PropTypes: {}, 21 | getInitialState() { 22 | return { 23 | languageList:null, 24 | loadingLanguageList:true, 25 | } 26 | }, 27 | componentWillMount() { 28 | OSCService.getLanguageList() 29 | .then(arr => { 30 | let languageList = arr; 31 | this.selectedLanguage = this._packLanguageData(languageList[0]); 32 | this.setState({ 33 | loadingLanguageList:false, 34 | languageList:languageList, 35 | }); 36 | }) 37 | }, 38 | _packLanguageData(o) { 39 | //优化//sum可以保存 40 | let sum = _.sumBy(this.state.languageList, (o) => o.projects_count); 41 | let label = o.name + " [" + o.projects_count + "-" + (o.projects_count / sum * 100).toFixed(2) + "%]"; 42 | return {label:label, value : o.id}; 43 | }, 44 | 45 | onSelectLanguage(selectedLanguage) { 46 | console.log("onSelectLanguage:" + selectedLanguage); 47 | this.selectedLanguage = selectedLanguage; 48 | this._listview.forceRefresh(); 49 | }, 50 | 51 | reloadPath(page = 1) { 52 | return OSCService.getLanguageProjectList(this.selectedLanguage.value, page); 53 | }, 54 | 55 | renderRow(rowData, sectionID, rowID, highlightRow) { 56 | return ( 57 | 58 | ) 59 | }, 60 | render() { 61 | let paddingTop = 64; 62 | if (Platform.OS == 'android') { 63 | paddingTop = 0; 64 | } 65 | if(this.state.loadingLanguageList) { 66 | return CommonComponents.renderLoadingView(); 67 | } 68 | //let languageSimpleList = this.state.languageList.map((d, i) => d.name); 69 | let languageSimpleList = _.map(this.state.languageList, this._packLanguageData); 70 | 71 | return ( 72 | 73 | 78 | this._listview=c} 80 | renderRow={this.renderRow} 81 | reloadPromise={(page) => this.reloadPath(page)} 82 | /> 83 | 84 | ); 85 | } 86 | }); 87 | module.exports = FamousComponent; -------------------------------------------------------------------------------- /components/FeedbackComponent.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by rplees on 3/8/16. 3 | */ 4 | const React = require('react-native'); 5 | const Platform = require('Platform'); 6 | const Colors = require('../common/Colors'); 7 | const DXRNUtils = require('../utils/DXRNUtils'); 8 | const StringUtils = require('../utils/Utils').StringUtils; 9 | const DateUtils = require('../utils/Utils').DateUtils; 10 | const OSCService = require('../service/OSCService'); 11 | 12 | const { 13 | View, 14 | Text, 15 | TouchableHighlight, 16 | TextInput, 17 | ScrollView, 18 | Alert, 19 | } = React; 20 | 21 | const FeedbackComponent = React.createClass({ 22 | PropTypes: {}, 23 | getInitialState() { 24 | return {text: ""} 25 | }, 26 | doLeaveMessage() { 27 | if(StringUtils.isNotBlank(this.state.text)) { 28 | OSCService.feedback("RN-OSCGIT留言反馈" + DateUtils.format(new Date()), this.state.text) 29 | .then((d) => { 30 | Alert.alert( 31 | "成功", 32 | '操作成功:', 33 | [ 34 | {text: '返回', onPress: () => { 35 | this.props.navigator.pop(); 36 | }}, 37 | ] 38 | ); 39 | }).catch(err => { 40 | Alert.alert( 41 | "Oops", 42 | '操作失败:' + err 43 | ); 44 | }); 45 | } else { 46 | Alert.alert( 47 | "Oops", 48 | '请输入一些内容吧.' 49 | ); 50 | } 51 | }, 52 | onTextChange(text){ 53 | this.setState({text:text}); 54 | }, 55 | render() { 56 | let paddingTop = 64; 57 | if (Platform.OS == 'android') { 58 | paddingTop = 0; 59 | } 60 | return ( 61 | 62 | 63 | 64 | {"请写下你对该项目的意见"} 65 | 66 | 67 | 87 | 88 | 89 | 103 | 发表意见 104 | 105 | 106 | 107 | ); 108 | } 109 | }); 110 | module.exports = FeedbackComponent; -------------------------------------------------------------------------------- /components/LoginComponent.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by rplees on 3/8/16. 3 | */ 4 | const React = require('react-native'); 5 | const Platform = require('Platform'); 6 | const Colors = require('../common/Colors'); 7 | const CommonStyles = require('../common/CommonStyles'); 8 | const DXRNUtils = require('../utils/DXRNUtils'); 9 | const ObjUtils = require('../utils/Utils').ObjUtils; 10 | const OSCService = require('../service/OSCService'); 11 | 12 | const { 13 | StyleSheet, 14 | ActivityIndicatorIOS, 15 | View, 16 | Text, 17 | TouchableHighlight, 18 | TextInput, 19 | ProgressBarAndroid 20 | } = React; 21 | 22 | const LoginComponent = React.createClass({ 23 | PropTypes: { 24 | nextPromise: React.PropTypes.object.isRequired, 25 | didLogin: React.PropTypes.func, 26 | }, 27 | 28 | getInitialState() { 29 | return { 30 | username:"rplees.i.ly@gmail.com", 31 | password:"**", 32 | logining:false, 33 | loginError:"", 34 | } 35 | }, 36 | 37 | onUserNameChanged (text){ 38 | this.setState({username: text}); 39 | }, 40 | 41 | onPwdChanged(text) { 42 | this.setState({password: text}); 43 | }, 44 | 45 | doLogin() { 46 | DXRNUtils.trackClick('clickUserLogin', {name: 'login页面用户登录'}); 47 | if (this.state.logining) return; 48 | if(this.state.username.length == 0 || this.state.password.length == 0) { 49 | this.setState({logining: false, loginError: "Please input valid word."}); 50 | return; 51 | } 52 | 53 | this.setState({ 54 | logining: true, 55 | loginError: null, 56 | }); 57 | 58 | OSCService.login(this.state.username, this.state.password) 59 | .then(user => { 60 | console.log("login :" + ObjUtils.toString(user)); 61 | this.setState({ 62 | logining: false, 63 | loginError: "", 64 | }); 65 | 66 | this.props.navigator && this.props.navigator.pop(); 67 | this.props.didLogin && this.props.didLogin(); 68 | 69 | let nextPromise = this.props.nextPromise && this.props.nextPromise(); 70 | return nextPromise; 71 | }).catch(err => { 72 | console.log('login error', err.message); 73 | 74 | this.setState({ 75 | logining: false, 76 | loginError: err.message, 77 | }); 78 | 79 | console.log('login state' + this.state.loginError); 80 | }).done(() => { 81 | console.log('login done'); 82 | this.setState({ 83 | logining: false, 84 | }); 85 | }); 86 | }, 87 | 88 | render() { 89 | let signIndicator; 90 | let introComponent; 91 | 92 | if(this.state.logining) { 93 | if (Platform.OS === "android") { 94 | signIndicator = (); 95 | } else if (Platform.OS == "ios") { 96 | signIndicator = (); 97 | } 98 | } 99 | 100 | if(this.state.loginError) { 101 | introComponent = {this.state.loginError}; 102 | } else { 103 | introComponent = Sign in to Git OSC; 104 | } 105 | 106 | return ( 107 | 108 | 109 | 110 | {introComponent} 111 | {signIndicator} 112 | 113 | 114 | 115 | Username 116 | 123 | Password 124 | 133 | 134 | 138 | Sign 139 | 140 | 141 | 142 | 143 | ); 144 | } 145 | }); 146 | 147 | const styles = StyleSheet.create({ 148 | container: { 149 | backgroundColor:Colors.white, 150 | }, 151 | 152 | loginContainer: { 153 | height: 270, 154 | marginTop: 64, 155 | margin: 10, 156 | borderWidth: 1, 157 | borderRadius: 8, 158 | borderColor: Colors.blue, 159 | }, 160 | 161 | indicator: { 162 | position:"absolute", 163 | right: 10, 164 | top: 10, 165 | }, 166 | top: { 167 | backgroundColor:Colors.blue, 168 | flexDirection:"row", 169 | height: 38.0, 170 | alignItems:"center", 171 | borderRadius: 6, 172 | }, 173 | 174 | introText:{ 175 | fontSize: 18, 176 | fontWeight:'bold', 177 | marginLeft:18, 178 | }, 179 | 180 | down: { 181 | margin:20, 182 | flexDirection:"column", 183 | }, 184 | textNP: { 185 | fontSize:18, 186 | fontWeight:'bold', 187 | color:Colors.black, 188 | }, 189 | textInput: { 190 | fontSize: 15, 191 | borderWidth: 1, 192 | height: 38, 193 | marginTop: 5, 194 | marginBottom: 10, 195 | borderRadius: 4, 196 | padding: 3, 197 | borderColor: Colors.blue, 198 | } 199 | }); 200 | module.exports = LoginComponent; -------------------------------------------------------------------------------- /components/MyProfileComponent.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by rplees on 3/8/16. 3 | */ 4 | const React = require('react-native'); 5 | const Platform = require('Platform'); 6 | const Colors = require('../common/Colors'); 7 | const DateUtils = require('../utils/Utils').DateUtils; 8 | const OSCService = require('../service/OSCService'); 9 | const CommonStyles = require('../common/CommonStyles'); 10 | 11 | const { 12 | View, 13 | Text, 14 | Alert, 15 | TouchableHighlight, 16 | Image, 17 | ScrollView 18 | } = React; 19 | 20 | const LoginComponent = React.createClass({ 21 | PropTypes: {}, 22 | render() { 23 | let user = OSCService.GLOBAL_USER; 24 | let paddingTop = 64; 25 | if (Platform.OS == 'android') { 26 | paddingTop = 0; 27 | } 28 | return ( 29 | 30 | 31 | 32 | 37 | {user.name} 38 | 39 | 40 | 41 | {"Following : " + user.follow.following} 42 | {"Followers : " + user.follow.followers} 43 | {"Stared : " + user.follow.starred} 44 | {"Watched : " + user.follow.watched} 45 | 46 | 47 | 48 | {"加入时间 : " + DateUtils.formatDate(user.created_at, "yyyy-MM-dd")} 49 | {"微博 : " + (user.weibo? user.weibo : '')} 50 | {"博客 : " + (user.blog? user.blog : '')} 51 | 52 | 53 | { 55 | Alert.alert( 56 | "确认操作", 57 | '确定要退出登陆么?', 58 | [ 59 | {text:"确定", onPress: function(){ OSCService.logout(); }}, 60 | {text:"不了", onPress: function(){}}, 61 | ] 62 | ); 63 | }} 64 | underlayColor={Colors.backGray} 65 | > 66 | 注销登陆 67 | 68 | 69 | 70 | ); 71 | } 72 | }); 73 | module.exports = LoginComponent; -------------------------------------------------------------------------------- /components/OSCRefreshListView.js: -------------------------------------------------------------------------------- 1 | const React = require('react-native'); 2 | const L = require('../utils/Log'); 3 | const CommonComponents = require('../common/CommonComponents'); 4 | const RefreshListView = require('../common/RefreshListView'); 5 | const PropTypes = React.PropTypes; 6 | const OSCService = require('../service/OSCService'); 7 | const ErrorPlacehoderComponent = require('../common/ErrorPlacehoderComponent'); 8 | const Platform = require('Platform'); 9 | 10 | const { 11 | StyleSheet, 12 | } = React; 13 | 14 | const LISTVIEW_REF = 'listview'; 15 | const OSCRefreshListView = React.createClass({ 16 | propTypes: { 17 | renderRow: PropTypes.func, 18 | reloadPromise: PropTypes.func, 19 | renderErrorPlaceholder: PropTypes.func, 20 | }, 21 | getInitialState() { 22 | return {lastError: {isReloadError: false, error: ""}}; 23 | }, 24 | 25 | listViewOnRefresh(page, callback) { 26 | this.props.reloadPromise(page) 27 | .then(data => { 28 | let pageSize = this.props.pageSize || 20; 29 | if(data.length > pageSize) {//有可能每页显示数量跟服务器返回的不同 30 | L.warn("服务器返回的数据{}长度大于设置的值{},请检查.", data.length, pageSize); 31 | } 32 | let allLoaded = data && (data.length < pageSize); 33 | callback(data, {allLoaded: allLoaded}); 34 | }) 35 | .catch(err => { 36 | this.setState({lastError: {isReloadError: true, error: err.message}}); 37 | }); 38 | }, 39 | forceRefresh() { 40 | this.refs[LISTVIEW_REF] && this.refs[LISTVIEW_REF].forceRefresh(); 41 | }, 42 | 43 | render() { 44 | if (this.state.lastError.isReloadError) { 45 | const error = this.state.lastError.error; 46 | if (this.props.renderErrorPlaceholder) { 47 | return this.props.renderErrorPlaceholder(error); 48 | } else { 49 | return CommonComponents.errorPlaceholder(error, 'Oops, tap to reload', () => { 50 | this.setState({lastError: {isReloadError: false, error: ""}}); 51 | }); 52 | } 53 | } else { 54 | return this.listViewOnRefresh(page, callback)} 57 | style={styles.listview} 58 | {...this.props} 59 | /> 60 | } 61 | } 62 | 63 | }); 64 | 65 | var styles = StyleSheet.create({ 66 | listview: { 67 | 68 | }, 69 | }); 70 | 71 | module.exports = OSCRefreshListView; -------------------------------------------------------------------------------- /components/PersonalComponent.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by rplees on 3/8/16. 3 | */ 4 | const React = require('react-native'); 5 | const Platform = require('Platform'); 6 | const Colors = require('../common/Colors'); 7 | const CommonComponents = require('../common/CommonComponents'); 8 | const OSCService = require('../service/OSCService'); 9 | const ScrollableTabView = require('react-native-scrollable-tab-view'); 10 | const PersonalProjectComponent = require('./PersonalProjectComponent'); 11 | const PersonalStarComponent = require('./PersonalStarComponent'); 12 | const PersonalEventComponent = require('./PersonalEventComponent'); 13 | const PersonalWatchComponent = require('./PersonalWatchComponent'); 14 | 15 | const { 16 | View, 17 | } = React; 18 | 19 | /** 20 | * 用户的UI 21 | */ 22 | const PersonalComponent = React.createClass({ 23 | getInitialState() { 24 | return {user: {}} 25 | }, 26 | componentWillMount() { 27 | if(!this.props.obj) { 28 | const promiseFunc = (() => { 29 | OSCService.getUserFromCache() 30 | .then((u) => { 31 | this.setState({user: u}); 32 | }); 33 | }); 34 | OSCService.checkNeedLoginWithPromise(promiseFunc, this.props.navigator); 35 | } else { 36 | this.setState({user:this.props.obj}); 37 | } 38 | }, 39 | 40 | render() { 41 | let paddingTop = 64; 42 | if (Platform.OS == 'android') { 43 | paddingTop = 0; 44 | } 45 | 46 | if(this.state.user && this.state.user.id) { 47 | return ( 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | ); 57 | } else { 58 | return CommonComponents.renderLoadingView(); 59 | } 60 | 61 | } 62 | }); 63 | module.exports = PersonalComponent; -------------------------------------------------------------------------------- /components/PersonalEventComponent.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by rplees on 3/8/16. 3 | */ 4 | const React = require('react-native'); 5 | const OSCService = require('../service/OSCService'); 6 | const OSCRefreshListView = require('../components/OSCRefreshListView'); 7 | const EventCell = require('../components/EventCell'); 8 | 9 | const PersonalEventComponent = React.createClass({ 10 | reloadPath(page = 1) { 11 | if(this.props.uId === OSCService.GLOBAL_USER.id) { 12 | return OSCService.getMyEvents(page); 13 | } else { 14 | return OSCService.getPersonalEvents(this.props.uId, page); 15 | } 16 | }, 17 | 18 | renderRow(rowData, sectionID, rowID, highlightRow) { 19 | return ( 20 | 21 | ) 22 | }, 23 | 24 | render() { 25 | return ( 26 | this.reloadPath(page)} 28 | /> 29 | ); 30 | }, 31 | }); 32 | 33 | module.exports = PersonalEventComponent; -------------------------------------------------------------------------------- /components/PersonalProjectComponent.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by rplees on 3/8/16. 3 | */ 4 | const React = require('react-native'); 5 | const OSCService = require('../service/OSCService'); 6 | const OSCRefreshListView = require('../components/OSCRefreshListView'); 7 | const RepoCell2 = require('../components/repo/RepoCell2'); 8 | 9 | const PersonalProjectComponent = React.createClass({ 10 | reloadPath(page = 1) { 11 | return OSCService.getPersonalProjects(this.props.uId, page); 12 | }, 13 | 14 | renderRow(rowData, sectionID, rowID, highlightRow) { 15 | return ( 16 | 17 | ) 18 | }, 19 | 20 | render() { 21 | return ( 22 | this.reloadPath(page)} 24 | /> 25 | ); 26 | }, 27 | }); 28 | 29 | module.exports = PersonalProjectComponent; -------------------------------------------------------------------------------- /components/PersonalStarComponent.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by rplees on 3/8/16. 3 | */ 4 | const React = require('react-native'); 5 | const OSCService = require('../service/OSCService'); 6 | const OSCRefreshListView = require('../components/OSCRefreshListView'); 7 | const RepoCell2 = require('../components/repo/RepoCell2'); 8 | 9 | const PersonalStarComponent = React.createClass({ 10 | reloadPath(page = 1) { 11 | return OSCService.getPersonalStarProjects(this.props.uId, page); 12 | }, 13 | 14 | renderRow(rowData, sectionID, rowID, highlightRow) { 15 | return ( 16 | 17 | ) 18 | }, 19 | 20 | render() { 21 | return ( 22 | this.reloadPath(page)} 24 | /> 25 | ); 26 | }, 27 | }); 28 | 29 | module.exports = PersonalStarComponent; -------------------------------------------------------------------------------- /components/PersonalWatchComponent.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by rplees on 3/8/16. 3 | */ 4 | const React = require('react-native'); 5 | const OSCService = require('../service/OSCService'); 6 | const OSCRefreshListView = require('../components/OSCRefreshListView'); 7 | const RepoCell2 = require('../components/repo/RepoCell2'); 8 | 9 | const PersonalWatchComponent = React.createClass({ 10 | reloadPath(page = 1) { 11 | return OSCService.getPersonalWatchProjects(this.props.uId, page); 12 | }, 13 | 14 | renderRow(rowData, sectionID, rowID, highlightRow) { 15 | return ( 16 | 17 | ) 18 | }, 19 | 20 | render() { 21 | return ( 22 | this.reloadPath(page)} 24 | /> 25 | ); 26 | }, 27 | }); 28 | 29 | module.exports = PersonalWatchComponent; -------------------------------------------------------------------------------- /components/ProjectCategoryComponent.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by rplees on 3/8/16. 3 | */ 4 | const React = require('react-native'); 5 | const Colors = require('../common/Colors'); 6 | const L = require('../utils/Log'); 7 | const OSCService = require('../service/OSCService'); 8 | const OSCRefreshListView = require('../components/OSCRefreshListView'); 9 | const RepoCell2 = require('../components/repo/RepoCell2'); 10 | 11 | const ProjectCategoryComponent = React.createClass({ 12 | reloadPath(page = 1) { 13 | var p = this.props.category? this.props.category: "featured"; 14 | if(p === "featured") { 15 | return OSCService.getExploreFeaturedProject(page); 16 | } else if(p === "popular") { 17 | return OSCService.getExplorePopularProject(page); 18 | } else if(p === "latest") { 19 | return OSCService.getExploreLatestProject(page); 20 | } else { 21 | L.error("ProjectCategoryComponent.reloadPath不支持的属性category:{}", this.props.category); 22 | } 23 | }, 24 | 25 | renderRow(rowData, sectionID, rowID, highlightRow) { 26 | return ( 27 | 28 | ) 29 | }, 30 | 31 | render() { 32 | return ( 33 | this.reloadPath(page)} 35 | /> 36 | ); 37 | }, 38 | }); 39 | 40 | module.exports = ProjectCategoryComponent; -------------------------------------------------------------------------------- /components/ProjectComponent.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by rplees on 3/8/16. 3 | */ 4 | const React = require('react-native'); 5 | const Platform = require('Platform'); 6 | const Colors = require('../common/Colors'); 7 | const OSCService = require('../service/OSCService'); 8 | const ScrollableTabView = require('react-native-scrollable-tab-view'); 9 | const ProjectCategoryComponent = require('../components/ProjectCategoryComponent'); 10 | 11 | const { 12 | View, 13 | } = React; 14 | 15 | const ProjectComponent = React.createClass({ 16 | render() { 17 | let paddingTop = 64; 18 | if (Platform.OS == 'android') { 19 | paddingTop = 0; 20 | } 21 | return ( 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | ); 30 | } 31 | }); 32 | module.exports = ProjectComponent; -------------------------------------------------------------------------------- /components/RootTab.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by rplees on 3/8/16. 3 | */ 4 | const React = require('react-native'); 5 | const Routes = require('../components/Routes'); 6 | const Ionicons = require('react-native-vector-icons/Ionicons'); 7 | const constant = require('../config').constant; 8 | 9 | const TabBarDic = [constant.scene.project.key, constant.scene.famous.key, constant.scene.personal.key];// 10 | const { 11 | TabBarIOS 12 | } = React; 13 | 14 | const RootTab = React.createClass({ 15 | getInitialState() { 16 | return { 17 | selectedTab:TabBarDic[0], 18 | } 19 | }, 20 | componentDidMount() { 21 | }, 22 | render() { 23 | let cp = TabBarDic.map((v, i) => { 24 | let iconName = ""; 25 | let selectedIconName = ""; 26 | let title = ""; 27 | if(v === constant.scene.project.key) { 28 | iconName = "ios-book-outline"; 29 | selectedIconName = "ios-book"; 30 | title = constant.scene.project.value; 31 | } else if(v === constant.scene.famous.key) { 32 | iconName = "ios-eye-outline"; 33 | selectedIconName = "ios-eye"; 34 | title = constant.scene.famous.value; 35 | } else if(v === constant.scene.personal.key) { 36 | iconName = "ios-person-outline"; 37 | selectedIconName = "ios-person"; 38 | title = constant.scene.personal.value; 39 | } 40 | 41 | return { 48 | this.setState({ 49 | selectedTab: v, 50 | }); 51 | }}> 52 | {Routes.navigator(v)} 53 | 54 | } 55 | ); 56 | 57 | return {cp} 58 | } 59 | }); 60 | module.exports = RootTab; -------------------------------------------------------------------------------- /components/SearchComponent.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by rplees on 3/8/16. 3 | */ 4 | const React = require('react-native'); 5 | const Platform = require('Platform'); 6 | const Colors = require('../common/Colors'); 7 | const GFDiskCache = require('../utils/GFDiskCache'); 8 | const OSCService = require('../service/OSCService'); 9 | const CommonComponents = require('../common/CommonComponents'); 10 | const RepoCell2 = require('../components/repo/RepoCell2'); 11 | const OSCRefreshListView = require('../components/OSCRefreshListView'); 12 | 13 | const { 14 | View, 15 | } = React; 16 | 17 | const SearchComponent = React.createClass({ 18 | getInitialState() { 19 | return { 20 | query:"" 21 | } 22 | }, 23 | 24 | componentWillMount() { 25 | const route = this.props.route; 26 | route.sp = this; 27 | }, 28 | 29 | componentWillUnmount() { 30 | const route = this.props.route; 31 | route.sp = null; 32 | }, 33 | onChangeText(text) { 34 | this.setState({query: text}); 35 | }, 36 | onSubmitEditing(text) { 37 | this._listview.forceRefresh(); 38 | }, 39 | 40 | reloadPath(page = 1) { 41 | let query = this.state.query || "null"; 42 | return OSCService.searchProjects(query, page); 43 | }, 44 | 45 | renderRow(rowData, sectionID, rowID, highlightRow) { 46 | return ( 47 | 48 | ) 49 | }, 50 | render() { 51 | let paddingTop = 64; 52 | if (Platform.OS == 'android') { 53 | paddingTop = 0; 54 | } 55 | return ( 56 | 57 | this._listview=c} 59 | renderRow={this.renderRow} 60 | reloadPromise={(page) => this.reloadPath(page)} 61 | /> 62 | 63 | ); 64 | } 65 | }); 66 | module.exports = SearchComponent; -------------------------------------------------------------------------------- /components/SettingsComponent.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by rplees on 3/8/16. 3 | */ 4 | const React = require('react-native'); 5 | const Platform = require('Platform'); 6 | const Colors = require('../common/Colors'); 7 | const DXRNUtils = require('../utils/DXRNUtils'); 8 | const GFDiskCache = require('../utils/GFDiskCache'); 9 | const OSCService = require('../service/OSCService'); 10 | const CommonComponents = require('../common/CommonComponents'); 11 | const SettingsCell = require('../common/SettingsCell'); 12 | const CommonStyles = require('../common/CommonStyles'); 13 | const constant = require('../config').constant; 14 | const Toast = require('@remobile/react-native-toast'); 15 | 16 | const { 17 | TouchableHighlight, 18 | ActionSheetIOS, 19 | Text, 20 | View, 21 | Alert, 22 | ScrollView 23 | } = React; 24 | 25 | const SettingComponent = React.createClass({ 26 | PropTypes: {}, 27 | getInitialState() { 28 | return { 29 | cachedSize:"...", 30 | appVersion: "", 31 | appBuild: "", 32 | appStoreURL: "", 33 | rateURL: "", 34 | } 35 | }, 36 | componentWillMount() { 37 | GFDiskCache.getDiskCacheCost((size) => { 38 | this.setState({ 39 | cachedSize: size, 40 | }); 41 | }); 42 | 43 | DXRNUtils.appInfo((appInfo) => { 44 | this.setState({ 45 | appVersion: appInfo.appVersion, 46 | appBuild: appInfo.appBuild, 47 | appStoreURL: appInfo.appStoreURL, 48 | rateURL: appInfo.rateURL, 49 | }); 50 | }); 51 | }, 52 | 53 | onShare() { 54 | if (Platform.OS === 'android') {//TODO:android分享 55 | return; 56 | } 57 | 58 | const message = '分享这款ReactNative OSCGit客户端,开源的.'; 59 | ActionSheetIOS.showShareActionSheetWithOptions({ 60 | message: message, 61 | url: 'https://github.com/rplees/react-native-gitosc', 62 | }, 63 | () => {}, 64 | () => {}); 65 | }, 66 | 67 | clearCache() { 68 | GFDiskCache.clearDiskCache((size) => { 69 | console.log("清空缓存:{}", size); 70 | Toast.showLongBottom("已经清空缓存:" + size); 71 | this.setState({ 72 | cachedSize: 0, 73 | }) 74 | }); 75 | }, 76 | 77 | render() { 78 | let currentVersion = "分享这款App! v: " + this.state.appVersion; 79 | currentVersion += ' b: ' + this.state.appBuild; 80 | 81 | let cachedSize = this.state.cachedSize; 82 | cachedSize = '清空缓存, 当前: ' + cachedSize; 83 | 84 | let paddingTop = 64; 85 | if (Platform.OS == 'android') { 86 | paddingTop = 0; 87 | } 88 | return ( 89 | 90 | 91 | { 97 | this.props.navigator.push({id: constant.scene.shake.key}); 98 | }} 99 | /> 100 | 101 | 106 | 107 | 113 | 114 | { 119 | this.props.navigator.push({id: constant.scene.feedback.key}); 120 | }} 121 | /> 122 | 123 | { 128 | this.props.navigator.push({id: constant.scene.personal.key, obj: 129 | {"name":"rplees","username":"rplees","id":95171} 130 | }); 131 | }} 132 | /> 133 | 134 | { 136 | Alert.alert( 137 | "确认操作", 138 | '确定要退出登陆么?', 139 | [ 140 | {text:"确定", onPress: function(){ OSCService.logout(); }}, 141 | {text:"不了", onPress: function(){}}, 142 | ] 143 | ); 144 | }} 145 | underlayColor={Colors.backGray} 146 | > 147 | 注销登陆 148 | 149 | 150 | 151 | ); 152 | } 153 | }); 154 | module.exports = SettingComponent; -------------------------------------------------------------------------------- /components/ShakeComponent.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by rplees on 3/8/16. 3 | */ 4 | const React = require('react-native'); 5 | const Platform = require('Platform'); 6 | const Colors = require('../common/Colors'); 7 | const DXRNUtils = require('../utils/DXRNUtils'); 8 | const OSCService = require('../service/OSCService'); 9 | const RNShakeEventIOS = require('react-native-shake-event-ios'); 10 | const FontAwesome = require('react-native-vector-icons/FontAwesome'); 11 | const RepoCell2 = require('../components/repo/RepoCell2'); 12 | const Dimensions = require('Dimensions'); 13 | 14 | const { 15 | View, 16 | Alert, 17 | } = React; 18 | 19 | const ShakeComponent = React.createClass({ 20 | PropTypes: {}, 21 | getInitialState() { 22 | return {repo: null} 23 | }, 24 | componentWillMount() { 25 | RNShakeEventIOS.addEventListener('shake', () => { 26 | console.log('Device shake!'); 27 | OSCService.getRandomProject() 28 | .then(repo => { 29 | this.setState({repo: repo}); 30 | }).catch(err => { 31 | Alert.alert("Oops", "摇一摇获取数据异常[" + err +"],请重试."); 32 | }); 33 | }); 34 | }, 35 | 36 | componentWillUnmount() { 37 | RNShakeEventIOS.removeEventListener('shake'); 38 | }, 39 | 40 | render() { 41 | let paddingTop = 64; 42 | if (Platform.OS == 'android') { 43 | paddingTop = 0; 44 | } 45 | let cp; 46 | if(this.state.repo) { 47 | cp = ( 48 | 49 | ); 50 | } 51 | return ( 52 | 53 | 58 | {cp} 59 | 60 | ); 61 | } 62 | }); 63 | module.exports = ShakeComponent; -------------------------------------------------------------------------------- /components/WebComponent.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by rplees on 3/16/16. 3 | */ 4 | const React = require('react-native'); 5 | const Icon = require('react-native-vector-icons/Ionicons'); 6 | const Colors = require('../common/Colors'); 7 | const Platform = require('Platform'); 8 | 9 | const { 10 | StyleSheet, 11 | WebView, 12 | View, 13 | TouchableOpacity, 14 | Text, 15 | Image, 16 | ActionSheetIOS, 17 | ProgressBarAndroid, 18 | ActivityIndicatorIOS 19 | } = React; 20 | const hideJS = ` 21 | ;(function GHHide() { 22 | var args = Array.prototype.slice.call(arguments); 23 | for (var i = 0; i < args.length; i++) { 24 | var className = args[i]; 25 | try { 26 | document.getElementsByClassName(className)[0].style.display="none"; 27 | } catch (e){}; 28 | } 29 | })('top', 30 | 'ui top attached tabular menu', 31 | 'clearfix', 32 | 'container', 33 | 'breadcrumb', 34 | 'breadcrumb blob-breadcrumb', 35 | 'discussion-block-header', 36 | 'discussion-reply-container', 37 | 'discussion-block-header', 38 | 'thread-subscription-status', 39 | 'follow' 40 | ); 41 | `; 42 | 43 | const WebComponent = React.createClass({ 44 | _debugTime: 0, 45 | 46 | PropTypes: { 47 | webURL: React.PropTypes.string, 48 | param: React.PropTypes.object, 49 | }, 50 | 51 | getInitialState() { 52 | let url = this.props.webURL; 53 | if (url && !url.match(/^[a-zA-Z]+:\/\//)) { 54 | url = 'http://' + url; 55 | } 56 | 57 | return { 58 | URL: url, 59 | backAble: false, 60 | forwardAble: false, 61 | refreshAble: false, 62 | } 63 | }, 64 | 65 | onNavigationStateChange(e) { 66 | console.log(e.url + ' ,loading takes' + (Date.now() - this._debugTime) / 1000 + 's'); 67 | this._debugTime = Date.now(); 68 | 69 | const title = e.title; 70 | let url = e.url; 71 | this.setState({ 72 | URL: url, 73 | backAble: e.canGoBack, 74 | forwardAble: e.canGoForward, 75 | refreshAble: !e.loading && title.length > 0 76 | }); 77 | }, 78 | 79 | onShare() { 80 | DXRNUtils.trackClick('clickWebShare', {name: 'web 点击分享:' + this.state.URL}); 81 | const message = ''; 82 | 83 | ActionSheetIOS.showShareActionSheetWithOptions({ 84 | message: message, 85 | url: this.state.URL, 86 | }, 87 | () => {}, 88 | () => {}); 89 | }, 90 | 91 | componentWillMount() { 92 | var navigator = this.props.navigator; 93 | var callback = (event) => { 94 | console.log(`WebComponent : event ${event.type}`,{ 95 | route: JSON.stringify(event.data.route), 96 | target: event.target, 97 | type: event.type, 98 | } 99 | ); 100 | 101 | if(event.data.route.obj 102 | && event.data.route.obj.t 103 | && event.data.route.obj.t === "issues") { 104 | this.webView && this.webView.reload(); 105 | } 106 | }; 107 | 108 | // Observe focus change events from this component. 109 | this._listeners = [ 110 | //navigator.navigationContext.addListener('willfocus', callback), 111 | navigator.navigationContext.addListener('didfocus', callback), 112 | ]; 113 | 114 | this._debugTime = Date.now(); 115 | this.props.route.onShare = this.onShare; 116 | }, 117 | 118 | componentWillUnmount: function() { 119 | this._listeners && this._listeners.forEach(listener => listener.remove()); 120 | }, 121 | 122 | renderLoading() { 123 | if (Platform.OS === 'android') { 124 | return ( 125 | 126 | 127 | 128 | ) 129 | } else if (Platform.OS === 'ios') { 130 | return ( 131 | 132 | 133 | 134 | ); 135 | } 136 | }, 137 | 138 | render() { 139 | let topInset = 64; 140 | let webToolBar; 141 | if (this.state.backAble || this.state.forwardAble) { 142 | webToolBar = ( 143 | this.webView.goBack()} 145 | goForward={() => this.webView.goForward()} 146 | onRefresh={() => this.webView.reload()} 147 | backAble={this.state.backAble} 148 | forwardAble={this.state.forwardAble} 149 | refreshAble={this.state.refreshAble} 150 | /> 151 | ) 152 | } 153 | 154 | return ( 155 | 156 | this.webView = webView} 158 | styles={{flex: 1}} 159 | source={{uri: this.state.URL}} 160 | onNavigationStateChange={this.onNavigationStateChange} 161 | injectedJavaScript={hideJS} 162 | automaticallyAdjustContentInsets={false} 163 | contentInset={{top: topInset, left: 0, bottom: 49, right: 0}} 164 | renderLoading={this.renderLoading} 165 | javaScriptEnabled={true} 166 | domStorageEnabled={true} 167 | startInLoadingState={true}> 168 | 169 | {webToolBar} 170 | 171 | ) 172 | }, 173 | }); 174 | 175 | 176 | const iconSize = 30; 177 | const WebToolBar = React.createClass({ 178 | PropTypes: { 179 | goBack: React.PropTypes.func, 180 | goForward: React.PropTypes.func, 181 | onRefresh: React.PropTypes.func, 182 | backAble: React.PropTypes.bool, 183 | forwardAble: React.PropTypes.bool, 184 | refreshAble: React.PropTypes.bool, 185 | }, 186 | 187 | goBack() { 188 | this.props.backAble && this.props.goBack && this.props.goBack(); 189 | }, 190 | 191 | goForward() { 192 | this.props.forwardAble && this.props.goForward && this.props.goForward(); 193 | }, 194 | 195 | onRefresh() { 196 | this.props.refreshAble && this.props.onRefresh && this.props.onRefresh(); 197 | }, 198 | 199 | render() { 200 | const backOpacity = this.props.backAble ? 0.5 : 1.0; 201 | const backColor = this.props.backAble ? Colors.blue : Colors.lightGray; 202 | 203 | const forwardOpacity = this.props.forwardAble ? 0.5 : 1.0; 204 | const forwardColor = this.props.forwardAble ? Colors.blue : Colors.lightGray; 205 | 206 | const refreshOpacity = this.props.refreshAble ? 0.5 : 1.0; 207 | const refreshColor = this.props.refreshAble ? Colors.blue : Colors.lightGray; 208 | 209 | let bottom = 49; 210 | if (Platform.OS === 'android') { 211 | bottom = 0; 212 | } 213 | 214 | return ( 215 | 216 | 217 | 221 | 227 | 228 | 232 | 238 | 239 | 240 | 244 | 250 | 251 | 252 | ) 253 | } 254 | }); 255 | 256 | var styles = StyleSheet.create({ 257 | leftAction: { 258 | padding: 3, 259 | backgroundColor: "#F2F2F2", 260 | flexDirection: 'row' 261 | }, 262 | rightAction: { 263 | padding: 3, 264 | backgroundColor: "white", 265 | }, 266 | actionText: { 267 | color: Colors.black, 268 | fontSize: 14, 269 | fontWeight: 'bold', 270 | alignSelf: 'center', 271 | }, 272 | webViewToolBar: { 273 | backgroundColor: 'rgba(255, 255, 255, 0.5)', 274 | height: 40, 275 | position: 'absolute', 276 | left: 0, 277 | bottom: 49, 278 | right: 0, 279 | flexDirection: 'row', 280 | justifyContent: 'space-between', 281 | alignItems: 'center', 282 | }, 283 | webLeft: { 284 | flexDirection: 'row', 285 | alignItems: 'center', 286 | marginLeft: 15, 287 | }, 288 | icon: { 289 | width: iconSize, 290 | height: iconSize, 291 | }, 292 | container: { 293 | flex: 1, 294 | justifyContent: 'center', 295 | alignItems: 'center', 296 | }, 297 | }); 298 | 299 | module.exports = WebComponent; -------------------------------------------------------------------------------- /components/repo/RepoCell2.js: -------------------------------------------------------------------------------- 1 | const React = require('react-native'); 2 | const CommonComponents = require('../../common/CommonComponents'); 3 | const FontAwesome = require('react-native-vector-icons/FontAwesome'); 4 | const Colors = require('../../common/Colors'); 5 | const Dimensions = require('Dimensions'); 6 | const DateUtils = require('../../utils/Utils').DateUtils; 7 | const constant = require("../../config").constant; 8 | 9 | const { 10 | View, 11 | Text, 12 | StyleSheet, 13 | TouchableHighlight, 14 | Image, 15 | TouchableOpacity, 16 | } = React; 17 | 18 | const ICON_SIZE = 12; 19 | 20 | const RepoCell2 = React.createClass({ 21 | propTypes: { 22 | repo: React.PropTypes.object, 23 | }, 24 | 25 | onPressCell() {//打开项目详情 26 | let repo = this.props.repo; 27 | this.props.navigator.push({id: constant.scene.repo_detail.key, obj: repo}); 28 | }, 29 | 30 | openAuthor() { 31 | const repo = this.props.repo; 32 | const user = repo.owner; 33 | this.props.navigator.push({id: constant.scene.personal.key, obj: user}); 34 | }, 35 | 36 | render() { 37 | const repo = this.props.repo; 38 | let recommCP; 39 | if(repo.recomm) { 40 | recommCP = ; 41 | } 42 | return ( 43 | 44 | 45 | 46 | 47 | 51 | 52 | 53 | 54 | 55 | 56 | {recommCP}{repo.path_with_namespace} 57 | 58 | 59 | 60 | 66 | 67 | {DateUtils.formatDiff(repo.last_push_at? repo.last_push_at : repo.created_at)} 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 80 | {repo.language} 81 | 82 | 83 | 88 | {repo.forks_count} 89 | 90 | 91 | 96 | {repo.stars_count} 97 | 98 | 99 | 104 | {repo.watches_count} 105 | 106 | 107 | 108 | 109 | 110 | 111 | {repo.description} 112 | 113 | {CommonComponents.renderSepLine()} 114 | 115 | 116 | ) 117 | }, 118 | }); 119 | 120 | var styles = StyleSheet.create({ 121 | /** 122 | * RepoCell2 123 | */ 124 | cellUp: { 125 | padding: 10, 126 | height: 40, 127 | flexDirection: 'column', 128 | flexWrap: 'wrap', 129 | alignItems: 'flex-start', 130 | justifyContent: 'flex-start', 131 | marginBottom: 15, 132 | }, 133 | avatar: { 134 | width: 40, 135 | height: 40, 136 | borderRadius: 8, 137 | backgroundColor: Colors.backGray 138 | }, 139 | username: { 140 | marginLeft: 10, 141 | color: '#4078C0', 142 | fontSize: 15, 143 | }, 144 | textActionContainer: { 145 | margin: 10, 146 | marginTop: 7, 147 | marginBottom: 10, 148 | marginLeft: 10, 149 | }, 150 | createAt: { 151 | marginLeft: 4, 152 | fontSize: 11, 153 | color: '#BFBFBF', 154 | }, 155 | textDesContainer: { 156 | margin: 10, 157 | marginTop: -5, 158 | marginBottom: 10, 159 | marginLeft: 25, 160 | borderStyle: 'dashed', 161 | }, 162 | 163 | rightAction: { 164 | padding: 3, 165 | backgroundColor: "white", 166 | }, 167 | }); 168 | 169 | module.exports = RepoCell2; 170 | -------------------------------------------------------------------------------- /config.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by rplees on 3/10/16. 3 | */ 4 | var config = { 5 | 'userAgent': 'ReactNative GIT@OSC', 6 | } 7 | 8 | const constant = { 9 | code_push: { 10 | STAGING_KEY : "dLhV8nYL7syV0OPIOrCwbnKSwNEY4k55Guhpl", 11 | PRODUCTION_KEY : "bpmwzaJmrsqlu_pMHgmzdep8no7b4k55Guhpl", 12 | }, 13 | scene: { 14 | project: {key: "project", value: "项目"}, 15 | famous: {key: "famous", value: "发现"}, 16 | personal: {key: "personal", value: "Me"}, 17 | login: {key: "login", value: "登陆"}, 18 | search: {key: "search", value: "搜索项目"}, 19 | create_issue: {key: "create_issue", value: "创建Issue"}, 20 | shake: {key: "shake", value: "摇一摇"}, 21 | feedback: {key: "feedback", value: "意见反馈"}, 22 | settings: {key: "settings", value: "设置"}, 23 | my_profile: {key: "my_profile", value: "我的资料"}, 24 | repo_detail: {key: "repo_detail", value: "项目详情"}, 25 | web: {key: "web", value: "web"}, 26 | } 27 | } 28 | module.exports = config; 29 | module.exports.constant = constant; 30 | -------------------------------------------------------------------------------- /entity/User.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by rplees on 3/11/16. 3 | * 用户实体 偷懒,使用JSON格式,想要new User需要使用 Object.create(User) 4 | */ 5 | var User = { 6 | "portrait": null, 7 | "private_token": "", 8 | "bio": null, 9 | "name": "", 10 | "can_create_group": true, 11 | "can_create_project": true, 12 | "state": "active", 13 | "id": 95171, 14 | "email": "rplees.i.ly@gmail.com", 15 | "is_admin": false, 16 | "blog": null, 17 | "weibo": null, 18 | "theme_id": 1, 19 | "created_at": "2014-06-04T09:54:18+08:00", 20 | "new_portrait": "http://secure.gravatar.com/avatar/c9871eff6e22bb6229fa6dd14ad09db2?s=40&d=mm", 21 | "can_create_team": true, 22 | "follow": { 23 | "followers": 1, 24 | "starred": 6, 25 | "following": 0, 26 | "watched": 4 27 | }, 28 | "username": "" 29 | } 30 | 31 | module.exports = User; -------------------------------------------------------------------------------- /icons/android/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rplees/react-native-gitosc/HEAD/icons/android/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /icons/android/mipmap-ldpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rplees/react-native-gitosc/HEAD/icons/android/mipmap-ldpi/ic_launcher.png -------------------------------------------------------------------------------- /icons/android/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rplees/react-native-gitosc/HEAD/icons/android/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /icons/android/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rplees/react-native-gitosc/HEAD/icons/android/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /icons/android/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rplees/react-native-gitosc/HEAD/icons/android/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /icons/android/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rplees/react-native-gitosc/HEAD/icons/android/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /icons/android/playstore-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rplees/react-native-gitosc/HEAD/icons/android/playstore-icon.png -------------------------------------------------------------------------------- /icons/ios/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "29x29", 6 | "scale" : "2x", 7 | "filename" : "Icon-Small@2x.png" 8 | }, 9 | { 10 | "idiom" : "iphone", 11 | "size" : "29x29", 12 | "scale" : "3x", 13 | "filename" : "Icon-Small@3x.png" 14 | }, 15 | { 16 | "idiom" : "iphone", 17 | "size" : "40x40", 18 | "scale" : "2x", 19 | "filename" : "Icon-40@2x.png" 20 | }, 21 | { 22 | "idiom" : "iphone", 23 | "size" : "40x40", 24 | "scale" : "3x", 25 | "filename" : "Icon-40@3x.png" 26 | }, 27 | { 28 | "idiom" : "iphone", 29 | "size" : "60x60", 30 | "scale" : "2x", 31 | "filename" : "Icon-60@2x.png" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "size" : "60x60", 36 | "scale" : "3x", 37 | "filename" : "Icon-60@3x.png" 38 | }, 39 | { 40 | "idiom" : "ipad", 41 | "size" : "29x29", 42 | "scale" : "1x", 43 | "filename" : "Icon-Small.png" 44 | }, 45 | { 46 | "idiom" : "ipad", 47 | "size" : "29x29", 48 | "scale" : "2x", 49 | "filename" : "Icon-Small@2x.png" 50 | }, 51 | { 52 | "idiom" : "ipad", 53 | "size" : "40x40", 54 | "scale" : "1x", 55 | "filename" : "Icon-40.png" 56 | }, 57 | { 58 | "idiom" : "ipad", 59 | "size" : "40x40", 60 | "scale" : "2x", 61 | "filename" : "Icon-40@2x.png" 62 | }, 63 | { 64 | "idiom" : "ipad", 65 | "size" : "76x76", 66 | "scale" : "1x", 67 | "filename" : "Icon-76.png" 68 | }, 69 | { 70 | "idiom" : "ipad", 71 | "size" : "76x76", 72 | "scale" : "2x", 73 | "filename" : "Icon-76@2x.png" 74 | }, 75 | { 76 | "idiom" : "ipad", 77 | "size" : "83.5x83.5", 78 | "scale" : "2x", 79 | "filename" : "Icon-83.5@2x.png" 80 | } 81 | ], 82 | "info" : { 83 | "version" : 1, 84 | "author" : "makeappicon" 85 | } 86 | } -------------------------------------------------------------------------------- /icons/ios/AppIcon.appiconset/Icon-40.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rplees/react-native-gitosc/HEAD/icons/ios/AppIcon.appiconset/Icon-40.png -------------------------------------------------------------------------------- /icons/ios/AppIcon.appiconset/Icon-40@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rplees/react-native-gitosc/HEAD/icons/ios/AppIcon.appiconset/Icon-40@2x.png -------------------------------------------------------------------------------- /icons/ios/AppIcon.appiconset/Icon-40@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rplees/react-native-gitosc/HEAD/icons/ios/AppIcon.appiconset/Icon-40@3x.png -------------------------------------------------------------------------------- /icons/ios/AppIcon.appiconset/Icon-60@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rplees/react-native-gitosc/HEAD/icons/ios/AppIcon.appiconset/Icon-60@2x.png -------------------------------------------------------------------------------- /icons/ios/AppIcon.appiconset/Icon-60@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rplees/react-native-gitosc/HEAD/icons/ios/AppIcon.appiconset/Icon-60@3x.png -------------------------------------------------------------------------------- /icons/ios/AppIcon.appiconset/Icon-76.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rplees/react-native-gitosc/HEAD/icons/ios/AppIcon.appiconset/Icon-76.png -------------------------------------------------------------------------------- /icons/ios/AppIcon.appiconset/Icon-76@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rplees/react-native-gitosc/HEAD/icons/ios/AppIcon.appiconset/Icon-76@2x.png -------------------------------------------------------------------------------- /icons/ios/AppIcon.appiconset/Icon-83.5@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rplees/react-native-gitosc/HEAD/icons/ios/AppIcon.appiconset/Icon-83.5@2x.png -------------------------------------------------------------------------------- /icons/ios/AppIcon.appiconset/Icon-Small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rplees/react-native-gitosc/HEAD/icons/ios/AppIcon.appiconset/Icon-Small.png -------------------------------------------------------------------------------- /icons/ios/AppIcon.appiconset/Icon-Small@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rplees/react-native-gitosc/HEAD/icons/ios/AppIcon.appiconset/Icon-Small@2x.png -------------------------------------------------------------------------------- /icons/ios/AppIcon.appiconset/Icon-Small@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rplees/react-native-gitosc/HEAD/icons/ios/AppIcon.appiconset/Icon-Small@3x.png -------------------------------------------------------------------------------- /icons/ios/Icon-60.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rplees/react-native-gitosc/HEAD/icons/ios/Icon-60.png -------------------------------------------------------------------------------- /icons/ios/Icon-72.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rplees/react-native-gitosc/HEAD/icons/ios/Icon-72.png -------------------------------------------------------------------------------- /icons/ios/Icon-72@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rplees/react-native-gitosc/HEAD/icons/ios/Icon-72@2x.png -------------------------------------------------------------------------------- /icons/ios/Icon-Small-50.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rplees/react-native-gitosc/HEAD/icons/ios/Icon-Small-50.png -------------------------------------------------------------------------------- /icons/ios/Icon-Small-50@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rplees/react-native-gitosc/HEAD/icons/ios/Icon-Small-50@2x.png -------------------------------------------------------------------------------- /icons/ios/Icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rplees/react-native-gitosc/HEAD/icons/ios/Icon.png -------------------------------------------------------------------------------- /icons/ios/Icon@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rplees/react-native-gitosc/HEAD/icons/ios/Icon@2x.png -------------------------------------------------------------------------------- /icons/ios/README.md: -------------------------------------------------------------------------------- 1 | ## iTunesArtwork & iTunesArtwork@2x (App Icon) file extension: 2 | 3 | PNG extension is prepended to these two files - 4 | 5 | While Apple suggested to omit the extension for these files, 6 | the '.png' extension is actually required for iTunesConnect submission. 7 | 8 | This is done for you so you don't have to. 9 | 10 | However, for Ad_hoc or Enterprise distirbution, the extension should be removed 11 | from the files before adding to XCode to avoid error. 12 | 13 | refs: https://developer.apple.com/library/ios/qa/qa1686/_index.html 14 | 15 | ## iTunesArtwork & iTunesArtwork@2x (App Icon) transparency handling: 16 | 17 | As images with alpha channels or transparencies cannot be set as an application's icon on 18 | iTunesConnect, all transparent pixels in your images will be converted into 19 | solid blacks. 20 | 21 | To achieve the best result, you're advised to adjust the transparency settings 22 | in your source files before converting them with makeAppIcon. 23 | 24 | refs: https://developer.apple.com/library/ios/documentation/UserExperience/Conceptual/MobileHIG/AppIcons.html 25 | -------------------------------------------------------------------------------- /icons/ios/iTunesArtwork.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rplees/react-native-gitosc/HEAD/icons/ios/iTunesArtwork.png -------------------------------------------------------------------------------- /icons/ios/iTunesArtwork@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rplees/react-native-gitosc/HEAD/icons/ios/iTunesArtwork@2x.png -------------------------------------------------------------------------------- /icons/watchkit/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "size" : "24x24", 5 | "idiom" : "watch", 6 | "scale" : "2x", 7 | "filename" : "Icon-24@2x.png", 8 | "role" : "notificationCenter", 9 | "subtype" : "38mm" 10 | }, 11 | { 12 | "size" : "27.5x27.5", 13 | "idiom" : "watch", 14 | "scale" : "2x", 15 | "filename" : "Icon-27.5@2x.png", 16 | "role" : "notificationCenter", 17 | "subtype" : "42mm" 18 | }, 19 | { 20 | "size" : "29x29", 21 | "idiom" : "watch", 22 | "filename" : "Icon-29@2x.png", 23 | "role" : "companionSettings", 24 | "scale" : "2x" 25 | }, 26 | { 27 | "size" : "29x29", 28 | "idiom" : "watch", 29 | "filename" : "Icon-29@3x.png", 30 | "role" : "companionSettings", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "size" : "40x40", 35 | "idiom" : "watch", 36 | "scale" : "2x", 37 | "filename" : "Icon-40@2x.png", 38 | "role" : "appLauncher", 39 | "subtype" : "38mm" 40 | }, 41 | { 42 | "size" : "44x44", 43 | "idiom" : "watch", 44 | "scale" : "2x", 45 | "filename" : "Icon-44@2x.png", 46 | "role" : "longLook", 47 | "subtype" : "42mm" 48 | }, 49 | { 50 | "size" : "86x86", 51 | "idiom" : "watch", 52 | "scale" : "2x", 53 | "filename" : "Icon-86@2x.png", 54 | "role" : "quickLook", 55 | "subtype" : "38mm" 56 | }, 57 | { 58 | "size" : "98x98", 59 | "idiom" : "watch", 60 | "scale" : "2x", 61 | "filename" : "Icon-98@2x.png", 62 | "role" : "quickLook", 63 | "subtype" : "42mm" 64 | } 65 | ], 66 | "info" : { 67 | "version" : 1, 68 | "author" : "makeappicon" 69 | } 70 | } -------------------------------------------------------------------------------- /icons/watchkit/AppIcon.appiconset/Icon-24@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rplees/react-native-gitosc/HEAD/icons/watchkit/AppIcon.appiconset/Icon-24@2x.png -------------------------------------------------------------------------------- /icons/watchkit/AppIcon.appiconset/Icon-27.5@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rplees/react-native-gitosc/HEAD/icons/watchkit/AppIcon.appiconset/Icon-27.5@2x.png -------------------------------------------------------------------------------- /icons/watchkit/AppIcon.appiconset/Icon-29@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rplees/react-native-gitosc/HEAD/icons/watchkit/AppIcon.appiconset/Icon-29@2x.png -------------------------------------------------------------------------------- /icons/watchkit/AppIcon.appiconset/Icon-29@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rplees/react-native-gitosc/HEAD/icons/watchkit/AppIcon.appiconset/Icon-29@3x.png -------------------------------------------------------------------------------- /icons/watchkit/AppIcon.appiconset/Icon-40@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rplees/react-native-gitosc/HEAD/icons/watchkit/AppIcon.appiconset/Icon-40@2x.png -------------------------------------------------------------------------------- /icons/watchkit/AppIcon.appiconset/Icon-44@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rplees/react-native-gitosc/HEAD/icons/watchkit/AppIcon.appiconset/Icon-44@2x.png -------------------------------------------------------------------------------- /icons/watchkit/AppIcon.appiconset/Icon-86@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rplees/react-native-gitosc/HEAD/icons/watchkit/AppIcon.appiconset/Icon-86@2x.png -------------------------------------------------------------------------------- /icons/watchkit/AppIcon.appiconset/Icon-98@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rplees/react-native-gitosc/HEAD/icons/watchkit/AppIcon.appiconset/Icon-98@2x.png -------------------------------------------------------------------------------- /index.android.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Sample React Native App 3 | * https://github.com/facebook/react-native 4 | */ 5 | 'use strict'; 6 | const React = require("react-native"); 7 | const { 8 | AppRegistry, 9 | Component, 10 | StyleSheet, 11 | Text, 12 | View, 13 | } = React; 14 | 15 | class OSCGit extends Component { 16 | render() { 17 | return ( 18 | 19 | 20 | Welcome to React Native! 21 | 22 | 23 | To get started, edit index.android.js 24 | 25 | 26 | Shake or press menu button for dev menu 27 | 28 | 29 | ); 30 | } 31 | } 32 | 33 | const styles = StyleSheet.create({ 34 | container: { 35 | flex: 1, 36 | justifyContent: 'center', 37 | alignItems: 'center', 38 | backgroundColor: '#F5FCFF', 39 | }, 40 | welcome: { 41 | fontSize: 20, 42 | textAlign: 'center', 43 | margin: 10, 44 | }, 45 | instructions: { 46 | textAlign: 'center', 47 | color: '#333333', 48 | marginBottom: 5, 49 | }, 50 | }); 51 | 52 | AppRegistry.registerComponent('OSCGit', () => OSCGit); 53 | -------------------------------------------------------------------------------- /index.ios.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Sample React Native App 3 | * https://github.com/facebook/react-native 4 | */ 5 | 'use strict'; 6 | 7 | const React = require("react-native"); 8 | const LoginComponent = require("./components/LoginComponent"); 9 | const RootTab = require("./components/RootTab"); 10 | const CommonComponents = require("./common/CommonComponents"); 11 | const OSCService = require("./service/OSCService"); 12 | const ShakeComponent = require('./components/ShakeComponent'); 13 | const codePush = require('react-native-code-push'); 14 | const constant = require('./config').constant; 15 | const Toast = require('@remobile/react-native-toast'); 16 | 17 | const { 18 | AppRegistry, 19 | } = React; 20 | 21 | const OSCGit = React.createClass({ 22 | getInitialState() { 23 | return {loading: true} 24 | }, 25 | 26 | getRandomInt(min, max) { 27 | return Math.floor(Math.random() * (max - min)) + min; 28 | }, 29 | 30 | _query() { 31 | OSCService.getUserFromCache() 32 | .then(() => { 33 | this.setState({loading: false}); 34 | }); 35 | }, 36 | componentWillMount() { 37 | this._query(); 38 | 39 | OSCService.addListener('didLogout', () => { 40 | Toast.showLongBottom("用户登出."); 41 | this.setState(this.getInitialState()); 42 | this._query(); 43 | }); 44 | 45 | const random = this.getRandomInt(1, 10); 46 | const cpKey = random % 2 == 0 ? constant.code_push.STAGING_KEY : constant.code_push.PRODUCTION_KEY; 47 | // Prompt the user when an update is available 48 | // and then display a "downloading" modal 49 | //, (status) => { 50 | // switch (status) { 51 | // case codePush.SyncStatus.DOWNLOADING_PACKAGE: 52 | // // Show "downloading" modal 53 | // break; 54 | // case codePush.SyncStatus.INSTALLING_UPDATE: 55 | // // Hide "downloading" modal 56 | // break; 57 | // } 58 | //} 59 | codePush.sync({ 60 | updateDialog: true, 61 | installMode: codePush.InstallMode.IMMEDIATE, 62 | deploymentKey:cpKey 63 | }); 64 | }, 65 | 66 | componentDidMount() { 67 | 68 | }, 69 | 70 | componentWillUnmount: function() { 71 | OSCService.removeListener('didLogout'); 72 | }, 73 | 74 | render() { 75 | if(this.state.loading) { 76 | return CommonComponents.renderLoadingView(); 77 | } 78 | 79 | return ; 80 | } 81 | }); 82 | AppRegistry.registerComponent('OSCGit', () => OSCGit); 83 | -------------------------------------------------------------------------------- /ios/OSCGit.xcodeproj/xcshareddata/xcschemes/OSCGit.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 16 | 22 | 23 | 24 | 31 | 37 | 38 | 39 | 40 | 41 | 46 | 47 | 49 | 55 | 56 | 57 | 58 | 59 | 65 | 66 | 67 | 68 | 69 | 70 | 80 | 82 | 88 | 89 | 90 | 91 | 92 | 93 | 99 | 101 | 107 | 108 | 109 | 110 | 112 | 113 | 116 | 117 | 118 | -------------------------------------------------------------------------------- /ios/OSCGit.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /ios/OSCGit/AppDelegate.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2015-present, Facebook, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | */ 9 | 10 | #import 11 | 12 | @interface AppDelegate : UIResponder 13 | 14 | @property (nonatomic, strong) UIWindow *window; 15 | 16 | @end 17 | -------------------------------------------------------------------------------- /ios/OSCGit/AppDelegate.m: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2015-present, Facebook, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | */ 9 | 10 | #import "AppDelegate.h" 11 | 12 | #import "RCTRootView.h" 13 | #import "CodePush.h" 14 | 15 | @implementation AppDelegate 16 | 17 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions 18 | { 19 | NSURL *jsCodeLocation; 20 | 21 | /** 22 | * Loading JavaScript code - uncomment the one you want. 23 | * 24 | * OPTION 1 25 | * Load from development server. Start the server from the repository root: 26 | * 27 | * $ npm start 28 | * 29 | * To run on device, change `localhost` to the IP address of your computer 30 | * (you can get this by typing `ifconfig` into the terminal and selecting the 31 | * `inet` value under `en0:`) and make sure your computer and iOS device are 32 | * on the same Wi-Fi network. 33 | */ 34 | 35 | jsCodeLocation = [NSURL URLWithString:@"http://localhost:8081/index.ios.bundle?platform=ios&dev=true"]; 36 | 37 | // jsCodeLocation = [CodePush bundleURLForResource: @"index.ios"]; 38 | 39 | /** 40 | * OPTION 2 41 | * Load from pre-bundled file on disk. The static bundle is automatically 42 | * generated by "Bundle React Native code and images" build step. 43 | */ 44 | 45 | // jsCodeLocation = [[NSBundle mainBundle] URLForResource:@"index.ios" withExtension:@"jsbundle"]; 46 | 47 | RCTRootView *rootView = [[RCTRootView alloc] initWithBundleURL:jsCodeLocation 48 | moduleName:@"OSCGit" 49 | initialProperties:nil 50 | launchOptions:launchOptions]; 51 | 52 | self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds]; 53 | UIViewController *rootViewController = [UIViewController new]; 54 | rootViewController.view = rootView; 55 | // [NSThread sleepForTimeInterval:1.0f]; 56 | self.window.rootViewController = rootViewController; 57 | [self.window makeKeyAndVisible]; 58 | return YES; 59 | } 60 | 61 | @end 62 | -------------------------------------------------------------------------------- /ios/OSCGit/Base.lproj/LaunchScreen.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 21 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /ios/OSCGit/Classes/Additions/NSString+GFAdditions.h: -------------------------------------------------------------------------------- 1 | @import Foundation; 2 | 3 | /** 4 | * 提供`NSString`的通用扩展方法 5 | */ 6 | @interface NSString (GFAdditions) 7 | 8 | /** 9 | * --------------------------------------------------------------------------- 10 | * @name 消息摘要 11 | * --------------------------------------------------------------------------- 12 | */ 13 | 14 | /** 15 | * 生成MD5信息摘要 16 | * 17 | * @return 返回MD5信息摘要 18 | */ 19 | - (NSString *)jm_MD5Digest; 20 | 21 | @end -------------------------------------------------------------------------------- /ios/OSCGit/Classes/Additions/NSString+GFAdditions.m: -------------------------------------------------------------------------------- 1 | #import "NSString+GFAdditions.h" 2 | #import 3 | 4 | @implementation NSString (GFAdditions) 5 | 6 | + (NSString *)jm_stringFromDigest:(uint8_t *)digest length:(int)length { 7 | NSMutableString *ms = [[NSMutableString alloc] initWithCapacity:length * 2]; 8 | for (int i = 0; i < length; i++) { 9 | [ms appendFormat: @"%02x", (int)digest[i]]; 10 | } 11 | 12 | return [ms copy]; 13 | } 14 | 15 | - (NSData *)jm_prehashData { 16 | const char *cstr = [self cStringUsingEncoding:NSUTF8StringEncoding]; 17 | return [NSData dataWithBytes:cstr length:self.length]; 18 | } 19 | 20 | - (NSString *)jm_MD5Digest { 21 | NSData *preHashData = [self jm_prehashData]; 22 | uint8_t digest[CC_MD5_DIGEST_LENGTH]; 23 | CC_MD5(preHashData.bytes, (CC_LONG)preHashData.length, digest); 24 | 25 | return [[self class] jm_stringFromDigest:digest length:CC_MD5_DIGEST_LENGTH]; 26 | } 27 | 28 | @end 29 | 30 | -------------------------------------------------------------------------------- /ios/OSCGit/Classes/DXTopMessageManager.h: -------------------------------------------------------------------------------- 1 | // 2 | // DXTopMessageManager.h 3 | // RN_CNNode 4 | // 5 | // Created by xiekw on 15/10/21. 6 | // Copyright © 2015年 Facebook. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "RCTBridgeModule.h" 11 | 12 | @interface DXTopMessageManager : NSObject 13 | 14 | @end 15 | -------------------------------------------------------------------------------- /ios/OSCGit/Classes/DXTopMessageManager.m: -------------------------------------------------------------------------------- 1 | // 2 | // DXTopMessageManager.m 3 | // RN_CNNode 4 | // 5 | // Created by xiekw on 15/10/21. 6 | // Copyright © 2015年 Facebook. All rights reserved. 7 | // 8 | 9 | #import "DXTopMessageManager.h" 10 | #import "RCTBridge.h" 11 | #import "RCTConvert.h" 12 | #import "RCTScrollView.h" 13 | #import "RCTUIManager.h" 14 | #import "RCTEventDispatcher.h" 15 | #import "UIView+react.h" 16 | #import "UIView+TopBarMessage.h" 17 | 18 | @implementation DXTopMessageManager 19 | 20 | @synthesize bridge = _bridge; 21 | 22 | - (dispatch_queue_t)methodQueue { 23 | return _bridge.uiManager.methodQueue; 24 | } 25 | 26 | RCT_EXPORT_MODULE(); 27 | 28 | /** 29 | * Show top message 30 | * 31 | * @param config { 32 | * background: '#111111', 33 | * textColor: '#111111', 34 | * font: {'fontFamily': 'hev', 'fontSize': 12, 'fontWeight': 11, 'fontStyle': 'bold'}, 35 | * icon: 'imagename', 36 | * offset: 64, 37 | * } 38 | * 39 | */ 40 | 41 | RCT_EXPORT_METHOD(showTopMessage:(nonnull NSNumber *)reactTag message:(nonnull NSString *)message config:(NSDictionary *)config callback:(RCTResponseSenderBlock)callback) { 42 | [self.bridge.uiManager addUIBlock:^(RCTUIManager *uiManager, NSDictionary *viewRegistry) { 43 | dispatch_async(dispatch_get_main_queue(), ^{ 44 | UIView *view = viewRegistry[reactTag]; 45 | if (!view) { 46 | NSLog(@"Cannot find view with tag #%@", reactTag); 47 | return; 48 | } 49 | 50 | [view showTopMessage:message topBarConfig:[self mapTopBarConfig:config] dismissDelay:3.0 withTapBlock:^{ 51 | [_bridge.eventDispatcher sendDeviceEventWithName:@"messageTapped" body:reactTag]; 52 | }]; 53 | 54 | callback(@[[NSNull null], reactTag]); 55 | }); 56 | }]; 57 | 58 | } 59 | 60 | - (NSDictionary *)mapTopBarConfig:(NSDictionary *)jsConfig { 61 | NSMutableDictionary *mdic = [NSMutableDictionary dictionaryWithCapacity:jsConfig.count]; 62 | id backColor = jsConfig[@"background"]; 63 | if (backColor) { 64 | mdic[kDXTopBarBackgroundColor] = [RCTConvert UIColor:backColor]; 65 | } 66 | 67 | id textColor = jsConfig[@"textColor"]; 68 | if (textColor) { 69 | mdic[kDXTopBarTextColor] = [RCTConvert UIColor:textColor]; 70 | } 71 | 72 | id font = jsConfig[@"font"]; 73 | if (font) { 74 | mdic[kDXTopBarTextFont] = [RCTConvert UIFont:font]; 75 | } 76 | 77 | id offset = jsConfig[@"offset"]; 78 | mdic[kDXTopBarOffset] = @([offset floatValue]); 79 | 80 | return mdic; 81 | } 82 | 83 | @end 84 | -------------------------------------------------------------------------------- /ios/OSCGit/Classes/GFDiskCacheManager.h: -------------------------------------------------------------------------------- 1 | // 2 | // GFDiskCacheManager.h 3 | // RN_CNNode 4 | // 5 | // Created by xiekw on 16/1/19. 6 | // Copyright © 2016年 Facebook. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "RCTBridgeModule.h" 11 | 12 | @interface GFDiskCacheManager : NSObject 13 | 14 | @end 15 | -------------------------------------------------------------------------------- /ios/OSCGit/Classes/GFDiskCacheManager.m: -------------------------------------------------------------------------------- 1 | // 2 | // GFDiskCacheManager.m 3 | // RN_CNNode 4 | // 5 | // Created by xiekw on 16/1/19. 6 | // Copyright © 2016年 Facebook. All rights reserved. 7 | // 8 | 9 | #import "GFDiskCacheManager.h" 10 | #import "RCTBridge.h" 11 | #import "RCTConvert.h" 12 | #import "GFWebResourceInterceptor.h" 13 | #import "GFWebResourceCache.h" 14 | 15 | @implementation GFDiskCacheManager 16 | 17 | RCT_EXPORT_MODULE(); 18 | 19 | RCT_EXPORT_METHOD(diskCacheCost:(RCTResponseSenderBlock)callback) { 20 | [[GFWebResourceInterceptor globalWebResourceInterceptor].cache cacheCost:^(NSUInteger fileSize) { 21 | callback(@[@(fileSize)]); 22 | }]; 23 | } 24 | 25 | RCT_EXPORT_METHOD(clearDiskCache:(RCTResponseSenderBlock)callback) { 26 | [[GFWebResourceInterceptor globalWebResourceInterceptor].cache clearCache:^(NSUInteger fileSize) { 27 | callback(@[@(fileSize)]); 28 | }]; 29 | } 30 | 31 | @end 32 | -------------------------------------------------------------------------------- /ios/OSCGit/Classes/H5/GFWebResourceCache.h: -------------------------------------------------------------------------------- 1 | @import Foundation; 2 | 3 | /** 4 | * Web资源缓存管理类 5 | */ 6 | @interface GFWebResourceCache : NSObject 7 | 8 | @property (nonatomic, strong, readonly) NSString *cachePath; 9 | 10 | /** 11 | * 检查是否有命中的缓存索引,用于提升检索效率,减少IO操作。 12 | * 13 | * @param key 缓存对应唯一的key 14 | * 15 | * @return 返回`YES`标示本地存在对应的缓存,反之则`NO` 16 | */ 17 | - (BOOL)hasCacheForKey:(NSString *)key; 18 | 19 | /** 20 | * 通过传入的key创建缓存检索索引。 21 | * 22 | * @param key 缓存对应唯一的key 23 | */ 24 | - (void)addCacheIndexForKey:(NSString *)key; 25 | 26 | /** 27 | * 移除该key关联的缓存检索索引。 28 | * 29 | * @param key 缓存对应唯一的key 30 | */ 31 | - (void)removeCacheIndexForKey:(NSString *)key; 32 | 33 | /** 34 | * 清除cache的index table和cache file 35 | * 36 | */ 37 | - (void)clearCache:(void (^)(NSUInteger fileSize))completionBlock; 38 | 39 | /** 40 | * 查看cache了多少大小 41 | * 42 | */ 43 | - (void)cacheCost:(void (^)(NSUInteger fileSize))completionBlock; 44 | 45 | /** 46 | * 通过缓存key获取关联的数据。 47 | * 48 | * @param key 缓存对应唯一的key 49 | * @param url 请求URL 50 | * @param response 响应头 51 | * 52 | * @return 返回本地缓存的数据,如果返回为`nil`,则需要手工移除该key关联的缓存索引 53 | */ 54 | - (NSData *)cacheDataForKey:(NSString *)key url:(NSURL *)url response:(NSURLResponse **)response; 55 | 56 | /** 57 | * 通过关联的key存储缓存数据 58 | * 59 | * @param data 数据 60 | * @param response 响应头 61 | * @param key 缓存对应唯一的key 62 | * @param completionBlock 完成回调 63 | */ 64 | - (void)storeCacheData:(NSData *)data 65 | response:(NSURLResponse *)response 66 | forKey:(NSString *)key 67 | completionBlock:(void (^)(BOOL succeed))completionBlock; 68 | 69 | 70 | @end 71 | -------------------------------------------------------------------------------- /ios/OSCGit/Classes/H5/GFWebResourceInterceptor.h: -------------------------------------------------------------------------------- 1 | @import Foundation; 2 | #import "GFWebResourceInterceptorSettings+Internal.h" 3 | 4 | @class GFWebResourceCache; 5 | 6 | /** 7 | * Web资源拦截器实现类 8 | */ 9 | @interface GFWebResourceInterceptor : NSObject 10 | 11 | /** 12 | * 全局共享的Web资源拦截器 13 | * 14 | * @return 返回全局共享的Web资源拦截器 15 | */ 16 | + (instancetype)globalWebResourceInterceptor; 17 | 18 | /** 19 | * 设置自定义的全局共享Web资源拦截器。当传入`nil`时,则清空全局的Web资源拦截器。 20 | * 21 | * @param interceptor 自定义拦截器或为`nil` 22 | */ 23 | + (void)setGlobalWebResourceInterceptor:(GFWebResourceInterceptor *)interceptor; 24 | 25 | /** 26 | * 拦截器设置 27 | */ 28 | @property (nonatomic, strong, readonly) id settings; 29 | 30 | /** 31 | * 拦截器资源缓存 32 | */ 33 | @property (nonatomic, strong, readonly) GFWebResourceCache *cache; 34 | 35 | /** 36 | * 配置默认Web资源拦截器的设置 37 | */ 38 | - (void)setupDefaultWebResourceInterceptorSettings; 39 | 40 | /** 41 | * 检查传入的主机名是否在白名单内 42 | * 43 | * @param host 主机名 44 | * 45 | * @return 返回`YES`则表示在白名单内,`NO`则表示不在白名单内 46 | */ 47 | - (BOOL)isWhitelistHost:(NSString *)host; 48 | 49 | /** 50 | * 检查传入的资源路径是否在黑名单内 51 | * 52 | * @param path 请求资源路径 53 | * 54 | * @return 返回`YES`则表示在黑名单内,`NO`则表示不在黑名单内 55 | */ 56 | - (BOOL)isBlacklistWithRequestPath:(NSString *)path; 57 | 58 | /** 59 | * 检查是否支持传入的文件后缀名 60 | * 61 | * @param extension 文件后缀名 62 | * 63 | * @return 返回`YES`则表示支持该后缀名,反之则为`NO` 64 | */ 65 | - (BOOL)isSupportedPathExtension:(NSString *)extension; 66 | 67 | /** 68 | * 更新Web资源拦截器设置 69 | * 70 | * @param settings Web资源拦截器设置 71 | */ 72 | - (void)updateInterceptorSettings:(id)settings; 73 | 74 | @end 75 | -------------------------------------------------------------------------------- /ios/OSCGit/Classes/H5/GFWebResourceInterceptor.m: -------------------------------------------------------------------------------- 1 | #import "GFWebResourceInterceptor.h" 2 | #import "GFWebResourceURLProtocol.h" 3 | #import "GFWebResourceCache.h" 4 | 5 | static GFWebResourceInterceptor *gWebResourceInterceptor = nil; 6 | 7 | @interface GFWebResourceInterceptor () 8 | 9 | @property (nonatomic, strong) id settings; 10 | @property (nonatomic, strong) GFWebResourceCache *cache; 11 | 12 | // external did update interceptor settings 13 | @property (atomic, assign) BOOL didUpdateInceptorSettings; 14 | 15 | @end 16 | 17 | @implementation GFWebResourceInterceptor { 18 | @private 19 | struct { 20 | unsigned int didRegisterURLProtocol:1; // did register custom url protocol 21 | }_interceptorFlags; 22 | } 23 | 24 | - (id)init { 25 | self = [super init]; 26 | if (self) { 27 | [self commonInit]; 28 | } 29 | 30 | return self; 31 | } 32 | 33 | + (instancetype)globalWebResourceInterceptor { 34 | @synchronized([self class]) { 35 | if (gWebResourceInterceptor == nil) { 36 | gWebResourceInterceptor = [[GFWebResourceInterceptor alloc] init]; 37 | } 38 | } 39 | 40 | return gWebResourceInterceptor; 41 | } 42 | 43 | + (void)setGlobalWebResourceInterceptor:(GFWebResourceInterceptor *)interceptor { 44 | @synchronized([self class]) { 45 | if (gWebResourceInterceptor != interceptor) { 46 | gWebResourceInterceptor = interceptor; 47 | } 48 | } 49 | } 50 | 51 | 52 | ////////////////////////////////////////////////////////////////////////////////// 53 | 54 | #pragma mark - 55 | #pragma mark private methods 56 | 57 | - (void)commonInit { 58 | // update flags 59 | _interceptorFlags.didRegisterURLProtocol = 0; 60 | self.didUpdateInceptorSettings = NO; 61 | 62 | // create web cache 63 | _cache = [[GFWebResourceCache alloc] init]; 64 | } 65 | 66 | - (void)registerWebResourceInterceptorURLProtocol { 67 | if (_settings.enabled 68 | && !_interceptorFlags.didRegisterURLProtocol) { 69 | _interceptorFlags.didRegisterURLProtocol = 1; // update flags 70 | 71 | // register url protocol handle class 72 | [NSURLProtocol registerClass:[GFWebResourceURLProtocol class]]; 73 | } 74 | } 75 | 76 | - (void)unregisterWebResourceInterceptorURLProtocol { 77 | if (_interceptorFlags.didRegisterURLProtocol) { 78 | _interceptorFlags.didRegisterURLProtocol = 0; // update flags 79 | 80 | // unregister url protocol handle class 81 | [NSURLProtocol unregisterClass:[GFWebResourceURLProtocol class]]; 82 | } 83 | } 84 | 85 | - (void)notifyDidChangeInterceptorSettings { 86 | if (_settings != nil && _settings.enabled) { 87 | // open web resource interceptor 88 | [self registerWebResourceInterceptorURLProtocol]; 89 | 90 | } else { 91 | // close web resource interceptor 92 | [self unregisterWebResourceInterceptorURLProtocol]; 93 | } 94 | } 95 | 96 | 97 | ////////////////////////////////////////////////////////////////////////////////// 98 | 99 | #pragma mark - 100 | #pragma mark public methods 101 | 102 | - (void)setupDefaultWebResourceInterceptorSettings { 103 | id settings = 104 | [GFWebResourceInterceptorSettings defaultInterceptorSettings]; 105 | 106 | // update with default interceptor settings 107 | [self updateInterceptorSettings:settings isDefaultSettings:YES]; 108 | } 109 | 110 | // NOTICE: Not thread safety 111 | - (BOOL)isWhitelistHost:(NSString *)host { 112 | if (host == nil || [host length] == 0) return NO; 113 | 114 | BOOL isValid = NO; 115 | 116 | // for performance issue, not synchronized thread to do whitelist checking. 117 | NSArray *whitelist = _settings.whitelist; 118 | if (whitelist != nil) { 119 | for (NSString *domain in whitelist) { 120 | // check the domain or sub domain has same suffix 121 | if (NSNotFound != [host rangeOfString:domain].location) { 122 | isValid = YES; 123 | 124 | break; 125 | } 126 | } 127 | } 128 | 129 | return isValid; 130 | } 131 | 132 | // NOTICE: Not thread safety 133 | - (BOOL)isBlacklistWithRequestPath:(NSString *)path { 134 | if (path == nil || [path length] == 0) return NO; 135 | 136 | BOOL exists = NO; 137 | 138 | // for performance issue, not synchronized thread to do blacklist checking. 139 | NSArray *blacklist = _settings.blacklist; 140 | if (blacklist != nil) { 141 | for (NSString *domain in blacklist) { 142 | // check the domain or sub domain has same suffix 143 | if (NSNotFound != [path rangeOfString:domain].location) { 144 | exists = YES; 145 | 146 | break; 147 | } 148 | } 149 | } 150 | 151 | return exists; 152 | } 153 | 154 | - (void)clearAgeCache { 155 | dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ 156 | NSString *cachePath = self.cache.cachePath; 157 | NSFileManager *fileManager = [NSFileManager defaultManager]; 158 | NSURL *diskCacheURL = [NSURL fileURLWithPath:cachePath isDirectory:YES]; 159 | NSArray *resourceKeys = @[NSURLIsDirectoryKey, NSURLContentModificationDateKey, NSURLTotalFileAllocatedSizeKey]; 160 | 161 | // This enumerator prefetches useful properties for our cache files. 162 | NSDirectoryEnumerator *fileEnumerator = [fileManager enumeratorAtURL:diskCacheURL 163 | includingPropertiesForKeys:resourceKeys 164 | options:NSDirectoryEnumerationSkipsHiddenFiles 165 | errorHandler:NULL]; 166 | 167 | NSDate *expirationDate = [NSDate dateWithTimeIntervalSinceNow:-self.settings.cacheMaxAge]; 168 | NSMutableDictionary *cacheFiles = [NSMutableDictionary dictionary]; 169 | NSUInteger currentCacheSize = 0; 170 | 171 | // Enumerate all of the files in the cache directory. This loop has two purposes: 172 | // 173 | // 1. Removing files that are older than the expiration date. 174 | // 2. Storing file attributes for the size-based cleanup pass. 175 | NSMutableArray *urlsToDelete = [[NSMutableArray alloc] init]; 176 | for (NSURL *fileURL in fileEnumerator) { 177 | NSDictionary *resourceValues = [fileURL resourceValuesForKeys:resourceKeys error:NULL]; 178 | 179 | // Skip directories. 180 | if ([resourceValues[NSURLIsDirectoryKey] boolValue]) { 181 | continue; 182 | } 183 | 184 | // Remove files that are older than the expiration date; 185 | NSDate *modificationDate = resourceValues[NSURLContentModificationDateKey]; 186 | if ([[modificationDate laterDate:expirationDate] isEqualToDate:expirationDate]) { 187 | [urlsToDelete addObject:fileURL]; 188 | continue; 189 | } 190 | 191 | // Store a reference to this file and account for its total size. 192 | NSNumber *totalAllocatedSize = resourceValues[NSURLTotalFileAllocatedSizeKey]; 193 | currentCacheSize += [totalAllocatedSize unsignedIntegerValue]; 194 | [cacheFiles setObject:resourceValues forKey:fileURL]; 195 | } 196 | 197 | for (NSURL *fileURL in urlsToDelete) { 198 | [fileManager removeItemAtURL:fileURL error:nil]; 199 | } 200 | }); 201 | } 202 | 203 | // NOTICE: Not thread safety 204 | - (BOOL)isSupportedPathExtension:(NSString *)extension { 205 | if (extension == nil || [extension length] == 0) return NO; 206 | 207 | BOOL isSupported = NO; 208 | if (_settings.extensions 209 | && [_settings.extensions rangeOfString:extension].location != NSNotFound) { 210 | 211 | isSupported = YES; 212 | } 213 | 214 | return isSupported; 215 | } 216 | 217 | - (void)updateInterceptorSettings:(id)settings { 218 | [self updateInterceptorSettings:settings isDefaultSettings:NO]; 219 | } 220 | 221 | - (void)updateInterceptorSettings:(id)settings 222 | isDefaultSettings:(BOOL)isDefaultSettings { 223 | 224 | // update flags 225 | if (!isDefaultSettings && !self.didUpdateInceptorSettings) { 226 | self.didUpdateInceptorSettings = YES; 227 | } 228 | 229 | @synchronized(self) { 230 | _settings = settings; // update interceptor settings 231 | } 232 | 233 | // notify did change interceptor settings 234 | [self notifyDidChangeInterceptorSettings]; 235 | 236 | // clear cache 237 | [self clearAgeCache]; 238 | } 239 | 240 | @end 241 | -------------------------------------------------------------------------------- /ios/OSCGit/Classes/H5/GFWebResourceInterceptorSettings+Internal.h: -------------------------------------------------------------------------------- 1 | #import "GFWebResourceInterceptorSettings.h" 2 | 3 | /** 4 | * Web资源拦截器规则配置类的内部方法。 5 | */ 6 | @interface GFWebResourceInterceptorSettings (Internal) 7 | 8 | // 创建默认拦截设置 9 | + (instancetype)defaultInterceptorSettings; 10 | 11 | @end 12 | -------------------------------------------------------------------------------- /ios/OSCGit/Classes/H5/GFWebResourceInterceptorSettings.h: -------------------------------------------------------------------------------- 1 | @import Foundation; 2 | 3 | 4 | /** 5 | * Web资源拦截器接口定义 6 | */ 7 | @protocol GFWebResourceInterceptorSettings 8 | @required 9 | 10 | /** 11 | * 配置版本,默认配置版本为`0`。 12 | * 当更新拦截器配置时候,检查之前配置版本与传入配置的版本是否一样, 13 | * 如果配置版本一样或者传入配置版本小于已生效的配置版本,则丢弃传入配置。 14 | * 15 | * @discussion 16 | * 如果配置已变更,采用数字自增方式管理配置版本。 17 | */ 18 | @property (nonatomic, assign) NSInteger version; 19 | 20 | /** 21 | * 控制是否开启Web资源拦截功能,`YES`为开启,`NO`为关闭。默认实现为开启。 22 | */ 23 | @property (nonatomic, assign) BOOL enabled; 24 | 25 | /** 26 | * Web资源拦截支持的后缀名。目前支持`js`和`css`。 27 | */ 28 | @property (nonatomic, strong) NSString *extensions; 29 | 30 | /** 31 | * Web资源拦截域白名单。 32 | * 检查当前请求关联的`Host`和`Referer`是否在白名单内, 33 | * 如果不在白名单内,则不会触发资源拦截。 34 | */ 35 | @property (nonatomic, strong) NSArray *whitelist; 36 | 37 | /** 38 | * Web资源拦截黑名单。 39 | * 检查当前请求关联的`Host`和`Referer`是否在黑名单内, 40 | * 如果在黑名单内,则不会触发资源拦截。 41 | */ 42 | @property (nonatomic, strong) NSArray *blacklist; 43 | 44 | /** 45 | * cache的自动清理时间,默认3天 24 * 60 * 60 * 3 46 | */ 47 | @property (nonatomic, assign) NSUInteger cacheMaxAge; 48 | 49 | @end 50 | 51 | /** 52 | * Web资源拦截配置默认实现类。 53 | */ 54 | @interface GFWebResourceInterceptorSettings : NSObject 55 | 56 | @end -------------------------------------------------------------------------------- /ios/OSCGit/Classes/H5/GFWebResourceInterceptorSettings.m: -------------------------------------------------------------------------------- 1 | #import "GFWebResourceInterceptorSettings+Internal.h" 2 | 3 | @implementation GFWebResourceInterceptorSettings 4 | 5 | @synthesize version = _version; 6 | @synthesize enabled = _enabled; 7 | @synthesize extensions = _extensions; 8 | @synthesize whitelist = _whitelist; 9 | @synthesize blacklist = _blacklist; 10 | @synthesize cacheMaxAge = _cacheMaxAge; 11 | 12 | + (instancetype)buildDefaultWebResourceInterceptorSettings { 13 | GFWebResourceInterceptorSettings *settings = [GFWebResourceInterceptorSettings new]; 14 | settings.version = 0; 15 | settings.enabled = YES; 16 | settings.extensions = @"js,css,md"; 17 | settings.whitelist = @[@"github.com"]; 18 | settings.blacklist = @[@"www.google-analytics.com"]; 19 | settings.cacheMaxAge = 24 * 60 * 60 * 3; 20 | 21 | return settings; 22 | } 23 | 24 | + (instancetype)defaultInterceptorSettings { 25 | return [[self class] buildDefaultWebResourceInterceptorSettings]; 26 | } 27 | 28 | @end 29 | -------------------------------------------------------------------------------- /ios/OSCGit/Classes/H5/GFWebResourceURLProtocol.h: -------------------------------------------------------------------------------- 1 | @import Foundation; 2 | 3 | @interface GFWebResourceURLProtocol : NSURLProtocol 4 | 5 | @end 6 | -------------------------------------------------------------------------------- /ios/OSCGit/Classes/UIView+TopBarMessage.h: -------------------------------------------------------------------------------- 1 | // 2 | // UIViewController+TopBarMessage.h 3 | // DXTopBarMessageView 4 | // 5 | // Created by xiekw on 14-3-17. 6 | // Copyright (c) 2014年 xiekw. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface TopWarningView : UIView 12 | 13 | @property (nonatomic, strong) NSString *warningText; 14 | @property (nonatomic, strong) UIImageView *iconIgv; 15 | @property (nonatomic, copy) dispatch_block_t tapHandler; 16 | @property (nonatomic, assign) CGFloat dimissDelay; 17 | @property (nonatomic, strong) UILabel *label; 18 | 19 | - (void)resetViews; 20 | 21 | @end 22 | 23 | @interface UIView (TopBarMessage) 24 | 25 | /** 26 | * Set the global default apperance of the top message, may be the appdelegate class is a good place to setup 27 | * 28 | * @param apperance the top bar view config, the whole version will be @{kDXTopBarBackgroundColor:[UIColor blueColor], kDXTopBarTextColor : [UIColor yellowColor], kDXTopBarIcon : [UIImage imageNamed:@"icon.png"], kDXTopBarTextFont : [UIFont boldSystemFontOfSize:15.0]} 29 | */ 30 | + (void)setTopMessageDefaultApperance:(NSDictionary *)apperance; 31 | 32 | /** 33 | * show the message with config on the top bar, note the config won't change the default top message apperance, this is the setTopMessageDefaultApperance: does. 34 | * 35 | * @param message the text to show 36 | * @param config the top bar view config, the whole version will be @{kDXTopBarBackgroundColor:[UIColor blueColor], kDXTopBarTextColor : [UIColor yellowColor], kDXTopBarIcon : [UIImage imageNamed:@"icon.png"], kDXTopBarTextFont : [UIFont boldSystemFontOfSize:15.0]} 37 | * @param delay time to dismiss 38 | * @param tapHandler the tap handler 39 | */ 40 | - (void)showTopMessage:(NSString *)message topBarConfig:(NSDictionary *)config dismissDelay:(CGFloat)delay withTapBlock:(dispatch_block_t)tapHandler; 41 | 42 | /** 43 | * Default style message something like Instagram does 44 | * 45 | * @param message message to tell 46 | */ 47 | - (void)showTopMessage:(NSString *)message; 48 | 49 | @end 50 | 51 | extern NSString * const kDXTopBarBackgroundColor; 52 | extern NSString * const kDXTopBarTextColor; 53 | extern NSString * const kDXTopBarTextFont; 54 | extern NSString * const kDXTopBarIcon; 55 | extern NSString * const kDXTopBarOffset; 56 | -------------------------------------------------------------------------------- /ios/OSCGit/Classes/UIView+TopBarMessage.m: -------------------------------------------------------------------------------- 1 | // 2 | // UIViewController+TopBarMessage.m 3 | // DXTopBarMessageView 4 | // 5 | // Created by xiekw on 14-3-17. 6 | // Copyright (c) 2014年 xiekw. All rights reserved. 7 | // 8 | 9 | #import "UIView+TopBarMessage.h" 10 | #import 11 | 12 | #define kTopBarHeight 38.0f 13 | #define kDefaultDimissDelay 3.0f 14 | 15 | 16 | NSString * const kDXTopBarBackgroundColor = @"dx.kDXTopBarBackgroundColor"; 17 | NSString * const kDXTopBarTextColor = @"dx.kDXTopBarTextColor"; 18 | NSString * const kDXTopBarTextFont = @"dx.kDXTopBarTextFont"; 19 | NSString * const kDXTopBarIcon = @"dx.kDXTopBarIcon"; 20 | NSString * const kDXTopBarOffset = @"dx.kDXTopBarOffset"; 21 | 22 | 23 | static NSMutableDictionary *__defaultTopMessageConfig = nil; 24 | 25 | @interface TopWarningView() 26 | 27 | @property (nonatomic, strong) NSTimer *dimissTimer; 28 | 29 | @end 30 | 31 | @implementation TopWarningView 32 | 33 | 34 | - (void)dealloc 35 | { 36 | [self.dimissTimer invalidate]; 37 | self.dimissTimer = nil; 38 | } 39 | 40 | - (instancetype)initWithFrame:(CGRect)frame 41 | { 42 | if (self = [super initWithFrame:frame]) { 43 | self.autoresizingMask = UIViewAutoresizingFlexibleWidth; 44 | self.label = [[UILabel alloc] initWithFrame:CGRectMake(0, 0, 50, 50)]; 45 | self.label.backgroundColor = [UIColor clearColor]; 46 | self.label.autoresizingMask = UIViewAutoresizingFlexibleWidth; 47 | self.label.adjustsFontSizeToFitWidth = YES; 48 | [self addSubview:self.label]; 49 | 50 | self.iconIgv = [[UIImageView alloc] init]; 51 | [self addSubview:self.iconIgv]; 52 | 53 | UISwipeGestureRecognizer *swipe = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:@selector(dismiss)]; 54 | swipe.direction = UISwipeGestureRecognizerDirectionUp; 55 | [self addGestureRecognizer:swipe]; 56 | 57 | UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(tapNow)]; 58 | [self addGestureRecognizer:tap]; 59 | 60 | [self resetViews]; 61 | } 62 | return self; 63 | } 64 | 65 | - (void)resetViews 66 | { 67 | if (!__defaultTopMessageConfig) { 68 | __defaultTopMessageConfig = [@{kDXTopBarBackgroundColor : [UIColor colorWithRed:0.64 green:0.65 blue:0.66 alpha:0.96], kDXTopBarTextColor : [UIColor whiteColor], kDXTopBarTextFont : [UIFont fontWithName:@"HelveticaNeue-Medium" size:17.0]} mutableCopy]; 69 | } 70 | 71 | self.iconIgv.image = __defaultTopMessageConfig[kDXTopBarIcon]; 72 | self.backgroundColor = __defaultTopMessageConfig[kDXTopBarBackgroundColor]; 73 | self.label.textColor = __defaultTopMessageConfig[kDXTopBarTextColor]; 74 | self.label.font = __defaultTopMessageConfig[kDXTopBarTextFont]; 75 | } 76 | 77 | - (void)layoutSubviews 78 | { 79 | CGSize textSize = [self.label.text boundingRectWithSize:CGSizeMake(CGRectGetWidth(self.bounds) * 0.9, CGFLOAT_MAX) options:NSStringDrawingUsesLineFragmentOrigin attributes:@{NSFontAttributeName:self.label.font} context:nil].size; 80 | CGFloat betweenIconAndText = 10.0f; 81 | CGFloat iconWidth = 20.0f; 82 | CGFloat labelDefaultHeight = 20.0; 83 | if (!self.iconIgv.image) { 84 | iconWidth = 0.0f; 85 | } 86 | self.iconIgv.frame = CGRectMake((CGRectGetWidth(self.bounds) - (textSize.width + iconWidth + betweenIconAndText)) * 0.5, (CGRectGetHeight(self.bounds) - iconWidth) * 0.5, iconWidth, iconWidth); 87 | self.label.frame = CGRectMake(CGRectGetMaxX(self.iconIgv.frame) + betweenIconAndText, (CGRectGetHeight(self.bounds) - labelDefaultHeight) * 0.5, textSize.width, labelDefaultHeight); 88 | } 89 | 90 | - (void)setWarningText:(NSString *)warningText 91 | { 92 | _warningText = warningText; 93 | self.label.text = _warningText; 94 | [self setNeedsLayout]; 95 | } 96 | 97 | - (void)tapNow 98 | { 99 | if (self.tapHandler) { 100 | self.tapHandler(); 101 | } 102 | } 103 | 104 | - (void)dismiss 105 | { 106 | CGRect selfFrame = self.frame; 107 | selfFrame.origin.y -= CGRectGetHeight(selfFrame); 108 | 109 | [UIView animateWithDuration:0.25f animations:^{ 110 | self.frame = selfFrame; 111 | self.alpha = 0.3; 112 | } completion:^(BOOL finished) { 113 | [self removeFromSuperview]; 114 | }]; 115 | } 116 | 117 | - (void)willMoveToSuperview:(UIView *)newSuperview 118 | { 119 | if (newSuperview) { 120 | self.alpha = 1.0; 121 | CGRect selfFrame = self.frame; 122 | CGFloat originY = self.frame.origin.y; 123 | selfFrame.origin.y -= CGRectGetHeight(selfFrame); 124 | self.frame = selfFrame; 125 | selfFrame.origin.y = originY; 126 | 127 | [UIView animateWithDuration:0.25f animations:^{ 128 | self.frame = selfFrame; 129 | } completion:^(BOOL finished) { 130 | [super willMoveToSuperview:newSuperview]; 131 | }]; 132 | 133 | [self.dimissTimer invalidate]; 134 | self.dimissTimer = nil; 135 | self.dimissTimer = [NSTimer scheduledTimerWithTimeInterval:MAX(self.dimissDelay, kDefaultDimissDelay) target:self selector:@selector(dismiss) userInfo:nil repeats:0]; 136 | }else { 137 | [self.dimissTimer invalidate]; 138 | self.dimissTimer = nil; 139 | [super willMoveToSuperview:newSuperview]; 140 | } 141 | } 142 | 143 | @end 144 | 145 | static char TopWarningKey; 146 | 147 | @implementation UIView (TopBarMessage) 148 | 149 | + (void)setTopMessageDefaultApperance:(NSDictionary *)apperance 150 | { 151 | if (!__defaultTopMessageConfig) { 152 | __defaultTopMessageConfig = [NSMutableDictionary dictionary]; 153 | } 154 | if (apperance) { 155 | UIColor *bgColor = apperance[kDXTopBarBackgroundColor]; 156 | if (bgColor && [bgColor isKindOfClass:[UIColor class]]) { 157 | __defaultTopMessageConfig[kDXTopBarBackgroundColor] = bgColor; 158 | } 159 | 160 | UIColor *textColor = apperance[kDXTopBarTextColor]; 161 | if (textColor && [textColor isKindOfClass:[UIColor class]]) { 162 | __defaultTopMessageConfig[kDXTopBarTextColor] = textColor; 163 | } 164 | 165 | UIImage *icon = apperance[kDXTopBarIcon]; 166 | if (icon && [icon isKindOfClass:[UIImage class]]) { 167 | __defaultTopMessageConfig[kDXTopBarIcon] = icon; 168 | } 169 | 170 | UIFont *font = apperance[kDXTopBarTextFont]; 171 | if (font && [font isKindOfClass:[UIFont class]]) { 172 | __defaultTopMessageConfig[kDXTopBarTextFont] = font; 173 | } 174 | } 175 | } 176 | 177 | - (void)showTopMessage:(NSString *)message 178 | { 179 | [self showTopMessage:message topBarConfig:nil dismissDelay:kDefaultDimissDelay withTapBlock:nil]; 180 | } 181 | 182 | - (void)showTopMessage:(NSString *)message topBarConfig:(NSDictionary *)config dismissDelay:(CGFloat)delay withTapBlock:(dispatch_block_t)tapHandler 183 | { 184 | TopWarningView *topV = objc_getAssociatedObject(self, &TopWarningKey); 185 | if (!topV) { 186 | topV = [[TopWarningView alloc] initWithFrame:CGRectMake(0, 0, CGRectGetWidth(self.bounds), kTopBarHeight)]; 187 | objc_setAssociatedObject(self, &TopWarningKey, topV, OBJC_ASSOCIATION_RETAIN_NONATOMIC); 188 | } 189 | 190 | CGFloat startY = [config[kDXTopBarOffset] floatValue]; 191 | 192 | if ([self isKindOfClass:[UIScrollView class]]) { 193 | UIScrollView *scv = (UIScrollView *)self; 194 | startY = scv.contentInset.top; 195 | } 196 | 197 | topV.frame = CGRectMake(0, startY, CGRectGetWidth(self.bounds), kTopBarHeight); 198 | topV.warningText = message; 199 | topV.tapHandler = tapHandler; 200 | topV.dimissDelay = delay; 201 | 202 | 203 | if (config) { 204 | UIColor *bgColor = config[kDXTopBarBackgroundColor]; 205 | if (bgColor && [bgColor isKindOfClass:[UIColor class]]) { 206 | topV.backgroundColor = bgColor; 207 | } 208 | 209 | UIColor *textColor = config[kDXTopBarTextColor]; 210 | if (textColor && [textColor isKindOfClass:[UIColor class]]) { 211 | topV.label.textColor = textColor; 212 | } 213 | 214 | UIImage *icon = config[kDXTopBarIcon]; 215 | if (icon && [icon isKindOfClass:[UIImage class]]) { 216 | topV.iconIgv.image = icon; 217 | } 218 | 219 | UIFont *font = config[kDXTopBarTextFont]; 220 | if (font && [font isKindOfClass:[UIFont class]]) { 221 | topV.label.font = font; 222 | } 223 | 224 | }else { 225 | [topV resetViews]; 226 | } 227 | 228 | [self addSubview:topV]; 229 | } 230 | 231 | 232 | @end 233 | -------------------------------------------------------------------------------- /ios/OSCGit/Classes/Utils.h: -------------------------------------------------------------------------------- 1 | // 2 | // DXRNUtils.h 3 | // RN_CNNode 4 | // 5 | // Created by xiekw on 15/11/18. 6 | // Copyright © 2015年 Facebook. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "RCTBridgeModule.h" 11 | 12 | @interface Utils : NSObject 13 | 14 | @end 15 | -------------------------------------------------------------------------------- /ios/OSCGit/Classes/Utils.m: -------------------------------------------------------------------------------- 1 | // 2 | // DXRNUtils.m 3 | // RN_CNNode 4 | // 5 | // Created by xiekw on 15/11/18. 6 | // Copyright © 2015年 Facebook. All rights reserved. 7 | // 8 | 9 | #import "Utils.h" 10 | 11 | static NSString * const kAppId = @"1079873993"; 12 | 13 | @implementation Utils 14 | 15 | @synthesize bridge = _bridge; 16 | 17 | RCT_EXPORT_MODULE() 18 | 19 | RCT_EXPORT_METHOD(clearCookies:(RCTResponseSenderBlock)callback) { 20 | NSHTTPCookieStorage *cookieStore = [NSHTTPCookieStorage sharedHTTPCookieStorage]; 21 | for (NSHTTPCookie *cookie in [cookieStore cookies]) { 22 | [cookieStore deleteCookie:cookie]; 23 | } 24 | 25 | callback(@[[NSNull null]]); 26 | } 27 | 28 | RCT_EXPORT_METHOD(trackClick:(nonnull NSString *)eventName attributes:(NSDictionary *)atr) { 29 | } 30 | 31 | RCT_EXPORT_METHOD(appInfo:(RCTResponseSenderBlock)callback) { 32 | NSString *appVersion = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleShortVersionString"]; 33 | NSString *appBuild = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleVersion"]; 34 | NSString *appStoreURL = [[NSString alloc] initWithFormat:@"http://phobos.apple.com/WebObjects/MZStore.woa/wa/viewSoftware?id=%@&mt=8", kAppId]; 35 | NSString *rateURL = [[NSString alloc] initWithFormat:@"itms-apps://ax.itunes.apple.com/WebObjects/MZStore.woa/wa/viewContentsUserReviews?type=Purple+Software&id=%@", kAppId]; 36 | 37 | callback(@[ 38 | @{@"appVersion": appVersion, 39 | @"appBuild": appBuild, 40 | @"appStoreURL": appStoreURL, 41 | @"rateURL": rateURL 42 | } 43 | ]); 44 | } 45 | 46 | @end 47 | -------------------------------------------------------------------------------- /ios/OSCGit/Images.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "29x29", 6 | "scale" : "2x", 7 | "filename" : "Icon-Small@2x.png" 8 | }, 9 | { 10 | "idiom" : "iphone", 11 | "size" : "29x29", 12 | "scale" : "3x", 13 | "filename" : "Icon-Small@3x.png" 14 | }, 15 | { 16 | "idiom" : "iphone", 17 | "size" : "40x40", 18 | "scale" : "2x", 19 | "filename" : "Icon-40@2x.png" 20 | }, 21 | { 22 | "idiom" : "iphone", 23 | "size" : "40x40", 24 | "scale" : "3x", 25 | "filename" : "Icon-40@3x.png" 26 | }, 27 | { 28 | "idiom" : "iphone", 29 | "size" : "60x60", 30 | "scale" : "2x", 31 | "filename" : "Icon-60@2x.png" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "size" : "60x60", 36 | "scale" : "3x", 37 | "filename" : "Icon-60@3x.png" 38 | }, 39 | { 40 | "idiom" : "ipad", 41 | "size" : "29x29", 42 | "scale" : "1x", 43 | "filename" : "Icon-Small.png" 44 | }, 45 | { 46 | "idiom" : "ipad", 47 | "size" : "29x29", 48 | "scale" : "2x", 49 | "filename" : "Icon-Small@2x.png" 50 | }, 51 | { 52 | "idiom" : "ipad", 53 | "size" : "40x40", 54 | "scale" : "1x", 55 | "filename" : "Icon-40.png" 56 | }, 57 | { 58 | "idiom" : "ipad", 59 | "size" : "40x40", 60 | "scale" : "2x", 61 | "filename" : "Icon-40@2x.png" 62 | }, 63 | { 64 | "idiom" : "ipad", 65 | "size" : "76x76", 66 | "scale" : "1x", 67 | "filename" : "Icon-76.png" 68 | }, 69 | { 70 | "idiom" : "ipad", 71 | "size" : "76x76", 72 | "scale" : "2x", 73 | "filename" : "Icon-76@2x.png" 74 | }, 75 | { 76 | "idiom" : "ipad", 77 | "size" : "83.5x83.5", 78 | "scale" : "2x", 79 | "filename" : "Icon-83.5@2x.png" 80 | } 81 | ], 82 | "info" : { 83 | "version" : 1, 84 | "author" : "makeappicon" 85 | } 86 | } -------------------------------------------------------------------------------- /ios/OSCGit/Images.xcassets/AppIcon.appiconset/Icon-40.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rplees/react-native-gitosc/HEAD/ios/OSCGit/Images.xcassets/AppIcon.appiconset/Icon-40.png -------------------------------------------------------------------------------- /ios/OSCGit/Images.xcassets/AppIcon.appiconset/Icon-40@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rplees/react-native-gitosc/HEAD/ios/OSCGit/Images.xcassets/AppIcon.appiconset/Icon-40@2x.png -------------------------------------------------------------------------------- /ios/OSCGit/Images.xcassets/AppIcon.appiconset/Icon-40@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rplees/react-native-gitosc/HEAD/ios/OSCGit/Images.xcassets/AppIcon.appiconset/Icon-40@3x.png -------------------------------------------------------------------------------- /ios/OSCGit/Images.xcassets/AppIcon.appiconset/Icon-60@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rplees/react-native-gitosc/HEAD/ios/OSCGit/Images.xcassets/AppIcon.appiconset/Icon-60@2x.png -------------------------------------------------------------------------------- /ios/OSCGit/Images.xcassets/AppIcon.appiconset/Icon-60@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rplees/react-native-gitosc/HEAD/ios/OSCGit/Images.xcassets/AppIcon.appiconset/Icon-60@3x.png -------------------------------------------------------------------------------- /ios/OSCGit/Images.xcassets/AppIcon.appiconset/Icon-76.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rplees/react-native-gitosc/HEAD/ios/OSCGit/Images.xcassets/AppIcon.appiconset/Icon-76.png -------------------------------------------------------------------------------- /ios/OSCGit/Images.xcassets/AppIcon.appiconset/Icon-76@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rplees/react-native-gitosc/HEAD/ios/OSCGit/Images.xcassets/AppIcon.appiconset/Icon-76@2x.png -------------------------------------------------------------------------------- /ios/OSCGit/Images.xcassets/AppIcon.appiconset/Icon-83.5@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rplees/react-native-gitosc/HEAD/ios/OSCGit/Images.xcassets/AppIcon.appiconset/Icon-83.5@2x.png -------------------------------------------------------------------------------- /ios/OSCGit/Images.xcassets/AppIcon.appiconset/Icon-Small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rplees/react-native-gitosc/HEAD/ios/OSCGit/Images.xcassets/AppIcon.appiconset/Icon-Small.png -------------------------------------------------------------------------------- /ios/OSCGit/Images.xcassets/AppIcon.appiconset/Icon-Small@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rplees/react-native-gitosc/HEAD/ios/OSCGit/Images.xcassets/AppIcon.appiconset/Icon-Small@2x.png -------------------------------------------------------------------------------- /ios/OSCGit/Images.xcassets/AppIcon.appiconset/Icon-Small@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rplees/react-native-gitosc/HEAD/ios/OSCGit/Images.xcassets/AppIcon.appiconset/Icon-Small@3x.png -------------------------------------------------------------------------------- /ios/OSCGit/Images.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /ios/OSCGit/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.0.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | LSRequiresIPhoneOS 24 | 25 | NSAppTransportSecurity 26 | 27 | NSAllowsArbitraryLoads 28 | 29 | 30 | NSLocationWhenInUseUsageDescription 31 | 32 | UILaunchStoryboardName 33 | LaunchScreen 34 | UIRequiredDeviceCapabilities 35 | 36 | armv7 37 | 38 | UISupportedInterfaceOrientations 39 | 40 | UIInterfaceOrientationPortrait 41 | UIInterfaceOrientationLandscapeLeft 42 | UIInterfaceOrientationLandscapeRight 43 | 44 | UIAppFonts 45 | 46 | FontAwesome.ttf 47 | MaterialIcons.ttf 48 | Ionicons.ttf 49 | Foundation.ttf 50 | 51 | UIViewControllerBasedStatusBarAppearance 52 | 53 | 54 | 55 | -------------------------------------------------------------------------------- /ios/OSCGit/main.m: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2015-present, Facebook, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | */ 9 | 10 | #import 11 | 12 | #import "AppDelegate.h" 13 | 14 | int main(int argc, char * argv[]) { 15 | @autoreleasepool { 16 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /ios/OSCGitTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | org.reactjs.native.example.$(PRODUCT_NAME:rfc1034identifier) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | 24 | 25 | -------------------------------------------------------------------------------- /ios/OSCGitTests/OSCGitTests.m: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2015-present, Facebook, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | */ 9 | 10 | #import 11 | #import 12 | 13 | #import "RCTLog.h" 14 | #import "RCTRootView.h" 15 | 16 | #define TIMEOUT_SECONDS 240 17 | #define TEXT_TO_LOOK_FOR @"Welcome to React Native!" 18 | 19 | @interface OSCGitTests : XCTestCase 20 | 21 | @end 22 | 23 | @implementation OSCGitTests 24 | 25 | - (BOOL)findSubviewInView:(UIView *)view matching:(BOOL(^)(UIView *view))test 26 | { 27 | if (test(view)) { 28 | return YES; 29 | } 30 | for (UIView *subview in [view subviews]) { 31 | if ([self findSubviewInView:subview matching:test]) { 32 | return YES; 33 | } 34 | } 35 | return NO; 36 | } 37 | 38 | - (void)testRendersWelcomeScreen 39 | { 40 | UIViewController *vc = [[[[UIApplication sharedApplication] delegate] window] rootViewController]; 41 | NSDate *date = [NSDate dateWithTimeIntervalSinceNow:TIMEOUT_SECONDS]; 42 | BOOL foundElement = NO; 43 | 44 | __block NSString *redboxError = nil; 45 | RCTSetLogFunction(^(RCTLogLevel level, RCTLogSource source, NSString *fileName, NSNumber *lineNumber, NSString *message) { 46 | if (level >= RCTLogLevelError) { 47 | redboxError = message; 48 | } 49 | }); 50 | 51 | while ([date timeIntervalSinceNow] > 0 && !foundElement && !redboxError) { 52 | [[NSRunLoop mainRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.1]]; 53 | [[NSRunLoop mainRunLoop] runMode:NSRunLoopCommonModes beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.1]]; 54 | 55 | foundElement = [self findSubviewInView:vc.view matching:^BOOL(UIView *view) { 56 | if ([view.accessibilityLabel isEqualToString:TEXT_TO_LOOK_FOR]) { 57 | return YES; 58 | } 59 | return NO; 60 | }]; 61 | } 62 | 63 | RCTSetLogFunction(RCTDefaultLogFunction); 64 | 65 | XCTAssertNil(redboxError, @"RedBox error: %@", redboxError); 66 | XCTAssertTrue(foundElement, @"Couldn't find element with text '%@' in %d seconds", TEXT_TO_LOOK_FOR, TIMEOUT_SECONDS); 67 | } 68 | 69 | 70 | @end 71 | -------------------------------------------------------------------------------- /ios/Podfile: -------------------------------------------------------------------------------- 1 | source 'https://github.com/CocoaPods/Specs.git' 2 | 3 | pod 'DXPopover' 4 | -------------------------------------------------------------------------------- /ios/Podfile.lock: -------------------------------------------------------------------------------- 1 | PODS: 2 | - DXPopover (0.1.1) 3 | 4 | DEPENDENCIES: 5 | - DXPopover 6 | 7 | SPEC CHECKSUMS: 8 | DXPopover: 13e6bbd1a0da4e62b2190de51f43a94850769e0f 9 | 10 | COCOAPODS: 0.39.0 11 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "OSCGit", 3 | "version": "0.0.1", 4 | "private": true, 5 | "scripts": { 6 | "start": "node node_modules/react-native/local-cli/cli.js start" 7 | }, 8 | "dependencies": { 9 | "@remobile/react-native-toast": "^1.0.4", 10 | "base-64": "^0.1.0", 11 | "events": "~1.1.0", 12 | "lodash": "^4.6.1", 13 | "react-native": "^0.21.0", 14 | "react-native-code-push": "~1.7.3-beta", 15 | "react-native-gifted-listview": "0.0.12", 16 | "react-native-scrollable-tab-view": "~0.3.4", 17 | "react-native-shake-event-ios": "^1.0.3", 18 | "react-native-vector-icons": "^1.1.1" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /screen/famous.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rplees/react-native-gitosc/HEAD/screen/famous.jpg -------------------------------------------------------------------------------- /screen/famous_choose.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rplees/react-native-gitosc/HEAD/screen/famous_choose.jpg -------------------------------------------------------------------------------- /screen/feedback.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rplees/react-native-gitosc/HEAD/screen/feedback.jpg -------------------------------------------------------------------------------- /screen/login.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rplees/react-native-gitosc/HEAD/screen/login.jpg -------------------------------------------------------------------------------- /screen/my_profile.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rplees/react-native-gitosc/HEAD/screen/my_profile.jpg -------------------------------------------------------------------------------- /screen/personal.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rplees/react-native-gitosc/HEAD/screen/personal.jpg -------------------------------------------------------------------------------- /screen/project.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rplees/react-native-gitosc/HEAD/screen/project.jpg -------------------------------------------------------------------------------- /screen/project_commits.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rplees/react-native-gitosc/HEAD/screen/project_commits.jpg -------------------------------------------------------------------------------- /screen/project_detail.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rplees/react-native-gitosc/HEAD/screen/project_detail.jpg -------------------------------------------------------------------------------- /screen/project_detail_code.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rplees/react-native-gitosc/HEAD/screen/project_detail_code.jpg -------------------------------------------------------------------------------- /screen/project_detail_readme.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rplees/react-native-gitosc/HEAD/screen/project_detail_readme.jpg -------------------------------------------------------------------------------- /screen/project_share.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rplees/react-native-gitosc/HEAD/screen/project_share.jpg -------------------------------------------------------------------------------- /screen/search.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rplees/react-native-gitosc/HEAD/screen/search.jpg -------------------------------------------------------------------------------- /screen/settings.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rplees/react-native-gitosc/HEAD/screen/settings.jpg -------------------------------------------------------------------------------- /screen/shake.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rplees/react-native-gitosc/HEAD/screen/shake.jpg -------------------------------------------------------------------------------- /service/OSCService.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by rplees on 3/10/16. 3 | */ 4 | const config = require("../config"); 5 | const {EventEmitter} = require("events"); 6 | const React = require('react-native'); 7 | const DXRNUtils = require('../utils/DXRNUtils'); 8 | const base64 = require('base-64'); 9 | const Utils = require('../utils/Utils'); 10 | const L = require('../utils/Log'); 11 | const User = require('../entity/User'); 12 | //const crypto = require('crypto'); 13 | const { 14 | AsyncStorage, 15 | Navigator, 16 | Alert, 17 | } = React; 18 | 19 | const HTTP = "http://"; 20 | const HOST = "git.oschina.net/"; 21 | const API_VERSION = "api/v3/";// API版本 22 | const BASE_URL = HTTP + HOST + API_VERSION; 23 | const NO_API_BASE_URL = HTTP + HOST; 24 | const PROJECTS = BASE_URL + "projects/"; 25 | const USER = BASE_URL + "user/"; 26 | const EVENT = BASE_URL + "events/"; 27 | 28 | var GLOBAL_USER = Object.create(User); 29 | class OSCService extends EventEmitter { 30 | 31 | constructor() { 32 | super(); 33 | } 34 | 35 | isSelf(id) { 36 | return GLOBAL_USER.id === id; 37 | } 38 | 39 | searchProjects(query, page) { 40 | return this.fetchPromise(PROJECTS + "search/" + encodeURI(query) + "?page=" + page); 41 | } 42 | 43 | /** 44 | * 创建一个issue 45 | */ 46 | pubCreateIssue(projectId, title, description, assignee_id, milestone_id) { 47 | return this.fetchPromise(PROJECTS + projectId + "/issues", "POST", {description: description, title: title, assignee_id:assignee_id,milestone_id:milestone_id}); 48 | } 49 | 50 | getProjectCodeTree(pId, path, refName) { 51 | var param = {}; 52 | if(path && refName) { 53 | param = {path: path, ref_name: refName}; 54 | } 55 | return this.fetchPromise(PROJECTS + pId + "/repository/tree", "GET", param); 56 | } 57 | 58 | /** 59 | * 获取语言列表 60 | * { 61 | * "created_at": "2013-08-01T22:39:56+08:00", 62 | "detail": null, 63 | "id": 5, 64 | "ident": "Java", 65 | "name": "Java", 66 | "order": 4, 67 | "parent_id": 1, 68 | "projects_count": 43479, 69 | "updated_at": "2013-08-01T22:39:56+08:00" 70 | * } 71 | */ 72 | getLanguageList() { 73 | return this.fetchPromise(PROJECTS + "languages"); 74 | } 75 | /** 76 | * 根据语言的ID获得项目的列表 77 | */ 78 | getLanguageProjectList(languageId, page) { 79 | let url = PROJECTS + "languages/" + languageId + "?page=" + page; 80 | return this.fetchPromise(url); 81 | } 82 | 83 | getRandomProject() { 84 | return this.fetchPromise(PROJECTS + "random", "GET", {luck: 1}) 85 | } 86 | getPersonalProjects(uId, page) { 87 | return this.fetchPromise(USER + uId + "/" + "projects?page=" + page); 88 | } 89 | getPersonalEvents(uId, page) { 90 | return this.fetchPromise(EVENT + "user" + "/" + uId + "?page=" + page); 91 | } 92 | 93 | getMyEvents(page) { 94 | return this.fetchPromise(EVENT + "?page=" + page); 95 | } 96 | 97 | getPersonalStarProjects(uId, page) { 98 | return this.fetchPromise(USER + uId + "/stared_projects?page=" + page); 99 | } 100 | getPersonalWatchProjects(uId, page) { 101 | return this.fetchPromise(USER + uId + "/watched_projects?page=" + page); 102 | } 103 | 104 | starProject(projectId){ 105 | return this.fetchPromise(PROJECTS + projectId + "/star", "POST"); 106 | } 107 | unStarProject(projectId){ 108 | return this.fetchPromise(PROJECTS + projectId + "/unstar", "POST"); 109 | } 110 | watchProject(projectId){ 111 | return this.fetchPromise(PROJECTS + projectId + "/watch", "POST"); 112 | } 113 | unWatchProject(projectId){ 114 | return this.fetchPromise(PROJECTS + projectId + "/unwatch", "POST"); 115 | } 116 | 117 | getExploreLatestProject(page = 1) { 118 | return this.fetchPromise(PROJECTS + "latest?page=" + page); 119 | } 120 | getExploreFeaturedProject(page = 1) { 121 | return this.fetchPromise(PROJECTS + "featured?page=" + page); 122 | } 123 | getExplorePopularProject(page = 1) { 124 | return this.fetchPromise(PROJECTS + "popular?page=" + page); 125 | } 126 | getProject(id) { 127 | return this.fetchPromise(PROJECTS + id); 128 | } 129 | 130 | /** 131 | * 实际是往rplees/react-native-gitosc创建一个issue 132 | * @param title 133 | * @param message 134 | * @returns {*} 135 | */ 136 | feedback(title, message) { 137 | return this.pubCreateIssue(834878,title, message, 95171, ""); 138 | } 139 | 140 | login(name, pwd) { 141 | let param = {email: name, password: pwd}; 142 | return this.fetchPromise(BASE_URL + "session", "POST", param) 143 | .then(json => { 144 | Object.assign(GLOBAL_USER, json); 145 | this.__saveUser2Disk(); 146 | return GLOBAL_USER; 147 | }); 148 | } 149 | 150 | packagePathWithToken(path) { 151 | if(this.isLogined()) { 152 | let split = path.indexOf("?") > -1 ? "&": "?"; 153 | path += split + "private_token=" + GLOBAL_USER.private_token; 154 | } 155 | 156 | return path; 157 | } 158 | 159 | fetchPromise(path, method="GET", param) { 160 | if(param) { 161 | path += (path.indexOf("?") > -1 ? "&" : "?"); 162 | path += Utils.JsonUtils.encode(param); 163 | } 164 | let url = this.packagePathWithToken(path); 165 | L.debug("准备请求地址:{}", url); 166 | return fetch(url, { 167 | method: method, 168 | timeout:5000,//FIXME:这个参数没生效 169 | headers: { 170 | 'User-Agent': config.userAgent, 171 | 'Accept': 'application/json; charset=utf-8' 172 | }, 173 | }).then(response => { 174 | const isValid = response.status < 400; 175 | L.debug("请求地址:{}, 返回值:{},是否成功:{}-{}", path, response._bodyInit, response.status,isValid); 176 | const json = JSON.parse(response._bodyInit); 177 | if (isValid) { 178 | return json; 179 | } else { 180 | //TODO:此处应该需要优化,Service接口类持有了UI类Alert 181 | if(json.message.indexOf("Unauthorized") > -1 && url.indexOf("api/v3/session") == -1) {//登陆时就不用alert 182 | Alert.alert( 183 | "Oops", 184 | '鉴权失败,请先重新登陆' 185 | ); 186 | } 187 | throw new Error(json.message, response.status); 188 | } 189 | }); 190 | } 191 | 192 | getUserFromCache() { 193 | //AsyncStorage.removeItem("_osc_user_"); 194 | return AsyncStorage.getItem("_osc_user_") 195 | .then((result) => { 196 | if (result) { 197 | L.info("getUserFromCache>OSC user:{}", result); 198 | Object.assign(GLOBAL_USER, JSON.parse(result)); 199 | } 200 | return GLOBAL_USER; 201 | }).catch(err => { 202 | L.info('getUserFromCache err is: ' + err); 203 | }); 204 | } 205 | 206 | logout(cb) { 207 | GLOBAL_USER = Object.create(User); 208 | AsyncStorage.removeItem("_osc_user_"); 209 | cb && cb(); 210 | this.emit('didLogout'); 211 | } 212 | 213 | isLogined() { 214 | return GLOBAL_USER 215 | && GLOBAL_USER.private_token 216 | && GLOBAL_USER.private_token.length > 0; 217 | } 218 | 219 | __saveUser2Disk() { 220 | L.info("__saveUser2Disk:{}", GLOBAL_USER) 221 | AsyncStorage.setItem("_osc_user_", JSON.stringify(GLOBAL_USER)); 222 | } 223 | 224 | checkNeedLoginWithPromise(promiseFunc, navigator) { 225 | if (!this.isLogined()) { 226 | navigator.push({ 227 | id: 'login', 228 | sceneConfig: Navigator.SceneConfigs.FloatFromBottom, 229 | title: '该操作需要登陆', 230 | nextPromiseFunc: promiseFunc, 231 | }); 232 | } else { 233 | return promiseFunc(); 234 | } 235 | } 236 | } 237 | 238 | const _OSCService = new OSCService(); 239 | module.exports = _OSCService; 240 | module.exports.GLOBAL_USER = GLOBAL_USER; -------------------------------------------------------------------------------- /utils/DXRNUtils.js: -------------------------------------------------------------------------------- 1 | const React = require('react-native'); 2 | const Platform = require('Platform'); 3 | const _ = require('lodash'); 4 | 5 | var { 6 | NativeModules: { 7 | Utils, 8 | } 9 | } = React; 10 | 11 | var Uitls = { 12 | clearCookie(cb) { 13 | if (Platform.OS === 'android') { 14 | // TODO: 15 | 16 | } else if (Platform.OS === 'ios') { 17 | console.log('clear cookies'); 18 | Utils.clearCookies((error, results) => { 19 | if (error) { 20 | console.log('clearCookie error occured' + error); 21 | } 22 | }); 23 | } 24 | }, 25 | 26 | trackClick(name, atr) { 27 | if (Platform.OS === 'android') { 28 | 29 | } else if (Platform.OS === 'ios') { 30 | console.log(name + ":" + atr); 31 | Utils.trackClick(name, atr); //TODO 32 | } 33 | }, 34 | 35 | appInfo(cb) { 36 | if (Platform.OS === 'android') { 37 | // TODO: 38 | } else if (Platform.OS === 'ios') { 39 | Utils.appInfo((info) => { 40 | cb && cb(info); 41 | }); 42 | } 43 | } 44 | }; 45 | 46 | module.exports = Uitls; 47 | -------------------------------------------------------------------------------- /utils/GFDiskCache.js: -------------------------------------------------------------------------------- 1 | const React = require('react-native'); 2 | const Platform = require('Platform'); 3 | 4 | const { 5 | NativeModules: { 6 | GFDiskCacheManager, 7 | } 8 | } = React; 9 | 10 | const GFDiskCache = { 11 | getDiskCacheCost(cb) { 12 | if (Platform.OS === 'android') { 13 | // TODO: 14 | } else if (Platform.OS === 'ios') { 15 | GFDiskCacheManager.diskCacheCost((size) => { 16 | console.log('get diskCacheCost', size); 17 | cb && cb(this.bytesToSize(size)); 18 | }); 19 | } 20 | }, 21 | 22 | clearDiskCache(cb) { 23 | if (Platform.OS === 'android') { 24 | // TODO: 25 | } else if (Platform.OS === 'ios') { 26 | GFDiskCacheManager.clearDiskCache((size) => { 27 | cb && cb(this.bytesToSize(size)); 28 | }); 29 | } 30 | }, 31 | 32 | bytesToSize(bytes) { 33 | var sizes = ['B', 'KB', 'MB', 'GB', 'TB']; 34 | if (bytes == 0) return '0'; 35 | var i = parseInt(Math.floor(Math.log(bytes) / Math.log(1024))); 36 | return Math.round(bytes / Math.pow(1024, i), 2) + ' ' + sizes[i]; 37 | } 38 | }; 39 | 40 | module.exports = GFDiskCache; 41 | -------------------------------------------------------------------------------- /utils/Log.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by rplees on 13-12-26. 3 | */ 4 | const Log = { 5 | _l(array) { 6 | if(!m) return; 7 | let m = array[0]; 8 | for (var i = 1; i < array.length; i++) { 9 | if (m.indexOf("%s") > -1) { 10 | m = m.replace("%s", array[i]); 11 | } else { 12 | m = m.replace("{}", array[i]); 13 | } 14 | } 15 | 16 | console.log(m); 17 | }, 18 | 19 | log(m){ 20 | this._l(Array.prototype.slice.apply(arguments)); 21 | }, 22 | 23 | info(m){ 24 | this._l(Array.prototype.slice.apply(arguments)); 25 | }, 26 | 27 | debug(m){ 28 | this._l(Array.prototype.slice.apply(arguments)); 29 | }, 30 | 31 | warn(m){ 32 | var arr = Array.prototype.slice.apply(arguments); 33 | if(arr) 34 | arr[0] = '[warn]' + arr[0]; 35 | 36 | this._l(arr); 37 | }, 38 | 39 | error(m){ 40 | var arr = Array.prototype.slice.apply(arguments); 41 | if(arr) 42 | arr[0] = '[error]' + arr[0]; 43 | this._l(arr); 44 | } 45 | } 46 | 47 | module.exports = Log; -------------------------------------------------------------------------------- /utils/Utils.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by rplees on 3/8/16. 3 | */ 4 | String.prototype.replaceAll = function(reallyDo, replaceWith, ignoreCase) { 5 | if (!RegExp.prototype.isPrototypeOf(reallyDo)) { 6 | return this.replace(new RegExp(reallyDo, (ignoreCase ? "gi": "g")), replaceWith); 7 | } else { 8 | return this.replace(reallyDo, replaceWith); 9 | } 10 | } 11 | 12 | /** 13 | * Created by Administrator on 13-12-20. 14 | */ 15 | const NullUtils = { 16 | NULL: "NULL", 17 | isNotNull: function(o) { 18 | return o != null && o != undefined; 19 | }, 20 | isNull: function(o) { 21 | return !this.isNotNull(o); 22 | } 23 | } 24 | 25 | const JsonUtils = { 26 | encode(json){ 27 | if (NullUtils.isNull(json)) { 28 | return ''; 29 | } 30 | 31 | var tmps = []; 32 | for (var key in json) { 33 | tmps.push(key + '=' + json[key]); 34 | } 35 | 36 | return tmps.join('&'); 37 | } 38 | } 39 | 40 | const StringUtils = { 41 | isBlank: function(s) { 42 | if(!s) { 43 | return true; 44 | } 45 | 46 | if(s == "" || (s.match && s.match(/^\s+$/g))) { 47 | return true; 48 | } 49 | 50 | return false; 51 | }, 52 | 53 | isNotBlank: function(s){ 54 | return !this.isBlank(s); 55 | }, 56 | 57 | equals: function(s1, s2) { 58 | return s1 == s2; 59 | }, 60 | 61 | equalsIgnoreCase: function(s1, s2){ 62 | if(s1 == s2) { 63 | return true; 64 | } 65 | 66 | if(NullUtils.isNull(s1) || NullUtils.isNull(s2)) { 67 | return false; 68 | } 69 | 70 | return s1.toLocaleUpperCase() == s2.toLocaleUpperCase(); 71 | }, 72 | 73 | subString: function(s, start, len) { 74 | if(this.isBlank(s)) { 75 | return s; 76 | } 77 | 78 | return s.substr(start, len); 79 | }, 80 | } 81 | 82 | CollectionUtils = { 83 | isEmpty: function(o) { 84 | if(! o) return true; 85 | 86 | var list = null; 87 | if(o instanceof Array) { 88 | list = o; 89 | } else if(o.list) { 90 | list = o.list; 91 | } else if(o.items) { 92 | list = o.items; 93 | } 94 | //后续继续 95 | 96 | if(!list || list.length < 1) { 97 | return true; 98 | } 99 | 100 | return false; 101 | }, 102 | 103 | isNotEmpty: function(o) { 104 | return !this.isEmpty(o); 105 | }, 106 | removeIndex: function(array,index) { // array.splice(index, 1) 107 | var pop = array.splice(index, 1); 108 | return pop; 109 | } 110 | } 111 | 112 | NumberUtils = { 113 | /** 114 | * 向上取整 115 | */ 116 | ceil: function(s){ 117 | var v = Math.ceil(s); 118 | if(isNaN(v)) { 119 | return -1; 120 | } 121 | 122 | return v; 123 | }, 124 | 125 | toInt: function(s) { 126 | return parseInt(s); 127 | }, 128 | 129 | isNumber: function(o) { 130 | if(o instanceof Number) { 131 | return true; 132 | } 133 | return this.toInt(o) == o 134 | }, 135 | 136 | toLocaleString: function(num) { 137 | if(num < 1000) { 138 | return num + ""; 139 | } 140 | 141 | var retV = ""; 142 | var wasContinue = true; 143 | var tempV = 0; 144 | while(wasContinue == true) { 145 | tempV = num%1000; 146 | num = this.toInt(num/1000); 147 | wasContinue = num >= 1; 148 | 149 | if(wasContinue) { 150 | if(tempV < 10) { 151 | tempV = "00" + tempV; 152 | }else if(tempV < 100) { //少于三位数 补0 153 | tempV = "0" + tempV; 154 | } 155 | } 156 | retV = tempV + retV; 157 | 158 | if(wasContinue) { 159 | retV = "," + retV; 160 | } 161 | } 162 | 163 | return retV; 164 | }, 165 | 166 | getNumberFromLocaleString: function(stringValue) { 167 | return this.toInt(stringValue.replaceAll(",", "")); 168 | } 169 | } 170 | 171 | BooleanUtils = { 172 | parseBooleanWithDefault: function(o, defaultValue) { 173 | if(o == null || o == undefined) { 174 | return defaultValue; 175 | } 176 | 177 | return this.parseBoolean(o); 178 | }, 179 | parseBoolean: function(o) { 180 | if(o == null || o == undefined) { 181 | return false; 182 | } 183 | 184 | if(o instanceof Number) { 185 | return o != 0; 186 | } 187 | 188 | if(o == "true" || o == "TRUE" || o == true) 189 | return true; 190 | 191 | return false; 192 | } 193 | } 194 | 195 | // 表单验证 196 | const TextUtils = { 197 | 198 | isPhone: function(phone) { 199 | var myreg = /^\d{11}$/; 200 | if(!myreg.test(phone)) { 201 | return false; 202 | } 203 | 204 | return true; 205 | }, 206 | /* 207 | * 是否邮件地址 208 | * */ 209 | 210 | isEmail: function(email) { 211 | var res = email.match(/\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*/); 212 | if (res !== null) { 213 | return res[0]; 214 | } 215 | return false; 216 | }, 217 | /** 218 | * 是否仅数字和字母 219 | * @param str 220 | * @param min 最小长度 默认0 221 | * @param max 最大长度 默认最大10000 222 | * @returns {boolean} 223 | */ 224 | isNumberCharacter: function (str, min, max) { 225 | min = min || 1; 226 | max = max || 10000; 227 | var len = str.length; 228 | if (min > len || len > max) { 229 | return false; 230 | } 231 | var res = new RegExp(/^[a-z0-9]+$/i); 232 | return res.test(str); 233 | }, 234 | 235 | /** 236 | * 是否URL格式 237 | * @param str 238 | * @returns {*} 239 | */ 240 | isHttp: function(str) { 241 | var regHttp = /^https?:\/\/(([a-zA-Z0-9_-])+(\.)?)*(:\d+)?(\/((\.)?(\?)?=?&?[a-zA-Z0-9_-](\?)?)*)*$/i; 242 | var resRegHttp = str.match(regHttp); 243 | 244 | if (resRegHttp) { 245 | return resRegHttp[0]; 246 | } 247 | return false; 248 | } 249 | } 250 | 251 | // 对Date的扩展,将 Date 转化为指定格式的String 252 | // 月(M)、日(d)、小时(h)、分(m)、秒(s)、季度(q) 可以用 1-2 个占位符, 253 | // 年(y)可以用 1-4 个占位符,毫秒(S)只能用 1 个占位符(是 1-3 位的数字) 254 | // 例子: 255 | // (new Date()).Format("yyyy-MM-dd hh:mm:ss.S") ==> 2006-07-02 08:09:04.423 256 | // (new Date()).Format("yyyy-M-d h:m:s.S") ==> 2006-7-2 8:9:4.18 257 | Date.prototype.format = function(fmt) { //author: meizz 258 | var o = { 259 | "M+" : this.getMonth()+1, //月份 260 | "d+" : this.getDate(), //日 261 | "h+" : this.getHours(), //小时 262 | "m+" : this.getMinutes(), //分 263 | "s+" : this.getSeconds(), //秒 264 | "q+" : Math.floor((this.getMonth()+3)/3), //季度 265 | "S" : this.getMilliseconds() //毫秒 266 | }; 267 | if(/(y+)/.test(fmt)) 268 | fmt=fmt.replace(RegExp.$1, (this.getFullYear()+"").substr(4 - RegExp.$1.length)); 269 | for(var k in o) 270 | if(new RegExp("("+ k +")").test(fmt)) 271 | fmt = fmt.replace(RegExp.$1, (RegExp.$1.length==1) ? (o[k]) : (("00"+ o[k]).substr((""+ o[k]).length))); 272 | return fmt; 273 | } 274 | const DateUtils = { 275 | format: function(dateTimeStamp){ 276 | return DateUtils.formatDate(dateTimeStamp, "yyyy-MM-dd hh:mm:ss"); 277 | }, 278 | 279 | formatDate: function(dateTimeStamp, f){ 280 | return new Date(dateTimeStamp).format(f); 281 | }, 282 | 283 | formatDiff: function(dateTimeStamp){//FIXME:该函数是与当前时间比较,几个小时前\几天前格式化显示 284 | return new Date(dateTimeStamp).format("yyyy-MM-dd"); 285 | } 286 | } 287 | 288 | const ObjUtils = { 289 | toString : function(o) { 290 | if(NullUtils.isNull(o)) { 291 | return "null"; 292 | } 293 | 294 | if((typeof o) == "string") { 295 | return o; 296 | } else if((typeof o) == "object") { 297 | var s = ""; 298 | for(var idx in o) { 299 | s += idx + "=" + ObjUtils.toString(o[idx]) + ","; 300 | } 301 | 302 | return s; 303 | } else if((typeof o) == "funtion") { 304 | var s = "fun[" + o +"]"; 305 | } else { 306 | return o; 307 | } 308 | } 309 | } 310 | 311 | exports.StringUtils = StringUtils; 312 | exports.BooleanUtils = BooleanUtils; 313 | exports.JsonUtils = JsonUtils; 314 | exports.CollectionUtils = CollectionUtils; 315 | exports.NullUtils = NullUtils; 316 | exports.NumberUtils = NumberUtils; 317 | exports.StringUtils = StringUtils; 318 | exports.TextUtils = TextUtils; 319 | exports.DateUtils = DateUtils; 320 | exports.ObjUtils = ObjUtils; --------------------------------------------------------------------------------