├── .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 | [](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;
--------------------------------------------------------------------------------