merge dev to master
This commit is contained in:
Родитель
e9ad1fcd7c
Коммит
24a72e3327
|
@ -2,6 +2,8 @@
|
|||
#
|
||||
.DS_Store
|
||||
|
||||
.vscode/
|
||||
|
||||
# Xcode
|
||||
#
|
||||
build/
|
||||
|
@ -51,3 +53,6 @@ android/app/libs
|
|||
fastlane/report.xml
|
||||
fastlane/Preview.html
|
||||
fastlane/screenshots
|
||||
|
||||
js/experiment/
|
||||
temp.md
|
54
README.md
54
README.md
|
@ -1,24 +1,47 @@
|
|||
# Mobile Center Mobile App
|
||||
|
||||
[Microsoft Mobile Center](https://www.visualstudio.com/vs/mobile-center/) is mission control to help mobile developers build mobile apps.
|
||||
# <a href='https://www.visualstudio.com/vs/mobile-center/'><img src='https://www.visualstudio.com/wp-content/uploads/2016/11/continuous-everything@2x-400x362.png' height='80'></a>
|
||||
|
||||
Microsoft Mobile Center Mobile App complement Microsoft Mobile Center portal to let mobile developers track their developed apps' usage from customers in a light and mobile manner.
|
||||
[Microsoft Mobile Center](https://www.visualstudio.com/vs/mobile-center/) is mission control to help mobile developers build mobile apps.
|
||||
|
||||
The key design philosophy of mobile version is to let developers retrieve app usage data from customizable notification mechanism. Developers only want to fetch data or message, view them in the mobile. They can further do the app update through the web portal.
|
||||
Microsoft Mobile Center Mobile App complements Microsoft Mobile Center portal to let mobile developers track developed apps' usage from customers in a light and mobile manner. The source code is 100% based on **React/React Native/Redux**.
|
||||
|
||||
## How We Build It
|
||||
Microsoft Mobile Center Mobile App is a light-and-thin client side app to access data from [Microsoft Mobile Center REST API End Point](https://docs.mobile.azure.com/api/). The source code is 100% based on *React Native*. We used the Auth0 with JWTs to consume REST APIs. Here it is a good tutorial how to [add authentication to react native app using JWTs](https://github.com/jeffreylees/reactnative-jwts).
|
||||
## Code Structure
|
||||
|
||||
Under **js** folder:
|
||||
|
||||
- **actions**: Action and Action Creator
|
||||
- **sagas**: Saga asyn request (encapsulation of actions)
|
||||
- **reducers**: State Controllers
|
||||
- **utils**: REST API Calls
|
||||
- **store**: Redux Store
|
||||
- **containers**: [Container Components](https://github.com/reactjs/redux/blob/master/docs/basics/UsageWithReact.md)
|
||||
- **pages**: [Presentational Components](https://github.com/reactjs/redux/blob/master/docs/basics/UsageWithReact.md)
|
||||
- **components**: React Components, use [react-storybook](https://github.com/storybooks/react-storybook) to polish
|
||||
|
||||
## Working Units -- Sync with Backlog
|
||||
|
||||
- **login**: User can login via Github/ MSA / Mobile Center Account.
|
||||
- **apps**: User can view all apps they have access too and navigate to each app they have access to.
|
||||
- **user**: User Information
|
||||
- **notification**: notification of apps (build, distribution, crash, analytics)
|
||||
- **app**: User can view an app information
|
||||
- **app.distribution.view**: User can view all distribution groups for an app.
|
||||
- **app.distribution.addemail**: User can add new user emails to a distribution group.
|
||||
- **app.distribution.group**: User can create/ delete distribution group.
|
||||
|
||||
## TODO List
|
||||
|
||||
- Continuous Integration with Fastlane and Bitrise, [Article](http://blog.thebakery.io/continuous-integration-for-react-native-applications-with-fastlane-and-bitrise-ios-version/)
|
||||
Search for the **TODO** tag in the code base to check for incomplete tasks.
|
||||
|
||||
- Code-Push
|
||||
- Continuous Integration with Fastlane and Bitrise, [Article](http://blog.thebakery.io/continuous-integration-for-react-native-applications-with-fastlane-and-bitrise-ios-version/)
|
||||
- Mobile Center React Native SDK to track app usage by developers, [mobile-center-sdk-react-native](https://github.com/Microsoft/mobile-center-sdk-react-native)
|
||||
- Push Notification (per app configuration),[react-native-push-notification](https://github.com/zo0r/react-native-push-notification)
|
||||
- Redux Immutable State, [redux-immutable](https://github.com/gajus/redux-immutable)
|
||||
- Code Review Service, [Code Review](https://codeclimate.com/dashboard)
|
||||
- Reselect : efficient rendering computation.
|
||||
|
||||
## Application Architecture
|
||||
## Third Party Tools and APIs
|
||||
|
||||
_We would like to thank and appreciate the efforts that the below library authors have made. We build upon your shoulders._
|
||||
|
||||
|
@ -29,9 +52,10 @@ _We would like to thank and appreciate the efforts that the below library author
|
|||
- [Jest](https://facebook.github.io/jest/) for testing [React Native](https://github.com/facebook/react-native) components and UT
|
||||
- [Eslint](https://github.com/eslint/eslint) is a tool for identifying and reporting on patterns found in reading application code
|
||||
- [redux-devtools](https://github.com/gaearon/redux-devtools) DevTools for Redux with hot reloading, action replay, and customizable UI
|
||||
- [F8App](https://github.com/fbsamples/f8app) A good reference to build a mobile app from scratch
|
||||
|
||||
## Development Workflow
|
||||
## Build
|
||||
|
||||
TODO, check with a new setup in a machine.
|
||||
|
||||
<!--### Step One
|
||||
|
||||
|
@ -62,4 +86,14 @@ npm test
|
|||
|
||||
# Contributing
|
||||
|
||||
There are several ways to contribute to the current app:
|
||||
|
||||
1. **Full stack contribution**: If you want to implement one [func sync with backlog](#Working Units -- Sync with Backlog), you need to implement actions, sagas, reducers, api calls, container and presentational containers. __Please make sure to write the saga and reducer jest tests, all tests should be passed for a PR.__ Here it is [Microsoft Mobile Center REST API End Point](https://docs.mobile.azure.com/api/).
|
||||
|
||||
2. **UI contribution**: Design is the most important factor other than implementation, feel free to [react-storybook](https://github.com/storybooks/react-storybook) to share your design idea and refactor the react components.
|
||||
|
||||
3. **Performance contribution**: Pick the right tool can greatly accelerate our development life cycle, we believe it. In terms of the development, react community is a fast changing world, we love it. Feel free to tell us what libraries we should use to further improve the dev-cycle or the running performance.
|
||||
|
||||
4. **Idea contribution**: Have you used the [Mobile Center Portal](https://mobile.azure.com/)? We believe you did. The key design philosophy of mobile version is to let developers retrieve app usage data from customizable notification mechanism. Developers only want to fetch data or message, view them in the mobile. They can further do the app update through the web portal. Do you agree with it? If not, can you tell us your opinions? If yes, what other special requirements do you think about? Tell us, we will add it in to fullfill your needs.
|
||||
|
||||
This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments.
|
|
@ -38,6 +38,7 @@
|
|||
967445A6F99543C49C47BCF0 /* SimpleLineIcons.ttf in Resources */ = {isa = PBXBuildFile; fileRef = D843D873E1AF4A358AD2EB31 /* SimpleLineIcons.ttf */; };
|
||||
AB7289AC917B4E5DABB33A85 /* FontAwesome.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 37C6442EE45D4A4D9F95DBD1 /* FontAwesome.ttf */; };
|
||||
ACD7101537094C4CAC2422F4 /* Entypo.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 3D32F9E9A5504A528C2EDC7D /* Entypo.ttf */; };
|
||||
C6F43F438CF14063A6B455E8 /* libRNSearchBar.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 08AD9C2359D24DDF9717B8D9 /* libRNSearchBar.a */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXContainerItemProxy section */
|
||||
|
@ -286,6 +287,8 @@
|
|||
D843D873E1AF4A358AD2EB31 /* SimpleLineIcons.ttf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = SimpleLineIcons.ttf; path = "../node_modules/react-native-vector-icons/Fonts/SimpleLineIcons.ttf"; sourceTree = "<group>"; };
|
||||
DCB78B73B64B4F47AEF36680 /* Foundation.ttf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = Foundation.ttf; path = "../node_modules/react-native-vector-icons/Fonts/Foundation.ttf"; sourceTree = "<group>"; };
|
||||
E8AACDB6442A404B970AC4B4 /* Ionicons.ttf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = Ionicons.ttf; path = "../node_modules/react-native-vector-icons/Fonts/Ionicons.ttf"; sourceTree = "<group>"; };
|
||||
9D305C7C431D4644A9582519 /* RNSearchBar.xcodeproj */ = {isa = PBXFileReference; name = "RNSearchBar.xcodeproj"; path = "../node_modules/react-native-search-bar/RNSearchBar.xcodeproj"; sourceTree = "<group>"; fileEncoding = undefined; lastKnownFileType = wrapper.pb-project; explicitFileType = undefined; includeInIndex = 0; };
|
||||
08AD9C2359D24DDF9717B8D9 /* libRNSearchBar.a */ = {isa = PBXFileReference; name = "libRNSearchBar.a"; path = "libRNSearchBar.a"; sourceTree = "<group>"; fileEncoding = undefined; lastKnownFileType = archive.ar; explicitFileType = undefined; includeInIndex = 0; };
|
||||
/* End PBXFileReference section */
|
||||
|
||||
/* Begin PBXFrameworksBuildPhase section */
|
||||
|
@ -316,6 +319,7 @@
|
|||
3F1096DC29134987B0963FA1 /* libCodePush.a in Frameworks */,
|
||||
406741C90D344A5C98BA37B3 /* libz.tbd in Frameworks */,
|
||||
6D9F611F827747FBAD25C03F /* libRNVectorIcons.a in Frameworks */,
|
||||
C6F43F438CF14063A6B455E8 /* libRNSearchBar.a in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
|
@ -512,6 +516,7 @@
|
|||
D10B24DEAE6D4CAB87D7A4E8 /* RNDeviceInfo.xcodeproj */,
|
||||
7B89936946D847F68C345331 /* RNVectorIcons.xcodeproj */,
|
||||
623CB73D55124A85B3F6C94B /* CodePush.xcodeproj */,
|
||||
9D305C7C431D4644A9582519 /* RNSearchBar.xcodeproj */,
|
||||
);
|
||||
name = Libraries;
|
||||
sourceTree = "<group>";
|
||||
|
@ -985,6 +990,7 @@
|
|||
"\"$(SRCROOT)/$(TARGET_NAME)\"",
|
||||
"\"$(SRCROOT)/$(TARGET_NAME)\"",
|
||||
"\"$(SRCROOT)/$(TARGET_NAME)\"",
|
||||
"\"$(SRCROOT)/$(TARGET_NAME)\"",
|
||||
);
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/MobileCenterReactNativeApp.app/MobileCenterReactNativeApp";
|
||||
|
@ -1004,6 +1010,7 @@
|
|||
"\"$(SRCROOT)/$(TARGET_NAME)\"",
|
||||
"\"$(SRCROOT)/$(TARGET_NAME)\"",
|
||||
"\"$(SRCROOT)/$(TARGET_NAME)\"",
|
||||
"\"$(SRCROOT)/$(TARGET_NAME)\"",
|
||||
);
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/MobileCenterReactNativeApp.app/MobileCenterReactNativeApp";
|
||||
|
|
|
@ -24,32 +24,20 @@
|
|||
*
|
||||
*/
|
||||
|
||||
import * as types from './types';
|
||||
import { REQUEST_APPS, RECEIVE_APPS } from './types';
|
||||
|
||||
export function requestAppList(isRefreshing, loading, tokenId, isLoadMore, page = 1) {
|
||||
export function requestAppList(tokenId) {
|
||||
return {
|
||||
type: types.REQUEST_APP_LIST,
|
||||
isRefreshing,
|
||||
loading,
|
||||
isLoadMore,
|
||||
type: REQUEST_APPS,
|
||||
tokenId,
|
||||
page,
|
||||
};
|
||||
}
|
||||
|
||||
export function fetchAppList(isRefreshing, loading, isLoadMore = false) {
|
||||
export function receiveAppList(tokenId, json) {
|
||||
return {
|
||||
type: types.FETCH_APP_LIST,
|
||||
isRefreshing,
|
||||
loading,
|
||||
isLoadMore
|
||||
type: RECEIVE_APPS,
|
||||
tokenId,
|
||||
apps: json.data.children.map(child => child.data),
|
||||
receivedAt: Date.now()
|
||||
};
|
||||
}
|
||||
|
||||
export function receiveAppList(appList, tokenId) {
|
||||
return {
|
||||
type: types.RECEIVE_APP_LIST,
|
||||
appList,
|
||||
tokenId
|
||||
};
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
import { LOGIN_REQUEST, LOGIN_SUCCESS , LOGIN_ERROR } from './types';
|
||||
|
||||
export function loginRequest({ username, password }) {
|
||||
return {
|
||||
type: LOGIN_REQUEST,
|
||||
username,
|
||||
password
|
||||
};
|
||||
}
|
||||
|
||||
export function loginSuccess(response) {
|
||||
return {
|
||||
type: LOGIN_SUCCESS,
|
||||
response
|
||||
};
|
||||
}
|
||||
|
||||
export function loginError(error) {
|
||||
return {
|
||||
type: LOGIN_ERROR,
|
||||
error
|
||||
};
|
||||
}
|
|
@ -24,51 +24,21 @@
|
|||
*
|
||||
*/
|
||||
|
||||
// GET /v0.1/api_tokens
|
||||
export const LOGGED_IN = 'LOGGED_IN';
|
||||
export const LOGGED_OUT = 'LOGGED_OUT';
|
||||
// REST APIs
|
||||
// https://docs.mobile.azure.com/api/#/
|
||||
|
||||
// GET /v0.1/apps
|
||||
export const REQUEST_APP_LIST = 'REQUEST_APP_LIST';
|
||||
export const FETCH_APP_LIST = 'FETCH_APP_LIST';
|
||||
export const RECEIVE_APP_LIST = 'RECEIVE_APP_LIST';
|
||||
export const LOGIN_REQUEST = 'LOGIN_REQUEST';
|
||||
export const LOGIN_SUCCESS = 'LOGIN_SUCCESS';
|
||||
export const LOGIN_ERROR = 'LOGIN_ERROR';
|
||||
|
||||
// GET /v0.1/user
|
||||
export const LOADED_USER = 'LOADED_USER';
|
||||
export const LOGOUT_REQUEST = 'LOGOUT_REQUEST';
|
||||
export const LOGOUT_SUCCESS = 'LOGOUT_SUCCESS';
|
||||
export const LOGOUT_ERROR = 'LOGOUT_ERROR';
|
||||
|
||||
// GET /v0.1/apps/{owner_name}/{app_name}
|
||||
export const REQUEST_APPS = 'REQUEST_APP_LIST';
|
||||
export const RECEIVE_APPS = 'RECEIVE_APP_LIST';
|
||||
export const RECEIVE_APPS_ERROR = 'RECEIVE_APP_LIST_ERROR';
|
||||
|
||||
// GET /v0.1/apps/{owner_name}/{app_name}/users
|
||||
|
||||
// GET /v0.1/apps/{owner_name}/{app_name}/testers
|
||||
|
||||
/*
|
||||
*
|
||||
* build action types
|
||||
*
|
||||
*/
|
||||
|
||||
// GET /v0.1/apps/{owner_name}/{app_name}/distribution_groups
|
||||
|
||||
// GET /v0.1/apps/{owner_name}/{app_name}/distribution_groups/{distribution_group_name}
|
||||
|
||||
// GET /v0.1/apps/{owner_name}/{app_name}/distribution_groups/{distribution_group_name}/members
|
||||
|
||||
|
||||
/*
|
||||
*
|
||||
* distribute action types
|
||||
*
|
||||
*/
|
||||
|
||||
/*
|
||||
*
|
||||
* crash action types
|
||||
*
|
||||
*/
|
||||
|
||||
/*
|
||||
*
|
||||
* analytics action types
|
||||
*
|
||||
*/
|
||||
export const REQUEST_USER = 'REQUEST_USER';
|
||||
export const FETCH_USER = 'FETCH_USER';
|
||||
export const RECEIVE_USER = 'RECEIVE_USER';
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
export const Container = ({ children }) => (
|
||||
{ children }
|
||||
);
|
|
@ -0,0 +1,25 @@
|
|||
import React from 'react';
|
||||
import {StyleSheet, Text, View} from "react-native";
|
||||
import Button from 'react-native-button';
|
||||
import { Actions } from 'react-native-router-flux';
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
flex: 1,
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
backgroundColor: '#F5FCFF',
|
||||
borderWidth: 2,
|
||||
borderColor: 'red',
|
||||
},
|
||||
});
|
||||
|
||||
const ManageApp = () => {
|
||||
return (
|
||||
<View>
|
||||
<Text>Manage App</Text>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
export default ManageApp;
|
|
@ -25,34 +25,29 @@
|
|||
*/
|
||||
|
||||
import React from 'react';
|
||||
import {
|
||||
ActivityIndicator,
|
||||
Text,
|
||||
StyleSheet,
|
||||
View
|
||||
} from 'react-native';
|
||||
import { ActivityIndicator, Text, StyleSheet, View } from 'react-native';
|
||||
|
||||
const LoadingView = () => {
|
||||
<View Style={styles.loading}>
|
||||
<ActivityIndicator
|
||||
size="large"
|
||||
color="#3e9ce9"
|
||||
/>
|
||||
<Text style={styles.loadingText}>Loading Data...</Text>
|
||||
</View>
|
||||
<View Style={styles.loading}>
|
||||
<ActivityIndicator
|
||||
size="large"
|
||||
color="#3e9ce9"
|
||||
/>
|
||||
<Text style={styles.loadingText}>Loading Data...</Text>
|
||||
</View>;
|
||||
};
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
loading: {
|
||||
flex: 1,
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
backgroundColor: 'white'
|
||||
},
|
||||
loadingText: {
|
||||
marginTop: 10,
|
||||
textAlign: 'center'
|
||||
}
|
||||
loading: {
|
||||
flex: 1,
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
backgroundColor: 'white'
|
||||
},
|
||||
loadingText: {
|
||||
marginTop: 10,
|
||||
textAlign: 'center'
|
||||
}
|
||||
});
|
||||
|
||||
export default LoadingView;
|
||||
export default LoadingView;
|
||||
|
|
|
@ -0,0 +1,28 @@
|
|||
import React, { PropTypes } from 'react';
|
||||
import Drawer from 'react-native-drawer';
|
||||
import { DefaultRenderer, Actions } from 'react-native-router-flux';
|
||||
import ManageApp from './app/manageApp';
|
||||
|
||||
const propTypes = {
|
||||
navigationState: PropTypes.object,
|
||||
}
|
||||
|
||||
class NavigationDrawer extends React.Component {
|
||||
render() {
|
||||
const state = this.props.navigationState;
|
||||
const children = state.children;
|
||||
return (
|
||||
<Drawer
|
||||
type="displace"
|
||||
content={<ManageApp />}
|
||||
side="right"
|
||||
>
|
||||
<DefaultRenderer navigationState={children[0]} onNavigate={this.props.onNavigate} />
|
||||
</Drawer>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
NavigationDrawer.propTypes = propTypes;
|
||||
|
||||
export default NavigationDrawer;
|
|
@ -57,4 +57,4 @@ const styles = StyleSheet.create({
|
|||
title: {
|
||||
fontSize: 14
|
||||
}
|
||||
});
|
||||
});
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
export const TOKENID = '1938077bc136d1b81c65c4d74fc4f593d9c28b02';
|
|
@ -0,0 +1,13 @@
|
|||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import Analytics from '../../pages/app/Analytics';
|
||||
|
||||
class AnalyticsContainer extends React.Component {
|
||||
render() {
|
||||
return (
|
||||
<Analytics />
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default connect()(AnalyticsContainer);
|
|
@ -0,0 +1,13 @@
|
|||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import Build from '../../pages/app/Build';
|
||||
|
||||
class BuildContainer extends React.Component {
|
||||
render() {
|
||||
return (
|
||||
<Build />
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default connect()(BuildContainer);
|
|
@ -0,0 +1,13 @@
|
|||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import Crash from '../../pages/app/Crash';
|
||||
|
||||
class CrashContainer extends React.Component {
|
||||
render() {
|
||||
return (
|
||||
<Crash />
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default connect()(CrashContainer);
|
|
@ -0,0 +1,13 @@
|
|||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import Distribute from '../../pages/app/Distribute';
|
||||
|
||||
class DistributeContainer extends React.Component {
|
||||
render() {
|
||||
return (
|
||||
<Distribute />
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default connect()(DistributeContainer);
|
|
@ -0,0 +1,13 @@
|
|||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import ManageApp from '../../../pages/app/Start/ManageApp';
|
||||
|
||||
class ManageAppContainer extends React.Component {
|
||||
render() {
|
||||
return (
|
||||
<ManageApp />
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default connect()(ManageAppContainer);
|
|
@ -0,0 +1,13 @@
|
|||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import Start from '../../pages/app/Start';
|
||||
|
||||
class StartContainer extends React.Component {
|
||||
render() {
|
||||
return (
|
||||
<Start />
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default connect()(StartContainer);
|
|
@ -0,0 +1,13 @@
|
|||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import Test from '../../pages/app/Test';
|
||||
|
||||
class TestContainer extends React.Component {
|
||||
render() {
|
||||
return (
|
||||
<Test />
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default connect()(TestContainer);
|
|
@ -1,60 +1,13 @@
|
|||
/**
|
||||
* Microsoft Mobile Center App
|
||||
*
|
||||
* Copyright (c) Microsoft Corporation
|
||||
*
|
||||
* All rights reserved.
|
||||
*
|
||||
* MIT License
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of this
|
||||
* software and associated documentation files (the "Software"), to deal in the Software
|
||||
* without restriction, including without limitation the rights to use, copy, modify,
|
||||
* merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
|
||||
* permit persons to whom the Software is furnished to do so, subject to the following
|
||||
* conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all copies or
|
||||
* substantial portions of the Software. THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF
|
||||
* ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR
|
||||
* THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { bindActionCreators } from 'redux';
|
||||
import Apps from '../pages/apps';
|
||||
import * as appsCreators from '../actions/apps';
|
||||
// import CodePush from 'react-native-code-push';
|
||||
import App from '../pages/App';
|
||||
|
||||
class AppsContainer extends React.Component{
|
||||
componentDidMount(){
|
||||
//TODO codepush
|
||||
}
|
||||
|
||||
render(){
|
||||
return (
|
||||
<Apps {...this.props}/>
|
||||
);
|
||||
}
|
||||
class AppContainer extends React.Component {
|
||||
render() {
|
||||
return (
|
||||
<App />
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const mapStateToProps = (state) => {
|
||||
const { app } = state;
|
||||
return {
|
||||
app
|
||||
};
|
||||
};
|
||||
|
||||
const mapDispatchToProps = (dispatch) => {
|
||||
const appsActions = bindActionCreators(appsCreators, dispatch);
|
||||
return {
|
||||
appsActions
|
||||
};
|
||||
};
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(AppsContainer);
|
||||
export default connect()(AppContainer);
|
||||
|
|
|
@ -24,18 +24,16 @@
|
|||
*
|
||||
*/
|
||||
|
||||
/* global expect, test action */
|
||||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import Apps from '../pages/Apps';
|
||||
|
||||
import * as types from '../types';
|
||||
import {receiveAppList} from '../apps';
|
||||
import TOKENID from '../../utils/RequestUtil';
|
||||
class AppsContainer extends React.Component {
|
||||
render() {
|
||||
return (
|
||||
<Apps />
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
describe('test apps action', () => {
|
||||
it('receiveAppList', () => {
|
||||
expect(receiveAppList('hockey', TOKENID)).toEqual({
|
||||
type: types.RECEIVE_APP_LIST,
|
||||
appList: 'hockey',
|
||||
tokenId: TOKENID
|
||||
})
|
||||
})
|
||||
});
|
||||
export default connect()(AppsContainer);
|
|
@ -0,0 +1,13 @@
|
|||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import Login from '../pages/Login';
|
||||
|
||||
class LoginContainer extends React.Component {
|
||||
render() {
|
||||
return (
|
||||
<Login />
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default connect()(LoginContainer);
|
|
@ -25,18 +25,138 @@
|
|||
*/
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import { StyleSheet, Navigator, View, Text } from 'react-native';
|
||||
import { Router, Scene, ActionConst } from 'react-native-router-flux';
|
||||
import { StyleSheet, Navigator, Image } from 'react-native';
|
||||
import { Router, Scene, ActionConst, Actions } from 'react-native-router-flux';
|
||||
import { connect } from 'react-redux';
|
||||
import User from '../pages/User';
|
||||
import Notification from '../pages/Notification';
|
||||
import AppsContainer from './appsContainer';
|
||||
import AppContainer from './appContainer';
|
||||
import StartContainer from './app/StartContainer';
|
||||
import BuildContainer from './app/BuildContainer';
|
||||
import TestContainer from './app/TestContainer';
|
||||
import DistributeContainer from './app/DistributeContainer';
|
||||
import CrashContainer from './app/CrashContainer';
|
||||
import AnalyticsContainer from './app/AnalyticsContainer';
|
||||
|
||||
import About from '../pages/about';
|
||||
import Notification from '../pages/notification';
|
||||
import User from '../pages/user';
|
||||
import AppsContainer from './appContainer';
|
||||
import Drawer from 'react-native-drawer';
|
||||
import Login from './loginContainer';
|
||||
import TabIcon from '../components/tabicon';
|
||||
import Splash from '../pages/Splash';
|
||||
import NavigationDrawer from '../components/navigationDrawer';
|
||||
import ManageApp from '../components/app/manageApp';
|
||||
|
||||
const RouterWithRedux = connect()(Router);
|
||||
|
||||
// const appBuildImg = require('../img/app-build.png');
|
||||
// const appTestImg = require('../img/app-test.png');
|
||||
// const appDistributeImg = require('../img/app-distribute.png');
|
||||
// const appCrashImg = require('../img/app-crashes.png');
|
||||
// const appAnalyticsImg = require('../img/app-analytics.png');
|
||||
|
||||
class MobileCenter extends React.Component {
|
||||
render() {
|
||||
return (
|
||||
<RouterWithRedux
|
||||
titleStyle={styles.navBarTitle}
|
||||
getSceneStyle={getSceneStyle}
|
||||
navigationBarStyle={styles.navBar}
|
||||
>
|
||||
<Scene key="root">
|
||||
<Scene key="splash" component={Splash} hideNavBar hideTabBar />
|
||||
<Scene key="login" component={Login} hideNavBar hideTabBar />
|
||||
<Scene key="tabbar" tabs pressOpacity={0.8} type={ActionConst.REPLACE} initial>
|
||||
<Scene
|
||||
key="apps"
|
||||
component={AppsContainer}
|
||||
title="Apps"
|
||||
icon={TabIcon}
|
||||
iconName="md-apps"
|
||||
/>
|
||||
<Scene
|
||||
key="notification"
|
||||
component={Notification}
|
||||
title="Notification"
|
||||
icon={TabIcon}
|
||||
iconName="md-notifications"
|
||||
/>
|
||||
<Scene
|
||||
key="user"
|
||||
component={User}
|
||||
title="My Info"
|
||||
icon={TabIcon}
|
||||
iconName="md-person"
|
||||
/>
|
||||
</Scene>
|
||||
|
||||
<Scene key="tabbar2">
|
||||
<Scene
|
||||
key="main" tabs
|
||||
tabBarStyle={tabstyles.tabBarStyle}
|
||||
tabBarSelectedItemStyle={tabstyles.tabBarSelectedItemStyle}
|
||||
>
|
||||
<Scene
|
||||
key="start"
|
||||
component={StartContainer}
|
||||
title="Start"
|
||||
icon={TabIcon}
|
||||
iconName="md-bulb"
|
||||
onLeft={() => Actions.pop()}
|
||||
leftTitle="Back"
|
||||
/>
|
||||
<Scene
|
||||
key="build"
|
||||
component={BuildContainer}
|
||||
title="Build"
|
||||
icon={TabIcon}
|
||||
iconName="md-play"
|
||||
onLeft={() => Actions.pop()}
|
||||
leftTitle="Back"
|
||||
/>
|
||||
<Scene
|
||||
key="test"
|
||||
component={TestContainer}
|
||||
title="Test"
|
||||
icon={TabIcon}
|
||||
iconName="ios-checkmark-circle-outline"
|
||||
onLeft={() => Actions.pop()}
|
||||
leftTitle="Back"
|
||||
/>
|
||||
<Scene
|
||||
key="distribute"
|
||||
component={DistributeContainer}
|
||||
title="Distribute"
|
||||
icon={TabIcon}
|
||||
iconName="ios-git-branch"
|
||||
onLeft={() => Actions.pop()}
|
||||
leftTitle="Back"
|
||||
/>
|
||||
<Scene
|
||||
key="crash"
|
||||
component={CrashContainer}
|
||||
title="Crash"
|
||||
icon={TabIcon}
|
||||
iconName="ios-warning-outline"
|
||||
onLeft={() => Actions.pop()}
|
||||
leftTitle="Back"
|
||||
/>
|
||||
<Scene
|
||||
key="analytics"
|
||||
component={AnalyticsContainer}
|
||||
title="Analytics"
|
||||
icon={TabIcon}
|
||||
iconName="ios-stats-outline"
|
||||
onLeft={() => Actions.pop()}
|
||||
leftTitle="Back"
|
||||
/>
|
||||
</Scene>
|
||||
</Scene>
|
||||
</Scene>
|
||||
</RouterWithRedux>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const getSceneStyle = (props, computedProps) => {
|
||||
const style = {
|
||||
flex: 1,
|
||||
|
@ -54,51 +174,6 @@ const getSceneStyle = (props, computedProps) => {
|
|||
return style;
|
||||
};
|
||||
|
||||
class MobileCenter extends React.Component{
|
||||
render(){
|
||||
return (
|
||||
<RouterWithRedux
|
||||
titleStyle={styles.navBarTitle}
|
||||
getSceneStyle={getSceneStyle}
|
||||
navigationBarStyle={styles.navBar}
|
||||
>
|
||||
<Scene key='root'>
|
||||
<Scene key='tabbar' tabs pressOpacity={0.8} type={ActionConst.REPLACE}>
|
||||
<Scene
|
||||
key="apps"
|
||||
component={AppsContainer}
|
||||
title="Apps"
|
||||
icon={TabIcon}
|
||||
iconName="md-apps"
|
||||
/>
|
||||
<Scene
|
||||
key="notification"
|
||||
component={Notification}
|
||||
title="Notification"
|
||||
icon={TabIcon}
|
||||
iconName="md-notifications"
|
||||
/>
|
||||
<Scene
|
||||
key="user"
|
||||
component={User}
|
||||
title="My Info"
|
||||
icon={TabIcon}
|
||||
iconName="md-person"
|
||||
/>
|
||||
<Scene
|
||||
key="about"
|
||||
component={About}
|
||||
title="About"
|
||||
icon={TabIcon}
|
||||
iconName="md-information-circle"
|
||||
/>
|
||||
</Scene>
|
||||
</Scene>
|
||||
</RouterWithRedux>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
navBar: {
|
||||
backgroundColor: '#3e9ce9'
|
||||
|
@ -109,4 +184,23 @@ const styles = StyleSheet.create({
|
|||
}
|
||||
});
|
||||
|
||||
export default MobileCenter;
|
||||
const drawerStyles = {
|
||||
drawer: { shadowColor: '#000000', shadowOpacity: 0.8, shadowRadius: 3 },
|
||||
main: { paddingLeft: 3 },
|
||||
};
|
||||
|
||||
const tabstyles = StyleSheet.create({
|
||||
container: { flex: 1,
|
||||
backgroundColor: 'transparent',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
},
|
||||
tabBarStyle: {
|
||||
backgroundColor: '#eee',
|
||||
},
|
||||
tabBarSelectedItemStyle: {
|
||||
backgroundColor: '#ddd',
|
||||
},
|
||||
});
|
||||
|
||||
export default MobileCenter;
|
||||
|
|
|
@ -1,34 +0,0 @@
|
|||
import {addTodo} from '../todo';
|
||||
|
||||
test('adds 1 + 2 to equal 3', () => {
|
||||
const sum = require('../sum');
|
||||
expect(sum(1, 2)).toBe(3);
|
||||
});
|
||||
|
||||
const myBeverage = {
|
||||
delicious: true,
|
||||
sour: false,
|
||||
};
|
||||
|
||||
describe('my beverage', () => {
|
||||
it('is delicious', () => {
|
||||
expect(myBeverage.delicious).toBeTruthy();
|
||||
});
|
||||
|
||||
it('is not sour', () => {
|
||||
expect(myBeverage.sour).toBeFalsy();
|
||||
});
|
||||
});
|
||||
|
||||
describe('actions', () => {
|
||||
it('should create an action to add a todo', () => {
|
||||
const text = "Go to the Vault"
|
||||
const id = 1
|
||||
const expectedAction = {
|
||||
type: 'ADD_TODO',
|
||||
text: "Go to the Vault",
|
||||
id: 1
|
||||
}
|
||||
expect(addTodo(text, id)).toEqual(expectedAction)
|
||||
})
|
||||
})
|
|
@ -1 +0,0 @@
|
|||
module.exports = (a, b) => a + b;
|
|
@ -1,7 +0,0 @@
|
|||
export const addTodo = (text, id) => {
|
||||
return {
|
||||
id: id,
|
||||
type: 'ADD_TODO',
|
||||
text
|
||||
}
|
||||
}
|
Двоичный файл не отображается.
После Ширина: | Высота: | Размер: 215 B |
Двоичный файл не отображается.
После Ширина: | Высота: | Размер: 277 B |
Двоичный файл не отображается.
После Ширина: | Высота: | Размер: 419 B |
Двоичный файл не отображается.
После Ширина: | Высота: | Размер: 445 B |
Двоичный файл не отображается.
После Ширина: | Высота: | Размер: 519 B |
Двоичный файл не отображается.
После Ширина: | Высота: | Размер: 38 KiB |
220
js/login/TODO.js
220
js/login/TODO.js
|
@ -1,220 +0,0 @@
|
|||
/**
|
||||
* Microsoft Mobile Center App
|
||||
*
|
||||
* Copyright (c) Microsoft Corporation
|
||||
*
|
||||
* All rights reserved.
|
||||
*
|
||||
* MIT License
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of this
|
||||
* software and associated documentation files (the "Software"), to deal in the Software
|
||||
* without restriction, including without limitation the rights to use, copy, modify,
|
||||
* merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
|
||||
* permit persons to whom the Software is furnished to do so, subject to the following
|
||||
* conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all copies or
|
||||
* substantial portions of the Software. THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF
|
||||
* ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR
|
||||
* THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*
|
||||
*/
|
||||
|
||||
// import {Actions, Scene, Router} from 'react-native-router-flux';
|
||||
|
||||
var React = require('react');
|
||||
var ReactNative = require('react-native');
|
||||
var t = require('tcomb-form-native');
|
||||
|
||||
var {
|
||||
AppRegistry,
|
||||
AsyncStorage,
|
||||
StyleSheet,
|
||||
Text,
|
||||
View,
|
||||
TouchableHighlight,
|
||||
AlertIOS,
|
||||
} = ReactNative;
|
||||
|
||||
global.Buffer = global.Buffer || require('buffer').Buffer;
|
||||
|
||||
var STORAGE_KEY = 'id_token';
|
||||
|
||||
var Form = t.form.Form;
|
||||
|
||||
var Person = t.struct({
|
||||
username: t.String,
|
||||
password: t.String
|
||||
});
|
||||
|
||||
const options = {};
|
||||
|
||||
let myApiUrl = "http://localhost:3001";
|
||||
let quotePath = "/api/protected/random-quote";
|
||||
let userPath = "/users";
|
||||
let userCreatePath = "/sessions/create";
|
||||
|
||||
let mc_api_url = "https://api.mobile.azure.com";
|
||||
let get_user = "/v0.1/user";
|
||||
let get_apps = "/v0.1/apps";
|
||||
let get_tokens = "/v0.1/api_tokens";
|
||||
|
||||
let userName = "buptkang@gmail.com";
|
||||
let userPassword = "Kb@241307684";
|
||||
|
||||
let storedSessionTokens = "2eb6d0e2250779ad71acde8f383158b48aa0b4b6";
|
||||
|
||||
var MobileCenter = React.createClass({
|
||||
|
||||
async _onValueChange(item, selectedValue) {
|
||||
try {
|
||||
await AsyncStorage.setItem(item, selectedValue);
|
||||
} catch (error) {
|
||||
console.log('AsyncStorage error: ' + error.message);
|
||||
}
|
||||
},
|
||||
|
||||
async _getUserApps() {
|
||||
var DEMO_TOKEN = await AsyncStorage.getItem(STORAGE_KEY);
|
||||
fetch(`${mc_api_url}${get_apps}`, {
|
||||
method: "GET",
|
||||
headers: {
|
||||
'Accept': 'application/json',
|
||||
'X-API-Token': storedSessionTokens
|
||||
}
|
||||
})
|
||||
.then((response) => response.json())
|
||||
.then((responseData) => {
|
||||
responseData.map(app => AlertIOS.alert("Your Apps:",app.display_name));
|
||||
//AlertIOS.alert("Your Apps:", responseData.map(app=>))
|
||||
})
|
||||
.done();
|
||||
},
|
||||
|
||||
async _userLogout() {
|
||||
try {
|
||||
await AsyncStorage.removeItem(STORAGE_KEY);
|
||||
AlertIOS.alert("Logout Success!")
|
||||
} catch (error) {
|
||||
console.log('AsyncStorage error: ' + error.message);
|
||||
}
|
||||
},
|
||||
|
||||
_userSignup() {
|
||||
var value = this.refs.form.getValue();
|
||||
if (value) { // if validation fails, value will be null
|
||||
fetch(`${myApiUrl}${userPath}`, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
'Accept': 'application/json',
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({
|
||||
username: value.username,
|
||||
password: value.password,
|
||||
})
|
||||
})
|
||||
.then((response) => response.json())
|
||||
.then((responseData) => {
|
||||
this._onValueChange(STORAGE_KEY, responseData.id_token),
|
||||
AlertIOS.alert(
|
||||
"Signup Success!",
|
||||
"Click the button to get a Chuck Norris quote!"
|
||||
)
|
||||
})
|
||||
.done();
|
||||
}
|
||||
},
|
||||
|
||||
_userLogin() {
|
||||
let concatStr = userName + ':' + userPassword;
|
||||
let hashStr = new Buffer(concatStr).toString('base64');
|
||||
//TODO: check null;
|
||||
//AlertIOS.alert(hashStr);
|
||||
// var value = this.refs.form.getValue();
|
||||
// if (value) { // if validation fails, value will be null
|
||||
//fetch(`${myApiUrl}${userCreatePath}`, {
|
||||
fetch(`${mc_api_url}${get_tokens}`, {
|
||||
method: "GET",
|
||||
headers: {
|
||||
'Accept': 'application/json',
|
||||
'Authorization': 'Basic ' + hashStr
|
||||
}
|
||||
})
|
||||
.then((response) => response.json())
|
||||
.then((responseData) => {
|
||||
AlertIOS.alert(
|
||||
"Login Success!",
|
||||
//responseData[0].id
|
||||
),
|
||||
this._onValueChange(STORAGE_KEY, responseData[0].id)
|
||||
})
|
||||
.done();
|
||||
// }
|
||||
},
|
||||
|
||||
render() {
|
||||
return (
|
||||
<View style={styles.container}>
|
||||
<View style={styles.row}>
|
||||
<Text style={styles.title}>Mobile Center RN Experiment</Text>
|
||||
</View>
|
||||
<View style={styles.row}>
|
||||
<Form
|
||||
ref="form"
|
||||
type={Person}
|
||||
options={options}
|
||||
/>
|
||||
</View>
|
||||
<View style={styles.row}>
|
||||
<TouchableHighlight style={styles.button} onPress={this._userLogin} underlayColor='#99d9f4'>
|
||||
<Text style={styles.buttonText}>Login</Text>
|
||||
</TouchableHighlight>
|
||||
<TouchableHighlight style={styles.button} onPress={this._userLogout} underlayColor='#99d9f4'>
|
||||
<Text style={styles.buttonText}>Logout</Text>
|
||||
</TouchableHighlight>
|
||||
</View>
|
||||
<View style={styles.row}>
|
||||
<TouchableHighlight onPress={this._getUserApps} style={styles.button}>
|
||||
<Text style={styles.buttonText}>Get your apps!</Text>
|
||||
</TouchableHighlight>
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
var styles = StyleSheet.create({
|
||||
container: {
|
||||
justifyContent: 'center',
|
||||
marginTop: 50,
|
||||
padding: 20,
|
||||
backgroundColor: '#ffffff',
|
||||
},
|
||||
title: {
|
||||
fontSize: 30,
|
||||
alignSelf: 'center',
|
||||
marginBottom: 30
|
||||
},
|
||||
buttonText: {
|
||||
fontSize: 18,
|
||||
color: 'white',
|
||||
alignSelf: 'center'
|
||||
},
|
||||
button: {
|
||||
height: 36,
|
||||
backgroundColor: '#48BBEC',
|
||||
borderColor: '#48BBEC',
|
||||
borderWidth: 1,
|
||||
borderRadius: 8,
|
||||
marginBottom: 10,
|
||||
alignSelf: 'stretch',
|
||||
justifyContent: 'center'
|
||||
},
|
||||
});
|
||||
|
||||
module.exports = MobileCenter;
|
|
@ -24,133 +24,82 @@
|
|||
*
|
||||
*/
|
||||
|
||||
import React, {PropTypes} from 'react';
|
||||
import React, { propTypes } from 'react';
|
||||
import { StyleSheet,
|
||||
ListView,
|
||||
RefreshControl,
|
||||
ScrollView,
|
||||
Text,
|
||||
TouchableOpacity,
|
||||
InteractionManager,
|
||||
ActivityIndicator,
|
||||
RecyclerViewBackedScrollView,
|
||||
Image,
|
||||
View,
|
||||
DeviceEventEmitter,
|
||||
Platform,
|
||||
AlertIOS} from 'react-native';
|
||||
ListView,
|
||||
View,
|
||||
Platform,
|
||||
Text,
|
||||
TouchableOpacity,
|
||||
Alert,
|
||||
Image,
|
||||
Button
|
||||
} from 'react-native';
|
||||
import { Actions } from 'react-native-router-flux';
|
||||
|
||||
import LoadingView from '../components/loading';
|
||||
import { toastShort } from '../utils/ToastUtil';
|
||||
import Storage from '../utils/StorageUtil';
|
||||
import { TOKENID } from '../utils/RequestUtil';
|
||||
|
||||
const propTypes = {
|
||||
appsActions: PropTypes.object,
|
||||
app: PropTypes.object.isRequired
|
||||
};
|
||||
|
||||
const pages = [];
|
||||
let loadMoreTime = 0;
|
||||
let currentLoadMoreTypeId;
|
||||
|
||||
data = [
|
||||
const mockData = [
|
||||
{
|
||||
"display_name": "Calculator-Test",
|
||||
"owner": {
|
||||
"display_name": "Bo Kang",
|
||||
},
|
||||
appName: 'Calculator-Test',
|
||||
abbrName: 'C',
|
||||
userName: 'buptkang'
|
||||
},
|
||||
{
|
||||
"display_name": "Moody",
|
||||
"owner": {
|
||||
"display_name": "Bo Kang",
|
||||
},
|
||||
},
|
||||
appName: 'Moody',
|
||||
abbrName: 'Moody',
|
||||
userName: 'buptkang'
|
||||
}
|
||||
];
|
||||
|
||||
export default class Apps extends React.Component{
|
||||
export default class Apps extends React.Component {
|
||||
|
||||
constructor(props){
|
||||
constructor(props) {
|
||||
super(props);
|
||||
const ds = new ListView.DataSource({rowHasChanged: (r1, r2) => r1 !== r2});
|
||||
|
||||
const ds = new ListView.DataSource({ rowHasChanged: (r1, r2) => r1 !== r2 });
|
||||
this.state = {
|
||||
dataSource: ds.cloneWithRows(data),
|
||||
tokenId: TOKENID
|
||||
dataSource: ds.cloneWithRows(mockData),
|
||||
};
|
||||
this.renderItem = this.renderItem.bind(this);
|
||||
this.onPressApp = this.onPressApp.bind(this);
|
||||
}
|
||||
|
||||
componentDidMount(){
|
||||
const {appsActions} = this.props;
|
||||
DeviceEventEmitter.addListener('loadApps', (tokenId) => {
|
||||
appsActions.requestAppList(false, true, tokenId);
|
||||
pages.push(1);
|
||||
this.setState({tokenId});
|
||||
});
|
||||
|
||||
// InteractionManager.runAfterInteractions(
|
||||
// Storage.get('getTokenId')
|
||||
// .then((tokenIds) => {
|
||||
// if(!tokenIds){
|
||||
// return;
|
||||
// }
|
||||
// //TODO
|
||||
// }
|
||||
// );
|
||||
}
|
||||
|
||||
componentWillReceiveProps(nextProps){
|
||||
const {app} = this.props;
|
||||
if (app.isLoadMore && !nextProps.app.isLoadMore && !nextProps.app.isRefreshing){
|
||||
if (nextProps.app.noMore) {
|
||||
toastShort('no more data');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
componentWillUnmount(){
|
||||
DeviceEventEmitter.removeAllListeners('loadApps');
|
||||
}
|
||||
|
||||
onRefresh(tokenId){
|
||||
const { appsActions } = this.props;
|
||||
appsActions.requestAppList(true, false, tokenId);
|
||||
onPressApp(app) {
|
||||
Actions.tabbar2();
|
||||
}
|
||||
|
||||
onPress(app){
|
||||
const { routes } = this.context;
|
||||
//TODO
|
||||
}
|
||||
|
||||
renderItem(app){
|
||||
return (
|
||||
<View style={styles.containerItem}>
|
||||
<View style={styles.itemRightContent} >
|
||||
<Text style={styles.title}>
|
||||
{app.display_name}
|
||||
</Text>
|
||||
<View style={styles.itemRightBottom} >
|
||||
<Text style={styles.userName} >
|
||||
{app.owner.display_name}
|
||||
renderApp(app) {
|
||||
return (
|
||||
<TouchableOpacity onPress={() => this.onPressApp(app)}>
|
||||
<View style={styles.containerItem}>
|
||||
<Image style={styles.itemImg}>
|
||||
<Text>
|
||||
{app.abbrName}
|
||||
</Text>
|
||||
</Image>
|
||||
<View style={styles.itemRightContent}>
|
||||
<Text style={styles.title}>
|
||||
{app.appName}
|
||||
</Text>
|
||||
<View style={styles.itemRightBottom}>
|
||||
<Text style={styles.userName}>
|
||||
{app.userName}
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
</View>
|
||||
</TouchableOpacity>
|
||||
);
|
||||
}
|
||||
|
||||
render(){
|
||||
const { app } = this.props;
|
||||
render() {
|
||||
return (
|
||||
<ListView
|
||||
dataSource={this.state.dataSource}
|
||||
renderRow={this.renderItem}
|
||||
style={styles.listView}
|
||||
/>
|
||||
<View style={styles.container}>
|
||||
<ListView
|
||||
initialListSize={1}
|
||||
dataSource={this.state.dataSource}
|
||||
renderRow={row => this.renderApp(row)}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
|
@ -267,4 +216,4 @@ const styles = StyleSheet.create({
|
|||
}
|
||||
});
|
||||
|
||||
Apps.propTypes = propTypes;
|
||||
Apps.propTypes = propTypes;
|
|
@ -0,0 +1,93 @@
|
|||
/**
|
||||
* Microsoft Mobile Center App
|
||||
*
|
||||
* Copyright (c) Microsoft Corporation
|
||||
*
|
||||
* All rights reserved.
|
||||
*
|
||||
* MIT License
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of this
|
||||
* software and associated documentation files (the "Software"), to deal in the Software
|
||||
* without restriction, including without limitation the rights to use, copy, modify,
|
||||
* merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
|
||||
* permit persons to whom the Software is furnished to do so, subject to the following
|
||||
* conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all copies or
|
||||
* substantial portions of the Software. THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF
|
||||
* ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR
|
||||
* THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*
|
||||
*/
|
||||
import React, { PropTypes } from 'react';
|
||||
import { StyleSheet, Text,
|
||||
View, TextInput, TouchableHighlight, Alert } from 'react-native';
|
||||
import { Actions } from 'react-native-router-flux';
|
||||
|
||||
export default class Login extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = { username: 'default', password: 'default' };
|
||||
}
|
||||
|
||||
onPress() {
|
||||
Actions.tabbar();
|
||||
}
|
||||
|
||||
render() {
|
||||
// const {login, dispatch} = this.props;
|
||||
return (
|
||||
<View style={styles.container} >
|
||||
<TextInput
|
||||
style={{ height: 40, borderColor: 'gray', borderWidth: 1 }}
|
||||
onChangeText={username => this.setState({ username })}
|
||||
value={this.state.username}
|
||||
/>
|
||||
<TextInput
|
||||
style={{ height: 40, borderColor: 'gray', borderWidth: 1 }}
|
||||
onChangeText={password => this.setState({ password })}
|
||||
value={this.state.password}
|
||||
/>
|
||||
<TouchableHighlight
|
||||
style={styles.button}
|
||||
onPress={this.onPress} underlayColor="#99d9f4"
|
||||
>
|
||||
<Text style={styles.buttonText}>Submit</Text>
|
||||
</TouchableHighlight>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
let styles = StyleSheet.create({
|
||||
container: {
|
||||
justifyContent: 'center',
|
||||
marginTop: 50,
|
||||
padding: 20,
|
||||
backgroundColor: '#ffffff',
|
||||
},
|
||||
title: {
|
||||
fontSize: 30,
|
||||
alignSelf: 'center',
|
||||
marginBottom: 30
|
||||
},
|
||||
buttonText: {
|
||||
fontSize: 18,
|
||||
color: 'white',
|
||||
alignSelf: 'center'
|
||||
},
|
||||
button: {
|
||||
height: 36,
|
||||
backgroundColor: '#48BBEC',
|
||||
borderColor: '#48BBEC',
|
||||
borderWidth: 1,
|
||||
borderRadius: 8,
|
||||
marginBottom: 10,
|
||||
alignSelf: 'stretch',
|
||||
justifyContent: 'center'
|
||||
}
|
||||
});
|
|
@ -25,19 +25,16 @@
|
|||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { StyleSheet, Image, Text, TextInput, Linking, View} from 'react-native';
|
||||
import { Text, View } from 'react-native';
|
||||
|
||||
import DeviceInfo from 'react-native-device-info';
|
||||
import { Actions } from 'react-native-router-flux';
|
||||
import Icon from 'react-native-vector-icons/Ionicons';
|
||||
import Button from '../components/button';
|
||||
// TODO: Add Notification functions
|
||||
|
||||
export default class User extends React.Component{
|
||||
render(){
|
||||
return (
|
||||
<View>
|
||||
<Text> TODO: Render User Information.</Text>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
}
|
||||
export default class Notification extends React.Component {
|
||||
render() {
|
||||
return (
|
||||
<View>
|
||||
<Text> Push Notification </Text>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,49 @@
|
|||
import React from 'react';
|
||||
import {
|
||||
Dimensions,
|
||||
Animated
|
||||
} from 'react-native';
|
||||
import { Actions } from 'react-native-router-flux';
|
||||
import store from 'react-native-simple-store';
|
||||
|
||||
const maxHeight = Dimensions.get('window').height;
|
||||
const maxWidth = Dimensions.get('window').width;
|
||||
const splashImg = require('../img/splash.png');
|
||||
|
||||
export default class Splash extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
bounceValue: new Animated.Value(0.4)
|
||||
};
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
Animated.timing(
|
||||
this.state.bounceValue, { toValue: 2.0, duration: 1000 }
|
||||
).start();
|
||||
this.timer = setTimeout(() => {
|
||||
store.get('isInit')
|
||||
.then((isInit) => {
|
||||
if (!isInit) {
|
||||
Actions.login();
|
||||
}
|
||||
});
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
clearTimeout(this.timer);
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<Animated.Image
|
||||
style={{ width: maxWidth,
|
||||
height: maxHeight,
|
||||
transform: [{ scale: this.state.bounceValue }] }}
|
||||
source={splashImg}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
|
@ -25,19 +25,16 @@
|
|||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { StyleSheet, Image, Text, TextInput, Linking, View} from 'react-native';
|
||||
import { Text, View } from 'react-native';
|
||||
|
||||
import DeviceInfo from 'react-native-device-info';
|
||||
import { Actions } from 'react-native-router-flux';
|
||||
import Icon from 'react-native-vector-icons/Ionicons';
|
||||
import Button from '../components/button';
|
||||
// TODO: User Information
|
||||
|
||||
export default class Notification extends React.Component{
|
||||
render(){
|
||||
return (
|
||||
<View>
|
||||
<Text> TODO: Push Notification </Text>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
}
|
||||
export default class User extends React.Component {
|
||||
render() {
|
||||
return (
|
||||
<View>
|
||||
<Text> Render User Information. </Text>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,38 @@
|
|||
/**
|
||||
* Microsoft Mobile Center App
|
||||
*
|
||||
* Copyright (c) Microsoft Corporation
|
||||
*
|
||||
* All rights reserved.
|
||||
*
|
||||
* MIT License
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of this
|
||||
* software and associated documentation files (the "Software"), to deal in the Software
|
||||
* without restriction, including without limitation the rights to use, copy, modify,
|
||||
* merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
|
||||
* permit persons to whom the Software is furnished to do so, subject to the following
|
||||
* conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all copies or
|
||||
* substantial portions of the Software. THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF
|
||||
* ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR
|
||||
* THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { Text, View } from 'react-native';
|
||||
|
||||
export default class Analytics extends React.Component {
|
||||
render() {
|
||||
return (
|
||||
<View>
|
||||
<Text> Analytics </Text>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,152 @@
|
|||
/**
|
||||
* Microsoft Mobile Center App
|
||||
*
|
||||
* Copyright (c) Microsoft Corporation
|
||||
*
|
||||
* All rights reserved.
|
||||
*
|
||||
* MIT License
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of this
|
||||
* software and associated documentation files (the "Software"), to deal in the Software
|
||||
* without restriction, including without limitation the rights to use, copy, modify,
|
||||
* merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
|
||||
* permit persons to whom the Software is furnished to do so, subject to the following
|
||||
* conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all copies or
|
||||
* substantial portions of the Software. THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF
|
||||
* ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR
|
||||
* THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { Text, View, ListView, StyleSheet, Platform, TextInput } from 'react-native';
|
||||
import SearchBar from 'react-native-search-bar';
|
||||
import Button from 'react-native-button';
|
||||
|
||||
import Drawer from 'react-native-drawer';
|
||||
|
||||
import ManageApp from '../../containers/app/Start/ManageAppContainer';
|
||||
|
||||
export default class Build extends React.Component {
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
const ds = new ListView.DataSource({
|
||||
rowHasChanged: (r1, r2) => r1 !== r2,
|
||||
sectionHeaderHasChanged: (s1, s2) => s1 !== s2
|
||||
});
|
||||
|
||||
this.state = {
|
||||
dataSource: ds.cloneWithRows(['Branches ', ' Status', ' Last Commit', ' Last build']),
|
||||
};
|
||||
}
|
||||
|
||||
renderRow(rowData) {
|
||||
return <Text>{rowData}</Text>;
|
||||
}
|
||||
|
||||
renderHeader(rowHeader) {
|
||||
return <Text> { rowHeader } </Text>;
|
||||
}
|
||||
|
||||
renderSectionHeader(sectionData, sectionID) {
|
||||
return (
|
||||
<View style={styles.section}>
|
||||
<Text style={styles.text}>
|
||||
{sectionData}
|
||||
</Text>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<Drawer
|
||||
ref={c => this.drawer = c}
|
||||
type="overlay"
|
||||
openDrawerOffset={0.2}
|
||||
panCloseMask={0.2}
|
||||
content={<ManageApp />}
|
||||
side="right"
|
||||
tapToClose
|
||||
negotiatePan
|
||||
>
|
||||
<View style={styles.container}>
|
||||
<SearchBar
|
||||
ref="searchBar"
|
||||
placeholder="Search"
|
||||
/>
|
||||
<ListView
|
||||
style={styles.listview}
|
||||
dataSource={this.state.dataSource}
|
||||
renderRow={this.renderRow}
|
||||
renderSectionHeader={this.renderSectionHeader}
|
||||
/>
|
||||
|
||||
<Button
|
||||
style={{ fontSize: 20, color: 'green' }}
|
||||
containerStyle={{ padding: 10, height: 45, overflow: 'hidden', borderRadius: 4, backgroundColor: 'orange' }}
|
||||
styleDisabled={{ color: 'red' }}
|
||||
onPress={() => alert('TODO: link to webview')}
|
||||
>
|
||||
Open on GitHub
|
||||
</Button>
|
||||
</View>
|
||||
</Drawer>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
var styles = StyleSheet.create({
|
||||
listview: {
|
||||
backgroundColor: '#B0C4DE',
|
||||
},
|
||||
header: {
|
||||
height: 40,
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
backgroundColor: '#3B5998',
|
||||
flexDirection: 'row',
|
||||
},
|
||||
text: {
|
||||
color: 'white',
|
||||
paddingHorizontal: 8,
|
||||
},
|
||||
rowText: {
|
||||
color: '#888888',
|
||||
},
|
||||
thumbText: {
|
||||
fontSize: 20,
|
||||
color: '#888888',
|
||||
},
|
||||
buttonContents: {
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
marginHorizontal: 5,
|
||||
marginVertical: 3,
|
||||
padding: 5,
|
||||
backgroundColor: '#EAEAEA',
|
||||
borderRadius: 3,
|
||||
paddingVertical: 10,
|
||||
},
|
||||
img: {
|
||||
width: 64,
|
||||
height: 64,
|
||||
marginHorizontal: 10,
|
||||
backgroundColor: 'transparent',
|
||||
},
|
||||
section: {
|
||||
flexDirection: 'column',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'flex-start',
|
||||
padding: 6,
|
||||
backgroundColor: '#5890ff',
|
||||
},
|
||||
});
|
|
@ -0,0 +1,38 @@
|
|||
/**
|
||||
* Microsoft Mobile Center App
|
||||
*
|
||||
* Copyright (c) Microsoft Corporation
|
||||
*
|
||||
* All rights reserved.
|
||||
*
|
||||
* MIT License
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of this
|
||||
* software and associated documentation files (the "Software"), to deal in the Software
|
||||
* without restriction, including without limitation the rights to use, copy, modify,
|
||||
* merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
|
||||
* permit persons to whom the Software is furnished to do so, subject to the following
|
||||
* conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all copies or
|
||||
* substantial portions of the Software. THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF
|
||||
* ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR
|
||||
* THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { Text, View } from 'react-native';
|
||||
|
||||
export default class Crash extends React.Component {
|
||||
render() {
|
||||
return (
|
||||
<View>
|
||||
<Text> Crash </Text>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
}
|
|
@ -25,79 +25,68 @@
|
|||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { StyleSheet, Image, Text, TextInput, Linking, View} from 'react-native';
|
||||
import { Text, View, StyleSheet, ScrollView, Image } from 'react-native';
|
||||
|
||||
import DeviceInfo from 'react-native-device-info';
|
||||
import { Actions } from 'react-native-router-flux';
|
||||
import Icon from 'react-native-vector-icons/Ionicons';
|
||||
import Button from '../components/button';
|
||||
import Button from 'react-native-button';
|
||||
|
||||
import {
|
||||
Card,
|
||||
CardImage,
|
||||
CardTitle,
|
||||
CardContent,
|
||||
CardAction
|
||||
} from 'react-native-card-view';
|
||||
|
||||
export default class Distribute extends React.Component {
|
||||
|
||||
class About extends React.Component{
|
||||
render(){
|
||||
return (
|
||||
<View style={styles.container}>
|
||||
<View style={styles.content}>
|
||||
<View style={styles.center}>
|
||||
_renderTitle (title) {
|
||||
return (
|
||||
<View style={{flex: 1, alignItems: 'center', marginTop: 20}}>
|
||||
<Text style={{fontSize: 10}}>{title}</Text>
|
||||
</View>
|
||||
)
|
||||
}
|
||||
|
||||
<Text style={styles.title}>
|
||||
React Native Mobile Center
|
||||
</Text>
|
||||
|
||||
</View>
|
||||
render() {
|
||||
return (
|
||||
<ScrollView>
|
||||
<View style={styles.container}>
|
||||
<Card styles={{card: {width: 300, height: 100 }}}>
|
||||
<CardTitle>
|
||||
<Text style={styles.title}>Collaborators</Text>
|
||||
</CardTitle>
|
||||
<CardContent>
|
||||
<Text>Who do you collaborate with?</Text>
|
||||
</CardContent>
|
||||
</Card>
|
||||
<Card styles={{card: {width: 300, height: 100 }}}>
|
||||
<CardTitle>
|
||||
<Text style={styles.title}>Alpha Testers</Text>
|
||||
</CardTitle>
|
||||
<CardContent>
|
||||
<Text>With whom?</Text>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
</ScrollView>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
flex: 1,
|
||||
flexDirection: 'column',
|
||||
},
|
||||
content: {
|
||||
flex: 1,
|
||||
justifyContent: 'center',
|
||||
paddingBottom: 10
|
||||
},
|
||||
center: {
|
||||
flex: 1,
|
||||
alignItems: 'center'
|
||||
},
|
||||
logo: {
|
||||
width: 110,
|
||||
height: 110,
|
||||
marginTop: 50
|
||||
},
|
||||
version: {
|
||||
fontSize: 16,
|
||||
textAlign: 'center',
|
||||
color: '#aaaaaa',
|
||||
marginTop: 5
|
||||
marginTop: 60,
|
||||
marginBottom: 60
|
||||
},
|
||||
title: {
|
||||
fontSize: 28,
|
||||
textAlign: 'center',
|
||||
color: '#313131',
|
||||
marginTop: 10
|
||||
fontSize: 16,
|
||||
backgroundColor: 'transparent'
|
||||
},
|
||||
subtitle: {
|
||||
fontSize: 18,
|
||||
textAlign: 'center',
|
||||
color: '#4e4e4e'
|
||||
button: {
|
||||
marginRight: 10
|
||||
},
|
||||
disclaimerContent: {
|
||||
flexDirection: 'column'
|
||||
},
|
||||
disclaimer: {
|
||||
fontSize: 14,
|
||||
textAlign: 'center'
|
||||
},
|
||||
bottomContainer: {
|
||||
alignItems: 'center'
|
||||
card: {
|
||||
width: 300
|
||||
}
|
||||
});
|
||||
|
||||
export default About;
|
|
@ -0,0 +1,66 @@
|
|||
/**
|
||||
* Microsoft Mobile Center App
|
||||
*
|
||||
* Copyright (c) Microsoft Corporation
|
||||
*
|
||||
* All rights reserved.
|
||||
*
|
||||
* MIT License
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of this
|
||||
* software and associated documentation files (the "Software"), to deal in the Software
|
||||
* without restriction, including without limitation the rights to use, copy, modify,
|
||||
* merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
|
||||
* permit persons to whom the Software is furnished to do so, subject to the following
|
||||
* conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all copies or
|
||||
* substantial portions of the Software. THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF
|
||||
* ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR
|
||||
* THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { Text, View } from 'react-native';
|
||||
import Button from 'react-native-button';
|
||||
import Drawer from 'react-native-drawer';
|
||||
|
||||
import ManageApp from '../../containers/app/Start/ManageAppContainer';
|
||||
|
||||
export default class Start extends React.Component {
|
||||
|
||||
render() {
|
||||
return (
|
||||
<Drawer
|
||||
ref={c => this.drawer = c}
|
||||
type="overlay"
|
||||
openDrawerOffset={0.2}
|
||||
panCloseMask={0.2}
|
||||
content={<ManageApp />}
|
||||
side="right"
|
||||
tapToClose
|
||||
negotiatePan
|
||||
>
|
||||
<View>
|
||||
<Button
|
||||
style={{ fontSize: 20, color: 'green'}}
|
||||
containerStyle={{ padding: 10, height: 45, overflow: 'hidden', borderRadius: 4, backgroundColor: 'orange'}}
|
||||
styleDisabled={{ color: 'red' }}
|
||||
onPress={() => this.drawer.open()}
|
||||
>
|
||||
Manage App
|
||||
</Button>
|
||||
<Text> Add Mobile Center’s SDK to your app. </Text>
|
||||
|
||||
<Text> 1. Integrate using CocoaPods </Text>
|
||||
<Text> 2. Start the SDK </Text>
|
||||
<Text> 3. Explore Data </Text>
|
||||
</View>
|
||||
</Drawer>
|
||||
);
|
||||
}
|
||||
}
|
|
@ -24,24 +24,26 @@
|
|||
*
|
||||
*/
|
||||
|
||||
/* global expect */
|
||||
import React from 'react';
|
||||
import { StyleSheet, Text, View } from 'react-native';
|
||||
|
||||
import app from '../apps';
|
||||
import { receiveAppList } from '../../actions/apps';
|
||||
import * as types from '../../actions/types';
|
||||
import { TOKENID } from '../../utils/RequestUtil';
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
flex: 1,
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
backgroundColor: '#F5FCFF',
|
||||
borderWidth: 2,
|
||||
borderColor: 'red',
|
||||
},
|
||||
});
|
||||
|
||||
describe('app reducer test', () => {
|
||||
it(`should handle RECEIVE_APP_LIST`, () => {
|
||||
expect(
|
||||
app({}, receiveAppList(['hokeyapp', 'test'], TOKENID))
|
||||
).toEqual(
|
||||
{
|
||||
isRefreshing: false,
|
||||
isLoadMore: false,
|
||||
noMore: false,
|
||||
appList: ['hokeyapp', 'test']
|
||||
}
|
||||
)
|
||||
})
|
||||
});
|
||||
export default class ManageApp extends React.Component {
|
||||
render() {
|
||||
return (
|
||||
<View style={styles.container}>
|
||||
<Text> Wow, managemy app </Text>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,38 @@
|
|||
/**
|
||||
* Microsoft Mobile Center App
|
||||
*
|
||||
* Copyright (c) Microsoft Corporation
|
||||
*
|
||||
* All rights reserved.
|
||||
*
|
||||
* MIT License
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of this
|
||||
* software and associated documentation files (the "Software"), to deal in the Software
|
||||
* without restriction, including without limitation the rights to use, copy, modify,
|
||||
* merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
|
||||
* permit persons to whom the Software is furnished to do so, subject to the following
|
||||
* conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all copies or
|
||||
* substantial portions of the Software. THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF
|
||||
* ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR
|
||||
* THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { Text, View } from 'react-native';
|
||||
|
||||
export default class Test extends React.Component {
|
||||
render() {
|
||||
return (
|
||||
<View>
|
||||
<Text> Mobile Center Test includes a free trial for UI testing. </Text>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,79 @@
|
|||
/**
|
||||
* Microsoft Mobile Center App
|
||||
*
|
||||
* Copyright (c) Microsoft Corporation
|
||||
*
|
||||
* All rights reserved.
|
||||
*
|
||||
* MIT License
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of this
|
||||
* software and associated documentation files (the "Software"), to deal in the Software
|
||||
* without restriction, including without limitation the rights to use, copy, modify,
|
||||
* merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
|
||||
* permit persons to whom the Software is furnished to do so, subject to the following
|
||||
* conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all copies or
|
||||
* substantial portions of the Software. THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF
|
||||
* ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR
|
||||
* THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*
|
||||
*/
|
||||
|
||||
import apps from '../apps';
|
||||
import {REQUEST_APPS, RECEIVE_APPS, RECEIVE_APPS_ERROR} from '../../actions/types';
|
||||
|
||||
describe('Reducers/ apps', () => {
|
||||
function getInitState() {
|
||||
return {
|
||||
status: 'init'
|
||||
};
|
||||
}
|
||||
let state = {};
|
||||
beforeEach(() => {
|
||||
state = getInitState();
|
||||
});
|
||||
|
||||
it('should handle initial state', () => {
|
||||
expect(
|
||||
apps(state, {})
|
||||
).toEqual(
|
||||
{ status: 'init' }
|
||||
);
|
||||
});
|
||||
it('should handle request apps', () => {
|
||||
expect(
|
||||
apps(state, {
|
||||
type: REQUEST_APPS
|
||||
})
|
||||
).toEqual({
|
||||
status: 'isFetching'
|
||||
});
|
||||
});
|
||||
it('should handle request apps success', () => {
|
||||
expect(apps(state, {
|
||||
type: RECEIVE_APPS,
|
||||
response: {
|
||||
apps: ['BOA', 'Chase'],
|
||||
receivedAt: '12314'
|
||||
}
|
||||
})).toEqual({
|
||||
status: 'receiveapps',
|
||||
apps: ['BOA', 'Chase'],
|
||||
lastUpdated: '12314'
|
||||
});
|
||||
});
|
||||
it('should handle request apps error', () => {
|
||||
expect(apps(state, {
|
||||
type: RECEIVE_APPS_ERROR,
|
||||
error: 'error message'
|
||||
})).toEqual({
|
||||
status: 'error',
|
||||
error: 'error message'
|
||||
});
|
||||
});
|
||||
});
|
|
@ -24,53 +24,61 @@
|
|||
*
|
||||
*/
|
||||
|
||||
import { put, call } from 'redux-saga/effects';
|
||||
import { requestAppList } from '../apps';
|
||||
import { request, TOKENID } from '../../utils/RequestUtil';
|
||||
import { fetchAppList, receiveAppList } from '../../actions/apps';
|
||||
import login from '../login';
|
||||
import { LOGIN_REQUEST, LOGIN_SUCCESS, LOGIN_ERROR } from '../../actions/types';
|
||||
|
||||
/* global expect */
|
||||
describe('apps saga tests', () => {
|
||||
const {
|
||||
isRefreshing,
|
||||
loading,
|
||||
tokenId,
|
||||
isLoadMore,
|
||||
page
|
||||
} = {
|
||||
isRefreshing: false,
|
||||
loading: false,
|
||||
tokenId: request.TOKENID,
|
||||
isLoadMore: false,
|
||||
page: 1
|
||||
};
|
||||
const generator = requestAppList(
|
||||
isRefreshing,
|
||||
loading,
|
||||
tokenId,
|
||||
isLoadMore,
|
||||
page
|
||||
);
|
||||
const mockArticleList = {
|
||||
showapi_res_body: {
|
||||
pagebean: {
|
||||
contentlist: [
|
||||
],
|
||||
},
|
||||
},
|
||||
};
|
||||
const step = input => generator.next(input).value;
|
||||
describe('Reducers/ login', () => {
|
||||
|
||||
it(`should put(fetchAppList(${isRefreshing}, ${loading}, ${isLoadMore}))`, () => {
|
||||
const next = step();
|
||||
// console.log(next);
|
||||
const tt = put(fetchAppList(isRefreshing, loading, isLoadMore));
|
||||
// console.log(tt);
|
||||
// expect(next).toEqual();
|
||||
});
|
||||
function getInitState() {
|
||||
return {
|
||||
status: 'init'
|
||||
};
|
||||
}
|
||||
|
||||
it('just for fun', () => {
|
||||
const tt = 1;
|
||||
expect(tt).toEqual(1);
|
||||
});
|
||||
});
|
||||
let state = {};
|
||||
beforeEach(() => {
|
||||
state = getInitState();
|
||||
});
|
||||
|
||||
it('should handle initial state', () => {
|
||||
expect(
|
||||
login(state, {})
|
||||
).toEqual(
|
||||
{ status: 'init' }
|
||||
);
|
||||
});
|
||||
|
||||
it('should handle request login', () => {
|
||||
expect(
|
||||
login(state, {
|
||||
type: LOGIN_REQUEST
|
||||
})
|
||||
).toEqual({
|
||||
status: 'loading'
|
||||
});
|
||||
});
|
||||
|
||||
it('should handle login success', () => {
|
||||
expect(login(state, {
|
||||
type: LOGIN_SUCCESS,
|
||||
response: {
|
||||
username: 'denny',
|
||||
token: '12314'
|
||||
}
|
||||
})).toEqual({
|
||||
status: 'logined',
|
||||
username: 'denny',
|
||||
token: '12314'
|
||||
});
|
||||
});
|
||||
|
||||
it('should handle login error', () => {
|
||||
expect(login(state, {
|
||||
type: LOGIN_ERROR,
|
||||
error: 'error message'
|
||||
})).toEqual({
|
||||
status: 'error',
|
||||
error: 'error message'
|
||||
});
|
||||
});
|
||||
});
|
|
@ -24,32 +24,28 @@
|
|||
*
|
||||
*/
|
||||
|
||||
import * as types from '../actions/types';
|
||||
import { REQUEST_APPS, RECEIVE_APPS, RECEIVE_APPS_ERROR } from '../actions/types';
|
||||
|
||||
const initialState = {
|
||||
isRefreshing: false,
|
||||
loading: false,
|
||||
isLoadMore: false,
|
||||
noMore: false,
|
||||
appList: []
|
||||
};
|
||||
|
||||
export default function app(state = initialState, action){
|
||||
switch(action.type){
|
||||
case types.FETCH_APP_LIST:
|
||||
return Object.assign({}, state, {
|
||||
isRefreshing: action.isRefreshing,
|
||||
loading: action.loading,
|
||||
isLoadMore: action.isLoadMore
|
||||
});
|
||||
case types.RECEIVE_APP_LIST:
|
||||
return Object.assign({}, state, {
|
||||
isRefreshing: false,
|
||||
isLoadMore: false,
|
||||
noMore: action.appList.length == 0,
|
||||
appList: action.appList
|
||||
});
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
}
|
||||
export default function apps(state = {
|
||||
status: 'init',
|
||||
}, action) {
|
||||
switch (action.type) {
|
||||
case REQUEST_APPS:
|
||||
return Object.assign({}, state, {
|
||||
status: 'isFetching',
|
||||
});
|
||||
case RECEIVE_APPS:
|
||||
return Object.assign({}, state, {
|
||||
status: 'receiveapps',
|
||||
apps: action.response.apps,
|
||||
lastUpdated: action.response.receivedAt
|
||||
});
|
||||
case RECEIVE_APPS_ERROR:
|
||||
return {
|
||||
status: 'error',
|
||||
error: action.error
|
||||
};
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -24,13 +24,15 @@
|
|||
*
|
||||
*/
|
||||
|
||||
import {combineReducers} from 'redux';
|
||||
import app from './apps'
|
||||
import routes from './routes'
|
||||
import { combineReducers } from 'redux';
|
||||
import app from './apps';
|
||||
import routes from './routes';
|
||||
import login from './login';
|
||||
|
||||
const rootReducer = combineReducers({
|
||||
app,
|
||||
routes
|
||||
app,
|
||||
routes,
|
||||
login
|
||||
});
|
||||
|
||||
export default rootReducer;
|
|
@ -0,0 +1,25 @@
|
|||
import { LOGIN_REQUEST, LOGIN_SUCCESS , LOGIN_ERROR } from '../actions/types';
|
||||
|
||||
export default function login(state = {
|
||||
status: 'init'
|
||||
}, action) {
|
||||
switch (action.type) {
|
||||
case LOGIN_REQUEST:
|
||||
return {
|
||||
status: 'loading'
|
||||
};
|
||||
case LOGIN_SUCCESS:
|
||||
return {
|
||||
status: 'logined',
|
||||
username: action.response.username,
|
||||
token: action.response.token
|
||||
};
|
||||
case LOGIN_ERROR:
|
||||
return {
|
||||
status: 'error',
|
||||
error: action.error
|
||||
};
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
}
|
|
@ -24,19 +24,19 @@
|
|||
*
|
||||
*/
|
||||
|
||||
import {ActionConst} from 'react-native-router-flux';
|
||||
import { ActionConst } from 'react-native-router-flux';
|
||||
|
||||
const initialState = {
|
||||
scene: {},
|
||||
scene: {},
|
||||
};
|
||||
|
||||
export default function reducer(state = initialState, action = {}){
|
||||
switch(action.type){
|
||||
case ActionConst.FOCUS:
|
||||
return Object.assign({}, state, {
|
||||
scene: action.scene
|
||||
});
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
}
|
||||
export default function reducer(state = initialState, action = {}) {
|
||||
switch (action.type) {
|
||||
case ActionConst.FOCUS:
|
||||
return Object.assign({}, state, {
|
||||
scene: action.scene
|
||||
});
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,82 @@
|
|||
/**
|
||||
* Microsoft Mobile Center App
|
||||
*
|
||||
* Copyright (c) Microsoft Corporation
|
||||
*
|
||||
* All rights reserved.
|
||||
*
|
||||
* MIT License
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of this
|
||||
* software and associated documentation files (the "Software"), to deal in the Software
|
||||
* without restriction, including without limitation the rights to use, copy, modify,
|
||||
* merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
|
||||
* permit persons to whom the Software is furnished to do so, subject to the following
|
||||
* conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all copies or
|
||||
* substantial portions of the Software. THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF
|
||||
* ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR
|
||||
* THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*
|
||||
*/
|
||||
|
||||
import { takeEvery, call, put, fork } from 'redux-saga/effects';
|
||||
import { REQUEST_APPS, RECEIVE_APPS, RECEIVE_APPS_ERROR } from '../../actions/types';
|
||||
import { fetchApps, fetchAppsFlow, watchFetchAppList } from '../apps';
|
||||
import { appsAPI } from '../../utils/RequestUtil';
|
||||
|
||||
describe('Sagas/ apps', () => {
|
||||
|
||||
describe('watchFetchAppList', () => {
|
||||
const generator = watchFetchAppList();
|
||||
it('should take every fetch app request', () => {
|
||||
const expected = takeEvery(REQUEST_APPS, fetchAppsFlow);
|
||||
const actual = generator.next().value;
|
||||
expect(expected.name).toEqual(actual.name);
|
||||
});
|
||||
});
|
||||
|
||||
describe('fetchAppsFlow', () => {
|
||||
const generator = fetchAppsFlow({ tokenId: '1938077bc136d1b81c65c4d74fc4f593d9c28b02' });
|
||||
it('should fork a fetchApps test', () => {
|
||||
const expected = fork(fetchApps, { tokenId: '1938077bc136d1b81c65c4d74fc4f593d9c28b02' });
|
||||
const actual = generator.next().value;
|
||||
expect(expected).toEqual(actual);
|
||||
});
|
||||
});
|
||||
|
||||
describe('fetchApps', () => {
|
||||
const generator = fetchApps({ tokenId: '1938077bc136d1b81c65c4d74fc4f593d9c28b02' });
|
||||
it('should call appsAPI', () => {
|
||||
const expected = call(appsAPI,
|
||||
{ tokenId: '1938077bc136d1b81c65c4d74fc4f593d9c28b02' }
|
||||
);
|
||||
const actual = generator.next().value;
|
||||
expect(expected).toEqual(actual);
|
||||
});
|
||||
|
||||
it('should handle fetchApps success', () => {
|
||||
const getResponse = () => ({ token: 'faketoken' });
|
||||
const expected = put({
|
||||
type: RECEIVE_APPS,
|
||||
response: { token: 'faketoken' }
|
||||
});
|
||||
const actual = generator.next(getResponse()).value;
|
||||
expect(expected).toEqual(actual);
|
||||
});
|
||||
|
||||
it('should handle fetchApps error', () => {
|
||||
const error = 'error message';
|
||||
const expected = put({
|
||||
type: RECEIVE_APPS_ERROR,
|
||||
error: 'error message'
|
||||
});
|
||||
const actual = generator.throw(error).value;
|
||||
expect(expected).toEqual(actual);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,98 @@
|
|||
/**
|
||||
* Microsoft Mobile Center App
|
||||
*
|
||||
* Copyright (c) Microsoft Corporation
|
||||
*
|
||||
* All rights reserved.
|
||||
*
|
||||
* MIT License
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of this
|
||||
* software and associated documentation files (the "Software"), to deal in the Software
|
||||
* without restriction, including without limitation the rights to use, copy, modify,
|
||||
* merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
|
||||
* permit persons to whom the Software is furnished to do so, subject to the following
|
||||
* conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all copies or
|
||||
* substantial portions of the Software. THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF
|
||||
* ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR
|
||||
* THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*
|
||||
*/
|
||||
|
||||
import { takeEvery, call, put, fork } from 'redux-saga/effects';
|
||||
import { LOGIN_REQUEST, LOGIN_SUCCESS, LOGIN_ERROR } from '../../actions/types';
|
||||
import { watchRequestLogin, loginFlow, authorize } from '../login';
|
||||
import { loginAPI } from '../../utils/RequestUtil';
|
||||
|
||||
describe('Sagas/ login', () => {
|
||||
describe('watchRequestLogin', () => {
|
||||
const generator = watchRequestLogin();
|
||||
it('should take every login request', () => {
|
||||
const expected = takeEvery(LOGIN_REQUEST, loginFlow);
|
||||
const actual = generator.next().value;
|
||||
expect(expected.name).toEqual(actual.name);
|
||||
});
|
||||
});
|
||||
describe('loginFlow', () => {
|
||||
const generator = loginFlow({
|
||||
type: LOGIN_REQUEST,
|
||||
username: 'buptkang@gmail.com',
|
||||
password: 'Kb@241307684'
|
||||
});
|
||||
it('should fork a authorize test', () => {
|
||||
const expected = fork(authorize, {
|
||||
username: 'buptkang@gmail.com',
|
||||
password: 'Kb@241307684'
|
||||
});
|
||||
const actual = generator.next().value;
|
||||
expect(expected).toEqual(actual);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Authorize', () => {
|
||||
const generator = authorize({
|
||||
username: 'buptkang@gmail.com',
|
||||
password: 'Kb@241307684'
|
||||
});
|
||||
|
||||
it('should call loginAPI', () => {
|
||||
const expected = call(loginAPI, {
|
||||
username: 'buptkang@gmail.com',
|
||||
password: 'Kb@241307684'
|
||||
});
|
||||
const actual = generator.next().value;
|
||||
expect(expected).toEqual(actual);
|
||||
});
|
||||
|
||||
it('should handle login success', () => {
|
||||
const getResponse = () => ({
|
||||
username: 'denny',
|
||||
token: 'fake token'
|
||||
});
|
||||
const expected = put({
|
||||
type: LOGIN_SUCCESS,
|
||||
response: {
|
||||
username: 'denny',
|
||||
token: 'fake token'
|
||||
}
|
||||
});
|
||||
const actual = generator.next(getResponse()).value;
|
||||
expect(expected).toEqual(actual);
|
||||
});
|
||||
|
||||
it('should handle login error', () => {
|
||||
const error = 'error message';
|
||||
const expected = put({
|
||||
type: LOGIN_ERROR,
|
||||
error: 'error message'
|
||||
});
|
||||
const actual = generator.throw(error).value;
|
||||
expect(expected).toEqual(actual);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -24,35 +24,34 @@
|
|||
*
|
||||
*/
|
||||
|
||||
import {put, take, call, fork} from 'redux-saga/effects';
|
||||
import { takeEvery, put, call, fork } from 'redux-saga/effects';
|
||||
import { REQUEST_APPS, RECEIVE_APPS, RECEIVE_APPS_ERROR } from '../actions/types';
|
||||
import { toastShort } from '../utils/ToastUtil';
|
||||
import { appsAPI } from '../utils/RequestUtil';
|
||||
|
||||
import * as types from '../actions/types';
|
||||
import {fetchAppList, receiveAppList} from '../actions/apps';
|
||||
import {toastShort} from '../utils/ToastUtil';
|
||||
import {request} from '../utils/RequestUtil';
|
||||
|
||||
const get_apps = "/v0.1/apps";
|
||||
|
||||
export function* requestAppList(isRefreshing, loading, tokenId, isLoadMore, page){
|
||||
try{
|
||||
yield put(fetchAppList(isRefreshing, loading, isLoadMore));
|
||||
const appList = yield call(request, get_apps, 'get');
|
||||
yield put(receiveAppList(appList,tokenId));
|
||||
}catch(error){
|
||||
yield put(receiveAppList([], tokenId));
|
||||
toastShort('Network Error, Please Retry!!!');
|
||||
}
|
||||
export function* watchFetchAppList() {
|
||||
yield takeEvery(REQUEST_APPS, fetchAppsFlow);
|
||||
}
|
||||
|
||||
export function* watchRequestAppList(){
|
||||
while(true){
|
||||
const {
|
||||
isRefreshing,
|
||||
loading,
|
||||
tokenId,
|
||||
isLoadMore,
|
||||
page
|
||||
} = yield take(types.REQUEST_APP_LIST);
|
||||
yield fork(requestAppList, isRefreshing, loading, tokenId, isLoadMore, page);
|
||||
}
|
||||
}
|
||||
export function* fetchAppsFlow(action) {
|
||||
yield fork(fetchApps,
|
||||
{ tokenId: action.tokenId });
|
||||
}
|
||||
|
||||
export function* fetchApps({ tokenId }) {
|
||||
try {
|
||||
const response = yield call(appsAPI, {
|
||||
tokenId
|
||||
});
|
||||
yield put({
|
||||
type: RECEIVE_APPS,
|
||||
response
|
||||
});
|
||||
} catch (error) {
|
||||
yield put({
|
||||
type: RECEIVE_APPS_ERROR,
|
||||
error
|
||||
});
|
||||
toastShort('Network Error, Please Retry!!!');
|
||||
}
|
||||
}
|
||||
|
|
|
@ -23,12 +23,14 @@
|
|||
* THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*
|
||||
*/
|
||||
|
||||
import { fork } from 'redux-saga/effects';
|
||||
import { watchRequestAppList } from './apps';
|
||||
|
||||
import { watchFetchAppList } from './apps';
|
||||
import { watchRequestLogin } from './login';
|
||||
|
||||
export default function* rootSaga(){
|
||||
yield [
|
||||
fork(watchRequestAppList)
|
||||
];
|
||||
}
|
||||
yield [
|
||||
fork(watchRequestLogin),
|
||||
fork(watchFetchAppList)
|
||||
];
|
||||
}
|
||||
|
|
|
@ -0,0 +1,59 @@
|
|||
/**
|
||||
* Microsoft Mobile Center App
|
||||
*
|
||||
* Copyright (c) Microsoft Corporation
|
||||
*
|
||||
* All rights reserved.
|
||||
*
|
||||
* MIT License
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of this
|
||||
* software and associated documentation files (the "Software"), to deal in the Software
|
||||
* without restriction, including without limitation the rights to use, copy, modify,
|
||||
* merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
|
||||
* permit persons to whom the Software is furnished to do so, subject to the following
|
||||
* conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all copies or
|
||||
* substantial portions of the Software. THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF
|
||||
* ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR
|
||||
* THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*
|
||||
*/
|
||||
|
||||
import { } from 'redux-saga';
|
||||
import { takeEvery, call, put, fork } from 'redux-saga/effects';
|
||||
|
||||
import { LOGIN_REQUEST, LOGIN_SUCCESS, LOGIN_ERROR } from '../actions/types';
|
||||
|
||||
import { loginAPI } from '../utils/RequestUtil';
|
||||
|
||||
export function* watchRequestLogin() {
|
||||
yield takeEvery(LOGIN_REQUEST, loginFlow);
|
||||
}
|
||||
|
||||
export function* loginFlow(action) {
|
||||
yield fork(authorize,
|
||||
{username: action.username, password: action.password });
|
||||
}
|
||||
|
||||
export function* authorize({username, password}) {
|
||||
try {
|
||||
const response = yield call(loginAPI, {
|
||||
username,
|
||||
password
|
||||
});
|
||||
yield put({
|
||||
type: LOGIN_SUCCESS,
|
||||
response
|
||||
});
|
||||
} catch (error) {
|
||||
yield put({
|
||||
type: LOGIN_ERROR,
|
||||
error
|
||||
});
|
||||
}
|
||||
}
|
|
@ -31,14 +31,12 @@ import rootSaga from './sagas/index';
|
|||
import MobileCenter from './containers/mobilecenter';
|
||||
|
||||
const store = configureStore();
|
||||
|
||||
//run root saga
|
||||
store.runSaga(rootSaga);
|
||||
|
||||
const setup = () => (
|
||||
<Provider store={store}>
|
||||
<MobileCenter/>
|
||||
</Provider>
|
||||
<Provider store={store}>
|
||||
<MobileCenter />
|
||||
</Provider>
|
||||
);
|
||||
|
||||
export default setup;
|
|
@ -24,28 +24,29 @@
|
|||
*
|
||||
*/
|
||||
|
||||
import {createStore, applyMiddleware} from 'redux';
|
||||
import createSagaMiddleware, {END} from 'redux-saga';
|
||||
import { createStore, applyMiddleware } from 'redux';
|
||||
import createSagaMiddleware, { END } from 'redux-saga';
|
||||
|
||||
import rootReducer from '../reducers/index';
|
||||
|
||||
const middlewares = [];
|
||||
const createLogger = require('redux-logger');
|
||||
|
||||
//configure saga middleware
|
||||
// configure saga middleware
|
||||
const sagaMiddleware = createSagaMiddleware();
|
||||
middlewares.push(sagaMiddleware);
|
||||
|
||||
if(process.env.NODE_ENV === 'development'){
|
||||
const logger = createLogger();
|
||||
middlewares.push(logger);
|
||||
if (process.env.NODE_ENV === 'development'){
|
||||
const logger = createLogger();
|
||||
middlewares.push(logger);
|
||||
}
|
||||
const createStoreWithMiddleware = applyMiddleware(...middlewares)(createStore);
|
||||
|
||||
export default function configureStore(initialState) {
|
||||
const store = createStoreWithMiddleware(rootReducer, initialState);
|
||||
// install saga run
|
||||
store.runSaga = sagaMiddleware.run;
|
||||
store.close = () => store.dispatch(END);
|
||||
return store;
|
||||
}
|
||||
const store = createStoreWithMiddleware(rootReducer, initialState);
|
||||
window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__();
|
||||
// install saga run
|
||||
store.runSaga = sagaMiddleware.run;
|
||||
store.close = () => store.dispatch(END);
|
||||
return store;
|
||||
}
|
||||
|
|
|
@ -24,38 +24,41 @@
|
|||
*
|
||||
*/
|
||||
|
||||
const HOST = "https://api.mobile.azure.com";
|
||||
function checkStatus(response) {
|
||||
if (response.status >= 200 && response.status < 300) {
|
||||
return response;
|
||||
}
|
||||
const error = new Error(response.statusText);
|
||||
error.response = response;
|
||||
throw error;
|
||||
}
|
||||
|
||||
export const TOKENID = "2eb6d0e2250779ad71acde8f383158b48aa0b4b6";
|
||||
function parseJSON(response) {
|
||||
return response.json();
|
||||
}
|
||||
|
||||
export const request = (url, method, body) => {
|
||||
let isOk;
|
||||
return new Promise((resolve, reject) => {
|
||||
fetch(HOST + url, {
|
||||
method,
|
||||
headers:{
|
||||
'Accept': 'application/json',
|
||||
'X-API-Token': TOKENID
|
||||
},
|
||||
body
|
||||
})
|
||||
.then((response) => {
|
||||
if(response.ok){
|
||||
isOk = true;
|
||||
}else{
|
||||
isOk = false;
|
||||
}
|
||||
return response.json();
|
||||
})
|
||||
.then((responseData) => {
|
||||
if(isOk){
|
||||
resolve(responseData);
|
||||
} else {
|
||||
reject(responseData);
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
reject(error);
|
||||
});
|
||||
});
|
||||
};
|
||||
function fetchAPI(url, options) {
|
||||
return fetch(url, options)
|
||||
.then(checkStatus)
|
||||
.then(parseJSON);
|
||||
}
|
||||
|
||||
export function loginAPI({ username, password }) {
|
||||
return fetchAPI('https://api.mobile.azure.com/v0.1/api_tokens', {
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
method: 'POST',
|
||||
body: JSON.stringify({ username, password })
|
||||
});
|
||||
}
|
||||
|
||||
export function appsAPI({ tokenID }) {
|
||||
return fetchAPI('https://api.mobile.azure.com/v0.1/apps', {
|
||||
headers: {
|
||||
'Content-Type': 'applicatin/json',
|
||||
'X-API-Token': tokenID
|
||||
},
|
||||
method: 'GET'
|
||||
});
|
||||
}
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"allowJs": true,
|
||||
"allowSyntheticDefaultImports": true
|
||||
},
|
||||
"exclude": [
|
||||
"node_modules"
|
||||
]
|
||||
}
|
18
package.json
18
package.json
|
@ -5,27 +5,38 @@
|
|||
"scripts": {
|
||||
"start": "node node_modules/react-native/local-cli/cli.js start",
|
||||
"test": "jest",
|
||||
"lint": "eslint ."
|
||||
"lint": "eslint .",
|
||||
"storybook": "storybook start -p 7007"
|
||||
},
|
||||
"dependencies": {
|
||||
"buffer": "^5.0.2",
|
||||
"moment": "^2.17.1",
|
||||
"react": "~15.4.0-rc.4",
|
||||
"react-native": "0.40.0",
|
||||
"react-native-button": "^1.8.2",
|
||||
"react-native-card-view": "0.0.3",
|
||||
"react-native-code-push": "^1.17.0-beta",
|
||||
"react-native-css": "^1.2.47",
|
||||
"react-native-device-info": "^0.9.9",
|
||||
"react-native-drawer": "^2.3.0",
|
||||
"react-native-push-notification": "^2.2.1",
|
||||
"react-native-router-flux": "^3.37.0",
|
||||
"react-native-scrollable-tab-view": "^0.7.0",
|
||||
"react-native-search-bar": "^3.0.0",
|
||||
"react-native-searchbar": "^1.8.0",
|
||||
"react-native-send-intent": "^1.0.14",
|
||||
"react-native-simple-store": "^1.1.0",
|
||||
"react-native-storage": "^0.1.5",
|
||||
"react-native-vector-icons": "^4.0.0",
|
||||
"react-redux": "^5.0.2",
|
||||
"react-toolbox": "^2.0.0-beta.6",
|
||||
"redux": "^3.6.0",
|
||||
"redux-logger": "^2.7.4",
|
||||
"redux-persist": "^4.0.1",
|
||||
"redux-saga": "^0.14.2",
|
||||
"redux-thunk": "^2.1.0",
|
||||
"reselect": "^2.5.4"
|
||||
"reselect": "^2.5.4",
|
||||
"tcomb-form-native": "^0.6.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"babel-jest": "18.0.0",
|
||||
|
@ -37,7 +48,8 @@
|
|||
"eslint-plugin-react": "^6.9.0",
|
||||
"jest": "18.1.0",
|
||||
"react-test-renderer": "~15.4.0-rc.4",
|
||||
"redux-devtools": "^3.3.2"
|
||||
"redux-devtools": "^3.3.2",
|
||||
"@kadira/react-native-storybook": "^2.0.0"
|
||||
},
|
||||
"jest": {
|
||||
"preset": "react-native"
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
import '@kadira/react-native-storybook/addons';
|
|
@ -0,0 +1,10 @@
|
|||
import { AppRegistry } from 'react-native';
|
||||
import { getStorybookUI, configure } from '@kadira/react-native-storybook';
|
||||
|
||||
// import stories
|
||||
configure(() => {
|
||||
require('./stories');
|
||||
}, module);
|
||||
|
||||
const StorybookUI = getStorybookUI({port: 7007, host: 'localhost'});
|
||||
AppRegistry.registerComponent('MobileCenterReactNativeApp', () => StorybookUI);
|
|
@ -0,0 +1,10 @@
|
|||
import { AppRegistry } from 'react-native';
|
||||
import { getStorybookUI, configure } from '@kadira/react-native-storybook';
|
||||
|
||||
// import stories
|
||||
configure(() => {
|
||||
require('./stories');
|
||||
}, module);
|
||||
|
||||
const StorybookUI = getStorybookUI({port: 7007, host: 'localhost'});
|
||||
AppRegistry.registerComponent('MobileCenterReactNativeApp', () => StorybookUI);
|
|
@ -0,0 +1,10 @@
|
|||
import React from 'react';
|
||||
import { TouchableNativeFeedback } from 'react-native';
|
||||
|
||||
export default function Button(props) {
|
||||
return (
|
||||
<TouchableNativeFeedback onPress={props.onPress || Function()}>
|
||||
{props.children}
|
||||
</TouchableNativeFeedback>
|
||||
);
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
import React from 'react';
|
||||
import { TouchableHighlight } from 'react-native';
|
||||
|
||||
export default function Button(props) {
|
||||
return (
|
||||
<TouchableHighlight onPress={props.onPress || Function()}>
|
||||
{props.children}
|
||||
</TouchableHighlight>
|
||||
);
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
import React from 'react';
|
||||
import { View } from 'react-native';
|
||||
import style from './style';
|
||||
|
||||
export default function CenterView(props) {
|
||||
return (
|
||||
<View style={style.main}>
|
||||
{props.children}
|
||||
</View>
|
||||
);
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
export default {
|
||||
main: {
|
||||
flex: 1,
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
backgroundColor: '#F5FCFF',
|
||||
},
|
||||
};
|
|
@ -0,0 +1,36 @@
|
|||
import React from 'react';
|
||||
import { View, Text } from 'react-native';
|
||||
|
||||
export default class Welcome extends React.Component {
|
||||
styles = {
|
||||
wrapper: {
|
||||
flex: 1,
|
||||
padding: 24,
|
||||
justifyContent: 'center',
|
||||
},
|
||||
header: {
|
||||
fontSize: 18,
|
||||
marginBottom: 18,
|
||||
},
|
||||
content: {
|
||||
fontSize: 12,
|
||||
marginBottom: 10,
|
||||
lineHeight: 18,
|
||||
},
|
||||
}
|
||||
|
||||
showApp(e) {
|
||||
e.preventDefault();
|
||||
if(this.props.showApp) this.props.showApp();
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<View style={this.styles.wrapper}>
|
||||
<Text style={this.styles.header}>Welcome to React Native Storybook</Text>
|
||||
<Text style={this.styles.content}>This is a UI Component development environment for your React Native app. Here you can display and interact with your UI components as stories. A story is a single state of one or more UI components. You can have as many stories as you want. In other words a story is like a visual test case.</Text>
|
||||
<Text style={this.styles.content}>We have added some stories inside the "storybook/stories" directory for examples. Try editing the "storybook/stories/Welcome.js" file to edit this message.</Text>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
import React from 'react';
|
||||
import { Text } from 'react-native';
|
||||
import { storiesOf, action, linkTo } from '@kadira/react-native-storybook';
|
||||
|
||||
import Button from './Button';
|
||||
import CenterView from './CenterView';
|
||||
import Welcome from './Welcome';
|
||||
|
||||
storiesOf('Welcome', module)
|
||||
.add('to Storybook', () => (
|
||||
<Welcome showApp={linkTo('Button')}/>
|
||||
));
|
||||
|
||||
storiesOf('Button', module)
|
||||
.addDecorator(getStory => (
|
||||
<CenterView>{getStory()}</CenterView>
|
||||
))
|
||||
.add('with text', () => (
|
||||
<Button onPress={action('clicked-text')}>
|
||||
<Text>Hello Button</Text>
|
||||
</Button>
|
||||
))
|
||||
.add('with some emoji', () => (
|
||||
<Button onPress={action('clicked-emoji')}>
|
||||
<Text>😀 😎 👍 💯</Text>
|
||||
</Button>
|
||||
));
|
3505
yarn.lock
3505
yarn.lock
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
Загрузка…
Ссылка в новой задаче