├── .gitattributes ├── docs ├── logo.png └── logo.sketch ├── src ├── modules │ ├── i18n │ │ ├── locales │ │ │ └── en.tsx │ │ ├── Detector.tsx │ │ ├── Detector.web.tsx │ │ └── Translator.tsx │ ├── router │ │ ├── Base.tsx │ │ └── Base.web.tsx │ ├── storage │ │ ├── AsyncStorage.tsx │ │ ├── AsyncStorage.web.tsx │ │ ├── AsyncStorage.android.tsx │ │ ├── SingleStorage.tsx │ │ ├── PreferencesStorage.tsx │ │ ├── CacheStorage.web.tsx │ │ └── CacheStorage.tsx │ ├── seo │ │ ├── SEO.tsx │ │ └── SEO.web.tsx │ ├── parser │ │ └── Query.tsx │ ├── listener │ │ ├── Emitter.tsx │ │ ├── Network.web.tsx │ │ ├── Screen.web.tsx │ │ ├── Network.tsx │ │ ├── Geolocation.web.tsx │ │ ├── Screen.tsx │ │ └── Geolocation.tsx │ ├── logger │ │ └── Log.tsx │ ├── theme │ │ ├── themes │ │ │ ├── Default.tsx │ │ │ └── Darker.tsx │ │ ├── ThemeStyleSheet.tsx │ │ └── Theme.tsx │ ├── analytics │ │ ├── Analytics.tsx │ │ └── Analytics.web.tsx │ ├── session │ │ └── Auth.tsx │ └── network │ │ └── Request.tsx ├── components │ ├── router │ │ ├── index.web.tsx │ │ └── index.tsx │ ├── ui │ │ ├── LinearGradient.tsx │ │ ├── Touchable.tsx │ │ ├── Image.tsx │ │ ├── RefreshControl.web.tsx │ │ ├── View.tsx │ │ ├── LinearGradient.web.tsx │ │ ├── Text.tsx │ │ ├── RefreshControl.tsx │ │ ├── StatusBarView.web.tsx │ │ ├── ViewSpacer.tsx │ │ ├── Switch.tsx │ │ ├── Loading.tsx │ │ ├── ScrollView.tsx │ │ ├── BlurImage.ios.tsx │ │ ├── ProgressBar.web.tsx │ │ ├── HeaderAction.tsx │ │ ├── BlurImage.tsx │ │ ├── StatusBarView.tsx │ │ ├── StatusBarView.ios.tsx │ │ ├── ListView.tsx │ │ ├── Icon.tsx │ │ ├── Title.tsx │ │ ├── Slider.tsx │ │ ├── ProgressBar.tsx │ │ ├── HeaderDrawerAction.tsx │ │ ├── Container.tsx │ │ ├── DrawerTablet.tsx │ │ ├── DrawerHeader.tsx │ │ ├── BlurImage.android.tsx │ │ ├── OptionItem.tsx │ │ ├── DrawerFooter.tsx │ │ ├── ListView.web.tsx │ │ ├── Panel.tsx │ │ ├── DrawerItem.tsx │ │ ├── MessageCenter.tsx │ │ ├── Link.tsx │ │ ├── MessageItem.tsx │ │ ├── FlexibleGrid.tsx │ │ ├── Input.tsx │ │ ├── Drawer.android.tsx │ │ ├── Drawer.tsx │ │ ├── ModalCenter.tsx │ │ ├── Header.tsx │ │ ├── Filter.tsx │ │ ├── Label.tsx │ │ ├── ModalItem.tsx │ │ ├── AlertMessage.tsx │ │ └── Button.tsx │ └── BaseComponent.tsx ├── typings │ ├── StringFormat.d.ts │ ├── ReactNativeGoogleAnalytics.d.ts │ ├── ReactNativeDeviceInfo.d.ts │ ├── ReactNativeSharedPreferences.d.ts │ └── Base.d.ts ├── utils │ └── StyleList.tsx └── index.tsx ├── .npmignore ├── .gitignore ├── .vscode └── settings.json ├── tslint.json ├── README.md ├── package.json └── tsconfig.json /.gitattributes: -------------------------------------------------------------------------------- 1 | *.pbxproj -text 2 | -------------------------------------------------------------------------------- /docs/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jerson/react-suite/HEAD/docs/logo.png -------------------------------------------------------------------------------- /src/modules/i18n/locales/en.tsx: -------------------------------------------------------------------------------- 1 | export default { 2 | demo: 'demoen' 3 | }; 4 | -------------------------------------------------------------------------------- /docs/logo.sketch: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jerson/react-suite/HEAD/docs/logo.sketch -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | src/ 2 | .idea/ 3 | .git/ 4 | node_modules/ 5 | npm-debug.log 6 | yarn-error.log -------------------------------------------------------------------------------- /src/modules/router/Base.tsx: -------------------------------------------------------------------------------- 1 | export function getBaseRoute(): string { 2 | return ''; 3 | } 4 | -------------------------------------------------------------------------------- /src/modules/router/Base.web.tsx: -------------------------------------------------------------------------------- 1 | export function getBaseRoute(): string { 2 | return document.location.origin; 3 | } 4 | -------------------------------------------------------------------------------- /src/modules/storage/AsyncStorage.tsx: -------------------------------------------------------------------------------- 1 | import { AsyncStorage } from 'react-native'; 2 | 3 | export default AsyncStorage; 4 | -------------------------------------------------------------------------------- /src/modules/seo/SEO.tsx: -------------------------------------------------------------------------------- 1 | export default class SEO { 2 | static isSEORequest(): boolean { 3 | return false; 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /src/modules/seo/SEO.web.tsx: -------------------------------------------------------------------------------- 1 | export default class SEO { 2 | static isSEORequest(): boolean { 3 | return typeof window.callPhantom === 'function'; 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Typescript 2 | build 3 | # OSX 4 | # 5 | .DS_Store 6 | 7 | # node.js 8 | # 9 | node_modules/ 10 | npm-debug.log 11 | yarn-error.log 12 | .idea 13 | -------------------------------------------------------------------------------- /src/components/router/index.web.tsx: -------------------------------------------------------------------------------- 1 | import {BrowserRouter as Router, Route, Switch} from 'react-router-dom'; 2 | 3 | export default { 4 | Router, 5 | Route, 6 | Switch 7 | }; 8 | -------------------------------------------------------------------------------- /src/modules/i18n/Detector.tsx: -------------------------------------------------------------------------------- 1 | const DeviceInfo = require('react-native-device-info'); 2 | 3 | export function getDefaultLocale(): string { 4 | return DeviceInfo.getDeviceLocale(); 5 | } 6 | -------------------------------------------------------------------------------- /src/modules/i18n/Detector.web.tsx: -------------------------------------------------------------------------------- 1 | export function getDefaultLocale(): string { 2 | if (navigator.language) { 3 | return navigator.language; 4 | } else { 5 | return ''; 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/typings/StringFormat.d.ts: -------------------------------------------------------------------------------- 1 | interface Params { 2 | [key: string]: string 3 | } 4 | 5 | declare module 'string-format' { 6 | function format(key: string, params: Params): string 7 | 8 | export = format; 9 | } 10 | 11 | -------------------------------------------------------------------------------- /src/typings/ReactNativeGoogleAnalytics.d.ts: -------------------------------------------------------------------------------- 1 | //TODO completar como debe ser 2 | declare module 'react-native-google-analytics' { 3 | export const Analytics: any; 4 | export const Experiment: any; 5 | export const Hits: any; 6 | } 7 | -------------------------------------------------------------------------------- /src/utils/StyleList.tsx: -------------------------------------------------------------------------------- 1 | export default function StyleList(...styles: T[]): T[] { 2 | let styleList: T[] = []; 3 | 4 | for (let style of styles) { 5 | if (style instanceof Array) { 6 | styleList.push(...style); 7 | } else if (style) { 8 | styleList.push(style); 9 | } 10 | } 11 | 12 | return styleList; 13 | } 14 | -------------------------------------------------------------------------------- /src/modules/parser/Query.tsx: -------------------------------------------------------------------------------- 1 | import qs from 'qs'; 2 | 3 | export interface QueryParams { 4 | [key: string]: string; 5 | } 6 | 7 | export default class Query { 8 | static decode(text: string): QueryParams { 9 | return qs.parse(text.slice(1)); 10 | } 11 | 12 | static encode(text: any): string { 13 | return qs.stringify(text); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/typings/ReactNativeDeviceInfo.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'react-native-device-info' { 2 | export function getDeviceLocale(): string; 3 | 4 | export function getUniqueID(): string; 5 | 6 | export function getUserAgent(): string; 7 | 8 | export function getReadableVersion(): string; 9 | 10 | export function getBundleId(): string; 11 | 12 | } 13 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | // Place your settings in this file to overwrite default and user settings. 2 | { 3 | "files.exclude": { 4 | ".git": true, 5 | "*.map": true, 6 | ".DS_Store": true, 7 | "node_modules": true, 8 | "samples": true, 9 | "build": true, 10 | "android": true, 11 | "docs": true, 12 | "ios": true, 13 | ".idea": true 14 | } 15 | } -------------------------------------------------------------------------------- /src/typings/ReactNativeSharedPreferences.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'react-native-shared-preferences' { 2 | export function getItem(key: string, callback?: (result?: string) => void): Promise 3 | 4 | export function setItem(key: string, value: string, callback?: (error?: Error) => void): Promise 5 | 6 | export function removeItem(key: string, callback?: (error?: Error) => void): Promise 7 | 8 | 9 | } 10 | -------------------------------------------------------------------------------- /src/modules/storage/AsyncStorage.web.tsx: -------------------------------------------------------------------------------- 1 | const simpleStorage = require('simplestorage.js'); 2 | 3 | export default class AsyncStorageWeb { 4 | static async setItem(key: string, value: string): Promise { 5 | simpleStorage.set(key, value); 6 | } 7 | 8 | static async getItem(key: string): Promise { 9 | return simpleStorage.get(key); 10 | } 11 | 12 | static async removeItem(key: string): Promise { 13 | simpleStorage.deleteKey(key); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/components/router/index.tsx: -------------------------------------------------------------------------------- 1 | import {AndroidBackButton, NativeRouter, Route, Switch} from 'react-router-native'; 2 | import * as React from 'react'; 3 | 4 | export interface Props { 5 | children?: JSX.Element | JSX.Element[]; 6 | } 7 | 8 | export interface State { 9 | } 10 | 11 | export class Router extends React.Component { 12 | render() { 13 | let {children, ...props} = this.props; 14 | 15 | return ( 16 | 17 | 18 | {children} 19 | 20 | 21 | ); 22 | } 23 | } 24 | 25 | export default { 26 | Router, 27 | Route, 28 | Switch 29 | }; 30 | -------------------------------------------------------------------------------- /src/components/ui/LinearGradient.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import {ViewStyle} from 'react-native'; 3 | import LinearGradientBase from 'react-native-linear-gradient'; 4 | import BaseComponent from '../BaseComponent'; 5 | import {ThemeVars} from '../../modules/theme/ThemeBuilder'; 6 | 7 | export interface LinearGradientProps { 8 | style?: ViewStyle; 9 | colors: string[]; 10 | } 11 | 12 | export interface State { 13 | } 14 | 15 | export default class LinearGradient extends BaseComponent { 17 | render() { 18 | let {...props} = this.props; 19 | 20 | return ; 21 | } 22 | 23 | loadStyles(theme: ThemeVars) { 24 | return {}; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/components/ui/Touchable.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import {TouchableOpacity, TouchableOpacityProperties, ViewStyle} from 'react-native'; 3 | import BaseComponent from '../BaseComponent'; 4 | import {ThemeVars} from '../../modules/theme/ThemeBuilder'; 5 | 6 | export interface TouchableProps extends TouchableOpacityProperties { 7 | style?: ViewStyle; 8 | onPress?: () => void; 9 | } 10 | 11 | export interface State { 12 | } 13 | 14 | export default class Touchable extends BaseComponent { 15 | 16 | render() { 17 | let {...props} = this.props; 18 | 19 | return ; 20 | } 21 | 22 | loadStyles(theme: ThemeVars) { 23 | return {}; 24 | } 25 | 26 | } 27 | -------------------------------------------------------------------------------- /src/modules/listener/Emitter.tsx: -------------------------------------------------------------------------------- 1 | const PubSub = require('pubsub-js'); 2 | 3 | export type Name = 4 | | 'onLocaleChange' 5 | | 'onThemeChange' 6 | | 'onLocationChange' 7 | | 'onDimensionsChange' 8 | | 'onOrientationChange' 9 | | 'onNoLogin' 10 | | 'onNetworkStateChange' 11 | | 'onSuccessLogin' 12 | | string; 13 | 14 | export default class Emitter { 15 | static emit(name: Name, data: any): boolean { 16 | return PubSub.publish(name, data); 17 | } 18 | 19 | static on(name: Name, callback: Function): string { 20 | return PubSub.subscribe(name, (name: string, data: any) => { 21 | callback(data); 22 | }); 23 | } 24 | 25 | static off(callback: string): void { 26 | return PubSub.unsubscribe(callback); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/modules/logger/Log.tsx: -------------------------------------------------------------------------------- 1 | export default class Log { 2 | static Enabled = __DEV__; 3 | 4 | static debug(message?: any, ...optionalParams: any[]): void { 5 | Log.Enabled && console.log(message, ...optionalParams); 6 | } 7 | 8 | static log(message?: any, ...optionalParams: any[]): void { 9 | Log.Enabled && console.log(message, ...optionalParams); 10 | } 11 | 12 | static warn(message?: any, ...optionalParams: any[]): void { 13 | Log.Enabled && console.warn(message, ...optionalParams); 14 | } 15 | 16 | static error(message?: any, ...optionalParams: any[]): void { 17 | Log.Enabled && console.error(message, ...optionalParams); 18 | } 19 | 20 | static info(message?: any, ...optionalParams: any[]): void { 21 | Log.Enabled && console.log(message, ...optionalParams); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/components/ui/Image.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import {Image as ImageBase, ImageProperties, ImageStyle} from 'react-native'; 3 | import BaseComponent from '../BaseComponent'; 4 | import {ThemeVars} from '../../modules/theme/ThemeBuilder'; 5 | 6 | export interface ImageProps extends ImageProperties { 7 | style?: ImageStyle; 8 | } 9 | 10 | export interface State { 11 | } 12 | 13 | export default class Image extends BaseComponent { 14 | render() { 15 | let {style, ...props} = this.props; 16 | const {theme, styles} = this; 17 | 18 | return ; 19 | } 20 | 21 | loadStyles(theme: ThemeVars) { 22 | return { 23 | container: {} as ImageStyle 24 | }; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/components/ui/RefreshControl.web.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import View from './View'; 3 | import Loading from './Loading'; 4 | import BaseComponent from '../BaseComponent'; 5 | import {ThemeVars} from '../../modules/theme/ThemeBuilder'; 6 | 7 | export interface RefreshControlProps { 8 | refreshing: boolean; 9 | } 10 | 11 | export interface State { 12 | } 13 | 14 | export default class RefreshControlWeb extends BaseComponent { 16 | render() { 17 | if (!this.props.refreshing) { 18 | return null; 19 | } 20 | 21 | return ( 22 | 23 | 24 | 25 | ); 26 | } 27 | 28 | loadStyles(theme: ThemeVars) { 29 | return {}; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/modules/storage/AsyncStorage.android.tsx: -------------------------------------------------------------------------------- 1 | const SharedPreferences = require('react-native-shared-preferences'); 2 | 3 | export default class AsyncStorageAndroid { 4 | static async setItem(key: string, value: string): Promise { 5 | return new Promise((resolve, reject) => { 6 | SharedPreferences.setItem(key, value); 7 | resolve(); 8 | }); 9 | } 10 | 11 | static async getItem(key: string): Promise { 12 | return new Promise((resolve, reject) => { 13 | SharedPreferences.getItem(key, (value: string) => { 14 | resolve(value); 15 | }); 16 | }); 17 | } 18 | 19 | static async removeItem(key: string): Promise { 20 | return new Promise((resolve, reject) => { 21 | SharedPreferences.removeItem(key); 22 | resolve(); 23 | }); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/components/ui/View.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import {LayoutChangeEvent, View as ViewBase, ViewProperties, ViewStyle} from 'react-native'; 3 | import BaseComponent from '../BaseComponent'; 4 | import {ThemeVars} from '../../modules/theme/ThemeBuilder'; 5 | 6 | export interface ViewProps extends ViewProperties { 7 | style?: ViewStyle; 8 | onLayout?: (event: LayoutChangeEvent) => void; 9 | } 10 | 11 | export interface State { 12 | } 13 | 14 | export default class View extends BaseComponent { 15 | 16 | render() { 17 | let {style, ...props} = this.props; 18 | const {styles} = this; 19 | 20 | return ; 21 | } 22 | 23 | loadStyles(theme: ThemeVars) { 24 | return { 25 | container: {} as ViewStyle 26 | }; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/modules/theme/themes/Default.tsx: -------------------------------------------------------------------------------- 1 | import ThemeBuilder, { ThemeDefaultVars, ThemeVars } from '../ThemeBuilder'; 2 | 3 | let defaults: ThemeDefaultVars = { 4 | darkMode: false, 5 | 6 | defaultColor: '#efefef', 7 | primaryColor: '#7367F0', 8 | dangerColor: '#EA5455', 9 | warningColor: '#F8D800', 10 | infoColor: '#0396FF', 11 | successColor: '#28C76F', 12 | 13 | textShadowColor: '#000', 14 | shadowColor: '#000', 15 | 16 | textColor: '#555', 17 | textSecondaryColor: '#999', 18 | 19 | textActiveColor: '#fff', 20 | textActiveSecondaryColor: 'rgba(255,255,255,0.7)', 21 | 22 | backgroundColor: '#fff', 23 | backgroundSecondaryColor: 'rgb(248, 248, 255)', 24 | 25 | backgroundDarkenColor: '#000', 26 | 27 | borderColor: '#f9f9f9', 28 | borderSecondaryColor: '#fafafa' 29 | }; 30 | let theme: ThemeVars = ThemeBuilder.build(defaults); 31 | export default theme; 32 | -------------------------------------------------------------------------------- /src/components/ui/LinearGradient.web.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import {ViewStyle} from 'react-native'; 3 | import View from './View'; 4 | import BaseComponent from '../BaseComponent'; 5 | import {ThemeVars} from '../../modules/theme/ThemeBuilder'; 6 | 7 | export interface LinearGradientProps { 8 | style?: ViewStyle; 9 | colors: string[]; 10 | } 11 | 12 | export interface State { 13 | } 14 | 15 | export default class LinearGradientWeb extends BaseComponent { 17 | render() { 18 | let {colors, style, ...props} = this.props; 19 | 20 | return ( 21 | 25 | ); 26 | } 27 | 28 | loadStyles(theme: ThemeVars) { 29 | return {}; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/components/ui/Text.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import {Text as TextBase, TextProperties, TextStyle} from 'react-native'; 3 | import BaseComponent from '../BaseComponent'; 4 | import {ThemeVars} from '../../modules/theme/ThemeBuilder'; 5 | 6 | export interface TextProps extends TextProperties { 7 | style?: TextStyle; 8 | } 9 | 10 | export interface State { 11 | } 12 | 13 | export default class Text extends BaseComponent { 14 | 15 | render() { 16 | let {style, ...props} = this.props; 17 | const {styles} = this; 18 | 19 | return ; 20 | } 21 | 22 | loadStyles(theme: ThemeVars) { 23 | return { 24 | container: { 25 | color: theme.textColor, 26 | // fontFamily: 'Heebo', 27 | fontSize: 14 28 | } as TextStyle 29 | }; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/components/ui/RefreshControl.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import {RefreshControl as RefreshControlBase} from 'react-native'; 3 | import BaseComponent from '../BaseComponent'; 4 | import {ThemeVars} from '../../modules/theme/ThemeBuilder'; 5 | 6 | export interface RefreshControlProps { 7 | refreshing: boolean; 8 | } 9 | 10 | export interface State { 11 | } 12 | 13 | export default class RefreshControl extends BaseComponent { 15 | render() { 16 | const {theme} = this; 17 | 18 | return ( 19 | 25 | ); 26 | 27 | } 28 | 29 | loadStyles(theme: ThemeVars) { 30 | return {}; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/modules/storage/SingleStorage.tsx: -------------------------------------------------------------------------------- 1 | import AsyncStorage from './AsyncStorage'; 2 | 3 | export default class SingleStorage { 4 | static async set(key: string, value: any): Promise { 5 | if (typeof value === 'undefined') { 6 | return false; 7 | } 8 | value = typeof value !== 'string' ? value.toString() : value; 9 | 10 | try { 11 | await AsyncStorage.setItem(key, value); 12 | } catch (e) { 13 | return false; 14 | } 15 | return true; 16 | } 17 | 18 | static async get(key: string): Promise { 19 | let data = ''; 20 | try { 21 | data = await AsyncStorage.getItem(key); 22 | } catch (e) { 23 | return data; 24 | } 25 | return data; 26 | } 27 | 28 | static async remove(key: string): Promise { 29 | try { 30 | await AsyncStorage.removeItem(key); 31 | } catch (e) { 32 | return false; 33 | } 34 | return true; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/components/ui/StatusBarView.web.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import {StatusBarProperties, ViewStyle} from 'react-native'; 3 | import BaseComponent from '../BaseComponent'; 4 | import {ThemeVars} from '../../modules/theme/ThemeBuilder'; 5 | 6 | export interface StatusBarViewProps extends StatusBarProperties { 7 | backgroundColor?: string; 8 | barStyle?: 'default' | 'light-content'; 9 | } 10 | 11 | export interface State { 12 | } 13 | 14 | export default class StatusBarViewWeb extends BaseComponent { 16 | 17 | render() { 18 | return null; 19 | 20 | // let { backgroundColor, barStyle, ...props } = this.props; 21 | // return ; 22 | } 23 | 24 | loadStyles(theme: ThemeVars) { 25 | return { 26 | container: { 27 | height: 20 28 | } as ViewStyle 29 | }; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/components/ui/ViewSpacer.tsx: -------------------------------------------------------------------------------- 1 | import {ViewProps} from './View'; 2 | import * as React from 'react'; 3 | import {View as ViewBase, ViewStyle} from 'react-native'; 4 | import BaseComponent from '../BaseComponent'; 5 | import {ThemeVars} from '../../modules/theme/ThemeBuilder'; 6 | 7 | export interface ViewSpacerProps extends ViewProps { 8 | style?: ViewStyle; 9 | spacing?: number; 10 | } 11 | 12 | export interface State { 13 | } 14 | 15 | export default class ViewSpacer extends BaseComponent { 17 | 18 | render() { 19 | let {style, spacing, ...props} = this.props; 20 | let margin = spacing || 10; 21 | const {styles} = this; 22 | 23 | return ( 24 | 25 | ); 26 | } 27 | 28 | loadStyles(theme: ThemeVars) { 29 | return { 30 | container: { 31 | //flex: 1 32 | } as ViewStyle 33 | }; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/components/ui/Switch.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import {Switch as SwitchBase, SwitchProperties} from 'react-native'; 3 | import BaseComponent from '../BaseComponent'; 4 | import {ThemeVars} from '../../modules/theme/ThemeBuilder'; 5 | 6 | export interface SwitchProps extends SwitchProperties { 7 | activeColor?: string; 8 | } 9 | 10 | export interface State { 11 | } 12 | 13 | export default class Switch extends BaseComponent { 14 | 15 | render() { 16 | let {activeColor, ...props} = this.props; 17 | const {theme, styles} = this; 18 | 19 | activeColor = activeColor || theme.switchTintColor; 20 | return ( 21 | 27 | ); 28 | } 29 | 30 | loadStyles(theme: ThemeVars) { 31 | return {}; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/modules/theme/ThemeStyleSheet.tsx: -------------------------------------------------------------------------------- 1 | import { ThemeVars } from './ThemeBuilder'; 2 | import Theme from './Theme'; 3 | import { ImageStyle, TextStyle, ViewStyle } from 'react-native'; 4 | 5 | export type Style = ViewStyle | TextStyle | ImageStyle; 6 | 7 | export type NamedStyles = { [P in keyof T]: Style }; 8 | 9 | export type ThemeCallback = (theme: ThemeVars) => T; 10 | 11 | export default class ThemeStyleSheet { 12 | // private static onThemeChangeListener: any; 13 | // private static theme: string; 14 | // 15 | // static init() { 16 | // this.onThemeChangeListener = Emitter.on( 17 | // 'onThemeChange', 18 | // this.onThemeChange.bind(this) 19 | // ); 20 | // } 21 | // 22 | // static destroy() { 23 | // Emitter.off(this.onThemeChangeListener); 24 | // } 25 | // 26 | // static onThemeChange(theme: string) { 27 | // this.theme = theme; 28 | // } 29 | 30 | static create>(callback: ThemeCallback): T { 31 | return callback(Theme.vars); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/components/ui/Loading.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import {ActivityIndicator as ActivityIndicatorBase, ViewStyle} from 'react-native'; 3 | import View from './View'; 4 | import BaseComponent from '../BaseComponent'; 5 | import {ThemeVars} from '../../modules/theme/ThemeBuilder'; 6 | 7 | export interface LoadingProps { 8 | style?: ViewStyle; 9 | //inverted?: boolean; 10 | color?: string; 11 | size: 'small' | 'large' | number; 12 | } 13 | 14 | export interface State { 15 | } 16 | 17 | export default class Loading extends BaseComponent { 18 | render() { 19 | let {style, color, ...props} = this.props; 20 | const {theme, styles} = this; 21 | 22 | color = color || theme.loadingColor; 23 | 24 | return ( 25 | 26 | 27 | 28 | ); 29 | } 30 | 31 | loadStyles(theme: ThemeVars) { 32 | return {}; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/components/ui/ScrollView.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import {ScrollView as ScrollViewBase, ScrollViewProperties, ScrollViewStyle} from 'react-native'; 3 | import BaseComponent from '../BaseComponent'; 4 | import {ThemeVars} from '../../modules/theme/ThemeBuilder'; 5 | 6 | export interface ScrollViewProps extends ScrollViewProperties { 7 | style?: ScrollViewStyle; 8 | } 9 | 10 | export interface State { 11 | } 12 | 13 | export default class ScrollView extends BaseComponent { 15 | 16 | refs: { 17 | [string: string]: any; 18 | scrollView: ScrollViewBase; 19 | }; 20 | 21 | render() { 22 | let {style, ...props} = this.props; 23 | const {styles} = this; 24 | 25 | return ( 26 | 31 | ); 32 | } 33 | 34 | loadStyles(theme: ThemeVars) { 35 | return { 36 | container: {} as ScrollViewStyle 37 | }; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/components/ui/BlurImage.ios.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import {Image, ImageStyle} from 'react-native'; 3 | import {BlurView} from 'react-native-blur'; 4 | import {ImageProps} from './Image'; 5 | import {ThemeVars} from '../../modules/theme/ThemeBuilder'; 6 | import BaseComponent from '../BaseComponent'; 7 | 8 | export interface BlurImageProps extends ImageProps { 9 | style?: ImageStyle; 10 | } 11 | 12 | export interface State { 13 | } 14 | 15 | export default class BlurImageIOS extends BaseComponent { 17 | render() { 18 | let {children, style, ...props} = this.props; 19 | const {theme, styles} = this; 20 | 21 | return ( 22 | 23 | 24 | {children} 25 | 26 | 27 | ); 28 | } 29 | 30 | loadStyles(theme: ThemeVars) { 31 | return { 32 | container: { 33 | backgroundColor: 'transparent' 34 | } as ImageStyle 35 | }; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/components/ui/ProgressBar.web.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import {ProgressBar as ProgressBarBase, ViewStyle} from 'react-native'; 3 | import BaseComponent from '../BaseComponent'; 4 | import {ThemeVars} from '../../modules/theme/ThemeBuilder'; 5 | 6 | export interface ProgressBarProps { 7 | trackStyle?: ViewStyle; 8 | style?: ViewStyle; 9 | value?: number; 10 | minimumValue?: number; 11 | maximumValue: number; 12 | } 13 | 14 | export interface State { 15 | } 16 | 17 | export default class ProgressBarWeb extends BaseComponent { 19 | render() { 20 | let { 21 | style, 22 | trackStyle, 23 | minimumValue, 24 | maximumValue, 25 | value, 26 | ...props 27 | } = this.props; 28 | const {theme} = this; 29 | 30 | let progress = (value || 1) / (maximumValue || 1); 31 | 32 | return ( 33 | 39 | ); 40 | } 41 | 42 | loadStyles(theme: ThemeVars) { 43 | return {}; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/components/ui/HeaderAction.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import {TextStyle, ViewStyle} from 'react-native'; 3 | import Link, {LinkProps} from './Link'; 4 | import {ThemeVars} from '../../modules/theme/ThemeBuilder'; 5 | import BaseComponent from '../BaseComponent'; 6 | 7 | export interface HeaderActionProps extends LinkProps { 8 | title?: string; 9 | onPress?: () => void; 10 | } 11 | 12 | export interface State { 13 | } 14 | 15 | export default class HeaderAction extends BaseComponent { 17 | render() { 18 | let {style, iconStyle, ...props} = this.props; 19 | const {theme, styles} = this; 20 | 21 | return ( 22 | 27 | ); 28 | } 29 | 30 | loadStyles(theme: ThemeVars) { 31 | return { 32 | link: { 33 | alignItems: 'center', 34 | justifyContent: 'center', 35 | padding: 4, 36 | marginLeft: 2, 37 | marginRight: 2 38 | } as ViewStyle, 39 | icon: { 40 | fontSize: 30, 41 | color: theme.headerActionIconColor 42 | } as TextStyle 43 | }; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/components/BaseComponent.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import {ImageStyle, TextStyle, ViewStyle} from 'react-native'; 3 | import {ThemeVars} from '../modules/theme/ThemeBuilder'; 4 | import Theme from '../modules/theme/Theme'; 5 | 6 | const PropTypes = require('prop-types'); 7 | 8 | export type Style = ViewStyle | TextStyle | ImageStyle; 9 | export type NamedStyles = { [P in keyof T]: Style }; 10 | 11 | // export type ThemeCallback = (theme: ThemeVars) => T; 12 | 13 | abstract class BaseComponent extends React.Component { 14 | 15 | static contextTypes = { 16 | theme: PropTypes.string, 17 | }; 18 | 19 | styles: any; 20 | theme: ThemeVars; 21 | 22 | constructor(props?: P, context?: any) { 23 | super(props, context); 24 | this.updateTheme(false); 25 | } 26 | 27 | updateTheme(reload: boolean) { 28 | this.theme = Theme.vars; 29 | this.styles = this.loadStyles(this.theme); 30 | reload && this.forceUpdate(); 31 | } 32 | 33 | abstract loadStyles>(theme: ThemeVars): T ; 34 | 35 | componentDidUpdate(prevProps: P, prevState: S, prevContext: any) { 36 | 37 | let reloadTheme = prevContext.theme !== this.context.theme; 38 | if (reloadTheme) { 39 | this.updateTheme(true); 40 | } 41 | 42 | } 43 | 44 | } 45 | 46 | export default BaseComponent; 47 | -------------------------------------------------------------------------------- /src/components/ui/BlurImage.tsx: -------------------------------------------------------------------------------- 1 | import View from './View'; 2 | import * as React from 'react'; 3 | import {Image, ImageStyle, ViewStyle} from 'react-native'; 4 | import {ImageProps} from './Image'; 5 | import BaseComponent from '../BaseComponent'; 6 | import {ThemeVars} from '../../modules/theme/ThemeBuilder'; 7 | 8 | export interface BlurImageProps extends ImageProps { 9 | style?: ImageStyle; 10 | } 11 | 12 | export interface State { 13 | } 14 | 15 | export default class BlurImage extends BaseComponent { 16 | render() { 17 | let {children, style, ...props} = this.props; 18 | const {styles} = this; 19 | 20 | return ( 21 | 22 | 23 | {children} 24 | 25 | 26 | ); 27 | } 28 | 29 | loadStyles(theme: ThemeVars) { 30 | return { 31 | container: { 32 | backgroundColor: theme.blurImageBackgroundColor, 33 | overflow: 'hidden' 34 | } as ViewStyle, 35 | image: { 36 | backgroundColor: 'transparent', 37 | filter: 'blur(14px)', 38 | overflow: 'hidden', 39 | position: 'relative', 40 | opacity: 0.8 41 | } as ImageStyle 42 | }; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/modules/listener/Network.web.tsx: -------------------------------------------------------------------------------- 1 | import Emitter from './Emitter'; 2 | 3 | export type NetworkType = 'OFF' | 'WIFI' | 'MOBILE' | 'UNKNOWN'; 4 | 5 | export default class NetworkWeb { 6 | private static last: NetworkType = 'UNKNOWN'; 7 | 8 | private static onOnlineListener: any; 9 | private static onOfflineListener: any; 10 | 11 | static init() { 12 | this.onOnlineListener = this.onOnline.bind(this); 13 | this.onOfflineListener = this.onOffline.bind(this); 14 | window.addEventListener('offline', this.onOfflineListener); 15 | window.addEventListener('online', this.onOnlineListener); 16 | } 17 | 18 | static destroy() { 19 | window.removeEventListener('offline', this.onOfflineListener); 20 | window.removeEventListener('online', this.onOnlineListener); 21 | } 22 | 23 | static isConnected(): boolean { 24 | return this.last !== 'OFF'; 25 | } 26 | 27 | static getType(): NetworkType { 28 | return this.last; 29 | } 30 | 31 | static async updateNetworkType(): Promise { 32 | if (navigator.onLine) { 33 | this.onOnline(); 34 | } else { 35 | this.onOffline(); 36 | } 37 | return this.last; 38 | } 39 | 40 | private static onOnline() { 41 | this.last = 'WIFI'; 42 | Emitter.emit('onNetworkStateChange', this.last); 43 | } 44 | 45 | private static onOffline() { 46 | this.last = 'OFF'; 47 | Emitter.emit('onNetworkStateChange', this.last); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/components/ui/StatusBarView.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import {Platform, StatusBar as StatusBarBase, StatusBarProperties, ViewStyle} from 'react-native'; 3 | import BaseComponent from '../BaseComponent'; 4 | import {ThemeVars} from '../../modules/theme/ThemeBuilder'; 5 | 6 | export interface StatusBarViewProps extends StatusBarProperties { 7 | backgroundColor?: string; 8 | barStyle?: 'default' | 'light-content'; 9 | } 10 | 11 | export interface State { 12 | } 13 | 14 | export default class StatusBarView extends BaseComponent { 16 | 17 | render() { 18 | let {backgroundColor, barStyle, ...props} = this.props; 19 | const {styles, theme} = this; 20 | 21 | let backgroundColorFinal = 22 | backgroundColor || 23 | (Platform.OS === 'android' 24 | ? theme.statusBarViewBackgroundAndroidColor 25 | : theme.statusBarViewBackgroundIOSColor); 26 | let barStyleFinal = 27 | barStyle || (Platform.OS === 'android' ? 'default' : 'default'); 28 | 29 | return ( 30 | 35 | ); 36 | } 37 | 38 | loadStyles(theme: ThemeVars) { 39 | 40 | return { 41 | container: {} as ViewStyle 42 | }; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/components/ui/StatusBarView.ios.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import {StatusBar, StatusBarProperties, ViewStyle} from 'react-native'; 3 | import View from './View'; 4 | import BaseComponent from '../BaseComponent'; 5 | import {ThemeVars} from '../../modules/theme/ThemeBuilder'; 6 | 7 | export interface StatusBarViewProps extends StatusBarProperties { 8 | backgroundColor?: string; 9 | barStyle?: 'default' | 'light-content'; 10 | } 11 | 12 | export interface State { 13 | } 14 | 15 | export default class StatusBarViewIOS extends BaseComponent { 17 | 18 | render() { 19 | let {backgroundColor, barStyle, ...props} = this.props; 20 | const {styles, theme} = this; 21 | 22 | let backgroundColorFinal = 23 | backgroundColor || theme.statusBarViewBackgroundIOSColor; 24 | let barStyleFinal = barStyle || 'default'; 25 | return ( 26 | 30 | 34 | 35 | ); 36 | } 37 | 38 | loadStyles(theme: ThemeVars) { 39 | return { 40 | container: { 41 | height: 20 42 | } as ViewStyle 43 | }; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "rules": { 3 | "align": [ 4 | true, 5 | "statements" 6 | ], 7 | "class-name": true, 8 | "curly": true, 9 | "eofline": true, 10 | "forin": true, 11 | "indent": [ 12 | true, 13 | "spaces" 14 | ], 15 | "label-position": true, 16 | "max-line-length": [ 17 | true, 18 | 140 19 | ], 20 | "no-arg": true, 21 | "no-bitwise": false, 22 | "no-console": [ 23 | true, 24 | "debug", 25 | "info", 26 | "time", 27 | "timeEnd", 28 | "trace" 29 | ], 30 | "no-consecutive-blank-lines": true, 31 | "no-construct": true, 32 | "no-debugger": true, 33 | "no-duplicate-variable": true, 34 | "no-empty": true, 35 | "no-eval": true, 36 | "no-switch-case-fall-through": true, 37 | "no-trailing-whitespace": false, 38 | "no-unused-expression": false, 39 | "one-line": [ 40 | true, 41 | "check-open-brace", 42 | "check-catch", 43 | "check-else", 44 | "check-whitespace" 45 | ], 46 | "quotemark": [ 47 | true, 48 | "single", 49 | "jsx-single" 50 | ], 51 | "radix": true, 52 | "semicolon": true, 53 | "triple-equals": [ 54 | true, 55 | "allow-null-check" 56 | ], 57 | "variable-name": false, 58 | "whitespace": [ 59 | true, 60 | "check-branch", 61 | "check-decl", 62 | "check-operator", 63 | "check-separator", 64 | "check-type" 65 | ] 66 | } 67 | } -------------------------------------------------------------------------------- /src/modules/theme/themes/Darker.tsx: -------------------------------------------------------------------------------- 1 | import ThemeBuilder, { ThemeDefaultVars, ThemeVars } from '../ThemeBuilder'; 2 | const TinyColor = require('tinycolor2'); 3 | 4 | // let toneColor = '#01060d'; 5 | let toneColor = '#01060d'; 6 | let tone2Color = '#121212'; 7 | 8 | //let primaryColor = '#80e82f'; 9 | let primaryColor = '#05ffee'; 10 | let darkenPrimary = TinyColor(primaryColor).darken(7).toRgbString(); 11 | 12 | let defaults: ThemeDefaultVars = { 13 | darkMode: true, 14 | // primaryColor: '#80e82f', 15 | defaultColor: '#444', 16 | primaryColor, 17 | // primaryColor: '#ff6c29', 18 | // primaryColor: '#97ffc0', 19 | // primaryColor: TinyColor(toneColor).lighten(56).toRgbString(), 20 | infoColor: darkenPrimary, 21 | successColor: darkenPrimary, 22 | dangerColor: '#ff312e', 23 | warningColor: '#f48024', 24 | 25 | textShadowColor: 'rgba(0,0,0,0.7)', 26 | shadowColor: '#000', 27 | 28 | textColor: '#fff', 29 | textSecondaryColor: TinyColor(toneColor) 30 | .lighten(90) 31 | .setAlpha(0.5) 32 | .toRgbString(), 33 | 34 | textActiveColor: '#fff', 35 | textActiveSecondaryColor: 'rgba(255,255,255,0.8)', 36 | 37 | backgroundColor: TinyColor(toneColor).lighten(3).toRgbString(), 38 | backgroundSecondaryColor: TinyColor(toneColor).lighten(5).toRgbString(), 39 | 40 | backgroundDarkenColor: TinyColor(toneColor).darken(3).toRgbString(), 41 | 42 | borderColor: TinyColor(toneColor).lighten(5).toRgbString(), 43 | borderSecondaryColor: TinyColor(toneColor).lighten(3).toRgbString() 44 | }; 45 | let theme: ThemeVars = ThemeBuilder.build(defaults); 46 | export default theme; 47 | -------------------------------------------------------------------------------- /src/components/ui/ListView.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { 3 | LayoutChangeEvent, 4 | ListView as BaseListView, 5 | ListViewProperties, 6 | MeasureOnSuccessCallback, 7 | NativeScrollEvent, 8 | NativeSyntheticEvent, 9 | ScrollViewStyle, 10 | ViewStyle 11 | } from 'react-native'; 12 | import BaseComponent from '../BaseComponent'; 13 | import {ThemeVars} from '../../modules/theme/ThemeBuilder'; 14 | 15 | export interface ListViewProps extends ListViewProperties { 16 | dataSource: any; 17 | renderRow: (rowData: any, 18 | sectionID: string | number, 19 | rowID: string | number, 20 | highlightRow?: boolean, 21 | extra?: any) => React.ReactElement; 22 | renderHeader?: () => React.ReactElement; 23 | renderFooter?: () => React.ReactElement; 24 | onScroll?: (event?: NativeSyntheticEvent) => void; 25 | contentContainerStyle?: ViewStyle; 26 | style?: ScrollViewStyle; 27 | onLayout?: (event: LayoutChangeEvent) => void; 28 | } 29 | 30 | export interface State { 31 | } 32 | 33 | export default class ListView extends BaseComponent { 34 | public static DataSource: any = BaseListView.DataSource; 35 | 36 | refs: { 37 | [string: string]: any; 38 | list: BaseListView; 39 | }; 40 | 41 | refreshView() { 42 | //FIXME no funciona del todo 43 | this.forceUpdate(); 44 | } 45 | 46 | measure(callback: MeasureOnSuccessCallback) { 47 | // this.refs.list.scrollResponderInputMeasureAndScrollToKeyboard(callback); 48 | } 49 | 50 | render() { 51 | let {style, ...props} = this.props; 52 | 53 | return ; 54 | } 55 | 56 | loadStyles(theme: ThemeVars) { 57 | return { 58 | container: {} as ViewStyle 59 | }; 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/components/ui/Icon.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import MaterialIcons from 'react-native-vector-icons/MaterialIcons'; 3 | import {TextStyle} from 'react-native'; 4 | import BaseComponent from '../BaseComponent'; 5 | import {ThemeVars} from '../../modules/theme/ThemeBuilder'; 6 | 7 | export type IconType = 'material'; 8 | 9 | //FIXME how to add support for this icon types in web 10 | /*export type IconType = 11 | | 'material' 12 | | 'fontAwesome' 13 | | 'zocial' 14 | | 'ionicons' 15 | | 'foundation' 16 | | 'octicons' 17 | | 'entypo' 18 | | 'evilIcons';*/ 19 | 20 | export interface IconProps { 21 | type?: IconType; 22 | style?: TextStyle; 23 | size?: number; 24 | name: string; 25 | } 26 | 27 | export interface State { 28 | } 29 | 30 | export default class Icon extends BaseComponent { 31 | render() { 32 | let {type, name, ...props} = this.props; 33 | let newName = name.replace(/_/g, '-'); 34 | 35 | switch (type) { 36 | default: 37 | case 'material': 38 | return ; 39 | /*case 'fontAwesome': 40 | return ; 41 | case 'zocial': 42 | return ; 43 | case 'ionicons': 44 | return ; 45 | case 'foundation': 46 | return ; 47 | case 'octicons': 48 | return ; 49 | case 'entypo': 50 | return ; 51 | case 'evilIcons': 52 | return ;*/ 53 | } 54 | } 55 | 56 | loadStyles(theme: ThemeVars) { 57 | return {}; 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/components/ui/Title.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import {Text as TextBase, TextStyle} from 'react-native'; 3 | import {TextProps} from './Text'; 4 | import BaseComponent from '../BaseComponent'; 5 | import {ThemeVars} from '../../modules/theme/ThemeBuilder'; 6 | 7 | export interface TitleProps extends TextProps { 8 | style?: TextStyle; 9 | center?: boolean; 10 | size?: 'small' | 'normal' | 'medium' | 'large' | number; 11 | } 12 | 13 | export interface State { 14 | } 15 | 16 | export default class Title extends BaseComponent { 17 | 18 | render() { 19 | let {style, center, size, ...props} = this.props; 20 | const {styles} = this; 21 | let fontSize = typeof size === 'number' ? size : 18; 22 | if (typeof size === 'string') { 23 | switch (size) { 24 | case 'small': 25 | fontSize = 16; 26 | break; 27 | case 'normal': 28 | default: 29 | fontSize = 18; 30 | break; 31 | case 'medium': 32 | fontSize = 20; 33 | break; 34 | case 'large': 35 | fontSize = 24; 36 | break; 37 | } 38 | } 39 | 40 | return ( 41 | 45 | ); 46 | } 47 | 48 | loadStyles(theme: ThemeVars) { 49 | return { 50 | center: {textAlign: 'center'}, 51 | container: { 52 | color: theme.titleColor, 53 | //fontFamily: 'Roboto,Helvetica,Arial', 54 | fontSize: 14, 55 | paddingTop: 4, 56 | paddingBottom: 4 57 | } as TextStyle 58 | }; 59 | } 60 | 61 | } 62 | -------------------------------------------------------------------------------- /src/components/ui/Slider.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import {ViewStyle} from 'react-native'; 3 | import SliderBase from 'react-native-slider'; 4 | import BaseComponent from '../BaseComponent'; 5 | import {ThemeVars} from '../../modules/theme/ThemeBuilder'; 6 | 7 | export interface SliderProps { 8 | trackStyle?: ViewStyle; 9 | style?: ViewStyle; 10 | value?: number; 11 | minimumValue?: number; 12 | maximumValue: number; 13 | } 14 | 15 | export interface State { 16 | } 17 | 18 | export default class Slider extends BaseComponent { 19 | 20 | render() { 21 | let { 22 | style, 23 | trackStyle, 24 | minimumValue, 25 | maximumValue, 26 | ...props 27 | } = this.props; 28 | const {styles, theme} = this; 29 | 30 | let minimumValueFinal = minimumValue || 0; 31 | let maximumValueFinal = maximumValue || 0; 32 | 33 | minimumValueFinal = minimumValueFinal < 0 ? 0 : minimumValueFinal; 34 | maximumValueFinal = maximumValueFinal < minimumValueFinal 35 | ? minimumValueFinal + 1 36 | : maximumValueFinal; 37 | 38 | return ( 39 | 48 | ); 49 | } 50 | 51 | loadStyles(theme: ThemeVars) { 52 | return { 53 | container: { 54 | marginTop: 2 55 | } as ViewStyle, 56 | track: {} as ViewStyle, 57 | thumb: { 58 | backgroundColor: theme.sliderThumbColor 59 | } as ViewStyle 60 | }; 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ![default view](https://github.com/jerson/react-suite/raw/master/docs/logo.png) 4 | 5 | [![Donate](https://img.shields.io/badge/Donate-PayPal-green.svg)](https://www.paypal.me/gpardogamez) 6 | 7 | react-suite, is a collection of components and modules build on top react-native 8 | and react-native-web made with typescript for build apps for Android, 9 | IOS and Web with the same code. 10 | 11 | 12 | ![default view](https://github.com/jerson/react-suite-samples/raw/master/docs/android02.png) 13 | ![default view](https://github.com/jerson/react-suite-samples/raw/master/docs/ios02.png) 14 | ![default view](https://github.com/jerson/react-suite-samples/raw/master/docs/web02.png) 15 | 16 | 17 | 18 | ## how to start 19 | 20 | you can clone this repo [react-suite-boilerplate](https://github.com/jerson/react-suite-boilerplate) an just code 21 | 22 | ## docs 23 | 24 | you can check docs and samples here [react-suite-samples](https://github.com/jerson/react-suite-samples) 25 | 26 | ## install 27 | 28 | yarn add react-suite 29 | 30 | 31 | ## TODO 32 | 33 | - [x] logo , #if you are a designer please help me 34 | - [ ] tests 35 | - [ ] more samples 36 | - [ ] more components and modules 37 | - [ ] spell check # sorry is too late for use google translate 38 | - [ ] add extractor for translations 39 | - [ ] themes 40 | - [ ] i need money :( 41 | 42 | 43 | ## links 44 | 45 | - [react-suite-samples](https://github.com/jerson/react-suite-samples) 46 | - [react-suite-boilerplate](https://github.com/jerson/react-suite-boilerplate) 47 | 48 | 49 | ## thanks to 50 | 51 | - react 52 | - react-dom 53 | - react-native 54 | - react-native-web 55 | - pouchdb-browser 56 | - react-native-blur 57 | - react-native-device-info 58 | - react-native-drawer 59 | - react-native-google-analytics 60 | - react-native-linear-gradient 61 | - react-native-orientation 62 | - react-native-shared-preferences 63 | - react-native-vector-icons 64 | - react-router-dom 65 | - react-router-native 66 | - realm 67 | -------------------------------------------------------------------------------- /src/modules/theme/Theme.tsx: -------------------------------------------------------------------------------- 1 | import PreferencesStorage from '../storage/PreferencesStorage'; 2 | import Emitter from '../listener/Emitter'; 3 | import Log from '../logger/Log'; 4 | import Default from './themes/Default'; 5 | import Darker from './themes/Darker'; 6 | import { ThemeVars } from './ThemeBuilder'; 7 | import ThemeStyleSheet from './ThemeStyleSheet'; 8 | 9 | export interface UserSettings { 10 | themes: Themes; 11 | defaultTheme?: string; 12 | } 13 | 14 | export interface Settings { 15 | themes: Themes; 16 | defaultTheme: string; 17 | } 18 | 19 | // export interface AppTheme { 20 | // [key: string]: any; 21 | // } 22 | 23 | export interface Themes { 24 | [key: string]: ThemeVars; 25 | } 26 | 27 | export default class Theme { 28 | public static settings: Settings = { 29 | themes: { Default, Darker }, 30 | defaultTheme: 'Default' 31 | }; 32 | static theme: string = Theme.getDefaultTheme(); 33 | static vars: ThemeVars = Default; 34 | 35 | static async init(settings: UserSettings) { 36 | if (settings) { 37 | this.settings = Object.assign({}, this.settings, settings); 38 | } 39 | 40 | try { 41 | let theme = await this.getUserTheme(); 42 | this.setTheme(theme); 43 | } catch (e) { 44 | Log.debug('[THEME]', e); 45 | } 46 | } 47 | 48 | static setTheme(theme: string): void { 49 | this.theme = theme; 50 | this.vars = { ...this.vars, ...this.settings.themes[this.theme] }; 51 | Emitter.emit('onThemeChange', this.theme); 52 | } 53 | 54 | static getTheme(): string { 55 | return this.theme; 56 | } 57 | 58 | static async getUserTheme(): Promise { 59 | try { 60 | let data = await PreferencesStorage.get('theme'); 61 | let theme = data.toString(); 62 | return theme ? theme : this.getDefaultTheme(); 63 | } catch (e) { 64 | return this.getDefaultTheme(); 65 | } 66 | } 67 | 68 | static getDefaultTheme(): string { 69 | return this.settings.defaultTheme; 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/components/ui/ProgressBar.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import {ViewStyle} from 'react-native'; 3 | import SliderBase from 'react-native-slider'; 4 | import BaseComponent from '../BaseComponent'; 5 | import {ThemeVars} from '../../modules/theme/ThemeBuilder'; 6 | 7 | export interface ProgressBarProps { 8 | trackStyle?: ViewStyle; 9 | style?: ViewStyle; 10 | value?: number; 11 | minimumValue?: number; 12 | maximumValue: number; 13 | } 14 | 15 | export interface State { 16 | } 17 | 18 | export default class ProgressBar extends BaseComponent { 20 | render() { 21 | let { 22 | style, 23 | trackStyle, 24 | minimumValue, 25 | maximumValue, 26 | ...props 27 | } = this.props; 28 | const {styles, theme} = this; 29 | 30 | let minimumValueFinal = minimumValue || 0; 31 | let maximumValueFinal = maximumValue || 0; 32 | 33 | minimumValueFinal = minimumValueFinal < 0 ? 0 : minimumValueFinal; 34 | maximumValueFinal = maximumValueFinal < minimumValueFinal 35 | ? minimumValueFinal + 1 36 | : maximumValueFinal; 37 | 38 | return ( 39 | 49 | ); 50 | } 51 | 52 | loadStyles(theme: ThemeVars) { 53 | return { 54 | container: {} as ViewStyle, 55 | track: {} as ViewStyle, 56 | thumb: { 57 | width: 1, 58 | height: 1, 59 | borderWidth: 0, 60 | opacity: 0 61 | } as ViewStyle 62 | }; 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/typings/Base.d.ts: -------------------------------------------------------------------------------- 1 | declare var __DEV__: boolean; 2 | declare const module: any; 3 | 4 | interface Window { 5 | callPhantom: any; 6 | prerenderReady: any; 7 | } 8 | 9 | declare module 'react-native-vector-icons/FontAwesome' { 10 | export = module; 11 | } 12 | declare module 'react-native-vector-icons/Zocial' { 13 | export = module; 14 | } 15 | declare module 'react-native-vector-icons/Ionicons' { 16 | export = module; 17 | } 18 | declare module 'react-native-vector-icons/Foundation' { 19 | export = module; 20 | } 21 | declare module 'react-native-vector-icons/MaterialIcons' { 22 | export = module; 23 | } 24 | declare module 'react-native-vector-icons/Octicons' { 25 | export = module; 26 | } 27 | declare module 'react-native-vector-icons/Entypo' { 28 | export = module; 29 | } 30 | declare module 'react-native-vector-icons/EvilIcons' { 31 | export = module; 32 | } 33 | declare module 'react-native-linear-gradient' { 34 | export = module; 35 | } 36 | declare module 'react-native-drawer' { 37 | export = module; 38 | } 39 | declare module 'react-native-slider' { 40 | export = module; 41 | } 42 | declare module 'react-native-blur' { 43 | export const BlurView: any; 44 | } 45 | 46 | declare module 'react-router-dom' { 47 | export const BrowserRouter: any; 48 | export const Route: any; 49 | export const Switch: any; 50 | } 51 | 52 | declare module 'react-router-native' { 53 | export const NativeRouter: any; 54 | export const Route: any; 55 | export const Switch: any; 56 | export const AndroidBackButton: any; 57 | } 58 | 59 | declare module '@storybook/react' { 60 | export const storiesOf: any; 61 | export const setAddon: any; 62 | } 63 | 64 | declare module '@storybook/addon-actions' { 65 | export const action: any; 66 | } 67 | declare module '@storybook/addon-links' { 68 | export const linkTo: any; 69 | } 70 | declare module '@storybook/addon-info' { 71 | export const linkTo: any; 72 | } 73 | declare module '@storybook/addon-centered' { 74 | //export default function ()=>void; 75 | } 76 | -------------------------------------------------------------------------------- /src/components/ui/HeaderDrawerAction.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import {Platform, TextStyle, ViewStyle} from 'react-native'; 3 | import HeaderAction from './HeaderAction'; 4 | import BaseComponent from '../BaseComponent'; 5 | import {ThemeVars} from '../../modules/theme/ThemeBuilder'; 6 | 7 | const PropTypes = require('prop-types'); 8 | 9 | export interface HeaderDrawerActionProps { 10 | style?: ViewStyle; 11 | iconStyle?: TextStyle; 12 | onPress?: () => void; 13 | } 14 | 15 | export interface State { 16 | isMain: boolean; 17 | } 18 | 19 | export default class HeaderDrawerAction extends BaseComponent { 21 | static contextTypes = { 22 | router: PropTypes.object.isRequired, 23 | theme: PropTypes.string, 24 | drawer: PropTypes.object 25 | }; 26 | 27 | state = { 28 | isMain: true 29 | }; 30 | 31 | toggle() { 32 | if (this.state.isMain) { 33 | this.context.drawer && this.context.drawer.toggle(); 34 | } else { 35 | const {history} = this.context.router; 36 | history.goBack(); 37 | } 38 | } 39 | 40 | componentDidMount() { 41 | const {history} = this.context.router; 42 | if (Platform.OS !== 'web') { 43 | let isMain = history.index === 0; 44 | this.setState({isMain}); 45 | } 46 | } 47 | 48 | render() { 49 | let {...props} = this.props; 50 | let {isMain} = this.state; 51 | 52 | if (Platform.OS !== 'web') { 53 | return ( 54 | 59 | ); 60 | } 61 | 62 | return ( 63 | 64 | ); 65 | } 66 | 67 | loadStyles(theme: ThemeVars) { 68 | return { 69 | icon: {} as TextStyle 70 | }; 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/modules/analytics/Analytics.tsx: -------------------------------------------------------------------------------- 1 | import Log from '../logger/Log'; 2 | import { 3 | Analytics as AnalyticsBase, 4 | Experiment, 5 | Hits 6 | } from 'react-native-google-analytics'; 7 | import Network from '../listener/Network'; 8 | 9 | const DeviceInfo = require('react-native-device-info'); 10 | 11 | export interface UserSettings { 12 | analyticsId: string; 13 | appName?: string; 14 | } 15 | 16 | export interface Settings { 17 | analyticsId: string; 18 | appName: string; 19 | } 20 | 21 | export default class Analytics { 22 | public static settings: Settings = { analyticsId: '', appName: '' }; 23 | private static ga: any; 24 | 25 | static init(settings: UserSettings) { 26 | if (settings) { 27 | this.settings = Object.assign({}, this.settings, settings); 28 | } 29 | 30 | if (!this.ga) { 31 | const userAgent = DeviceInfo.getUserAgent().replace(/[^\w\s./;()]/gi, ''); 32 | this.ga = new AnalyticsBase( 33 | this.settings.analyticsId, 34 | DeviceInfo.getUniqueID(), 35 | 1, 36 | userAgent 37 | ); 38 | } 39 | 40 | Log.info('[GA]', 'init'); 41 | } 42 | 43 | static screenView(path: string): void { 44 | if (!Network.isConnected()) { 45 | return; 46 | } 47 | 48 | let screenView = new Hits.ScreenView( 49 | this.settings.appName, 50 | path, 51 | DeviceInfo.getReadableVersion(), 52 | DeviceInfo.getBundleId() 53 | ); 54 | this.ga.send(screenView); 55 | } 56 | 57 | static event(params: EventObject): void { 58 | if (!Network.isConnected()) { 59 | return; 60 | } 61 | 62 | let experiment = new Experiment(this.settings.appName, 'Exp'); 63 | let screenView = new Hits.Event( 64 | params.eventCategory, 65 | params.eventAction, 66 | params.eventLabel, 67 | params.eventValue || 0, 68 | experiment 69 | ); 70 | this.ga.send(screenView); 71 | } 72 | } 73 | 74 | export interface EventObject { 75 | eventCategory: string; 76 | eventAction: string; 77 | eventLabel?: string; 78 | eventValue?: number; 79 | nonInteraction?: boolean; 80 | } 81 | -------------------------------------------------------------------------------- /src/components/ui/Container.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import {ViewStyle} from 'react-native'; 3 | import View, {ViewProps} from './View'; 4 | import ScrollView from './ScrollView'; 5 | import BaseComponent from '../BaseComponent'; 6 | import {ThemeVars} from '../../modules/theme/ThemeBuilder'; 7 | 8 | export interface ContainerProps extends ViewProps { 9 | maxWidth?: number; 10 | noScroll?: boolean; 11 | style?: ViewStyle; 12 | } 13 | 14 | export interface State { 15 | } 16 | 17 | export default class Container extends BaseComponent { 18 | render() { 19 | let {noScroll, maxWidth, style, ...props} = this.props; 20 | const {theme, styles} = this; 21 | 22 | let maxWidthFinal = maxWidth || 900; 23 | 24 | if (noScroll) { 25 | return ( 26 | 27 | 36 | 37 | ); 38 | } 39 | 40 | return ( 41 | 42 | 51 | 52 | ); 53 | } 54 | 55 | loadStyles(theme: ThemeVars) { 56 | return { 57 | scrollViewContainer: { 58 | flexDirection: 'row', 59 | justifyContent: 'center' 60 | } as ViewStyle, 61 | container: { 62 | padding: 20 63 | } as ViewStyle 64 | }; 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/components/ui/DrawerTablet.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import {ViewStyle} from 'react-native'; 3 | import View from './View'; 4 | import {DrawerProps} from './Drawer'; 5 | import BaseComponent from '../BaseComponent'; 6 | import {ThemeVars} from '../../modules/theme/ThemeBuilder'; 7 | 8 | const PropTypes = require('prop-types'); 9 | 10 | export interface DrawerTabletProps extends DrawerProps { 11 | children?: JSX.Element; 12 | content: JSX.Element; 13 | leftStyle?: ViewStyle; 14 | onOpenStart?: () => void; 15 | onCloseStart?: () => void; 16 | } 17 | 18 | export interface State { 19 | } 20 | 21 | export default class DrawerTablet extends BaseComponent { 23 | render() { 24 | let {content, leftStyle, children, ...props} = this.props; 25 | const {theme, styles} = this; 26 | 27 | return ( 28 | 29 | 30 | {content} 31 | 32 | 33 | {children} 34 | 35 | 36 | ); 37 | } 38 | 39 | loadStyles(theme: ThemeVars) { 40 | return { 41 | container: { 42 | backgroundColor: theme.drawerBackgroundColor, 43 | flexDirection: 'row', 44 | flex: 1 45 | } as ViewStyle, 46 | left: { 47 | backgroundColor: theme.drawerBackgroundColor, 48 | zIndex: 2, 49 | elevation: 2, 50 | shadowColor: theme.drawerShadowColor, 51 | shadowOpacity: 0.1, 52 | shadowRadius: 2, 53 | shadowOffset: { 54 | height: 1, 55 | width: 2 56 | } 57 | } as ViewStyle, 58 | right: { 59 | backgroundColor: theme.drawerContentBackgroundColor, 60 | flex: 1, 61 | zIndex: 1 62 | } as ViewStyle 63 | }; 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/components/ui/DrawerHeader.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import {ImageURISource, TextStyle, ViewStyle} from 'react-native'; 3 | import View from './View'; 4 | import Text from './Text'; 5 | import Image from './Image'; 6 | import BaseComponent from '../BaseComponent'; 7 | import {ThemeVars} from '../../modules/theme/ThemeBuilder'; 8 | 9 | export interface DrawerHeaderProps extends ViewStyle { 10 | children?: JSX.Element | JSX.Element[]; 11 | title?: string; 12 | logo?: ImageURISource; 13 | style?: ViewStyle; 14 | logoStyle?: TextStyle; 15 | titleStyle?: TextStyle; 16 | } 17 | 18 | export interface State { 19 | } 20 | 21 | export default class DrawerHeader extends BaseComponent { 23 | render() { 24 | let { 25 | style, 26 | children, 27 | title, 28 | logo, 29 | logoStyle, 30 | titleStyle, 31 | ...props 32 | } = this.props; 33 | const {theme, styles} = this; 34 | 35 | return ( 36 | 37 | {children} 38 | {logo && 39 | } 44 | {title && {title}} 45 | 46 | ); 47 | } 48 | 49 | loadStyles(theme: ThemeVars) { 50 | return { 51 | container: { 52 | backgroundColor: theme.drawerHeaderBackgroundColor, 53 | justifyContent: 'center', 54 | padding: 13, 55 | paddingLeft: 10, 56 | paddingRight: 10, 57 | flexDirection: 'column', 58 | alignItems: 'center' 59 | } as ViewStyle, 60 | logoIcon: { 61 | width: 150, 62 | height: 70, 63 | alignSelf: 'center' 64 | } as ViewStyle, 65 | label: { 66 | margin: 10, 67 | fontSize: 18, 68 | color: theme.drawerHeaderTextColor 69 | } as TextStyle 70 | }; 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/components/ui/BlurImage.android.tsx: -------------------------------------------------------------------------------- 1 | import View from './View'; 2 | /** 3 | * @flow 4 | */ 5 | import * as React from 'react'; 6 | import {findNodeHandle, Image, ImageStyle, ViewStyle} from 'react-native'; 7 | import {BlurView} from 'react-native-blur'; 8 | import {ImageProps} from './Image'; 9 | import BaseComponent from '../BaseComponent'; 10 | import {ThemeVars} from '../../modules/theme/ThemeBuilder'; 11 | 12 | export interface BlurImageProps extends ImageProps { 13 | style?: ImageStyle; 14 | } 15 | 16 | export interface State { 17 | viewRef: any; 18 | } 19 | 20 | export default class BlurImageAndroid extends BaseComponent { 22 | state = { 23 | viewRef: null 24 | }; 25 | 26 | refs: { 27 | [string: string]: any; 28 | input: Image; 29 | }; 30 | private viewRef: any; 31 | 32 | imageLoaded() { 33 | this.setState({viewRef: findNodeHandle(this.refs.backgroundImage)}); 34 | } 35 | 36 | render() { 37 | let {style, children, ...props} = this.props; 38 | const {theme, styles} = this; 39 | let {viewRef} = this.state; 40 | 41 | return ( 42 | 43 | 49 | {viewRef && 50 | } 57 | 58 | {children} 59 | 60 | 61 | ); 62 | } 63 | 64 | loadStyles(theme: ThemeVars) { 65 | return { 66 | container: { 67 | backgroundColor: 'transparent' 68 | } as ImageStyle, 69 | blurView: { 70 | position: 'absolute', 71 | left: 0, 72 | top: 0, 73 | bottom: 0, 74 | right: 0 75 | } as ViewStyle 76 | }; 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/components/ui/OptionItem.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import {TextStyle, ViewStyle} from 'react-native'; 3 | //import Icon, {IconType} from './Icon'; 4 | import Touchable from './Touchable'; 5 | import View from './View'; 6 | import {ButtonProps} from './Button'; 7 | import Image from './Image'; 8 | import Text from './Text'; 9 | import BaseComponent from '../BaseComponent'; 10 | import {ThemeVars} from '../../modules/theme/ThemeBuilder'; 11 | 12 | export interface OptionItemProps extends ButtonProps { 13 | title: string; 14 | image?: string; 15 | icon?: string; 16 | //iconType?: IconType; 17 | onPress: () => void; 18 | } 19 | 20 | export interface State { 21 | } 22 | 23 | export default class OptionItem extends BaseComponent { 25 | render() { 26 | let {icon, image, title, onPress} = this.props; 27 | const {styles} = this; 28 | 29 | return ( 30 | 31 | 32 | 33 | {image && } 34 | {title} 35 | 36 | 37 | ); 38 | } 39 | 40 | loadStyles(theme: ThemeVars) { 41 | return { 42 | containerAlter: { 43 | paddingTop: 2, 44 | paddingBottom: 2, 45 | borderTopWidth: 0.5, 46 | borderTopColor: theme.optionItemBorderColor 47 | } as ViewStyle, 48 | container: { 49 | flexDirection: 'row', 50 | alignItems: 'center', 51 | paddingTop: 10, 52 | paddingBottom: 10 53 | } as ViewStyle, 54 | icon: { 55 | marginLeft: 10, 56 | color: theme.optionItemIconColor 57 | } as ViewStyle, 58 | image: { 59 | marginLeft: 10, 60 | width: 40, 61 | height: 40 62 | } as ViewStyle, 63 | title: { 64 | marginLeft: 10, 65 | fontSize: 14, 66 | color: theme.optionItemTextColor, 67 | fontWeight: '400' 68 | } as TextStyle, 69 | button: { 70 | flex: 1 71 | } as ViewStyle 72 | }; 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/modules/storage/PreferencesStorage.tsx: -------------------------------------------------------------------------------- 1 | import SingleStorage from './SingleStorage'; 2 | import Log from '../logger/Log'; 3 | 4 | export interface UserSettings { 5 | defaults?: Preferences; 6 | } 7 | 8 | export interface Settings { 9 | defaults: Preferences; 10 | } 11 | 12 | export type PreferenceValue = boolean | string; 13 | 14 | export interface Preferences { 15 | [key: string]: PreferenceValue; 16 | } 17 | 18 | export default class PreferencesStorage { 19 | public static settings: Settings = { 20 | defaults: { 21 | locale: 'auto', 22 | sampleBool: true 23 | } 24 | }; 25 | static cache: any = {}; 26 | 27 | static init(settings?: UserSettings) { 28 | if (settings) { 29 | this.settings = Object.assign({}, this.settings, settings); 30 | } 31 | } 32 | 33 | static getDefault(key: string): PreferenceValue { 34 | return this.settings.defaults[key]; 35 | } 36 | 37 | static async set(key: string, value: PreferenceValue): Promise { 38 | if (typeof value === 'undefined') { 39 | return false; 40 | } 41 | this.cache[key] = value; 42 | let savedValue = ''; 43 | Log.info('[PREF]', '[FAST]', 'set', key, this.cache[key]); 44 | if (typeof value === 'boolean') { 45 | savedValue = value ? 'ON' : 'OFF'; 46 | } else { 47 | savedValue = value; 48 | } 49 | 50 | return SingleStorage.set(key, savedValue); 51 | } 52 | 53 | static async get(key: string): Promise { 54 | if (typeof this.cache[key] !== 'undefined') { 55 | Log.info('[PREF]', '[FAST]', 'get', key, this.cache[key]); 56 | return this.cache[key]; 57 | } 58 | 59 | let defaultValue = this.getDefault(key); 60 | let value: PreferenceValue = ''; 61 | try { 62 | let data = await SingleStorage.get(key); 63 | 64 | if ( 65 | (typeof data === 'undefined' || data === null) && 66 | typeof defaultValue !== 'undefined' 67 | ) { 68 | value = defaultValue; 69 | } else if (data === 'ON') { 70 | value = true; 71 | } else if (data === 'OFF') { 72 | value = false; 73 | } else { 74 | value = data; 75 | } 76 | 77 | this.cache[key] = value; 78 | } catch (e) { 79 | if (typeof defaultValue !== 'undefined') { 80 | value = defaultValue; 81 | } 82 | } 83 | 84 | return value; 85 | } 86 | 87 | static async remove(key: string): Promise { 88 | return SingleStorage.remove(key); 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /src/modules/storage/CacheStorage.web.tsx: -------------------------------------------------------------------------------- 1 | const PouchDB = require('pouchdb-browser'); 2 | import Log from '../logger/Log'; 3 | 4 | export interface UserSettings { 5 | schemaVersion?: number; 6 | path?: string; 7 | } 8 | 9 | export interface Settings { 10 | schemaVersion: number; 11 | path: string; 12 | } 13 | 14 | export interface Cache { 15 | _id: string; 16 | value?: string; 17 | } 18 | 19 | export default class CacheStorageWeb { 20 | public static settings: Settings = { path: 'cache.realm', schemaVersion: 1 }; 21 | private static db: any; 22 | 23 | static init(settings: UserSettings) { 24 | if (settings) { 25 | this.settings = Object.assign({}, this.settings, settings); 26 | } 27 | 28 | if (this.db) { 29 | return; 30 | } 31 | 32 | this.db = new PouchDB(this.settings.path); 33 | } 34 | 35 | static async set(key: string, value: any): Promise { 36 | let keyEncoded = this.hashCode(key); 37 | let doc: Cache = { _id: '', value: '' }; 38 | 39 | try { 40 | doc = await this.db.get(keyEncoded); 41 | } catch (err) { 42 | Log.warn('[CACHE]', err); 43 | } 44 | 45 | doc.value = value; 46 | if (!doc._id) { 47 | doc._id = keyEncoded; 48 | } 49 | 50 | let result = false; 51 | try { 52 | await this.db.put(doc); 53 | result = true; 54 | } catch (e) { 55 | Log.warn('[CACHE]', e); 56 | } 57 | 58 | return result; 59 | } 60 | 61 | static async get(key: string): Promise { 62 | let data: Cache = { _id: '', value: '' }; 63 | 64 | try { 65 | data = await this.db.get(this.hashCode(key)); 66 | } catch (e) { 67 | Log.warn('[CACHE]', e); 68 | } 69 | return data.value; 70 | } 71 | 72 | static async remove(key: string): Promise { 73 | let data = { 74 | _id: this.hashCode(key) 75 | }; 76 | let result = false; 77 | try { 78 | await this.db.remove(data); 79 | result = true; 80 | } catch (e) { 81 | Log.warn('[CACHE]', e); 82 | } 83 | return result; 84 | } 85 | 86 | private static hashCode(text: string): string { 87 | let hash = 0, 88 | i, 89 | chr, 90 | len; 91 | if (text.length === 0) { 92 | return hash.toString(); 93 | } 94 | for (i = 0, len = text.length; i < len; i++) { 95 | chr = text.charCodeAt(i); 96 | hash = (hash << 5) - hash + chr; 97 | hash |= 0; // Convert to 32bit integer 98 | } 99 | return 'K' + hash; 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /src/components/ui/DrawerFooter.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import {TextStyle, ViewStyle} from 'react-native'; 3 | import View from './View'; 4 | import Icon from './Icon'; 5 | import Text from './Text'; 6 | import {_} from '../../modules/i18n/Translator'; 7 | import BaseComponent from '../BaseComponent'; 8 | import {ThemeVars} from '../../modules/theme/ThemeBuilder'; 9 | 10 | export interface DrawerFooterProps extends ViewStyle { 11 | children?: JSX.Element; 12 | text?: string; 13 | icon?: string; 14 | style?: ViewStyle; 15 | iconStyle?: TextStyle; 16 | textStyle?: TextStyle; 17 | } 18 | 19 | export interface State { 20 | } 21 | 22 | export default class DrawerFooter extends BaseComponent { 24 | render() { 25 | let {style, text, icon, iconStyle, textStyle, ...props} = this.props; 26 | const {theme, styles} = this; 27 | 28 | return ( 29 | 30 | 31 | {icon && 32 | } 33 | {typeof icon === 'undefined' && 34 | } 39 | 40 | {text || _('{app} - All rights reserved', {app: 'React Suite'})} 41 | 42 | 43 | 44 | ); 45 | } 46 | 47 | loadStyles(theme: ThemeVars) { 48 | return { 49 | container: { 50 | borderTopWidth: 1, 51 | borderTopColor: theme.drawerFooterBorderColor, 52 | padding: 12, 53 | paddingLeft: 10, 54 | paddingRight: 10, 55 | flexDirection: 'row' 56 | } as ViewStyle, 57 | labelContainer: { 58 | flexDirection: 'row', 59 | alignItems: 'center', 60 | flex: 1 61 | } as ViewStyle, 62 | label: { 63 | textAlignVertical: 'center', 64 | marginLeft: 15, 65 | fontSize: 12, 66 | color: theme.drawerFooterTextColor 67 | } as TextStyle, 68 | icon: { 69 | marginLeft: 5, 70 | color: theme.drawerFooterIconColor 71 | } as TextStyle 72 | }; 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/modules/i18n/Translator.tsx: -------------------------------------------------------------------------------- 1 | import PreferencesStorage from '../storage/PreferencesStorage'; 2 | import Emitter from '../listener/Emitter'; 3 | import en from './locales/en'; 4 | import Log from '../logger/Log'; 5 | import { getDefaultLocale } from './Detector'; 6 | 7 | const format = require('string-format'); 8 | 9 | export interface UserSettings { 10 | translations: Translations; 11 | defaultLocale?: string; 12 | } 13 | 14 | export interface Settings { 15 | translations: Translations; 16 | defaultLocale: string; 17 | } 18 | 19 | export type TranslationText = string | number; 20 | 21 | export interface Locale { 22 | [key: string]: string; 23 | } 24 | 25 | export interface Params { 26 | [key: string]: TranslationText; 27 | } 28 | 29 | export interface Translations { 30 | [key: string]: Locale; 31 | } 32 | 33 | export function _(key: string, params: Params = {}): string { 34 | return Translator.translate(key, params); 35 | } 36 | 37 | export default class Translator { 38 | public static settings: Settings = { 39 | translations: { en }, 40 | defaultLocale: 'en' 41 | }; 42 | static locale: string = Translator.getSystemLocale(); 43 | 44 | static async init(settings: UserSettings) { 45 | if (settings) { 46 | this.settings = Object.assign({}, this.settings, settings); 47 | } 48 | 49 | try { 50 | let locale = await this.getUserLocale(); 51 | this.setLocale(locale); 52 | } catch (e) { 53 | Log.debug('[TRANSLATOR]', e); 54 | } 55 | } 56 | 57 | static setLocale(locale: string): void { 58 | this.locale = locale === 'auto' ? this.getSystemLocale() : locale; 59 | Emitter.emit('onLocaleChange', this.locale); 60 | } 61 | 62 | static getLocale(): string { 63 | return this.locale; 64 | } 65 | 66 | static translate(text: string, params: Params = {}): string { 67 | if (!text) { 68 | return ''; 69 | } 70 | let translated = text; 71 | if (this.settings.translations[this.locale]) { 72 | translated = this.settings.translations[this.locale][text]; 73 | } 74 | translated = translated ? translated : text; 75 | return format(translated, params); 76 | } 77 | 78 | static async getUserLocale(): Promise { 79 | try { 80 | let data = await PreferencesStorage.get('locale'); 81 | let locale = data.toString(); 82 | return locale ? locale : this.getSystemLocale(); 83 | } catch (e) { 84 | return this.getSystemLocale(); 85 | } 86 | } 87 | 88 | static getSystemLocale(): string { 89 | let initialLocale = getDefaultLocale(); 90 | let locale = initialLocale.split('-'); 91 | return locale[0] ? locale[0] : this.settings.defaultLocale; 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /src/modules/listener/Screen.web.tsx: -------------------------------------------------------------------------------- 1 | import Emitter from '../listener/Emitter'; 2 | import Log from '../logger/Log'; 3 | 4 | export type Orientation = 'PORTRAIT' | 'LANDSCAPE' | string; 5 | 6 | export interface Dimensions { 7 | width: number; 8 | height: number; 9 | scale: number; 10 | fontScale: number; 11 | } 12 | 13 | export default class ScreenWeb { 14 | private static dimensions: Dimensions = { 15 | scale: 1, 16 | fontScale: 1, 17 | width: 0, 18 | height: 0 19 | }; 20 | private static last: Orientation = 'PORTRAIT'; 21 | private static first: Orientation; 22 | 23 | private static handleResizeBinded: any; 24 | private static listener: any; 25 | 26 | static init(): void { 27 | this.handleResizeBinded = this.handleResize.bind(this); 28 | 29 | this.updateOrientation(); 30 | this.updateDimensions(); 31 | 32 | window.addEventListener('resize', this.handleResizeBinded); 33 | } 34 | 35 | static destroy(): void { 36 | window.removeEventListener('resize', this.handleResizeBinded); 37 | } 38 | 39 | static getDimensions(): Dimensions { 40 | return this.dimensions; 41 | } 42 | 43 | static getOrientation(): Orientation { 44 | return this.last; 45 | } 46 | 47 | static async updateOrientation(): Promise { 48 | if (!this.first) { 49 | this.first = this.last; 50 | } 51 | Emitter.emit('onOrientationChange', this.last); 52 | return this.last; 53 | } 54 | 55 | static async updateDimensions(): Promise { 56 | return this.didUpdateDimensions(); 57 | } 58 | 59 | private static getWidth() { 60 | let body = document.body || {}; 61 | let element = document.documentElement || {}; 62 | return ( 63 | window.innerWidth || 64 | body.offsetWidth || 65 | element.clientWidth || 66 | body.clientWidth 67 | ); 68 | } 69 | 70 | private static getHeight() { 71 | let body = document.body || {}; 72 | let element = document.documentElement || {}; 73 | return ( 74 | window.innerHeight || 75 | body.offsetHeight || 76 | element.clientHeight || 77 | body.clientHeight 78 | ); 79 | } 80 | 81 | private static getScreenDimensions(): Dimensions { 82 | return { 83 | width: this.getWidth(), 84 | height: this.getHeight(), 85 | scale: 1, 86 | fontScale: 1 87 | }; 88 | } 89 | 90 | private static didUpdateDimensions(): Dimensions { 91 | this.dimensions = this.getScreenDimensions(); 92 | 93 | Emitter.emit('onDimensionsChange', this.dimensions); 94 | 95 | return this.dimensions; 96 | } 97 | 98 | private static handleResize(): void { 99 | if (this.listener) { 100 | clearTimeout(this.listener); 101 | } 102 | this.listener = setTimeout(() => { 103 | Log.log('handleResize'); 104 | this.didUpdateDimensions(); 105 | }, 200); 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /src/modules/storage/CacheStorage.tsx: -------------------------------------------------------------------------------- 1 | import * as Realm from 'realm'; 2 | 3 | export interface UserSettings { 4 | schemaVersion?: number; 5 | path?: string; 6 | } 7 | 8 | export interface Settings { 9 | schemaVersion: number; 10 | path: string; 11 | } 12 | 13 | const CacheSchema = { 14 | name: 'Cache', 15 | primaryKey: 'key', 16 | properties: { 17 | key: 'string', 18 | value: 'string' 19 | } 20 | }; 21 | 22 | export interface Cache { 23 | key: string; 24 | value: string; 25 | } 26 | 27 | export default class CacheStorage { 28 | public static settings: Settings = { path: 'cache.realm', schemaVersion: 1 }; 29 | private static realm: Realm; 30 | 31 | static init(settings: UserSettings) { 32 | if (settings) { 33 | this.settings = Object.assign({}, this.settings, settings); 34 | } 35 | 36 | if (this.realm) { 37 | return; 38 | } 39 | 40 | this.realm = new Realm({ 41 | schema: [CacheSchema], 42 | path: this.settings.path, 43 | schemaVersion: this.settings.schemaVersion 44 | }); 45 | } 46 | 47 | static async set(key: string, value: any): Promise { 48 | return new Promise((resolve, reject) => { 49 | this.realm.write(() => { 50 | let cache = this.realm.create( 51 | 'Cache', 52 | { 53 | key: this.hashCode(key), 54 | value: JSON.stringify(value) 55 | }, 56 | true 57 | ); 58 | 59 | resolve(true); 60 | }); 61 | }); 62 | } 63 | 64 | static async get(key: string): Promise { 65 | return new Promise((resolve, reject) => { 66 | let keyEncoded = this.hashCode(key); 67 | let result = this.realm.objectForPrimaryKey('Cache', keyEncoded); 68 | 69 | if (result && result.value) { 70 | resolve(JSON.parse(result.value)); 71 | } else { 72 | reject({ code: 404, error: true }); 73 | } 74 | }); 75 | } 76 | 77 | static async remove(key: string): Promise { 78 | return new Promise((resolve, reject) => { 79 | let keyEncoded = this.hashCode(key); 80 | let results = this.realm 81 | .objects('Cache') 82 | .filtered(`key = '${keyEncoded}'`); 83 | this.realm.write(() => { 84 | if (results) { 85 | this.realm.delete(results); 86 | resolve(true); 87 | } else { 88 | reject(false); 89 | } 90 | }); 91 | }); 92 | } 93 | 94 | private static hashCode(text: string): string { 95 | let hash = 0, 96 | i, 97 | chr, 98 | len; 99 | if (text.length === 0) { 100 | return hash.toString(); 101 | } 102 | for (i = 0, len = text.length; i < len; i++) { 103 | chr = text.charCodeAt(i); 104 | hash = (hash << 5) - hash + chr; 105 | hash |= 0; // Convert to 32bit integer 106 | } 107 | return 'K' + hash; 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /src/modules/session/Auth.tsx: -------------------------------------------------------------------------------- 1 | import SingleStorage from '../storage/SingleStorage'; 2 | import Request from '../network/Request'; 3 | import Emitter from '../listener/Emitter'; 4 | import Log from '../logger/Log'; 5 | 6 | export interface UserSettings { 7 | headerName?: string; 8 | authPath?: string; 9 | } 10 | 11 | export interface Settings { 12 | headerName: string; 13 | authPath: string; 14 | } 15 | 16 | export interface User { 17 | [key: string]: string | boolean | number; 18 | } 19 | 20 | export default class Auth { 21 | public static settings: Settings = { 22 | headerName: 'X-APIKey', 23 | authPath: 'auth/me' 24 | }; 25 | private static user: User = {}; 26 | private static accessToken = ''; 27 | 28 | static async init(settings?: UserSettings) { 29 | if (settings) { 30 | this.settings = Object.assign({}, this.settings, settings); 31 | } 32 | Log.info('[AUTH]', 'init'); 33 | await this.checkLogin(); 34 | } 35 | 36 | static async checkLogin() { 37 | if (this.isLoggedIn()) { 38 | return; 39 | } 40 | 41 | Log.info('[AUTH]', 'checkLogin'); 42 | let data = await SingleStorage.get('accessToken'); 43 | if (data) { 44 | await this.login(data); 45 | } else { 46 | Log.info('[AUTH]', 'onNoLogin'); 47 | Emitter.emit('onNoLogin', true); 48 | } 49 | } 50 | 51 | static async login(tmpAccessToken: string): Promise { 52 | Log.info('[AUTH]', 'login', tmpAccessToken); 53 | let isOk = false; 54 | try { 55 | this.accessToken = tmpAccessToken; 56 | let response = await Request.get( 57 | this.settings.authPath, 58 | {}, 59 | 'auth_login', 60 | { secure: true } 61 | ); 62 | this.user = response.body; 63 | await this.setAccessToken(tmpAccessToken); 64 | Log.info('[AUTH]', 'onSuccessLogin'); 65 | Emitter.emit('onSuccessLogin', this.user); 66 | isOk = true; 67 | } catch (e) { 68 | Log.warn('[AUTH]', 'login', e); 69 | this.accessToken = ''; 70 | Emitter.emit('onNoLogin', true); 71 | } 72 | 73 | return isOk; 74 | } 75 | 76 | static isLoggedIn(): boolean { 77 | return !!this.accessToken; 78 | } 79 | 80 | static getAccessToken(): string { 81 | return this.accessToken; 82 | } 83 | 84 | static getUser(): User { 85 | return this.user; 86 | } 87 | 88 | static async setAccessToken(value: string): Promise { 89 | this.accessToken = value; 90 | return SingleStorage.set('accessToken', value); 91 | } 92 | 93 | static async logout(): Promise { 94 | Log.info('[AUTH]', 'logout'); 95 | 96 | this.accessToken = ''; 97 | this.user = {}; 98 | Emitter.emit('onNoLogin', true); 99 | 100 | let ok = false; 101 | try { 102 | ok = await SingleStorage.remove('accessToken'); 103 | } catch (e) { 104 | Log.error('[AUTH]', 'logout', e); 105 | } 106 | 107 | return ok; 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /src/modules/listener/Network.tsx: -------------------------------------------------------------------------------- 1 | import Emitter from './Emitter'; 2 | import { AppState, NetInfo, NetInfoReturnType, Platform } from 'react-native'; 3 | import Log from '../logger/Log'; 4 | 5 | export type NetworkType = 'OFF' | 'WIFI' | 'MOBILE' | 'UNKNOWN'; 6 | 7 | export default class Network { 8 | protected static isReady = true; 9 | private static last: NetworkType = 'UNKNOWN'; 10 | private static changeListener: any; 11 | private static checkAppStateBinded: any; 12 | 13 | static init() { 14 | this.isReady = true; 15 | this.changeListener = this.stateChange.bind(this); 16 | NetInfo.addEventListener('change', this.changeListener); 17 | this.checkAppStateBinded = this.checkAppState.bind(this); 18 | AppState.addEventListener('change', this.checkAppStateBinded); 19 | } 20 | 21 | static destroy() { 22 | this.isReady = false; 23 | NetInfo.removeEventListener('change', this.changeListener); 24 | AppState.removeEventListener('change', this.checkAppStateBinded); 25 | } 26 | 27 | static isConnected(): boolean { 28 | return this.last !== 'OFF'; 29 | } 30 | 31 | static getType(): NetworkType { 32 | return this.last; 33 | } 34 | 35 | static async updateNetworkType(): Promise { 36 | Log.log('[NETWORK]', 'updateNetworkType'); 37 | let info: NetInfoReturnType = await NetInfo.fetch(); 38 | return await this.stateChange(info); 39 | } 40 | 41 | protected static checkAppState(newState: string) { 42 | if (newState === 'active') { 43 | this.updateNetworkType(); 44 | } 45 | } 46 | 47 | protected static parse(reach: string): NetworkType { 48 | reach = reach.toUpperCase(); 49 | let type: NetworkType = 'UNKNOWN'; 50 | 51 | if (Platform.OS === 'ios') { 52 | switch (reach) { 53 | case 'NONE': 54 | type = 'OFF'; 55 | break; 56 | case 'WIFI': 57 | type = 'WIFI'; 58 | break; 59 | case 'CELL': 60 | type = 'MOBILE'; 61 | break; 62 | case 'UNKNOWN': 63 | default: 64 | type = 'UNKNOWN'; 65 | break; 66 | } 67 | } else if (Platform.OS === 'android') { 68 | switch (reach) { 69 | case 'NONE': 70 | type = 'OFF'; 71 | break; 72 | case 'DUMMY': 73 | case 'ETHERNET': 74 | case 'VPN': 75 | case 'WIFI': 76 | case 'WIMAX': 77 | type = 'WIFI'; 78 | break; 79 | case 'MOBILE': 80 | case 'MOBILE_DUN': 81 | case 'MOBILE_HIPRI': 82 | case 'MOBILE_SUPL': 83 | type = 'MOBILE'; 84 | break; 85 | case 'BLUETOOTH': 86 | case 'UNKNOWN': 87 | default: 88 | type = 'UNKNOWN'; 89 | break; 90 | } 91 | } 92 | 93 | return type; 94 | } 95 | 96 | protected static async stateChange(reach: string): Promise { 97 | Log.log('[NETWORK]', 'onNetworkStateChange', reach); 98 | this.last = Network.parse(reach); 99 | Emitter.emit('onNetworkStateChange', this.last); 100 | return this.last; 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /src/modules/listener/Geolocation.web.tsx: -------------------------------------------------------------------------------- 1 | import Log from '../logger/Log'; 2 | import { _ } from '../i18n/Translator'; 3 | import Emitter from './Emitter'; 4 | 5 | export interface UserSettings { 6 | messageTitle?: string; 7 | messageDescription?: string; 8 | } 9 | 10 | export interface Settings { 11 | messageTitle: string; 12 | messageDescription: string; 13 | } 14 | 15 | export default class GeolocationWeb { 16 | public static settings: Settings = { 17 | messageTitle: _('Permission required'), 18 | messageDescription: _('must allow to continue') 19 | }; 20 | private static lastPosition: Position; 21 | private static initialPosition: Position; 22 | private static watchID: number = 0; 23 | 24 | static init(settings?: UserSettings) { 25 | if (settings) { 26 | this.settings = Object.assign({}, this.settings, settings); 27 | } 28 | 29 | this.checkWatcher(true); 30 | } 31 | 32 | static getPosition(): Coordinates { 33 | Log.log('[GEOLOCATION]', 'getPosition'); 34 | let lastPosition = this.lastPosition || { coords: {} }; 35 | let initialPosition = this.initialPosition || { coords: {} }; 36 | return lastPosition.coords || initialPosition.coords; 37 | } 38 | 39 | static async checkWatcher(forced: boolean = true) { 40 | Log.log('[GEOLOCATION]', 'checkWatcher', forced); 41 | this.initWatcher(); 42 | } 43 | 44 | static destroy() { 45 | try { 46 | navigator.geolocation.clearWatch(this.watchID); 47 | } catch (e) { 48 | Log.warn('[GEOLOCATION]', '[ERROR]', e); 49 | } 50 | } 51 | 52 | static async updateLocation(): Promise { 53 | Log.log('[GEOLOCATION]', 'updateLocation'); 54 | return new Promise((resolve, reject) => { 55 | navigator.geolocation.getCurrentPosition( 56 | initialPosition => { 57 | this.initialPosition = initialPosition; 58 | let coords = initialPosition.coords; 59 | 60 | Emitter.emit('onLocationChange', coords); 61 | Log.log('[GEOLOCATION]', 'navigator', 'getCurrentPosition', coords); 62 | 63 | resolve(coords); 64 | }, 65 | error => { 66 | reject(error); 67 | Log.warn('[GEOLOCATION]', '[ERROR]', error); 68 | }, 69 | { 70 | enableHighAccuracy: true, 71 | timeout: 1000 * 30, 72 | maximumAge: 0 73 | } 74 | ); 75 | }); 76 | } 77 | 78 | protected static async initWatcher() { 79 | Log.log('[GEOLOCATION]', 'initWatcher'); 80 | 81 | if (this.watchID) { 82 | navigator.geolocation.clearWatch(this.watchID); 83 | } 84 | await this.updateLocation(); 85 | try { 86 | this.watchID = navigator.geolocation.watchPosition( 87 | lastPosition => { 88 | this.lastPosition = lastPosition; 89 | let coords = lastPosition.coords; 90 | 91 | Emitter.emit('onLocationChange', coords); 92 | Log.log('[GEOLOCATION]', 'navigator', 'watchPosition', coords); 93 | }, 94 | error => { 95 | Log.warn('[GEOLOCATION]', '[ERROR]', error); 96 | }, 97 | { 98 | enableHighAccuracy: true, 99 | maximumAge: 0 100 | } 101 | ); 102 | } catch (e) { 103 | Log.warn('[GEOLOCATION]', '[ERROR]', e); 104 | } 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /src/components/ui/ListView.web.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { 3 | LayoutChangeEvent, 4 | ListViewProperties, 5 | NativeScrollEvent, 6 | NativeSyntheticEvent, 7 | ScrollViewStyle, 8 | ViewStyle 9 | } from 'react-native'; 10 | import ScrollView from './ScrollView'; 11 | import Log from '../../modules/logger/Log'; 12 | import View from './View'; 13 | import BaseComponent from '../BaseComponent'; 14 | import {ThemeVars} from '../../modules/theme/ThemeBuilder'; 15 | 16 | export interface ListViewProps extends ListViewProperties { 17 | dataSource: any; 18 | renderRow: (rowData: any, 19 | sectionID: string | number, 20 | rowID: string | number, 21 | highlightRow?: boolean, 22 | extra?: any) => React.ReactElement; 23 | renderHeader?: () => React.ReactElement; 24 | renderFooter?: () => React.ReactElement; 25 | onScroll?: (event?: NativeSyntheticEvent) => void; 26 | contentContainerStyle?: ViewStyle; 27 | style?: ScrollViewStyle; 28 | onLayout?: (event: LayoutChangeEvent) => void; 29 | } 30 | 31 | export interface State { 32 | } 33 | 34 | export interface DataItem { 35 | } 36 | 37 | export class DataSource { 38 | data = []; 39 | 40 | constructor(config: any) { 41 | Log.info('DataSource'); 42 | } 43 | 44 | cloneWithRows(results: any) { 45 | this.data = results; 46 | return this; 47 | } 48 | } 49 | 50 | export default class ListViewWeb extends BaseComponent { 51 | public static DataSource: any = DataSource; 52 | 53 | refs: { 54 | [string: string]: any; 55 | listView: ScrollView; 56 | }; 57 | 58 | refreshView() { 59 | this.forceUpdate(); 60 | } 61 | 62 | render() { 63 | let { 64 | renderRow, 65 | onLayout, 66 | contentContainerStyle, 67 | style, 68 | renderHeader, 69 | dataSource, 70 | renderFooter 71 | } = this.props; 72 | const {theme, styles} = this; 73 | 74 | return ( 75 | 80 | 81 | 82 | {renderHeader} 83 | 84 | {dataSource && 85 | dataSource.data && 86 | dataSource.data.map( 87 | (rowData: any, 88 | sectionID: string | number, 89 | rowID: string | number, 90 | highlightRow?: boolean) => { 91 | return ( 92 | 93 | {renderRow(rowData, 0, sectionID, false)} 94 | 95 | ); 96 | } 97 | )} 98 | 99 | {renderFooter} 100 | 101 | 102 | 103 | ); 104 | } 105 | 106 | loadStyles(theme: ThemeVars) { 107 | return { 108 | container: {} as ViewStyle 109 | }; 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /src/components/ui/Panel.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import {Text, TextStyle, View, ViewStyle} from 'react-native'; 3 | import Button, {ButtonProps} from './Button'; 4 | import {ThemeVars} from '../../modules/theme/ThemeBuilder'; 5 | import BaseComponent from '../BaseComponent'; 6 | 7 | export interface PanelProps { 8 | title: string | JSX.Element; 9 | actions?: PanelAction[]; 10 | toolbarHeight?: number; 11 | children?: JSX.Element | JSX.Element[]; 12 | style?: ViewStyle; 13 | } 14 | 15 | export interface PanelAction extends ButtonProps { 16 | title?: string; 17 | onPress: () => void; 18 | } 19 | 20 | export interface State { 21 | } 22 | 23 | export default class Panel extends BaseComponent { 24 | render() { 25 | let {title, style, children, actions, toolbarHeight} = this.props; 26 | const {styles} = this; 27 | 28 | let minHeight = toolbarHeight || 50; 29 | 30 | return ( 31 | 32 | 33 | {typeof title === 'object' && 34 | 35 | {title} 36 | } 37 | 38 | {typeof title !== 'object' && 39 | 40 | 41 | {title} 42 | 43 | 44 | {actions && 45 | actions.length > 0 && 46 | 47 | {actions.map((item, index) => { 48 | return