packages = new PackageList(this).getPackages();
27 | // Packages that cannot be autolinked yet can be added manually here, for example:
28 | // packages.add(new MyReactNativePackage());
29 | return packages;
30 | }
31 |
32 | @Override
33 | protected String getJSMainModuleName() {
34 | return "index";
35 | }
36 | };
37 |
38 | @Override
39 | public ReactNativeHost getReactNativeHost() {
40 | return mReactNativeHost;
41 | }
42 |
43 | @Override
44 | public void onCreate() {
45 | super.onCreate();
46 | SoLoader.init(this, /* native exopackage */ false);
47 | initializeFlipper(this, getReactNativeHost().getReactInstanceManager());
48 | }
49 |
50 | /**
51 | * Loads Flipper in React Native templates. Call this in the onCreate method with something like
52 | * initializeFlipper(this, getReactNativeHost().getReactInstanceManager());
53 | *
54 | * @param context
55 | * @param reactInstanceManager
56 | */
57 | private static void initializeFlipper(
58 | Context context, ReactInstanceManager reactInstanceManager) {
59 | if (BuildConfig.DEBUG) {
60 | try {
61 | /*
62 | We use reflection here to pick up the class that initializes Flipper,
63 | since Flipper library is not available in release mode
64 | */
65 | Class> aClass = Class.forName("com.reactnativestarterkit.ReactNativeFlipper");
66 | aClass
67 | .getMethod("initializeFlipper", Context.class, ReactInstanceManager.class)
68 | .invoke(null, context, reactInstanceManager);
69 | } catch (ClassNotFoundException e) {
70 | e.printStackTrace();
71 | } catch (NoSuchMethodException e) {
72 | e.printStackTrace();
73 | } catch (IllegalAccessException e) {
74 | e.printStackTrace();
75 | } catch (InvocationTargetException e) {
76 | e.printStackTrace();
77 | }
78 | }
79 | }
80 | }
81 |
--------------------------------------------------------------------------------
/android/gradlew.bat:
--------------------------------------------------------------------------------
1 | @rem
2 | @rem Copyright 2015 the original author or authors.
3 | @rem
4 | @rem Licensed under the Apache License, Version 2.0 (the "License");
5 | @rem you may not use this file except in compliance with the License.
6 | @rem You may obtain a copy of the License at
7 | @rem
8 | @rem https://www.apache.org/licenses/LICENSE-2.0
9 | @rem
10 | @rem Unless required by applicable law or agreed to in writing, software
11 | @rem distributed under the License is distributed on an "AS IS" BASIS,
12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | @rem See the License for the specific language governing permissions and
14 | @rem limitations under the License.
15 | @rem
16 |
17 | @if "%DEBUG%" == "" @echo off
18 | @rem ##########################################################################
19 | @rem
20 | @rem Gradle startup script for Windows
21 | @rem
22 | @rem ##########################################################################
23 |
24 | @rem Set local scope for the variables with windows NT shell
25 | if "%OS%"=="Windows_NT" setlocal
26 |
27 | set DIRNAME=%~dp0
28 | if "%DIRNAME%" == "" set DIRNAME=.
29 | set APP_BASE_NAME=%~n0
30 | set APP_HOME=%DIRNAME%
31 |
32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter.
33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
34 |
35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
37 |
38 | @rem Find java.exe
39 | if defined JAVA_HOME goto findJavaFromJavaHome
40 |
41 | set JAVA_EXE=java.exe
42 | %JAVA_EXE% -version >NUL 2>&1
43 | if "%ERRORLEVEL%" == "0" goto execute
44 |
45 | echo.
46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
47 | echo.
48 | echo Please set the JAVA_HOME variable in your environment to match the
49 | echo location of your Java installation.
50 |
51 | goto fail
52 |
53 | :findJavaFromJavaHome
54 | set JAVA_HOME=%JAVA_HOME:"=%
55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
56 |
57 | if exist "%JAVA_EXE%" goto execute
58 |
59 | echo.
60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
61 | echo.
62 | echo Please set the JAVA_HOME variable in your environment to match the
63 | echo location of your Java installation.
64 |
65 | goto fail
66 |
67 | :execute
68 | @rem Setup the command line
69 |
70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
71 |
72 |
73 | @rem Execute Gradle
74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
75 |
76 | :end
77 | @rem End local scope for the variables with windows NT shell
78 | if "%ERRORLEVEL%"=="0" goto mainEnd
79 |
80 | :fail
81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
82 | rem the _cmd.exe /c_ return code!
83 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
84 | exit /b 1
85 |
86 | :mainEnd
87 | if "%OS%"=="Windows_NT" endlocal
88 |
89 | :omega
90 |
--------------------------------------------------------------------------------
/src/containers/Articles/List.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import PropTypes from 'prop-types';
3 | import { connect } from 'react-redux';
4 | import Layout from '../../components/Articles/List';
5 |
6 | class ArticlesListContainer extends Component {
7 | constructor(props) {
8 | super();
9 |
10 | // Prioritize (web) page route over last meta value
11 | const page = props.page || props.meta.page;
12 |
13 | this.state = {
14 | error: null, loading: false, page: parseInt(page, 10) || 1,
15 | };
16 | }
17 |
18 | componentDidMount = () => this.fetchData();
19 |
20 | /**
21 | * If the page prop changes, update state
22 | */
23 | componentDidUpdate = (prevProps) => {
24 | const { page } = this.props;
25 | const { page: prevPage } = prevProps;
26 |
27 | if (page !== prevPage) {
28 | // eslint-disable-next-line
29 | this.setState({
30 | error: null, loading: false, page: parseInt(page, 10) || 1,
31 | }, this.fetchData);
32 | }
33 | }
34 |
35 | /**
36 | * Fetch Data
37 | */
38 | fetchData = async ({ forceSync = false, incrementPage = false } = {}) => {
39 | const { fetchData } = this.props;
40 |
41 | let { page } = this.state;
42 | page = incrementPage ? page + 1 : page; // Force fetch the next page worth of data when requested
43 | page = forceSync ? 1 : page; // Start from scratch
44 |
45 | this.setState({ loading: true, error: null, page });
46 |
47 | try {
48 | await fetchData({ forceSync, page });
49 | this.setState({ loading: false, error: null });
50 | } catch (err) {
51 | this.setState({ loading: false, error: err.message });
52 | }
53 | };
54 |
55 | /**
56 | * Render
57 | */
58 | render = () => {
59 | const {
60 | listFlat, listPaginated, pagination, meta,
61 | } = this.props;
62 | const { loading, error, page } = this.state;
63 |
64 | return (
65 |
75 | );
76 | };
77 | }
78 |
79 | ArticlesListContainer.propTypes = {
80 | listFlat: PropTypes.arrayOf(PropTypes.shape({})).isRequired,
81 | listPaginated: PropTypes.shape({}).isRequired,
82 | meta: PropTypes.shape({
83 | page: PropTypes.number,
84 | }).isRequired,
85 | fetchData: PropTypes.func.isRequired,
86 | pagination: PropTypes.arrayOf(PropTypes.shape()).isRequired,
87 | page: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
88 | };
89 |
90 | ArticlesListContainer.defaultProps = {
91 | page: 1,
92 | };
93 |
94 | const mapStateToProps = (state) => ({
95 | listFlat: state.articles.listFlat || [],
96 | listPaginated: state.articles.listPaginated || {},
97 | meta: state.articles.meta || [],
98 | pagination: state.articles.pagination || {},
99 | });
100 |
101 | const mapDispatchToProps = (dispatch) => ({
102 | fetchData: dispatch.articles.fetchList,
103 | });
104 |
105 | export default connect(mapStateToProps, mapDispatchToProps)(ArticlesListContainer);
106 |
--------------------------------------------------------------------------------
/android/app/src/debug/java/com/reactnativestarterkit/ReactNativeFlipper.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (c) Facebook, Inc. and its affiliates.
3 | *
4 | * This source code is licensed under the MIT license found in the LICENSE file in the root
5 | * directory of this source tree.
6 | */
7 | package com.reactnativestarterkit;
8 |
9 | import android.content.Context;
10 | import com.facebook.flipper.android.AndroidFlipperClient;
11 | import com.facebook.flipper.android.utils.FlipperUtils;
12 | import com.facebook.flipper.core.FlipperClient;
13 | import com.facebook.flipper.plugins.crashreporter.CrashReporterPlugin;
14 | import com.facebook.flipper.plugins.databases.DatabasesFlipperPlugin;
15 | import com.facebook.flipper.plugins.fresco.FrescoFlipperPlugin;
16 | import com.facebook.flipper.plugins.inspector.DescriptorMapping;
17 | import com.facebook.flipper.plugins.inspector.InspectorFlipperPlugin;
18 | import com.facebook.flipper.plugins.network.FlipperOkhttpInterceptor;
19 | import com.facebook.flipper.plugins.network.NetworkFlipperPlugin;
20 | import com.facebook.flipper.plugins.react.ReactFlipperPlugin;
21 | import com.facebook.flipper.plugins.sharedpreferences.SharedPreferencesFlipperPlugin;
22 | import com.facebook.react.ReactInstanceManager;
23 | import com.facebook.react.bridge.ReactContext;
24 | import com.facebook.react.modules.network.NetworkingModule;
25 | import okhttp3.OkHttpClient;
26 |
27 | public class ReactNativeFlipper {
28 | public static void initializeFlipper(Context context, ReactInstanceManager reactInstanceManager) {
29 | if (FlipperUtils.shouldEnableFlipper(context)) {
30 | final FlipperClient client = AndroidFlipperClient.getInstance(context);
31 |
32 | client.addPlugin(new InspectorFlipperPlugin(context, DescriptorMapping.withDefaults()));
33 | client.addPlugin(new ReactFlipperPlugin());
34 | client.addPlugin(new DatabasesFlipperPlugin(context));
35 | client.addPlugin(new SharedPreferencesFlipperPlugin(context));
36 | client.addPlugin(CrashReporterPlugin.getInstance());
37 |
38 | NetworkFlipperPlugin networkFlipperPlugin = new NetworkFlipperPlugin();
39 | NetworkingModule.setCustomClientBuilder(
40 | new NetworkingModule.CustomClientBuilder() {
41 | @Override
42 | public void apply(OkHttpClient.Builder builder) {
43 | builder.addNetworkInterceptor(new FlipperOkhttpInterceptor(networkFlipperPlugin));
44 | }
45 | });
46 | client.addPlugin(networkFlipperPlugin);
47 | client.start();
48 |
49 | // Fresco Plugin needs to ensure that ImagePipelineFactory is initialized
50 | // Hence we run if after all native modules have been initialized
51 | ReactContext reactContext = reactInstanceManager.getCurrentReactContext();
52 | if (reactContext == null) {
53 | reactInstanceManager.addReactInstanceEventListener(
54 | new ReactInstanceManager.ReactInstanceEventListener() {
55 | @Override
56 | public void onReactContextInitialized(ReactContext reactContext) {
57 | reactInstanceManager.removeReactInstanceEventListener(this);
58 | reactContext.runOnNativeModulesQueueThread(
59 | new Runnable() {
60 | @Override
61 | public void run() {
62 | client.addPlugin(new FrescoFlipperPlugin());
63 | }
64 | });
65 | }
66 | });
67 | } else {
68 | client.addPlugin(new FrescoFlipperPlugin());
69 | }
70 | }
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/native-base-theme/components/Footer.js:
--------------------------------------------------------------------------------
1 | // @flow
2 |
3 | import variable from './../variables/platform';
4 | import { PLATFORM } from './../variables/commonColor';
5 |
6 | export default (variables /* : * */ = variable) => {
7 | const platformStyle = variables.platformStyle;
8 | const platform = variables.platform;
9 |
10 | const iconCommon = {
11 | 'NativeBase.Icon': {
12 | color: variables.tabBarActiveTextColor
13 | }
14 | };
15 | const iconNBCommon = {
16 | 'NativeBase.IconNB': {
17 | color: variables.tabBarActiveTextColor
18 | }
19 | };
20 | const textCommon = {
21 | 'NativeBase.Text': {
22 | color: variables.tabBarActiveTextColor
23 | }
24 | };
25 | const footerTheme = {
26 | 'NativeBase.Left': {
27 | 'NativeBase.Button': {
28 | '.transparent': {
29 | backgroundColor: 'transparent',
30 | borderColor: null,
31 | elevation: 0,
32 | shadowColor: null,
33 | shadowOffset: null,
34 | shadowRadius: null,
35 | shadowOpacity: null,
36 | ...iconCommon,
37 | ...iconNBCommon,
38 | ...textCommon
39 | },
40 | alignSelf: null,
41 | ...iconCommon,
42 | ...iconNBCommon
43 | // ...textCommon
44 | },
45 | flex: 1,
46 | alignSelf: 'center',
47 | alignItems: 'flex-start'
48 | },
49 | 'NativeBase.Body': {
50 | flex: 1,
51 | alignItems: 'center',
52 | alignSelf: 'center',
53 | flexDirection: 'row',
54 | 'NativeBase.Button': {
55 | alignSelf: 'center',
56 | '.transparent': {
57 | backgroundColor: 'transparent',
58 | borderColor: null,
59 | elevation: 0,
60 | shadowColor: null,
61 | shadowOffset: null,
62 | shadowRadius: null,
63 | shadowOpacity: null,
64 | ...iconCommon,
65 | ...iconNBCommon,
66 | ...textCommon
67 | },
68 | '.full': {
69 | height: variables.footerHeight,
70 | paddingBottom: variables.footerPaddingBottom,
71 | flex: 1
72 | },
73 | ...iconCommon,
74 | ...iconNBCommon
75 | // ...textCommon
76 | }
77 | },
78 | 'NativeBase.Right': {
79 | 'NativeBase.Button': {
80 | '.transparent': {
81 | backgroundColor: 'transparent',
82 | borderColor: null,
83 | elevation: 0,
84 | shadowColor: null,
85 | shadowOffset: null,
86 | shadowRadius: null,
87 | shadowOpacity: null,
88 | ...iconCommon,
89 | ...iconNBCommon,
90 | ...textCommon
91 | },
92 | alignSelf: null,
93 | ...iconCommon,
94 | ...iconNBCommon
95 | // ...textCommon
96 | },
97 | flex: 1,
98 | alignSelf: 'center',
99 | alignItems: 'flex-end'
100 | },
101 | backgroundColor: variables.footerDefaultBg,
102 | flexDirection: 'row',
103 | justifyContent: 'center',
104 | borderTopWidth:
105 | platform === PLATFORM.IOS && platformStyle !== PLATFORM.MATERIAL
106 | ? variables.borderWidth
107 | : undefined,
108 | borderColor:
109 | platform === PLATFORM.IOS && platformStyle !== PLATFORM.MATERIAL
110 | ? '#cbcbcb'
111 | : undefined,
112 | height: variables.footerHeight,
113 | paddingBottom: variables.footerPaddingBottom,
114 | elevation: 3,
115 | left: 0,
116 | right: 0
117 | };
118 | return footerTheme;
119 | };
120 |
--------------------------------------------------------------------------------
/ios/ReactNativeStarterKit.xcodeproj/xcshareddata/xcschemes/ReactNativeStarterKit.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
30 |
31 |
33 |
39 |
40 |
41 |
42 |
43 |
53 |
55 |
61 |
62 |
63 |
64 |
70 |
72 |
78 |
79 |
80 |
81 |
83 |
84 |
87 |
88 |
89 |
--------------------------------------------------------------------------------
/src/components/Articles/List.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import { Actions } from 'react-native-router-flux';
4 | import { FlatList, TouchableOpacity, Image } from 'react-native';
5 | import {
6 | Container, Card, CardItem, Body, Text, Button,
7 | } from 'native-base';
8 | import { Error, Spacer } from '../UI';
9 | import { errorMessages } from '../../constants/messages';
10 |
11 | const ArticlesList = ({
12 | error, loading, listFlat, reFetch, meta,
13 | }) => {
14 | if (error) {
15 | return ;
16 | }
17 |
18 | if (listFlat.length < 1) {
19 | return ;
20 | }
21 |
22 | return (
23 |
24 | reFetch({ forceSync: true })}
27 | refreshing={loading}
28 | renderItem={({ item }) => (
29 |
30 | (
33 | !item.placeholder
34 | ? Actions.articlesSingle({ id: item.id, title: item.name })
35 | : null
36 | )}
37 | style={{ flex: 1 }}
38 | >
39 |
40 | {!!item.image && (
41 |
53 | )}
54 |
55 |
56 |
57 |
58 | {item.name}
59 |
60 | {!!item.excerpt && {item.excerpt} }
61 |
62 |
63 |
64 |
65 |
66 | )}
67 | keyExtractor={(item) => `${item.id}-${item.name}`}
68 | ListFooterComponent={(meta && meta.page && meta.lastPage && meta.page < meta.lastPage)
69 | ? () => (
70 |
71 |
72 | reFetch({ incrementPage: true })}
76 | >
77 | Load More
78 |
79 |
80 | ) : null}
81 | />
82 |
83 |
84 |
85 | );
86 | };
87 |
88 | ArticlesList.propTypes = {
89 | error: PropTypes.string,
90 | loading: PropTypes.bool,
91 | listFlat: PropTypes.arrayOf(
92 | PropTypes.shape({
93 | placeholder: PropTypes.bool,
94 | id: PropTypes.number,
95 | name: PropTypes.string,
96 | date: PropTypes.string,
97 | content: PropTypes.string,
98 | excerpt: PropTypes.string,
99 | image: PropTypes.string,
100 | }),
101 | ),
102 | reFetch: PropTypes.func,
103 | meta: PropTypes.shape({ page: PropTypes.number, lastPage: PropTypes.number }),
104 | };
105 |
106 | ArticlesList.defaultProps = {
107 | listFlat: [],
108 | error: null,
109 | reFetch: null,
110 | meta: { page: null, lastPage: null },
111 | loading: false,
112 | };
113 |
114 | export default ArticlesList;
115 |
--------------------------------------------------------------------------------
/native-base-theme/components/InputGroup.js:
--------------------------------------------------------------------------------
1 | // @flow
2 |
3 | import variable from './../variables/platform';
4 |
5 | export default (variables /* : * */ = variable) => {
6 | const inputGroupTheme = {
7 | 'NativeBase.Icon': {
8 | fontSize: 24,
9 | color: variables.sTabBarActiveTextColor,
10 | paddingHorizontal: 5
11 | },
12 | 'NativeBase.IconNB': {
13 | fontSize: 24,
14 | color: variables.sTabBarActiveTextColor,
15 | paddingHorizontal: 5
16 | },
17 | 'NativeBase.Input': {
18 | height: variables.inputHeightBase,
19 | color: variables.inputColor,
20 | paddingLeft: 5,
21 | paddingRight: 5,
22 | flex: 1,
23 | fontSize: variables.inputFontSize,
24 | lineHeight: variables.inputLineHeight
25 | },
26 | '.underline': {
27 | '.success': {
28 | borderColor: variables.inputSuccessBorderColor
29 | },
30 | '.error': {
31 | borderColor: variables.inputErrorBorderColor
32 | },
33 | paddingLeft: 5,
34 | borderWidth: variables.borderWidth,
35 | borderTopWidth: 0,
36 | borderRightWidth: 0,
37 | borderLeftWidth: 0,
38 | borderColor: variables.inputBorderColor
39 | },
40 | '.regular': {
41 | '.success': {
42 | borderColor: variables.inputSuccessBorderColor
43 | },
44 | '.error': {
45 | borderColor: variables.inputErrorBorderColor
46 | },
47 | paddingLeft: 5,
48 | borderWidth: variables.borderWidth,
49 | borderColor: variables.inputBorderColor
50 | },
51 | '.rounded': {
52 | '.success': {
53 | borderColor: variables.inputSuccessBorderColor
54 | },
55 | '.error': {
56 | borderColor: variables.inputErrorBorderColor
57 | },
58 | paddingLeft: 5,
59 | borderWidth: variables.borderWidth,
60 | borderRadius: variables.inputGroupRoundedBorderRadius,
61 | borderColor: variables.inputBorderColor
62 | },
63 |
64 | '.success': {
65 | 'NativeBase.Icon': {
66 | color: variables.inputSuccessBorderColor
67 | },
68 | 'NativeBase.IconNB': {
69 | color: variables.inputSuccessBorderColor
70 | },
71 | '.rounded': {
72 | borderRadius: 30,
73 | borderColor: variables.inputSuccessBorderColor
74 | },
75 | '.regular': {
76 | borderColor: variables.inputSuccessBorderColor
77 | },
78 | '.underline': {
79 | borderWidth: variables.borderWidth,
80 | borderTopWidth: 0,
81 | borderRightWidth: 0,
82 | borderLeftWidth: 0,
83 | borderColor: variables.inputSuccessBorderColor
84 | },
85 | borderColor: variables.inputSuccessBorderColor
86 | },
87 |
88 | '.error': {
89 | 'NativeBase.Icon': {
90 | color: variables.inputErrorBorderColor
91 | },
92 | 'NativeBase.IconNB': {
93 | color: variables.inputErrorBorderColor
94 | },
95 | '.rounded': {
96 | borderRadius: 30,
97 | borderColor: variables.inputErrorBorderColor
98 | },
99 | '.regular': {
100 | borderColor: variables.inputErrorBorderColor
101 | },
102 | '.underline': {
103 | borderWidth: variables.borderWidth,
104 | borderTopWidth: 0,
105 | borderRightWidth: 0,
106 | borderLeftWidth: 0,
107 | borderColor: variables.inputErrorBorderColor
108 | },
109 | borderColor: variables.inputErrorBorderColor
110 | },
111 | '.disabled': {
112 | 'NativeBase.Icon': {
113 | color: '#384850'
114 | },
115 | 'NativeBase.IconNB': {
116 | color: '#384850'
117 | }
118 | },
119 |
120 | paddingLeft: 5,
121 | borderWidth: variables.borderWidth,
122 | borderTopWidth: 0,
123 | borderRightWidth: 0,
124 | borderLeftWidth: 0,
125 | borderColor: variables.inputBorderColor,
126 | backgroundColor: 'transparent',
127 | flexDirection: 'row',
128 | alignItems: 'center'
129 | };
130 |
131 | return inputGroupTheme;
132 | };
133 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
26 |
27 | ---
28 |
29 | ### Looking for something else?
30 |
31 | - [React Native Starter Kit (Expo) / Boilerplate](https://github.com/mcnamee/react-native-expo-starter-kit)
32 | - [React Starter Kit (web) / Boilerplate](https://github.com/mcnamee/react-starter-kit)
33 | - [Previous Version (React Starter Kit (Web + Native) w/ Firebase)](https://github.com/mcnamee/react-native-starter-kit/tree/archive/v3)
34 |
35 | ---
36 |
37 | ## 👋 Intro
38 |
39 | This project was bootstrapped with the [React Boilerplate Builder](https://github.com/mcnamee/react-native-boilerplate-builder) by [Matt McNamee](https://mcnam.ee).
40 |
41 | The project is _super_ helpful to kick-start your next project, as it provides a lot of the common tools you may reach for, all ready to go. Specifically:
42 |
43 | - __Flux architecture__
44 | - [Redux](https://redux.js.org/docs/introduction/)
45 | - Redux Wrapper: [Rematch](https://github.com/rematch/rematch)
46 | - __Routing and navigation__
47 | - [React Native Router Flux](https://github.com/aksonov/react-native-router-flux) for native mobile navigation
48 | - __Data Caching / Offline__
49 | - [Redux Persist](https://github.com/rt2zz/redux-persist)
50 | - __UI Toolkit/s__
51 | - [Native Base](https://nativebase.io/) for native mobile
52 | - __Code Linting__ with
53 | - [Airbnb's JS Linting](https://github.com/airbnb/javascript) guidelines
54 | - __Deployment strategy__
55 | - [Both manual and automated strategies](documentation/deploy.md)
56 | - __Splash Screen + Assets__
57 | - [React Native Splash Screen](https://github.com/crazycodeboy/react-native-splash-screen)
58 |
59 | ---
60 |
61 | ## 🚀 Getting Started
62 |
63 | - Install [React Native Debugger](https://github.com/jhen0409/react-native-debugger/releases) and open before running the app
64 | - Install `eslint`, `prettier` and `editor config` plugins into your IDE
65 | - Ensure your machine has the [React Native dependencies installed](https://facebook.github.io/react-native/docs/getting-started)
66 |
67 | ```bash
68 | # Install dependencies
69 | yarn install && ( cd ios && pod install )
70 | ```
71 |
72 | #### iOS
73 |
74 | ```bash
75 | # Start in the iOS Simulator
76 | npx react-native run-ios --simulator="iPhone 11"
77 | ```
78 |
79 | #### Android
80 |
81 | ```bash
82 | # Start in the Android Simulator
83 | # - Note: open Android Studio > Tools > AVD > Run a device
84 | # - Example device specs: https://medium.com/pvtl/react-native-android-development-on-mac-ef7481f65e47#d5da
85 | npx react-native run-android
86 | ```
87 |
88 | ---
89 |
90 | ## 📖 Docs
91 |
92 | - [Contributing to this project](documentation/contributing.md)
93 | - [FAQs & Opinions](documentation/faqs.md)
94 | - [Tests & testing](documentation/testing.md)
95 | - [Understanding the file structure](documentation/file-structure.md)
96 | - [Deploy the app](documentation/deploy.md)
97 |
98 | ---
99 |
100 | ## 👊 Further Help?
101 |
102 | This repo is a great place to start. But...if you'd prefer to sit back and have your new project built for you or just need some consultation, [get in touch with me directly](https://mcnam.ee) and I can organise a quote.
103 |
--------------------------------------------------------------------------------
/ios/ReactNativeStarterKit/LaunchScreen.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
--------------------------------------------------------------------------------
/src/tests/models/articles.test.js:
--------------------------------------------------------------------------------
1 | import moment from 'moment';
2 | import Api from '../../lib/api';
3 | import model from '../../models/articles';
4 | import { successMessages } from '../../constants/messages';
5 |
6 | /**
7 | * Mocks
8 | */
9 | jest.mock('axios');
10 | afterEach(jest.resetAllMocks);
11 |
12 | const mockInput = {
13 | title: { rendered: 'hello world' },
14 | content: { rendered: 'Hello there fellows
' },
15 | excerpt: { rendered: 'Hello there fellows
' },
16 | _embedded: {
17 | 'wp:featuredmedia': [{
18 | media_details: {
19 | sizes: {
20 | full: {
21 | source_url: 'https://www.digitalsupply.co/wp-content/uploads/2018/03/glacier-blue.jpg',
22 | },
23 | },
24 | },
25 | }],
26 | },
27 | date: '2017-04-14T15:32:29',
28 | slug: 'using-open-source-software-to-build-your-instagram-followers',
29 | link: 'https://www.digitalsupply.co/using-open-source-software-to-build-your-instagram-followers/',
30 | };
31 |
32 | const mockOutput = {
33 | id: 0,
34 | name: 'Hello world',
35 | content: 'Hello there fellows',
36 | contentRaw: 'Hello there fellows
',
37 | excerpt: 'Hello there fellows',
38 | image: 'https://www.digitalsupply.co/wp-content/uploads/2018/03/glacier-blue.jpg',
39 | date: '14th Apr 2017',
40 | slug: 'using-open-source-software-to-build-your-instagram-followers',
41 | link: 'https://www.digitalsupply.co/using-open-source-software-to-build-your-instagram-followers/',
42 | };
43 |
44 | /**
45 | * Tests
46 | */
47 | it('Articles fetchList() returns correctly', async () => {
48 | Api.get.mockResolvedValue({ data: [mockInput], headers: { 'x-wp-totalpages': 3 } });
49 | const initialState = { articles: { lastSync: {} } };
50 | const dispatch = { articles: { replace: jest.fn((res) => res) } };
51 |
52 | await model.effects(dispatch).fetchList({ page: 2 }, initialState).then((res) => {
53 | expect(Api.get).toHaveBeenCalledWith('/v2/posts?per_page=4&page=2&orderby=modified&_embed');
54 | expect(dispatch.articles.replace).toHaveBeenCalledTimes(1);
55 | expect(res).toEqual({
56 | data: [mockInput],
57 | headers: { 'x-wp-totalpages': 3 },
58 | page: 2,
59 | });
60 | });
61 | });
62 |
63 | it('Articles fetchList() does not go to API if lastSync just set', async () => {
64 | const initialState = { articles: { lastSync: { 2: moment() } } };
65 | await model.effects().fetchList({ page: 2 }, initialState).then((res) => expect(res).toEqual(true));
66 | });
67 |
68 | it('Articles fetchSingle() returns correctly', async () => {
69 | Api.get.mockResolvedValue({ data: mockInput });
70 |
71 | await model.effects().fetchSingle(222).then((res) => {
72 | expect(Api.get).toHaveBeenCalledWith('/v2/posts/222?_embed');
73 | expect(res).toEqual(mockOutput);
74 | });
75 | });
76 |
77 | it('Articles Model returns correctly', () => {
78 | expect(model.reducers.replace({}, {
79 | page: 1,
80 | headers: {
81 | 'x-wp-totalpages': 2,
82 | 'x-wp-total': 2,
83 | },
84 | data: [mockInput],
85 | })).toMatchObject({
86 | meta: {
87 | lastPage: 2,
88 | total: 2,
89 | },
90 | pagination: [
91 | { title: 1, link: '/articles/' },
92 | { title: 2, link: '/articles/2' },
93 | ],
94 | listPaginated: {
95 | 1: [mockOutput],
96 | },
97 | });
98 | });
99 |
100 | it('Articles save() returns correctly', () => {
101 | const dispatch = { articles: { replaceUserInput: jest.fn((res) => res) } };
102 |
103 | model.effects(dispatch).save('hello@hello.com').then((res) => {
104 | expect(res).toEqual(successMessages.defaultForm);
105 | });
106 | });
107 |
108 | it('Articles Model Save returns correctly', () => {
109 | expect(model.reducers.replaceUserInput({
110 | meta: {
111 | lastPage: 2,
112 | total: 2,
113 | },
114 | pagination: [
115 | { title: 1, link: '/articles/' },
116 | { title: 2, link: '/articles/2' },
117 | ],
118 | listPaginated: {
119 | 1: [mockOutput],
120 | },
121 | }, 'hello@hello.com')).toMatchObject({
122 | meta: {
123 | lastPage: 2,
124 | total: 2,
125 | },
126 | pagination: [
127 | { title: 1, link: '/articles/' },
128 | { title: 2, link: '/articles/2' },
129 | ],
130 | listPaginated: {
131 | 1: [mockOutput],
132 | },
133 | userInput: 'hello@hello.com',
134 | });
135 | });
136 |
--------------------------------------------------------------------------------
/documentation/deploy.md:
--------------------------------------------------------------------------------
1 | # 🚀 Deploying
2 |
3 | ## Setting up a new app:
4 |
5 | The following steps should be followed for new projects. Once completed for your project, you won't need these steps again.
6 |
7 | *General*
8 |
9 | 1. Ensure you have admin access to the destination Google Play and Apple/iTunesConnect Developer accounts
10 | 1. Ensure you've named your app correctly and set a unique bundle identifier:
11 | - Use [react-native-rename](https://www.npmjs.com/package/react-native-rename)
12 | - eg. `react-native-rename "Travel App" -b com.junedomingo.travelapp`
13 | - Open the project in Xcode and double check that the Bundle ID has been updated (if not, correct it)
14 | 1. In both Google Play and iTunes Connect:
15 | - Setup a new app
16 | - Use the _manual_ method below to build and deploy the app for the first time
17 | - _iOS Note: when you deploy the iOS app for the first time, you'll select 'Automatic Key Management'. Xcode will generate a private distribution key. Ensure you save this (eg. to a password safe) so that others can distribute the app too_
18 |
19 | *Android*
20 |
21 | 1. Generate/configure Android key:
22 | - `( cd android/app && keytool -genkeypair -v -keystore android-release-key.keystore -alias jims-app-release-key -keyalg RSA -keysize 2048 -validity 10000 )` (note: change `jims-app-release-key` to your own alias)
23 | - Save the key to a secure password safe (don't commit it to the repo)
24 | 1. [Setup the Gradle variables](https://reactnative.dev/docs/signed-apk-android#setting-up-gradle-variables), using the alias and password/s (that you set in the previous command) in: `android/gradle.properties`
25 | 1. [Add the release signing config to your app's Gradle config](https://reactnative.dev/docs/signed-apk-android#adding-signing-config-to-your-apps-gradle-config) in: `android/app/build.gradle`
26 |
27 | *Fastlane*
28 |
29 | 1. Using the __account owner's__ login (i.e. we want to create the API credentials from the owner's account) - follow the [steps here](https://docs.fastlane.tools/actions/supply/#setup) to generate API credentials for Google Play. Download and place the json file here: `android/app/google-play-android-developer.json`. Save the key to a secure password safe (don't commit it to the repo)
30 | 1. Update the `package_name` and `itc_team_id` (App Store Connect Team ID) in `faslane/Appfile` to match the bundle of your app
31 | 1. Update the following in `fastlane/Fastfile`:
32 | - `app_identifier: com.app.bundle` - where com.app.bundle is your bundle id
33 | - `name.xcodeproj` - to the name of your Xcode project file
34 | - `scheme: 'name'` - where name is your scheme (eg. AppName)
35 | 1. Run `fastlane supply init` (which will download the meta data of the uploaded app, from the stores)
36 |
37 | ---
38 |
39 | ## Configuring your machine to deploy:
40 |
41 | The following steps are provided for developers who have the project setup on their machine, but have not yet deployed the app. Follow these once, and you won't need these steps again.
42 |
43 | 1. Android (Google Play):
44 | - Add the Android keys (found in the password safe) to your local project:
45 | - `android/app/android-release-key.keystore`
46 | - `android/app/google-play-android-developer.json`
47 | - [Android/Google dependencies](https://facebook.github.io/react-native/docs/getting-started#installing-dependencies-1)
48 | 1. iOS (Apple iTunes Connect):
49 | - In Xcode, login to the appropriate account to give you access to deploy
50 | - Install the appropriate distribution private key (found in your password safe)
51 | - Download the file and double click it to add to Keychain
52 | 1. Fastlane (for automated deployments on both platforms):
53 | - Install Fastlane - `brew cask install fastlane`
54 | - Install Xcode command line tools - `xcode-select --install`
55 |
56 | ---
57 |
58 | ## Deploying
59 |
60 | - Update the __app version__ - `bash fastlane/update-app-version.sh`
61 | - __Merge__ `develop` branch into `master` branch with a _merge commit_
62 | - Git __Tag__ the master merge commit. The tag name should be the new version number
63 | - Bundle and deploy by the following:
64 |
65 | ### 1.0 (Automated) Fastlane
66 |
67 | Fastlane automatically builds and deploys the app to the app stores (TestFlight and Play Store Beta).
68 |
69 | 1. _Hint: Did you update the version number, merge to master and tag?_
70 | 1. __iOS__: Deploy to Apple TestFlight - `fastlane ios beta`
71 | 1. __Android__: Deploy to Google Play Beta - `fastlane android beta`
72 |
73 | ### 2.0 Manual
74 |
75 | *2.2.1 iOS*
76 |
77 | _*Note: it may be required to use the legacy build system (XCode -> File -> Project Settings -> Change the build system to 'Legacy Build System')_
78 |
79 | 1. _Hint: Did you update the version number, merge to master and tag?_
80 | 1. Ensure you've changed the Xcode 'Build Config' to Release
81 | 1. Select 'Generic iOS Device' from devices
82 | 1. Product > Archive
83 | 1. Open Organiser
84 | - Find the archive and click 'Validate' to check that it's ok
85 | - Click the big 'Upload to App Store...' when ready (untick BitCode checkbox)
86 |
87 | *2.2.2 Android*
88 |
89 | 1. _Hint: Did you update the version number, merge to master and tag?_
90 | 1. `( cd android && ./gradlew app:bundleRelease )`
91 | 1. Upload the generated file (`/android/app/build/outputs/bundle/release/app.aab`) to Google Play
92 |
--------------------------------------------------------------------------------
/src/models/articles.js:
--------------------------------------------------------------------------------
1 | import moment from 'moment';
2 | import Api from '../lib/api';
3 | import HandleErrorMessage from '../lib/format-error-messages';
4 | import initialState from '../store/articles';
5 | import Config from '../constants/config';
6 | import { getFeaturedImageUrl } from '../lib/images';
7 | import { ucfirst, stripHtml } from '../lib/string';
8 | import { errorMessages, successMessages } from '../constants/messages';
9 | import pagination from '../lib/pagination';
10 |
11 | /**
12 | * Transform the endpoint data structure into our redux store format
13 | * @param {obj} item
14 | */
15 | const transform = (item) => ({
16 | id: item.id || 0,
17 | name: item.title && item.title.rendered ? ucfirst(stripHtml(item.title.rendered)) : '',
18 | content: item.content && item.content.rendered ? stripHtml(item.content.rendered) : '',
19 | contentRaw: item.content && item.content.rendered,
20 | excerpt: item.excerpt && item.excerpt.rendered ? stripHtml(item.excerpt.rendered) : '',
21 | date: moment(item.date).format(Config.dateFormat) || '',
22 | slug: item.slug || null,
23 | link: item.link || null,
24 | image: getFeaturedImageUrl(item),
25 | });
26 |
27 | export default {
28 | namespace: 'articles',
29 |
30 | /**
31 | * Initial state
32 | */
33 | state: initialState,
34 |
35 | /**
36 | * Effects/Actions
37 | */
38 | effects: (dispatch) => ({
39 | /**
40 | * Get a list from the API
41 | * @param {obj} rootState
42 | * @returns {Promise}
43 | */
44 | async fetchList({ forceSync = false, page = 1 } = {}, rootState) {
45 | const { articles = {} } = rootState;
46 | const { lastSync = {}, meta = {} } = articles;
47 | const { lastPage } = meta;
48 |
49 | // Only sync when it's been 5mins since last sync
50 | if (lastSync[page]) {
51 | if (!forceSync && moment().isBefore(moment(lastSync[page]).add(5, 'minutes'))) {
52 | return true;
53 | }
54 | }
55 |
56 | // We've reached the end of the list
57 | if (page && lastPage && page > lastPage) {
58 | throw HandleErrorMessage({ message: `Page ${page} does not exist` });
59 | }
60 |
61 | try {
62 | const response = await Api.get(`/v2/posts?per_page=4&page=${page}&orderby=modified&_embed`);
63 | const { data, headers } = response;
64 |
65 | return !data || data.length < 1
66 | ? true
67 | : dispatch.articles.replace({ data, headers, page });
68 | } catch (error) {
69 | throw HandleErrorMessage(error);
70 | }
71 | },
72 |
73 | /**
74 | * Get a single item from the API
75 | * @param {number} id
76 | * @returns {Promise[obj]}
77 | */
78 | async fetchSingle(id) {
79 | try {
80 | const response = await Api.get(`/v2/posts/${id}?_embed`);
81 | const { data } = response;
82 |
83 | if (!data) {
84 | throw new Error({ message: errorMessages.articles404 });
85 | }
86 |
87 | return transform(data);
88 | } catch (error) {
89 | throw HandleErrorMessage(error);
90 | }
91 | },
92 |
93 | /**
94 | * Save date to redux store
95 | * @param {obj} data
96 | * @returns {Promise[obj]}
97 | */
98 | async save(data) {
99 | try {
100 | if (Object.keys(data).length < 1) {
101 | throw new Error({ message: errorMessages.missingData });
102 | }
103 |
104 | dispatch.articles.replaceUserInput(data);
105 | return successMessages.defaultForm; // Message for the UI
106 | } catch (error) {
107 | throw HandleErrorMessage(error);
108 | }
109 | },
110 | }),
111 |
112 | /**
113 | * Reducers
114 | */
115 | reducers: {
116 | /**
117 | * Replace list in store
118 | * @param {obj} state
119 | * @param {obj} payload
120 | */
121 | replace(state, payload) {
122 | let newList = null;
123 | const { data, headers, page } = payload;
124 |
125 | // Loop data array, saving items in a usable format
126 | if (data && typeof data === 'object') {
127 | newList = data.map((item) => transform(item));
128 | }
129 |
130 | // Create our paginated and flat lists
131 | const listPaginated = page === 1 ? { [page]: newList } : { ...state.listPaginated, [page]: newList };
132 | const listFlat = Object.keys(listPaginated).map((k) => listPaginated[k]).flat() || [];
133 |
134 | return newList
135 | ? {
136 | ...state,
137 | listPaginated,
138 | listFlat,
139 | lastSync: page === 1
140 | ? { [page]: moment().format() }
141 | : { ...state.lastSync, [page]: moment().format() },
142 | meta: {
143 | page,
144 | lastPage: parseInt(headers['x-wp-totalpages'], 10) || null,
145 | total: parseInt(headers['x-wp-total'], 10) || null,
146 | },
147 | pagination: pagination(headers['x-wp-totalpages'], '/articles/'),
148 | }
149 | : initialState;
150 | },
151 |
152 | /**
153 | * Save form data
154 | * @param {obj} state
155 | * @param {obj} payload
156 | */
157 | replaceUserInput(state, payload) {
158 | return {
159 | ...state,
160 | userInput: payload,
161 | };
162 | },
163 | },
164 | };
165 |
--------------------------------------------------------------------------------
/src/tests/components/UI/__snapshots__/Error.test.js.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[` renders with a button 1`] = `
4 |
18 |
28 |
39 |
53 | hello boy
54 |
55 |
71 | An unexpected error came up
72 |
73 |
112 |
127 | Try Again
128 |
129 |
130 |
141 |
142 |
143 | `;
144 |
145 | exports[` renders with message 1`] = `
146 |
160 |
170 |
181 |
195 | hello boy
196 |
197 |
213 | An unexpected error came up
214 |
215 |
226 |
227 |
228 | `;
229 |
--------------------------------------------------------------------------------
/native-base-theme/components/CardItem.js:
--------------------------------------------------------------------------------
1 | // @flow
2 |
3 | import { StyleSheet } from 'react-native';
4 |
5 | import variable from './../variables/platform';
6 | import { PLATFORM } from './../variables/commonColor';
7 |
8 | export default (variables /* : * */ = variable) => {
9 | const platform = variables.platform;
10 | const transparentBtnCommon = {
11 | 'NativeBase.Text': {
12 | fontSize: variables.DefaultFontSize - 3,
13 | color: variables.sTabBarActiveTextColor
14 | },
15 | 'NativeBase.Icon': {
16 | fontSize: variables.iconFontSize - 10,
17 | color: variables.sTabBarActiveTextColor,
18 | marginHorizontal: null
19 | },
20 | 'NativeBase.IconNB': {
21 | fontSize: variables.iconFontSize - 10,
22 | color: variables.sTabBarActiveTextColor
23 | },
24 | paddingVertical: null,
25 | paddingHorizontal: null
26 | };
27 |
28 | const cardItemTheme = {
29 | 'NativeBase.Left': {
30 | 'NativeBase.Body': {
31 | 'NativeBase.Text': {
32 | '.note': {
33 | color: variables.listNoteColor,
34 | fontWeight: '400',
35 | marginRight: 20
36 | }
37 | },
38 | flex: 1,
39 | marginLeft: 10,
40 | alignItems: null
41 | },
42 | 'NativeBase.Icon': {
43 | fontSize: variables.iconFontSize
44 | },
45 | 'NativeBase.IconNB': {
46 | fontSize: variables.iconFontSize
47 | },
48 | 'NativeBase.Text': {
49 | marginLeft: 10,
50 | alignSelf: 'center'
51 | },
52 | 'NativeBase.Button': {
53 | '.transparent': {
54 | ...transparentBtnCommon,
55 | paddingRight: variables.cardItemPadding + 5
56 | }
57 | },
58 | flex: 1,
59 | flexDirection: 'row',
60 | alignItems: 'center'
61 | },
62 | '.content': {
63 | 'NativeBase.Text': {
64 | color: platform === PLATFORM.IOS ? '#555' : '#222',
65 | fontSize: variables.DefaultFontSize - 2
66 | }
67 | },
68 | '.cardBody': {
69 | padding: -5,
70 | 'NativeBase.Text': {
71 | marginTop: 5
72 | }
73 | },
74 | 'NativeBase.Body': {
75 | 'NativeBase.Text': {
76 | '.note': {
77 | color: variables.listNoteColor,
78 | fontWeight: '200',
79 | marginRight: 20
80 | }
81 | },
82 | 'NativeBase.Button': {
83 | '.transparent': {
84 | ...transparentBtnCommon,
85 | paddingRight: variables.cardItemPadding + 5,
86 | alignSelf: 'stretch'
87 | }
88 | },
89 | flex: 1,
90 | alignSelf: 'stretch',
91 | alignItems: 'flex-start'
92 | },
93 | 'NativeBase.Right': {
94 | 'NativeBase.Badge': {
95 | alignSelf: null
96 | },
97 | 'NativeBase.Button': {
98 | '.transparent': {
99 | ...transparentBtnCommon
100 | },
101 | alignSelf: null
102 | },
103 | 'NativeBase.Icon': {
104 | alignSelf: null,
105 | fontSize: variables.iconFontSize - 8,
106 | color: variables.cardBorderColor
107 | },
108 | 'NativeBase.IconNB': {
109 | alignSelf: null,
110 | fontSize: variables.iconFontSize - 8,
111 | color: variables.cardBorderColor
112 | },
113 | 'NativeBase.Text': {
114 | fontSize: variables.DefaultFontSize - 1,
115 | alignSelf: null
116 | },
117 | 'NativeBase.Thumbnail': {
118 | alignSelf: null
119 | },
120 | 'NativeBase.Image': {
121 | alignSelf: null
122 | },
123 | 'NativeBase.Radio': {
124 | alignSelf: null
125 | },
126 | 'NativeBase.Checkbox': {
127 | alignSelf: null
128 | },
129 | 'NativeBase.Switch': {
130 | alignSelf: null
131 | },
132 | flex: 0.8
133 | },
134 | '.header': {
135 | 'NativeBase.Text': {
136 | fontSize: 16,
137 | fontWeight: platform === PLATFORM.IOS ? '600' : '500'
138 | },
139 | '.bordered': {
140 | 'NativeBase.Text': {
141 | color: variables.brandPrimary,
142 | fontWeight: platform === PLATFORM.IOS ? '600' : '500'
143 | },
144 | borderBottomWidth: variables.borderWidth
145 | },
146 | borderBottomWidth: null,
147 | paddingVertical: variables.cardItemPadding + 5
148 | },
149 | '.footer': {
150 | 'NativeBase.Text': {
151 | fontSize: 16,
152 | fontWeight: platform === PLATFORM.IOS ? '600' : '500'
153 | },
154 | '.bordered': {
155 | 'NativeBase.Text': {
156 | color: variables.brandPrimary,
157 | fontWeight: platform === PLATFORM.IOS ? '600' : '500'
158 | },
159 | borderTopWidth: variables.borderWidth
160 | },
161 | borderBottomWidth: null
162 | },
163 | 'NativeBase.Text': {
164 | '.note': {
165 | color: variables.listNoteColor,
166 | fontWeight: '200'
167 | }
168 | },
169 | 'NativeBase.Icon': {
170 | width: variables.iconFontSize + 5,
171 | fontSize: variables.iconFontSize - 2
172 | },
173 | 'NativeBase.IconNB': {
174 | width: variables.iconFontSize + 5,
175 | fontSize: variables.iconFontSize - 2
176 | },
177 | '.bordered': {
178 | borderBottomWidth: StyleSheet.hairlineWidth,
179 | borderColor: variables.cardBorderColor
180 | },
181 | '.first': {
182 | borderTopLeftRadius: variables.cardBorderRadius,
183 | borderTopRightRadius: variables.cardBorderRadius
184 | },
185 | '.last': {
186 | borderBottomLeftRadius: variables.cardBorderRadius,
187 | borderBottomRightRadius: variables.cardBorderRadius
188 | },
189 | flexDirection: 'row',
190 | alignItems: 'center',
191 | borderRadius: variables.cardBorderRadius,
192 | padding: variables.cardItemPadding + 5,
193 | paddingVertical: variables.cardItemPadding,
194 | backgroundColor: variables.cardDefaultBg
195 | };
196 |
197 | return cardItemTheme;
198 | };
199 |
--------------------------------------------------------------------------------
/android/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 |
3 | #
4 | # Copyright 2015 the original author or authors.
5 | #
6 | # Licensed under the Apache License, Version 2.0 (the "License");
7 | # you may not use this file except in compliance with the License.
8 | # You may obtain a copy of the License at
9 | #
10 | # https://www.apache.org/licenses/LICENSE-2.0
11 | #
12 | # Unless required by applicable law or agreed to in writing, software
13 | # distributed under the License is distributed on an "AS IS" BASIS,
14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | # See the License for the specific language governing permissions and
16 | # limitations under the License.
17 | #
18 |
19 | ##############################################################################
20 | ##
21 | ## Gradle start up script for UN*X
22 | ##
23 | ##############################################################################
24 |
25 | # Attempt to set APP_HOME
26 | # Resolve links: $0 may be a link
27 | PRG="$0"
28 | # Need this for relative symlinks.
29 | while [ -h "$PRG" ] ; do
30 | ls=`ls -ld "$PRG"`
31 | link=`expr "$ls" : '.*-> \(.*\)$'`
32 | if expr "$link" : '/.*' > /dev/null; then
33 | PRG="$link"
34 | else
35 | PRG=`dirname "$PRG"`"/$link"
36 | fi
37 | done
38 | SAVED="`pwd`"
39 | cd "`dirname \"$PRG\"`/" >/dev/null
40 | APP_HOME="`pwd -P`"
41 | cd "$SAVED" >/dev/null
42 |
43 | APP_NAME="Gradle"
44 | APP_BASE_NAME=`basename "$0"`
45 |
46 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
47 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
48 |
49 | # Use the maximum available, or set MAX_FD != -1 to use that value.
50 | MAX_FD="maximum"
51 |
52 | warn () {
53 | echo "$*"
54 | }
55 |
56 | die () {
57 | echo
58 | echo "$*"
59 | echo
60 | exit 1
61 | }
62 |
63 | # OS specific support (must be 'true' or 'false').
64 | cygwin=false
65 | msys=false
66 | darwin=false
67 | nonstop=false
68 | case "`uname`" in
69 | CYGWIN* )
70 | cygwin=true
71 | ;;
72 | Darwin* )
73 | darwin=true
74 | ;;
75 | MINGW* )
76 | msys=true
77 | ;;
78 | NONSTOP* )
79 | nonstop=true
80 | ;;
81 | esac
82 |
83 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
84 |
85 |
86 | # Determine the Java command to use to start the JVM.
87 | if [ -n "$JAVA_HOME" ] ; then
88 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
89 | # IBM's JDK on AIX uses strange locations for the executables
90 | JAVACMD="$JAVA_HOME/jre/sh/java"
91 | else
92 | JAVACMD="$JAVA_HOME/bin/java"
93 | fi
94 | if [ ! -x "$JAVACMD" ] ; then
95 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
96 |
97 | Please set the JAVA_HOME variable in your environment to match the
98 | location of your Java installation."
99 | fi
100 | else
101 | JAVACMD="java"
102 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
103 |
104 | Please set the JAVA_HOME variable in your environment to match the
105 | location of your Java installation."
106 | fi
107 |
108 | # Increase the maximum file descriptors if we can.
109 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
110 | MAX_FD_LIMIT=`ulimit -H -n`
111 | if [ $? -eq 0 ] ; then
112 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
113 | MAX_FD="$MAX_FD_LIMIT"
114 | fi
115 | ulimit -n $MAX_FD
116 | if [ $? -ne 0 ] ; then
117 | warn "Could not set maximum file descriptor limit: $MAX_FD"
118 | fi
119 | else
120 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
121 | fi
122 | fi
123 |
124 | # For Darwin, add options to specify how the application appears in the dock
125 | if $darwin; then
126 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
127 | fi
128 |
129 | # For Cygwin or MSYS, switch paths to Windows format before running java
130 | if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
131 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
132 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
133 |
134 | JAVACMD=`cygpath --unix "$JAVACMD"`
135 |
136 | # We build the pattern for arguments to be converted via cygpath
137 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
138 | SEP=""
139 | for dir in $ROOTDIRSRAW ; do
140 | ROOTDIRS="$ROOTDIRS$SEP$dir"
141 | SEP="|"
142 | done
143 | OURCYGPATTERN="(^($ROOTDIRS))"
144 | # Add a user-defined pattern to the cygpath arguments
145 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
146 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
147 | fi
148 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
149 | i=0
150 | for arg in "$@" ; do
151 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
152 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
153 |
154 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
155 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
156 | else
157 | eval `echo args$i`="\"$arg\""
158 | fi
159 | i=`expr $i + 1`
160 | done
161 | case $i in
162 | 0) set -- ;;
163 | 1) set -- "$args0" ;;
164 | 2) set -- "$args0" "$args1" ;;
165 | 3) set -- "$args0" "$args1" "$args2" ;;
166 | 4) set -- "$args0" "$args1" "$args2" "$args3" ;;
167 | 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
168 | 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
169 | 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
170 | 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
171 | 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
172 | esac
173 | fi
174 |
175 | # Escape application args
176 | save () {
177 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
178 | echo " "
179 | }
180 | APP_ARGS=`save "$@"`
181 |
182 | # Collect all arguments for the java command, following the shell quoting and substitution rules
183 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
184 |
185 | exec "$JAVACMD" "$@"
186 |
--------------------------------------------------------------------------------
/native-base-theme/components/index.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable no-param-reassign */
2 | // @flow
3 |
4 | import _ from 'lodash';
5 |
6 | import bodyTheme from './Body';
7 | import leftTheme from './Left';
8 | import rightTheme from './Right';
9 | import headerTheme from './Header';
10 | import switchTheme from './Switch';
11 | import thumbnailTheme from './Thumbnail';
12 | import containerTheme from './Container';
13 | import contentTheme from './Content';
14 | import buttonTheme from './Button';
15 | import titleTheme from './Title';
16 | import subtitleTheme from './Subtitle';
17 | import inputGroupTheme from './InputGroup';
18 | import badgeTheme from './Badge';
19 | import checkBoxTheme from './CheckBox';
20 | import cardTheme from './Card';
21 | import radioTheme from './Radio';
22 | import h3Theme from './H3';
23 | import h2Theme from './H2';
24 | import h1Theme from './H1';
25 | import footerTheme from './Footer';
26 | import footerTabTheme from './FooterTab';
27 | import fabTheme from './Fab';
28 | import itemTheme from './Item';
29 | import labelTheme from './Label';
30 | import textAreaTheme from './Textarea';
31 | import textTheme from './Text';
32 | import toastTheme from './Toast';
33 | import tabTheme from './Tab';
34 | import tabBarTheme from './TabBar';
35 | import tabContainerTheme from './TabContainer';
36 | import viewTheme from './View';
37 | import tabHeadingTheme from './TabHeading';
38 | import iconTheme from './Icon';
39 | import inputTheme from './Input';
40 | import swipeRowTheme from './SwipeRow';
41 | import segmentTheme from './Segment';
42 | import spinnerTheme from './Spinner';
43 | import cardItemTheme from './CardItem';
44 | import listItemTheme from './ListItem';
45 | import formTheme from './Form';
46 | import separatorTheme from './Separator';
47 | import pickerTheme from './Picker';
48 | import variable from './../variables/platform';
49 |
50 | export default (variables /* : * */ = variable) => {
51 | const theme = {
52 | variables,
53 | 'NativeBase.Left': {
54 | ...leftTheme(variables)
55 | },
56 | 'NativeBase.Right': {
57 | ...rightTheme(variables)
58 | },
59 | 'NativeBase.Body': {
60 | ...bodyTheme(variables)
61 | },
62 |
63 | 'NativeBase.Header': {
64 | ...headerTheme(variables)
65 | },
66 |
67 | 'NativeBase.Button': {
68 | ...buttonTheme(variables)
69 | },
70 |
71 | 'NativeBase.Title': {
72 | ...titleTheme(variables)
73 | },
74 | 'NativeBase.Subtitle': {
75 | ...subtitleTheme(variables)
76 | },
77 |
78 | 'NativeBase.InputGroup': {
79 | ...inputGroupTheme(variables)
80 | },
81 |
82 | 'NativeBase.Input': {
83 | ...inputTheme(variables)
84 | },
85 |
86 | 'NativeBase.Badge': {
87 | ...badgeTheme(variables)
88 | },
89 |
90 | 'NativeBase.CheckBox': {
91 | ...checkBoxTheme(variables)
92 | },
93 |
94 | 'NativeBase.Radio': {
95 | ...radioTheme(variables)
96 | },
97 |
98 | 'NativeBase.Card': {
99 | ...cardTheme(variables)
100 | },
101 |
102 | 'NativeBase.CardItem': {
103 | ...cardItemTheme(variables)
104 | },
105 |
106 | 'NativeBase.Toast': {
107 | ...toastTheme(variables)
108 | },
109 |
110 | 'NativeBase.H1': {
111 | ...h1Theme(variables)
112 | },
113 | 'NativeBase.H2': {
114 | ...h2Theme(variables)
115 | },
116 | 'NativeBase.H3': {
117 | ...h3Theme(variables)
118 | },
119 | 'NativeBase.Form': {
120 | ...formTheme(variables)
121 | },
122 |
123 | 'NativeBase.Container': {
124 | ...containerTheme(variables)
125 | },
126 | 'NativeBase.Content': {
127 | ...contentTheme(variables)
128 | },
129 |
130 | 'NativeBase.Footer': {
131 | ...footerTheme(variables)
132 | },
133 |
134 | 'NativeBase.Tabs': {
135 | flex: 1
136 | },
137 |
138 | 'NativeBase.FooterTab': {
139 | ...footerTabTheme(variables)
140 | },
141 |
142 | 'NativeBase.ListItem': {
143 | ...listItemTheme(variables)
144 | },
145 |
146 | 'NativeBase.ListItem1': {
147 | ...listItemTheme(variables)
148 | },
149 |
150 | 'NativeBase.Icon': {
151 | ...iconTheme(variables)
152 | },
153 | 'NativeBase.IconNB': {
154 | ...iconTheme(variables)
155 | },
156 | 'NativeBase.Text': {
157 | ...textTheme(variables)
158 | },
159 | 'NativeBase.Spinner': {
160 | ...spinnerTheme(variables)
161 | },
162 |
163 | 'NativeBase.Fab': {
164 | ...fabTheme(variables)
165 | },
166 |
167 | 'NativeBase.Item': {
168 | ...itemTheme(variables)
169 | },
170 |
171 | 'NativeBase.Label': {
172 | ...labelTheme(variables)
173 | },
174 |
175 | 'NativeBase.Textarea': {
176 | ...textAreaTheme(variables)
177 | },
178 |
179 | 'NativeBase.PickerNB': {
180 | ...pickerTheme(variables),
181 | 'NativeBase.Button': {
182 | 'NativeBase.Text': {}
183 | }
184 | },
185 |
186 | 'NativeBase.Tab': {
187 | ...tabTheme(variables)
188 | },
189 |
190 | 'NativeBase.Segment': {
191 | ...segmentTheme(variables)
192 | },
193 |
194 | 'NativeBase.TabBar': {
195 | ...tabBarTheme(variables)
196 | },
197 | 'NativeBase.ViewNB': {
198 | ...viewTheme(variables)
199 | },
200 | 'NativeBase.TabHeading': {
201 | ...tabHeadingTheme(variables)
202 | },
203 | 'NativeBase.TabContainer': {
204 | ...tabContainerTheme(variables)
205 | },
206 | 'NativeBase.Switch': {
207 | ...switchTheme(variables)
208 | },
209 | 'NativeBase.Separator': {
210 | ...separatorTheme(variables)
211 | },
212 | 'NativeBase.SwipeRow': {
213 | ...swipeRowTheme(variables)
214 | },
215 | 'NativeBase.Thumbnail': {
216 | ...thumbnailTheme(variables)
217 | }
218 | };
219 |
220 | const cssifyTheme = (grandparent, parent, parentKey) => {
221 | _.forEach(parent, (style, styleName) => {
222 | if (
223 | styleName.indexOf('.') === 0 &&
224 | parentKey &&
225 | parentKey.indexOf('.') === 0
226 | ) {
227 | if (grandparent) {
228 | if (!grandparent[styleName]) {
229 | grandparent[styleName] = {};
230 | } else {
231 | grandparent[styleName][parentKey] = style;
232 | }
233 | }
234 | }
235 | if (
236 | style &&
237 | typeof style === 'object' &&
238 | styleName !== 'fontVariant' &&
239 | styleName !== 'transform'
240 | ) {
241 | cssifyTheme(parent, style, styleName);
242 | }
243 | });
244 | };
245 |
246 | cssifyTheme(null, theme, null);
247 |
248 | return theme;
249 | };
250 |
--------------------------------------------------------------------------------
/native-base-theme/components/Item.js:
--------------------------------------------------------------------------------
1 | // @flow
2 |
3 | import { Platform } from 'react-native';
4 |
5 | import variable from './../variables/platform';
6 | import { PLATFORM } from './../variables/commonColor';
7 |
8 | export default (variables /* : * */ = variable) => {
9 | const itemTheme = {
10 | '.floatingLabel': {
11 | 'NativeBase.Input': {
12 | height: 50,
13 | top: 8,
14 | paddingTop: 3,
15 | paddingBottom: 7,
16 | '.multiline': {
17 | minHeight: variables.inputHeightBase,
18 | paddingTop: Platform.OS === PLATFORM.IOS ? 10 : 3,
19 | paddingBottom: Platform.OS === PLATFORM.IOS ? 14 : 10
20 | }
21 | },
22 | 'NativeBase.Label': {
23 | paddingTop: 5
24 | },
25 | 'NativeBase.Icon': {
26 | top: 6,
27 | paddingTop: 8
28 | },
29 | 'NativeBase.IconNB': {
30 | top: 6,
31 | paddingTop: 8
32 | }
33 | },
34 | '.fixedLabel': {
35 | 'NativeBase.Label': {
36 | position: null,
37 | top: null,
38 | left: null,
39 | right: null,
40 | flex: 1,
41 | height: null,
42 | width: null,
43 | fontSize: variables.inputFontSize
44 | },
45 | 'NativeBase.Input': {
46 | flex: 2,
47 | fontSize: variables.inputFontSize
48 | }
49 | },
50 | '.stackedLabel': {
51 | 'NativeBase.Label': {
52 | position: null,
53 | top: null,
54 | left: null,
55 | right: null,
56 | paddingTop: 5,
57 | alignSelf: 'flex-start',
58 | fontSize: variables.inputFontSize - 2
59 | },
60 | 'NativeBase.Icon': {
61 | marginTop: 36
62 | },
63 | 'NativeBase.Input': {
64 | // alignSelf: Platform.OS === PLATFORM.IOS ? 'stretch' : 'flex-start',
65 | alignSelf: 'stretch',
66 | flex: 1,
67 | // width: Platform.OS === PLATFORM.IOS ? null : variables.deviceWidth - 25,
68 | fontSize: variables.inputFontSize,
69 | lineHeight: variables.inputLineHeight - 6,
70 | '.secureTextEntry': {
71 | fontSize: variables.inputFontSize
72 | },
73 | '.multiline': {
74 | paddingTop: Platform.OS === PLATFORM.IOS ? 9 : undefined,
75 | paddingBottom: Platform.OS === PLATFORM.IOS ? 9 : undefined
76 | }
77 | },
78 | flexDirection: null,
79 | minHeight: variables.inputHeightBase + 15
80 | },
81 | '.inlineLabel': {
82 | 'NativeBase.Label': {
83 | position: null,
84 | top: null,
85 | left: null,
86 | right: null,
87 | paddingRight: 20,
88 | height: null,
89 | width: null,
90 | fontSize: variables.inputFontSize
91 | },
92 | 'NativeBase.Input': {
93 | paddingLeft: 5,
94 | fontSize: variables.inputFontSize
95 | },
96 | flexDirection: 'row'
97 | },
98 | 'NativeBase.Label': {
99 | fontSize: variables.inputFontSize,
100 | color: variables.inputColorPlaceholder,
101 | paddingRight: 5
102 | },
103 | 'NativeBase.Icon': {
104 | fontSize: 24,
105 | paddingRight: 8
106 | },
107 | 'NativeBase.IconNB': {
108 | fontSize: 24,
109 | paddingRight: 8
110 | },
111 | 'NativeBase.Input': {
112 | '.multiline': {
113 | height: null
114 | },
115 | height: variables.inputHeightBase,
116 | color: variables.inputColor,
117 | flex: 1,
118 | top: Platform.OS === PLATFORM.IOS ? 1.5 : undefined,
119 | fontSize: variables.inputFontSize
120 | },
121 | '.underline': {
122 | 'NativeBase.Input': {
123 | paddingLeft: 15
124 | },
125 | '.success': {
126 | borderColor: variables.inputSuccessBorderColor
127 | },
128 | '.error': {
129 | borderColor: variables.inputErrorBorderColor
130 | },
131 | borderWidth: variables.borderWidth * 2,
132 | borderTopWidth: 0,
133 | borderRightWidth: 0,
134 | borderLeftWidth: 0,
135 | borderColor: variables.inputBorderColor
136 | },
137 | '.regular': {
138 | 'NativeBase.Input': {
139 | paddingLeft: 8
140 | },
141 | 'NativeBase.Icon': {
142 | paddingLeft: 10
143 | },
144 | '.success': {
145 | borderColor: variables.inputSuccessBorderColor
146 | },
147 | '.error': {
148 | borderColor: variables.inputErrorBorderColor
149 | },
150 | borderWidth: variables.borderWidth * 2,
151 | borderColor: variables.inputBorderColor
152 | },
153 | '.rounded': {
154 | 'NativeBase.Input': {
155 | paddingLeft: 8
156 | },
157 | 'NativeBase.Icon': {
158 | paddingLeft: 10
159 | },
160 | '.success': {
161 | borderColor: variables.inputSuccessBorderColor
162 | },
163 | '.error': {
164 | borderColor: variables.inputErrorBorderColor
165 | },
166 | borderWidth: variables.borderWidth * 2,
167 | borderRadius: 30,
168 | borderColor: variables.inputBorderColor
169 | },
170 |
171 | '.success': {
172 | 'NativeBase.Icon': {
173 | color: variables.inputSuccessBorderColor
174 | },
175 | 'NativeBase.IconNB': {
176 | color: variables.inputSuccessBorderColor
177 | },
178 | '.rounded': {
179 | borderRadius: 30,
180 | borderColor: variables.inputSuccessBorderColor
181 | },
182 | '.regular': {
183 | borderColor: variables.inputSuccessBorderColor
184 | },
185 | '.underline': {
186 | borderWidth: variables.borderWidth * 2,
187 | borderTopWidth: 0,
188 | borderRightWidth: 0,
189 | borderLeftWidth: 0,
190 | borderColor: variables.inputSuccessBorderColor
191 | },
192 | borderColor: variables.inputSuccessBorderColor
193 | },
194 |
195 | '.error': {
196 | 'NativeBase.Icon': {
197 | color: variables.inputErrorBorderColor
198 | },
199 | 'NativeBase.IconNB': {
200 | color: variables.inputErrorBorderColor
201 | },
202 | '.rounded': {
203 | borderRadius: 30,
204 | borderColor: variables.inputErrorBorderColor
205 | },
206 | '.regular': {
207 | borderColor: variables.inputErrorBorderColor
208 | },
209 | '.underline': {
210 | borderWidth: variables.borderWidth * 2,
211 | borderTopWidth: 0,
212 | borderRightWidth: 0,
213 | borderLeftWidth: 0,
214 | borderColor: variables.inputErrorBorderColor
215 | },
216 | borderColor: variables.inputErrorBorderColor
217 | },
218 | '.disabled': {
219 | 'NativeBase.Icon': {
220 | color: '#384850'
221 | },
222 | 'NativeBase.IconNB': {
223 | color: '#384850'
224 | }
225 | },
226 | '.picker': {
227 | marginLeft: 0
228 | },
229 |
230 | borderWidth: variables.borderWidth * 2,
231 | borderTopWidth: 0,
232 | borderRightWidth: 0,
233 | borderLeftWidth: 0,
234 | borderColor: variables.inputBorderColor,
235 | backgroundColor: 'transparent',
236 | flexDirection: 'row',
237 | alignItems: 'center',
238 | marginLeft: 2
239 | };
240 |
241 | return itemTheme;
242 | };
243 |
--------------------------------------------------------------------------------