From 3755a83f8d2cd9777dff43ee757983a6a6181cf2 Mon Sep 17 00:00:00 2001 From: AndreyKogut Date: Thu, 22 Jun 2017 16:52:45 +0300 Subject: [PATCH 1/8] Additional message url patterns --- app/components/ParsedText/index.js | 53 ++++++++++++++++++++++++-- app/constants.js | 7 ++++ app/screens/Message/index.js | 13 ++++++- app/screens/Room/Message/index.js | 21 ++++------ app/screens/Room/MessagesList/index.js | 2 + app/screens/Room/index.js | 1 + app/utils/parseUrl.js | 29 ++++++++++++++ 7 files changed, 109 insertions(+), 17 deletions(-) create mode 100644 app/utils/parseUrl.js diff --git a/app/components/ParsedText/index.js b/app/components/ParsedText/index.js index 7f7381e..a5d2aa8 100644 --- a/app/components/ParsedText/index.js +++ b/app/components/ParsedText/index.js @@ -1,10 +1,15 @@ import PropTypes from 'prop-types' import React from 'react' -import {Text} from 'react-native'; +import _ from 'lodash' +import {Text, Linking, Alert} from 'react-native' import Parser from 'react-native-parsed-text' +import {parseGitterGroupUrl, parseGitterMessageUrl, parseGitterRoomUrl} from '../../utils/parseUrl' +import {GITTER_REGEXPS} from '../../constants' import Emoji from '../Emoji' import s from './styles' +const { baseUrl, groupParamsExp, messageParamsExp, roomParamsExp } = GITTER_REGEXPS + const MENTION_REGEX = /(([^`]|^)@([a-zA-Z0-9_\-]+))/ const GROUP_MENTION_REGEX = /^(@\/([a-zA-Z0-9_\-]+))/ const EMOJI_REGEX = /:([a-z0-9A-Z_-]+):/ @@ -33,9 +38,51 @@ const renderCodespan = (matchingString, matches) => { return component } -const ParsedText = ({text, username, handleUrlPress}) => { +const ParsedText = ({text, username, handleUrlPress, navigator}) => { + const handleGitterRoomUrlClick = (url) => { + const {ownerName, roomName} = parseGitterRoomUrl(url) + + navigator.push({screen: 'gm.Room'}) + } + + const handleGitterMessageUrlClick = (url) => { + const {roomName, atParam} = parseGitterMessageUrl(url) + + navigator.push({screen: 'gm.Message', passProps: {route: {roomName, messageId: atParam}}}) + } + + const handleGitterGroupUrlClick = (url) => { + const {groupName} = parseGitterGroupUrl(url) + + navigator.push({screen: 'gm.Home'}) + } + + const handleUrlClick = _.curry((type, url) => { + if (!type) return Linking.openURL(url) + + Alert.alert( + 'How to open url?', + 'Select type or cancel to hide this alert.', + [ + {text: 'Browser', onPress: () => Linking.openURL(url)}, + {text: 'Cancel'}, + {text: 'App', onPress: () => { + switch (type) { + case 'message': return handleGitterMessageUrlClick(url) + case 'group': return handleGitterGroupUrlClick(url) + case 'room': return handleGitterRoomUrlClick(url) + default: break; + } + }} + ] + ) + }) + const patterns = [ - {type: 'url', style: s.url, onPress: handleUrlPress}, + {pattern: new RegExp(`${baseUrl.source}${messageParamsExp.source}`), style: s.url, onPress: handleUrlClick('message')}, + {pattern: new RegExp(`${baseUrl.source}${groupParamsExp.source}`), style: s.url, onPress: handleUrlClick('group')}, + {pattern: new RegExp(`${baseUrl.source}${roomParamsExp.source}`), style: s.url, onPress: handleUrlClick('room')}, + {type: 'url', style: s.url, onPress: Linking.openUrl}, {pattern: new RegExp(`@${username}`), style: s.selfMention}, {pattern: MENTION_REGEX, style: s.mention}, {pattern: GROUP_MENTION_REGEX, style: s.groupMention}, diff --git a/app/constants.js b/app/constants.js index 6eb8c67..4cc0df7 100644 --- a/app/constants.js +++ b/app/constants.js @@ -55,3 +55,10 @@ export const icons = { 'forward': iOS ? {icon: 'chevron-right', color: 'white', size: 40} : {icon: 'arrow-forward', color: 'white', size: 24}, 'expand-more': {icon: 'expand-more', color: 'white', size: 24} } + +export const GITTER_REGEXPS = { + baseUrl: /\bhttps:\/\/gitter.im\/\b/, + roomParamsExp: /([a-zA-Z0-9]+)\/([a-zA-Z0-9]+)/, + messageParamsExp: /([a-zA-Z0-9]+)\/api\?at=([a-z0-9]{24})/, + groupParamsExp: /([a-zA-Z0-9]+)\/home/ +} diff --git a/app/screens/Message/index.js b/app/screens/Message/index.js index 6794ff9..80e3548 100644 --- a/app/screens/Message/index.js +++ b/app/screens/Message/index.js @@ -5,6 +5,7 @@ import {connect} from 'react-redux' import s from './styles' import navigationStyles from '../../styles/common/navigationStyles' +import { getSingleMessage } from '../../modules/messages' import {subscribeToReadBy, unsubscribeFromReadBy} from '../../modules/realtime' import ReadBy from './ReadBy' @@ -34,7 +35,13 @@ class Message extends Component { } componentWillMount() { - const {dispatch, roomId, messageId} = this.props + const {dispatch, roomId, messageId, route} = this.props + if (route) { + const {roomName, messageId: routeMessageId} = route + + dispatch(getSingleMessage(roomName, routeMessageId)) + } + dispatch(subscribeToReadBy(roomId, messageId)) } @@ -98,6 +105,10 @@ class Message extends Component { } Message.propTypes = { + fromSearch: PropTypes.bool, + roomId: PropTypes.string, + messageId: PropTypes.string, + navigator: PropTypes.object, dispatch: PropTypes.func, route: PropTypes.object, messages: PropTypes.object, diff --git a/app/screens/Room/Message/index.js b/app/screens/Room/Message/index.js index 7b0495c..16897ca 100644 --- a/app/screens/Room/Message/index.js +++ b/app/screens/Room/Message/index.js @@ -12,6 +12,8 @@ import Avatar from '../../../components/Avatar' import StatusMessage from '../StatusMessage' import Button from '../../../components/Button' +const handleUrlPress = (url) => Linking.openURL(url) + class Message extends Component { constructor(props) { super(props) @@ -22,16 +24,12 @@ class Message extends Component { } shouldComponentUpdate(nextProps) { - if (!_.isEqual(this.props, nextProps)) { - return true - } else { - return false - } + return !_.isEqual(this.props, nextProps) } onMessagePress() { const {id, onPress, text, rowId} = this.props - const failed = !!this.props.failed && this.props.failed === true + const failed = this.props.failed === true onPress(id, rowId, text, failed) } @@ -40,10 +38,6 @@ class Message extends Component { onLongPress(id) } - handleUrlPress(url) { - Linking.openURL(url) - } - renderDate() { const {sent} = this.props @@ -76,9 +70,9 @@ class Message extends Component { } return ( + username={username} /> ) } @@ -106,7 +100,7 @@ class Message extends Component { text={text} onLongPress={this.onLongPress.bind(this)} onPress={this.onMessagePress.bind(this)} - handleUrlPress={this.handleUrlPress.bind(this)} + handleUrlPress={handleUrlPress} backgroundColor={backgroundColor} opacity={readStatusOpacity} /> ) @@ -188,6 +182,7 @@ Message.defaultProps = { } Message.propTypes = { + navigator: PropTypes.object, id: PropTypes.string, rowId: PropTypes.number, text: PropTypes.string, diff --git a/app/screens/Room/MessagesList/index.js b/app/screens/Room/MessagesList/index.js index b52824f..71ad2c0 100644 --- a/app/screens/Room/MessagesList/index.js +++ b/app/screens/Room/MessagesList/index.js @@ -86,6 +86,7 @@ export default class MessagesList extends Component { this.handleOnLayout(e, rowId)} onPress={onPress} + navigator={this.props.navigator} rowId={rowId} isCollapsed={isCollapsed} onLongPress={onLongPress} @@ -153,6 +154,7 @@ export default class MessagesList extends Component { MessagesList.propTypes = { onPress: PropTypes.func, + navigator: PropTypes.object, listViewData: PropTypes.object, dispatch: PropTypes.func, onEndReached: PropTypes.func, diff --git a/app/screens/Room/index.js b/app/screens/Room/index.js index 724e217..3e5328a 100644 --- a/app/screens/Room/index.js +++ b/app/screens/Room/index.js @@ -670,6 +670,7 @@ class Room extends Component { { + const [, ownerName, roomName] = roomParamsExp.exec(url.replace(baseUrl, '')) + + return { + ownerName, + roomName + } +} + +export const parseGitterGroupUrl = (url) => { + const [, groupName] = groupParamsExp.exec(url.replace(baseUrl, '')) + + return { + groupName + } +} + +export const parseGitterMessageUrl = (url) => { + const [, atParam, roomName] = messageParamsExp.exec(url.replace(baseUrl, '')) + + return { + atParam, + roomName + } +} From 55dc3976f979277a522e1ad317b78e0447cb29aa Mon Sep 17 00:00:00 2001 From: AndreyKogut Date: Wed, 28 Jun 2017 12:57:02 +0300 Subject: [PATCH 2/8] Deep linking configs --- android/app/src/main/AndroidManifest.xml | 6 ++ android/gradle.properties | 5 ++ app/components/ParsedText/index.js | 54 ++------------ app/constants.js | 4 +- app/screens/Room/index.js | 48 ++++++++++++- ios/GitterMobile/GitterMobile.entitlements | 10 +++ ios/gittermobile.xcodeproj/project.pbxproj | 84 ++++++---------------- ios/gittermobile/AppDelegate.m | 27 +++++-- ios/gittermobile/Info.plist | 18 ++++- 9 files changed, 133 insertions(+), 123 deletions(-) create mode 100644 ios/GitterMobile/GitterMobile.entitlements diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index c41804c..449d859 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -25,6 +25,12 @@ + + + + + diff --git a/android/gradle.properties b/android/gradle.properties index 1fd964e..a7f048c 100644 --- a/android/gradle.properties +++ b/android/gradle.properties @@ -17,4 +17,9 @@ # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects # org.gradle.parallel=true +MYAPP_RELEASE_STORE_FILE=my-release-key.keystore +MYAPP_RELEASE_KEY_ALIAS=my-key-alias +MYAPP_RELEASE_STORE_PASSWORD=12345678 +MYAPP_RELEASE_KEY_PASSWORD=12345678 + android.useDeprecatedNdk=true diff --git a/app/components/ParsedText/index.js b/app/components/ParsedText/index.js index a5d2aa8..ca127b4 100644 --- a/app/components/ParsedText/index.js +++ b/app/components/ParsedText/index.js @@ -1,15 +1,10 @@ import PropTypes from 'prop-types' import React from 'react' -import _ from 'lodash' -import {Text, Linking, Alert} from 'react-native' +import {Text, Linking} from 'react-native' import Parser from 'react-native-parsed-text' -import {parseGitterGroupUrl, parseGitterMessageUrl, parseGitterRoomUrl} from '../../utils/parseUrl' -import {GITTER_REGEXPS} from '../../constants' import Emoji from '../Emoji' import s from './styles' -const { baseUrl, groupParamsExp, messageParamsExp, roomParamsExp } = GITTER_REGEXPS - const MENTION_REGEX = /(([^`]|^)@([a-zA-Z0-9_\-]+))/ const GROUP_MENTION_REGEX = /^(@\/([a-zA-Z0-9_\-]+))/ const EMOJI_REGEX = /:([a-z0-9A-Z_-]+):/ @@ -38,51 +33,10 @@ const renderCodespan = (matchingString, matches) => { return component } -const ParsedText = ({text, username, handleUrlPress, navigator}) => { - const handleGitterRoomUrlClick = (url) => { - const {ownerName, roomName} = parseGitterRoomUrl(url) - - navigator.push({screen: 'gm.Room'}) - } - - const handleGitterMessageUrlClick = (url) => { - const {roomName, atParam} = parseGitterMessageUrl(url) - - navigator.push({screen: 'gm.Message', passProps: {route: {roomName, messageId: atParam}}}) - } - - const handleGitterGroupUrlClick = (url) => { - const {groupName} = parseGitterGroupUrl(url) - - navigator.push({screen: 'gm.Home'}) - } - - const handleUrlClick = _.curry((type, url) => { - if (!type) return Linking.openURL(url) - - Alert.alert( - 'How to open url?', - 'Select type or cancel to hide this alert.', - [ - {text: 'Browser', onPress: () => Linking.openURL(url)}, - {text: 'Cancel'}, - {text: 'App', onPress: () => { - switch (type) { - case 'message': return handleGitterMessageUrlClick(url) - case 'group': return handleGitterGroupUrlClick(url) - case 'room': return handleGitterRoomUrlClick(url) - default: break; - } - }} - ] - ) - }) - +const ParsedText = ({text, username}) => { const patterns = [ - {pattern: new RegExp(`${baseUrl.source}${messageParamsExp.source}`), style: s.url, onPress: handleUrlClick('message')}, - {pattern: new RegExp(`${baseUrl.source}${groupParamsExp.source}`), style: s.url, onPress: handleUrlClick('group')}, - {pattern: new RegExp(`${baseUrl.source}${roomParamsExp.source}`), style: s.url, onPress: handleUrlClick('room')}, - {type: 'url', style: s.url, onPress: Linking.openUrl}, + {pattern: /\bgitterim\b:\/\//, style: s.url, onPress: (url) => Linking.openURL(url)}, + {type: 'url', style: s.url, onPress: (url) => Linking.openURL(url)}, {pattern: new RegExp(`@${username}`), style: s.selfMention}, {pattern: MENTION_REGEX, style: s.mention}, {pattern: GROUP_MENTION_REGEX, style: s.groupMention}, diff --git a/app/constants.js b/app/constants.js index 4cc0df7..0a425a2 100644 --- a/app/constants.js +++ b/app/constants.js @@ -58,7 +58,7 @@ export const icons = { export const GITTER_REGEXPS = { baseUrl: /\bhttps:\/\/gitter.im\/\b/, - roomParamsExp: /([a-zA-Z0-9]+)\/([a-zA-Z0-9]+)/, + roomParamsExp: /([a-zA-Z0-9]+)\/\b([a-zA-Z0-9]+)\b/, messageParamsExp: /([a-zA-Z0-9]+)\/api\?at=([a-z0-9]{24})/, - groupParamsExp: /([a-zA-Z0-9]+)\/home/ + groupParamsExp: /([a-zA-Z0-9]+)\/\bhome\b/ } diff --git a/app/screens/Room/index.js b/app/screens/Room/index.js index c9527d5..2dda329 100644 --- a/app/screens/Room/index.js +++ b/app/screens/Room/index.js @@ -1,6 +1,6 @@ import PropTypes from 'prop-types' import React, { Component } from 'react' -import {Keyboard, ActionSheetIOS, DrawerLayoutAndroid, ToastAndroid, Clipboard, Alert, ListView, View, Platform, KeyboardAvoidingView} from 'react-native'; +import {Linking, Keyboard, ActionSheetIOS, DrawerLayoutAndroid, ToastAndroid, Clipboard, Alert, ListView, View, Platform, KeyboardAvoidingView} from 'react-native'; import {connect} from 'react-redux' import Share from 'react-native-share' import navigationStyles from '../../styles/common/navigationStyles' @@ -11,6 +11,8 @@ import BottomSheet from '../../../libs/react-native-android-bottom-sheet/index' import s from './styles' import {quoteLink} from '../../utils/links' import {THEMES} from '../../constants' +import {parseGitterGroupUrl, parseGitterMessageUrl, parseGitterRoomUrl} from '../../utils/parseUrl' +import {GITTER_REGEXPS} from '../../constants' import { getRoom, selectRoom, @@ -43,6 +45,7 @@ import JoinRoomField from './JoinRoomField' import FailedToLoad from '../../components/FailedToLoad' import {iconsMap} from '../../utils/iconsMap' +const {baseUrl, groupParamsExp, messageParamsExp, roomParamsExp} = GITTER_REGEXPS const COMMAND_REGEX = /\/\S+/ const iOS = Platform.OS === 'ios' const {colors} = THEMES.gitterDefault @@ -79,6 +82,10 @@ class Room extends Component { this.handleSharingMessage = this.handleSharingMessage.bind(this) this.handleShowModal = this.handleShowModal.bind(this) this.toggleDrawerState = this.toggleDrawerState.bind(this) + this.handleUrlClick = this.handleUrlClick.bind(this) + this.handleGitterRoomUrlClick = this.handleGitterRoomUrlClick.bind(this) + this.handleGitterGroupUrlClick = this.handleGitterGroupUrlClick.bind(this) + this.handleGitterMessageUrlClick = this.handleGitterMessageUrlClick.bind(this) this.props.navigator.setOnNavigatorEvent(this.onNavigatorEvent.bind(this)) @@ -111,6 +118,8 @@ class Room extends Component { } componentDidMount() { + Linking.addEventListener('url', this.handleUrlClick); + this.prepareDataSources() const {activeRoom, rooms, route: { roomId }, dispatch, listViewData} = this.props // dispatch(subscribeToChatMessages(roomId)) @@ -141,6 +150,7 @@ class Room extends Component { } componentWillUnmount() { + Linking.removeEventListener('url', this.handleUrlClick); // const {dispatch, route: {roomId}} = this.props // dispatch(unsubscribeToChatMessages(roomId)) } @@ -378,6 +388,42 @@ class Room extends Component { return onlyFiltered ? actions.filter(item => item.showInBottomSheet !== true) : actions } + handleUrlClick({url}) { + const messageUrlPattern = new RegExp(`${baseUrl.source}${messageParamsExp.source}`) + const groupUrlPattern = new RegExp(`${baseUrl.source}${groupParamsExp.source}`) + const roomUrlPattern = new RegExp(`${baseUrl.source}${roomParamsExp.source}`) + + if (messageUrlPattern.test(url)) { + return this.handleGitterMessageUrlClick(url) + } + + if (groupUrlPattern.test(url)) { + return this.handleGitterGroupUrlClick(url) + } + + if (roomUrlPattern.test(url)) { + return this.handleGitterRoomUrlClick(url) + } + } + + handleGitterRoomUrlClick(url) { + const {ownerName, roomName} = parseGitterRoomUrl(url) + console.log(ownerName, roomName) + this.props.navigator.push({screen: 'gm.Room'}) + } + + handleGitterMessageUrlClick(url) { + const {roomName, atParam} = parseGitterMessageUrl(url) + console.log(atParam, roomName) + this.props.navigator.push({screen: 'gm.Message', passProps: {route: {roomName, messageId: atParam}}}) + } + + handleGitterGroupUrlClick(url) { + const {groupName} = parseGitterGroupUrl(url) + console.log(groupName) + this.props.navigator.push({screen: 'gm.Home'}) + } + handleOverflowClick() { const {title: titleProp, room} = this.props const actions = this.getButtons(room, false) diff --git a/ios/GitterMobile/GitterMobile.entitlements b/ios/GitterMobile/GitterMobile.entitlements new file mode 100644 index 0000000..8047797 --- /dev/null +++ b/ios/GitterMobile/GitterMobile.entitlements @@ -0,0 +1,10 @@ + + + + + com.apple.developer.associated-domains + + webcredentials:gitter.im + + + diff --git a/ios/gittermobile.xcodeproj/project.pbxproj b/ios/gittermobile.xcodeproj/project.pbxproj index cd28c28..8699df1 100644 --- a/ios/gittermobile.xcodeproj/project.pbxproj +++ b/ios/gittermobile.xcodeproj/project.pbxproj @@ -256,20 +256,6 @@ remoteGlobalIDString = D8AFADBD1BEE6F3F00A4592D; remoteInfo = ReactNativeNavigation; }; - DE99757D1EF9A87700C50752 /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = 146833FF1AC3E56700842450 /* React.xcodeproj */; - proxyType = 2; - remoteGlobalIDString = 3D383D3C1EBD27B6005632C8; - remoteInfo = "third-party-tvOS"; - }; - DE99757F1EF9A87700C50752 /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = 146833FF1AC3E56700842450 /* React.xcodeproj */; - proxyType = 2; - remoteGlobalIDString = 3D383D621EBD27B9005632C8; - remoteInfo = "double-conversion-tvOS"; - }; DEFF81AF1EF983C100E38121 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 146833FF1AC3E56700842450 /* React.xcodeproj */; @@ -277,24 +263,9 @@ remoteGlobalIDString = 83CBBA2D1A601D0E00E9B192; remoteInfo = React; }; - DEFF81CC1EF983C100E38121 /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = 146833FF1AC3E56700842450 /* React.xcodeproj */; - proxyType = 2; - remoteGlobalIDString = 139D7ECE1E25DB7D00323FB7; - remoteInfo = "third-party"; - }; - DEFF81CE1EF983C100E38121 /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = 146833FF1AC3E56700842450 /* React.xcodeproj */; - proxyType = 2; - remoteGlobalIDString = 139D7E881E25C6D100323FB7; - remoteInfo = "double-conversion"; - }; /* End PBXContainerItemProxy section */ /* Begin PBXFileReference section */ - 008F07F21AC5B25A0029DE68 /* main.jsbundle */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = main.jsbundle; sourceTree = ""; }; 00C302A71ABCB8CE00DB3ED1 /* RCTActionSheet.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTActionSheet.xcodeproj; path = "../node_modules/react-native/Libraries/ActionSheetIOS/RCTActionSheet.xcodeproj"; sourceTree = ""; }; 00C302B51ABCB90400DB3ED1 /* RCTGeolocation.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTGeolocation.xcodeproj; path = "../node_modules/react-native/Libraries/Geolocation/RCTGeolocation.xcodeproj"; sourceTree = ""; }; 00C302BB1ABCB91800DB3ED1 /* RCTImage.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTImage.xcodeproj; path = "../node_modules/react-native/Libraries/Image/RCTImage.xcodeproj"; sourceTree = ""; }; @@ -320,6 +291,7 @@ 78C398B01ACF4ADC00677621 /* RCTLinking.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTLinking.xcodeproj; path = "../node_modules/react-native/Libraries/LinkingIOS/RCTLinking.xcodeproj"; sourceTree = ""; }; 832341B01AAA6A8300B99B32 /* RCTText.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTText.xcodeproj; path = "../node_modules/react-native/Libraries/Text/RCTText.xcodeproj"; sourceTree = ""; }; 8BA6F015997B8EC10E29E478 /* Pods-GitterMobile.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-GitterMobile.debug.xcconfig"; path = "Pods/Target Support Files/Pods-GitterMobile/Pods-GitterMobile.debug.xcconfig"; sourceTree = ""; }; + AE4355901F038FB3006AAC02 /* GitterMobile.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; name = GitterMobile.entitlements; path = GitterMobile/GitterMobile.entitlements; sourceTree = ""; }; BF06B00B1EA29D9A0090FB51 /* RNVectorIcons.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RNVectorIcons.xcodeproj; path = "../node_modules/react-native-vector-icons/RNVectorIcons.xcodeproj"; sourceTree = ""; }; DE151CB61EA81238002A2063 /* ReactNativeNavigation.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = ReactNativeNavigation.xcodeproj; path = "../node_modules/react-native-navigation/ios/ReactNativeNavigation.xcodeproj"; sourceTree = ""; }; DE7E019E1EA2A52F0081B152 /* FayeManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = FayeManager.h; path = GitterMobile/FayeManager.h; sourceTree = ""; }; @@ -350,6 +322,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 133E29F31AD74F7200F7D852 /* libRCTLinking.a in Frameworks */, DEAB4DB01EA816D200A44FF8 /* libRNVectorIcons.a in Frameworks */, DE151CD61EA8124E002A2063 /* libReactNativeNavigation.a in Frameworks */, 146834051AC3E58100842450 /* libReact.a in Frameworks */, @@ -357,7 +330,6 @@ 00C302E51ABCBA2D00DB3ED1 /* libRCTActionSheet.a in Frameworks */, 00C302E71ABCBA2D00DB3ED1 /* libRCTGeolocation.a in Frameworks */, 00C302E81ABCBA2D00DB3ED1 /* libRCTImage.a in Frameworks */, - 133E29F31AD74F7200F7D852 /* libRCTLinking.a in Frameworks */, 00C302E91ABCBA2D00DB3ED1 /* libRCTNetwork.a in Frameworks */, 139105C61AF99C1200B5F7CC /* libRCTSettings.a in Frameworks */, 832341BD1AAA6AB300B99B32 /* libRCTText.a in Frameworks */, @@ -472,7 +444,7 @@ 13B07FAE1A68108700A75B9A /* GitterMobile */ = { isa = PBXGroup; children = ( - 008F07F21AC5B25A0029DE68 /* main.jsbundle */, + AE4355901F038FB3006AAC02 /* GitterMobile.entitlements */, 13B07FAF1A68108700A75B9A /* AppDelegate.h */, 13B07FB01A68108700A75B9A /* AppDelegate.m */, 13B07FB51A68108700A75B9A /* Images.xcassets */, @@ -494,10 +466,6 @@ 3DAD3EAB1DF850E9000B6D8A /* libcxxreact.a */, 3DAD3EAD1DF850E9000B6D8A /* libjschelpers.a */, 3DAD3EAF1DF850E9000B6D8A /* libjschelpers.a */, - DEFF81CD1EF983C100E38121 /* libthird-party.a */, - DE99757E1EF9A87700C50752 /* libthird-party.a */, - DEFF81CF1EF983C100E38121 /* libdouble-conversion.a */, - DE9975801EF9A87700C50752 /* libdouble-conversion.a */, ); name = Products; sourceTree = ""; @@ -728,6 +696,14 @@ CreatedOnToolsVersion = 6.2; TestTargetID = 13B07F861A680F5B00A75B9A; }; + 13B07F861A680F5B00A75B9A = { + ProvisioningStyle = Manual; + SystemCapabilities = { + com.apple.SafariKeychain = { + enabled = 1; + }; + }; + }; 2D02E47A1E0B4A5D006451C7 = { CreatedOnToolsVersion = 8.2.1; ProvisioningStyle = Automatic; @@ -1004,34 +980,6 @@ remoteRef = DE151CD31EA81238002A2063 /* PBXContainerItemProxy */; sourceTree = BUILT_PRODUCTS_DIR; }; - DE99757E1EF9A87700C50752 /* libthird-party.a */ = { - isa = PBXReferenceProxy; - fileType = archive.ar; - path = "libthird-party.a"; - remoteRef = DE99757D1EF9A87700C50752 /* PBXContainerItemProxy */; - sourceTree = BUILT_PRODUCTS_DIR; - }; - DE9975801EF9A87700C50752 /* libdouble-conversion.a */ = { - isa = PBXReferenceProxy; - fileType = archive.ar; - path = "libdouble-conversion.a"; - remoteRef = DE99757F1EF9A87700C50752 /* PBXContainerItemProxy */; - sourceTree = BUILT_PRODUCTS_DIR; - }; - DEFF81CD1EF983C100E38121 /* libthird-party.a */ = { - isa = PBXReferenceProxy; - fileType = archive.ar; - path = "libthird-party.a"; - remoteRef = DEFF81CC1EF983C100E38121 /* PBXContainerItemProxy */; - sourceTree = BUILT_PRODUCTS_DIR; - }; - DEFF81CF1EF983C100E38121 /* libdouble-conversion.a */ = { - isa = PBXReferenceProxy; - fileType = archive.ar; - path = "libdouble-conversion.a"; - remoteRef = DEFF81CE1EF983C100E38121 /* PBXContainerItemProxy */; - sourceTree = BUILT_PRODUCTS_DIR; - }; /* End PBXReferenceProxy section */ /* Begin PBXResourcesBuildPhase section */ @@ -1267,14 +1215,17 @@ baseConfigurationReference = 8BA6F015997B8EC10E29E478 /* Pods-GitterMobile.debug.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CODE_SIGN_ENTITLEMENTS = GitterMobile/GitterMobile.entitlements; CURRENT_PROJECT_VERSION = 1; DEAD_CODE_STRIPPING = NO; + DEVELOPMENT_TEAM = ""; HEADER_SEARCH_PATHS = ( "$(inherited)", "\"${PODS_ROOT}/Headers/Public\"", "\"${PODS_ROOT}/Headers/Public/MZFayeClient\"", "\"${PODS_ROOT}/Headers/Public/SocketRocket\"", "$(SRCROOT)/../node_modules/react-native-navigation/ios/**", + "$(SRCROOT)/../node_modules/react-native/Libraries/LinkingIOS/**", ); INFOPLIST_FILE = GitterMobile/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; @@ -1290,6 +1241,7 @@ ); PRODUCT_BUNDLE_IDENTIFIER = "org.reactjs.native.example.$(PRODUCT_NAME:rfc1034identifier)"; PRODUCT_NAME = GitterMobile; + PROVISIONING_PROFILE_SPECIFIER = ""; VERSIONING_SYSTEM = "apple-generic"; }; name = Debug; @@ -1299,13 +1251,16 @@ baseConfigurationReference = EAA5A150C150D13C821F7649 /* Pods-GitterMobile.release.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CODE_SIGN_ENTITLEMENTS = GitterMobile/GitterMobile.entitlements; CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = ""; HEADER_SEARCH_PATHS = ( "$(inherited)", "\"${PODS_ROOT}/Headers/Public\"", "\"${PODS_ROOT}/Headers/Public/MZFayeClient\"", "\"${PODS_ROOT}/Headers/Public/SocketRocket\"", "$(SRCROOT)/../node_modules/react-native-navigation/ios/**", + "$(SRCROOT)/../node_modules/react-native/Libraries/LinkingIOS/**", ); INFOPLIST_FILE = GitterMobile/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; @@ -1321,6 +1276,7 @@ ); PRODUCT_BUNDLE_IDENTIFIER = "org.reactjs.native.example.$(PRODUCT_NAME:rfc1034identifier)"; PRODUCT_NAME = GitterMobile; + PROVISIONING_PROFILE_SPECIFIER = ""; VERSIONING_SYSTEM = "apple-generic"; }; name = Release; @@ -1438,6 +1394,7 @@ CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; COPY_PHASE_STRIP = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; @@ -1483,6 +1440,7 @@ CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; COPY_PHASE_STRIP = YES; ENABLE_NS_ASSERTIONS = NO; diff --git a/ios/gittermobile/AppDelegate.m b/ios/gittermobile/AppDelegate.m index d469877..81e298a 100644 --- a/ios/gittermobile/AppDelegate.m +++ b/ios/gittermobile/AppDelegate.m @@ -1,5 +1,6 @@ #import "AppDelegate.h" #import +#import "RCTLinkingManager.h" // ********************************************** // *** DON'T MISS: THE NEXT LINE IS IMPORTANT *** @@ -13,6 +14,22 @@ @implementation AppDelegate + +- (BOOL)application:(UIApplication *)application continueUserActivity:(NSUserActivity *)userActivity + restorationHandler:(void (^)(NSArray * _Nullable))restorationHandler +{ + return [RCTLinkingManager application:application + continueUserActivity:userActivity + restorationHandler:restorationHandler]; +} + +- (BOOL)application:(UIApplication *)application openURL:(NSURL *)url + sourceApplication:(NSString *)sourceApplication annotation:(id)annotation +{ + return [RCTLinkingManager application:application openURL:url + sourceApplication:sourceApplication annotation:annotation]; +} + - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { NSURL *jsCodeLocation; @@ -22,15 +39,15 @@ - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:( #else jsCodeLocation = [[NSBundle mainBundle] URLForResource:@"main" withExtension:@"jsbundle"]; #endif - - + + // ********************************************** // *** DON'T MISS: THIS IS HOW WE BOOTSTRAP ***** // ********************************************** self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds]; self.window.backgroundColor = [UIColor whiteColor]; [[RCCManager sharedInstance] initBridgeWithBundleURL:jsCodeLocation]; - + /* // original RN bootstrap - remove this part RCTRootView *rootView = [[RCTRootView alloc] initWithBundleURL:jsCodeLocation @@ -43,8 +60,8 @@ - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:( self.window.rootViewController = rootViewController; [self.window makeKeyAndVisible]; */ - - + + return YES; } diff --git a/ios/gittermobile/Info.plist b/ios/gittermobile/Info.plist index 7c68fff..2854b37 100644 --- a/ios/gittermobile/Info.plist +++ b/ios/gittermobile/Info.plist @@ -2,8 +2,6 @@ - UIViewControllerBasedStatusBarAppearance - CFBundleDevelopmentRegion en CFBundleDisplayName @@ -22,8 +20,22 @@ 1.0 CFBundleSignature ???? + CFBundleURLTypes + + + CFBundleTypeRole + Editor + CFBundleURLSchemes + + https + gitterim + + + CFBundleVersion 1 + LSApplicationCategoryType + LSRequiresIPhoneOS NSAppTransportSecurity @@ -62,5 +74,7 @@ UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight + UIViewControllerBasedStatusBarAppearance + From a94f8060961fe9b7295b2ba4ba357154249ca9a2 Mon Sep 17 00:00:00 2001 From: AndreyKogut Date: Wed, 28 Jun 2017 13:15:39 +0300 Subject: [PATCH 3/8] Keystore vars removed --- android/gradle.properties | 5 ----- 1 file changed, 5 deletions(-) diff --git a/android/gradle.properties b/android/gradle.properties index a7f048c..1fd964e 100644 --- a/android/gradle.properties +++ b/android/gradle.properties @@ -17,9 +17,4 @@ # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects # org.gradle.parallel=true -MYAPP_RELEASE_STORE_FILE=my-release-key.keystore -MYAPP_RELEASE_KEY_ALIAS=my-key-alias -MYAPP_RELEASE_STORE_PASSWORD=12345678 -MYAPP_RELEASE_KEY_PASSWORD=12345678 - android.useDeprecatedNdk=true From 678d0733d3ddf7f589df17def8a2929c4833ceb1 Mon Sep 17 00:00:00 2001 From: AndreyKogut Date: Thu, 29 Jun 2017 18:15:25 +0300 Subject: [PATCH 4/8] Linking event handler refactoring --- app/components/ParsedText/index.js | 1 - app/screens/Room/index.js | 17 ++++++++++++----- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/app/components/ParsedText/index.js b/app/components/ParsedText/index.js index ca127b4..d9b7ed3 100644 --- a/app/components/ParsedText/index.js +++ b/app/components/ParsedText/index.js @@ -35,7 +35,6 @@ const renderCodespan = (matchingString, matches) => { const ParsedText = ({text, username}) => { const patterns = [ - {pattern: /\bgitterim\b:\/\//, style: s.url, onPress: (url) => Linking.openURL(url)}, {type: 'url', style: s.url, onPress: (url) => Linking.openURL(url)}, {pattern: new RegExp(`@${username}`), style: s.selfMention}, {pattern: MENTION_REGEX, style: s.mention}, diff --git a/app/screens/Room/index.js b/app/screens/Room/index.js index 2dda329..6849a26 100644 --- a/app/screens/Room/index.js +++ b/app/screens/Room/index.js @@ -118,8 +118,6 @@ class Room extends Component { } componentDidMount() { - Linking.addEventListener('url', this.handleUrlClick); - this.prepareDataSources() const {activeRoom, rooms, route: { roomId }, dispatch, listViewData} = this.props // dispatch(subscribeToChatMessages(roomId)) @@ -150,15 +148,24 @@ class Room extends Component { } componentWillUnmount() { - Linking.removeEventListener('url', this.handleUrlClick); // const {dispatch, route: {roomId}} = this.props // dispatch(unsubscribeToChatMessages(roomId)) } - onNavigatorEvent(event) { - if (event.type === 'NavBarButtonPress') { + onNavigatorEvent({type, id}) { + if (type === 'NavBarButtonPress') { this.handleToolbarActionSelected(event) } + + switch (id) { + case 'willAppear': { + return Linking.addEventListener('url', this.handleUrlClick) + } + case 'willDisappear': { + return Linking.removeEventListener('url', this.handleUrlClick); + } + default: break + } } onEndReached() { From e780d1f655ba7a40eaa6ad72122c96303259101e Mon Sep 17 00:00:00 2001 From: AndreyKogut Date: Tue, 11 Jul 2017 16:04:01 +0300 Subject: [PATCH 5/8] Handle room url --- app/api/gitter.js | 4 ++++ app/modules/rooms.js | 20 ++++++++++++++++++++ app/screens/Room/index.js | 18 +++++++++++------- ios/gittermobile/Info.plist | 1 - 4 files changed, 35 insertions(+), 8 deletions(-) diff --git a/app/api/gitter.js b/app/api/gitter.js index b661e82..5e5ce7d 100644 --- a/app/api/gitter.js +++ b/app/api/gitter.js @@ -71,6 +71,10 @@ export function room(token, id) { return callApi('rooms/' + id, token) } +export function roomByUrl(token, url) { + return callApi(`rooms?q=${url}`, token) +} + export function roomMessages(token, id, limit) { return callApi(`rooms/${id}/chatMessages?limit=${limit}`, token) } diff --git a/app/modules/rooms.js b/app/modules/rooms.js index 354ce20..e29b1ff 100644 --- a/app/modules/rooms.js +++ b/app/modules/rooms.js @@ -93,6 +93,25 @@ export function getRoom(id) { } } +/** + * Return room by uri + */ + +export function getRoomByUrl(url, navigateOnSuccess) { + return async (dispatch, getState) => { + const {token} = getState().auth + dispatch({type: ROOM}) + try { + const {results: [room]} = await Api.roomByUrl(token, url) + dispatch({type: ROOM_RECEIVED, payload: room}) + + navigateOnSuccess(room.id) + } catch (error) { + dispatch({type: ROOM_FAILED, error}) + } + } +} + /** * Returns suggested rooms by user id */ @@ -172,6 +191,7 @@ export function joinUserRoom(username) { const payload = await Api.joinRoomByUserName(token, username) dispatch({type: JOIN_USER_ROOM_OK, payload}) + return Promise.resolve(payload) } catch (error) { dispatch({type: JOIN_USER_ROOM_FAILED, error}) } diff --git a/app/screens/Room/index.js b/app/screens/Room/index.js index 6849a26..9d12020 100644 --- a/app/screens/Room/index.js +++ b/app/screens/Room/index.js @@ -21,7 +21,8 @@ import { leaveRoom, markAllAsRead, getNotificationSettings, - changeNotificationSettings + changeNotificationSettings, + getRoomByUrl } from '../../modules/rooms' import { getRoomMessages, @@ -154,7 +155,7 @@ class Room extends Component { onNavigatorEvent({type, id}) { if (type === 'NavBarButtonPress') { - this.handleToolbarActionSelected(event) + this.handleToolbarActionSelected(id) } switch (id) { @@ -414,9 +415,11 @@ class Room extends Component { } handleGitterRoomUrlClick(url) { + const {dispatch, navigator} = this.props const {ownerName, roomName} = parseGitterRoomUrl(url) - console.log(ownerName, roomName) - this.props.navigator.push({screen: 'gm.Room'}) + const navigateOnSuccess = (roomId) => { navigator.push({screen: 'gm.Room', passProps: {roomId}})} + + dispatch(getRoomByUrl(`${ownerName}/${roomName}`, navigateOnSuccess)) } handleGitterMessageUrlClick(url) { @@ -544,7 +547,7 @@ class Room extends Component { this.roomInfoDrawer.closeDrawer() } - handleToolbarActionSelected({id}) { + handleToolbarActionSelected(id) { const {dispatch, route: {roomId}, navigator} = this.props switch (id) { case 'drawerMenu': return navigator.toggleDrawer({side: 'left', animated: true}) @@ -552,7 +555,7 @@ class Room extends Component { case 'roomInfo': { dispatch(roomUsers(roomId)) return iOS - ? navigator.push({screen: 'gm.RoomInfo', passProps: {route: {roomId}}}) + ? navigator.push({screen: 'gm.RoomInfo', passProps: {roomId}}) : this.toggleDrawerState() } case 'toggleFavorite': return dispatch(changeFavoriteStatus(roomId)) @@ -666,6 +669,7 @@ class Room extends Component { prepareDataSources() { const {listViewData, route: {roomId}, dispatch} = this.props + if (!listViewData[roomId]) { const ds = new ListView.DataSource({rowHasChanged: (row1, row2) => { return row1 !== row2 @@ -818,7 +822,7 @@ function mapStateToProps(state, ownProps) { const {activeRoom, rooms, notifications} = state.rooms const {roomInfoDrawerState} = state.ui - const room = rooms[ownProps.roomId] + const room = rooms[ownProps.roomId || ownProps.route.roomId] let title = !!room ? room.name : 'Room' title = title.split('/').reverse()[0] diff --git a/ios/gittermobile/Info.plist b/ios/gittermobile/Info.plist index 2854b37..4dbdcdf 100644 --- a/ios/gittermobile/Info.plist +++ b/ios/gittermobile/Info.plist @@ -27,7 +27,6 @@ Editor CFBundleURLSchemes - https gitterim From 3af5939792c12181f06741185959cb07360edfe6 Mon Sep 17 00:00:00 2001 From: AndreyKogut Date: Wed, 12 Jul 2017 17:31:52 +0300 Subject: [PATCH 6/8] Handle group url. Group screen --- app/api/gitter.js | 12 +++- app/modules/groups.js | 99 ++++++++++++++++++++++++++++++++ app/modules/index.js | 2 + app/modules/rooms.js | 4 +- app/screens/Group/index.js | 110 ++++++++++++++++++++++++++++++++++++ app/screens/Group/styles.js | 13 +++++ app/screens/Room/index.js | 13 +++-- app/screens/index.js | 4 +- app/utils/parseUrl.js | 4 +- 9 files changed, 250 insertions(+), 11 deletions(-) create mode 100644 app/modules/groups.js create mode 100644 app/screens/Group/index.js create mode 100644 app/screens/Group/styles.js diff --git a/app/api/gitter.js b/app/api/gitter.js index 5e5ce7d..8701a65 100644 --- a/app/api/gitter.js +++ b/app/api/gitter.js @@ -63,6 +63,14 @@ export function currentUserSuggested(token, id) { return callApi(endpoint, token) } +/** + * Groups + */ + +export function groupRooms(token, id) { + return callApi(`groups/${id}/rooms`, token) +} + /** * Rooms resource */ @@ -71,8 +79,8 @@ export function room(token, id) { return callApi('rooms/' + id, token) } -export function roomByUrl(token, url) { - return callApi(`rooms?q=${url}`, token) +export function roomsByUri(token, uri) { + return callApi(`rooms?q=${uri}`, token) } export function roomMessages(token, id, limit) { diff --git a/app/modules/groups.js b/app/modules/groups.js new file mode 100644 index 0000000..dd09914 --- /dev/null +++ b/app/modules/groups.js @@ -0,0 +1,99 @@ +import _ from 'lodash' +import * as Api from '../api/gitter' +import {LOGOUT} from './auth' + +/** + * Action Creators + */ + +export const GROUP_ROOMS = 'groups/GROUPS_ROOMS' +export const GROUP_ROOMS_RECEIVED = 'groups/GROUP_ROOMS_RECEIVED' +export const GROUP_ROOMS_FAILED = 'groups/GROUP_ROOMS_FAILED' + +export function getGroupIdByName(groupName, navigateOnSuccess = () => {}) { + return async (dispatch, getState) => { + try { + const {token} = getState().auth + const {results: rooms} = await Api.roomsByUri(token, groupName) + const groupRegExp = new RegExp(`\\b${groupName}\/`) + + // Get group id + const matchedGroup = _.find(rooms, ({uri}) => groupRegExp.test(uri)) + const {groupId} = matchedGroup + + navigateOnSuccess(groupId) + } catch (error) { + // TODO: Handle error + } + } +} + +export function getGroupRooms(groupId) { + return async (dispatch, getState) => { + dispatch({type: GROUP_ROOMS}) + + try { + const {token} = getState().auth + + const results = await Api.groupRooms(token, groupId) + const payload = { + groupId, + rooms: results + } + + dispatch({type: GROUP_ROOMS_RECEIVED, payload}) + } catch (error) { + dispatch({type: GROUP_ROOMS_FAILED, error}) + } + } +} + +/** + * Reducer + */ + +const initialState = { + isLoading: false, + ids: [], + groups: {}, + error: false, + errors: [] +} + +export default function groups(state = initialState, action) { + switch (action.type) { + case GROUP_ROOMS: { + return {...state, + isLoading: true + } + } + + case GROUP_ROOMS_RECEIVED: { + const currentGroupId = action.payload.groupId + + return {...state, + isLoading: false, + ids: state.ids.concat(currentGroupId), + groups: { + ...state.groups, + [currentGroupId]: action.payload.rooms + } + } + } + + case GROUP_ROOMS_FAILED: { + return {...state, + isLoading: false, + error: true, + errors: action.error + } + } + + case LOGOUT: { + return initialState + } + + default : + return state + } +} diff --git a/app/modules/index.js b/app/modules/index.js index a5518fa..a7ff1b2 100644 --- a/app/modules/index.js +++ b/app/modules/index.js @@ -14,6 +14,7 @@ import settings from './settings' import realtime from './realtime' import activity from './activity' import navigation from './navigation' +import groups from './groups' const rootReducer = combineReducers({ ui, @@ -29,6 +30,7 @@ const rootReducer = combineReducers({ settings, realtime, activity, + groups, navigation }) diff --git a/app/modules/rooms.js b/app/modules/rooms.js index e29b1ff..fb13857 100644 --- a/app/modules/rooms.js +++ b/app/modules/rooms.js @@ -97,12 +97,12 @@ export function getRoom(id) { * Return room by uri */ -export function getRoomByUrl(url, navigateOnSuccess) { +export function getRoomByUrl(url, navigateOnSuccess = () => {}) { return async (dispatch, getState) => { const {token} = getState().auth dispatch({type: ROOM}) try { - const {results: [room]} = await Api.roomByUrl(token, url) + const {results: [room]} = await Api.roomsByUri(token, url) dispatch({type: ROOM_RECEIVED, payload: room}) navigateOnSuccess(room.id) diff --git a/app/screens/Group/index.js b/app/screens/Group/index.js new file mode 100644 index 0000000..ac67f44 --- /dev/null +++ b/app/screens/Group/index.js @@ -0,0 +1,110 @@ +import React, {PropTypes, Component} from 'react' +import {connect} from 'react-redux' +import {View, ListView} from 'react-native' +import RoomItem from '../Home/HomeRoomItem' +import Loading from '../../components/Loading' +import {THEMES} from '../../constants' +import s from './styles' +import {getGroupRooms} from '../../modules/groups' +import {iconsMap} from '../../utils/iconsMap' + +const {colors} = THEMES.gitterDefault + +const ds = new ListView.DataSource({rowHasChanged: (r1, r2) => r1 !== r2}); + +class Group extends Component { + constructor(props) { + super(props) + + this.props.navigator.setOnNavigatorEvent(this.onNavigatorEvent.bind(this)) + this.props.navigator.setTitle({title: this.props.groupName}) + this.props.navigator.setButtons({ + leftButtons: [{ + title: 'Menu', + id: 'sideMenu', + icon: iconsMap['menu-white'], + iconColor: 'white', + showAsAction: 'always' + }] + }) + + this.renderListItem = this.renderListItem.bind(this) + this.onRoomPress = this.onRoomPress.bind(this) + } + + componentWillMount() { + const {groupId, dispatch} = this.props + + dispatch(getGroupRooms(groupId)) + } + + onNavigatorEvent(event) { + if (event.type === 'NavBarButtonPress') { + if (event.id === 'sideMenu') { + this.props.navigator.toggleDrawer({side: 'left', animated: true}) + } + } + } + + onRoomPress(id) { + this.props.navigator.push({screen: 'gm.Room', passProps: {roomId: id}}) + } + + renderListItem(item) { + return + } + + render() { + const {isLoading} = this.props + + if (isLoading) { + return ( + + + + ) + } + + return ( + + ) + } +} + +Group.navigatorStyle = { + navBarBackgroundColor: colors.raspberry, + navBarButtonColor: 'white', + navBarTextColor: 'white', + topBarElevationShadowEnabled: true, + statusBarColor: colors.darkRed, + statusBarTextColorScheme: 'dark' +} + +Group.propTypes = { + groupId: PropTypes.string, + rooms: PropTypes.array, + navigator: PropTypes.object, + isLoading: PropTypes.bool, + dispatch: PropTypes.func, + groupName: PropTypes.string +} + +const mapStateToProps = ({groups: {groups, ...info}}, {groupId}) => { + const rooms = groups[groupId] || [] + + return ({ + rooms, + ...info + }) +} + +export default connect(mapStateToProps)(Group) diff --git a/app/screens/Group/styles.js b/app/screens/Group/styles.js new file mode 100644 index 0000000..8f1df1c --- /dev/null +++ b/app/screens/Group/styles.js @@ -0,0 +1,13 @@ +import {StyleSheet} from 'react-native' + +const styles = StyleSheet.create({ + container: { + backgroundColor: 'white', + flex: 1 + }, + loadingWrap: { + height: 200 + } +}) + +export default styles diff --git a/app/screens/Room/index.js b/app/screens/Room/index.js index 9d12020..f8aa8e0 100644 --- a/app/screens/Room/index.js +++ b/app/screens/Room/index.js @@ -37,6 +37,7 @@ import { readMessages, sendStatusMessage } from '../../modules/messages' +import { getGroupIdByName } from '../../modules/groups' import {changeRoomInfoDrawerState} from '../../modules/ui' import RoomInfoScreen from '../RoomInfo' import Loading from '../../components/Loading' @@ -416,10 +417,10 @@ class Room extends Component { handleGitterRoomUrlClick(url) { const {dispatch, navigator} = this.props - const {ownerName, roomName} = parseGitterRoomUrl(url) - const navigateOnSuccess = (roomId) => { navigator.push({screen: 'gm.Room', passProps: {roomId}})} + const {uri} = parseGitterRoomUrl(url) + const navigateOnSuccess = roomId => navigator.push({screen: 'gm.Room', passProps: {roomId}}) - dispatch(getRoomByUrl(`${ownerName}/${roomName}`, navigateOnSuccess)) + dispatch(getRoomByUrl(uri, navigateOnSuccess)) } handleGitterMessageUrlClick(url) { @@ -429,9 +430,11 @@ class Room extends Component { } handleGitterGroupUrlClick(url) { + const {dispatch, navigator} = this.props const {groupName} = parseGitterGroupUrl(url) - console.log(groupName) - this.props.navigator.push({screen: 'gm.Home'}) + const navigateOnSuccess = groupId => navigator.push({screen: 'gm.Group', passProps: {groupId, groupName}}) + + dispatch(getGroupIdByName(groupName, navigateOnSuccess)) } handleOverflowClick() { diff --git a/app/screens/index.js b/app/screens/index.js index b642202..975b6af 100644 --- a/app/screens/index.js +++ b/app/screens/index.js @@ -20,6 +20,7 @@ import SearchMessages from './SearchMessages' import RoomInfo from './RoomInfo' import RoomSettings from './RoomSettings' import LoginByWebView from './LoginByWebView' +import Group from './Group' export default class Application { constructor(store, Provider) { @@ -48,7 +49,8 @@ export default class Application { Settings, SearchMessages, RoomSettings, - LoginByWebView + LoginByWebView, + Group } Object.keys(screens).map(key => { diff --git a/app/utils/parseUrl.js b/app/utils/parseUrl.js index 608e91f..c204d77 100644 --- a/app/utils/parseUrl.js +++ b/app/utils/parseUrl.js @@ -3,9 +3,11 @@ import { GITTER_REGEXPS } from '../constants' const { baseUrl, groupParamsExp, messageParamsExp, roomParamsExp } = GITTER_REGEXPS export const parseGitterRoomUrl = (url) => { - const [, ownerName, roomName] = roomParamsExp.exec(url.replace(baseUrl, '')) + const uri = url.replace(baseUrl, '') + const [, ownerName, roomName] = roomParamsExp.exec(uri) return { + uri, ownerName, roomName } From 51f2ee5fc519172a588031ec5517ea9e885ad1fb Mon Sep 17 00:00:00 2001 From: AndreyKogut Date: Fri, 14 Jul 2017 11:17:36 +0300 Subject: [PATCH 7/8] Handle message url --- app/constants.js | 2 +- app/modules/groups.js | 33 +++++++++++++++++++++++--- app/modules/messages.js | 8 +++---- app/screens/Group/index.js | 9 +++---- app/screens/Message/index.js | 46 ++++++++++++++++++++++++++---------- app/screens/Room/index.js | 11 +++++---- app/utils/parseUrl.js | 4 ++-- 7 files changed, 83 insertions(+), 30 deletions(-) diff --git a/app/constants.js b/app/constants.js index 0a425a2..9d0d732 100644 --- a/app/constants.js +++ b/app/constants.js @@ -59,6 +59,6 @@ export const icons = { export const GITTER_REGEXPS = { baseUrl: /\bhttps:\/\/gitter.im\/\b/, roomParamsExp: /([a-zA-Z0-9]+)\/\b([a-zA-Z0-9]+)\b/, - messageParamsExp: /([a-zA-Z0-9]+)\/api\?at=([a-z0-9]{24})/, + messageParamsExp: /\b([a-zA-Z0-9/]+)\b\?at=([a-z0-9]{24})/, groupParamsExp: /([a-zA-Z0-9]+)\/\bhome\b/ } diff --git a/app/modules/groups.js b/app/modules/groups.js index dd09914..58778d0 100644 --- a/app/modules/groups.js +++ b/app/modules/groups.js @@ -8,6 +8,7 @@ import {LOGOUT} from './auth' export const GROUP_ROOMS = 'groups/GROUPS_ROOMS' export const GROUP_ROOMS_RECEIVED = 'groups/GROUP_ROOMS_RECEIVED' +export const GROUP_FOUND = 'groups/GROUP_FOUND' export const GROUP_ROOMS_FAILED = 'groups/GROUP_ROOMS_FAILED' export function getGroupIdByName(groupName, navigateOnSuccess = () => {}) { @@ -18,11 +19,16 @@ export function getGroupIdByName(groupName, navigateOnSuccess = () => {}) { const groupRegExp = new RegExp(`\\b${groupName}\/`) // Get group id - const matchedGroup = _.find(rooms, ({uri}) => groupRegExp.test(uri)) - const {groupId} = matchedGroup + const {groupId} = _.find(rooms, ({uri}) => groupRegExp.test(uri)) + const payload = { + id: groupId, + name: groupName + } + dispatch({type: GROUP_FOUND, payload}) navigateOnSuccess(groupId) } catch (error) { + console.log(error) // TODO: Handle error } } @@ -68,15 +74,36 @@ export default function groups(state = initialState, action) { } } + case GROUP_FOUND: { + const groupId = action.payload.id + const group = state.groups[groupId] + + return {...state, + isLoading: false, + ids: state.ids.concat(groupId), + groups: { + ...state.groups, + [groupId]: { + ...group, + ...action.payload + } + } + } + } + case GROUP_ROOMS_RECEIVED: { const currentGroupId = action.payload.groupId + const group = state.groups[currentGroupId] return {...state, isLoading: false, ids: state.ids.concat(currentGroupId), groups: { ...state.groups, - [currentGroupId]: action.payload.rooms + [currentGroupId]: { + ...group, + rooms: action.payload.rooms + } } } } diff --git a/app/modules/messages.js b/app/modules/messages.js index 0be57f0..8532c7f 100644 --- a/app/modules/messages.js +++ b/app/modules/messages.js @@ -150,12 +150,12 @@ export function getRoomMessagesIfNeeded(roomId) { export function receiveRoomMessagesSnapshot(roomId, snapshot) { return (dispatch, getState) => { const listView = getState().messages.listView[roomId] - if (!listView.data.length | !snapshot.length) { + if (!listView || !listView.data.length || !snapshot.length) { return } const index = _.findIndex(snapshot, listView.data[0]) - if (index === -1 | index === 29) { + if (index === -1 || index === 29) { return } @@ -297,8 +297,8 @@ export function deleteFailedMessage(rowId, roomId) { export function getSingleMessage(roomId, messageId) { return async (dispatch, getState) => { + dispatch({type: SINGLE_MESSAGE}) const {token} = getState().auth - dispatch({type: SINGLE_MESSAGE, roomId, messageId}) try { const payload = await Api.getMessage(token, roomId, messageId) @@ -752,7 +752,7 @@ export default function messages(state = initialState, action) { case SINGLE_MESSAGE_OK: return {...state, isLoadingMessage: false, - messages: {...state.messages, + entities: {...state.entities, [action.messageId]: action.payload } } diff --git a/app/screens/Group/index.js b/app/screens/Group/index.js index ac67f44..40e54db 100644 --- a/app/screens/Group/index.js +++ b/app/screens/Group/index.js @@ -17,7 +17,7 @@ class Group extends Component { super(props) this.props.navigator.setOnNavigatorEvent(this.onNavigatorEvent.bind(this)) - this.props.navigator.setTitle({title: this.props.groupName}) + this.props.navigator.setTitle({title: this.props.name}) this.props.navigator.setButtons({ leftButtons: [{ title: 'Menu', @@ -95,14 +95,15 @@ Group.propTypes = { navigator: PropTypes.object, isLoading: PropTypes.bool, dispatch: PropTypes.func, - groupName: PropTypes.string + name: PropTypes.string } const mapStateToProps = ({groups: {groups, ...info}}, {groupId}) => { - const rooms = groups[groupId] || [] + const {rooms, name} = groups[groupId] return ({ - rooms, + rooms: rooms || [], + name, ...info }) } diff --git a/app/screens/Message/index.js b/app/screens/Message/index.js index 80e3548..4461869 100644 --- a/app/screens/Message/index.js +++ b/app/screens/Message/index.js @@ -1,22 +1,25 @@ import PropTypes from 'prop-types' -import React, { Component } from 'react' +import React, {Component} from 'react' import {View, ScrollView, Platform} from 'react-native'; import {connect} from 'react-redux' import s from './styles' import navigationStyles from '../../styles/common/navigationStyles' - -import { getSingleMessage } from '../../modules/messages' +import Loading from '../../components/Loading' +import {THEMES} from '../../constants' +import {getSingleMessage} from '../../modules/messages' import {subscribeToReadBy, unsubscribeFromReadBy} from '../../modules/realtime' import ReadBy from './ReadBy' import Msg from './Message' +const {colors} = THEMES.gitterDefault + class Message extends Component { constructor(props) { super(props) this.renderMessage = this.renderMessage.bind(this) - // this.renderReadBy = this.renderReadBy.bind(this) + this.renderReadBy = this.renderReadBy.bind(this) this.handleAvatarPress = this.handleAvatarPress.bind(this) this.props.navigator.setTitle({title: 'Message'}) @@ -35,14 +38,14 @@ class Message extends Component { } componentWillMount() { - const {dispatch, roomId, messageId, route} = this.props + const {dispatch, route, roomId, messageId} = this.props if (route) { - const {roomName, messageId: routeMessageId} = route - - dispatch(getSingleMessage(roomName, routeMessageId)) + dispatch(getSingleMessage(roomId, messageId)) } - dispatch(subscribeToReadBy(roomId, messageId)) + if (!this.props.isLoadingMessage) { + dispatch(subscribeToReadBy(roomId, messageId)) + } } componentWillUnmount() { @@ -71,6 +74,10 @@ class Message extends Component { ? roomMessagesResult.find(item => item.id === messageId) : messages[messageId] + if (!message) { + return null + } + return ( + + + ) + } + return ( @@ -110,7 +125,11 @@ Message.propTypes = { messageId: PropTypes.string, navigator: PropTypes.object, dispatch: PropTypes.func, - route: PropTypes.object, + route: PropTypes.shape({ + roomId: PropTypes.string, + messageId: PropTypes.string + }), + isLoadingMessage: PropTypes.bool, messages: PropTypes.object, readBy: PropTypes.object, viewer: PropTypes.object, @@ -122,12 +141,15 @@ Message.navigatorStyle = { screenBackgroundColor: 'white' } -function mapStateToProps(state) { +function mapStateToProps(state, props) { return { + isLoadingMessage: state.messages.isLoadingMessage, messages: state.messages.entities, readBy: state.readBy.byMessage, viewer: state.viewer.user, - roomMessagesResult: state.search.roomMessagesResult + roomMessagesResult: state.search.roomMessagesResult, + roomId: props.roomId || props.route.roomId, + messageId: props.messageId || props.route.messageId } } diff --git a/app/screens/Room/index.js b/app/screens/Room/index.js index f8aa8e0..4611b1f 100644 --- a/app/screens/Room/index.js +++ b/app/screens/Room/index.js @@ -424,15 +424,18 @@ class Room extends Component { } handleGitterMessageUrlClick(url) { - const {roomName, atParam} = parseGitterMessageUrl(url) - console.log(atParam, roomName) - this.props.navigator.push({screen: 'gm.Message', passProps: {route: {roomName, messageId: atParam}}}) + const {dispatch, navigator} = this.props + const {roomName, atParam: messageId} = parseGitterMessageUrl(url) + const navigateOnSuccess = roomId => + navigator.push({screen: 'gm.Message', passProps: {route: {roomId, messageId}}}) + + dispatch(getRoomByUrl(roomName, navigateOnSuccess)) } handleGitterGroupUrlClick(url) { const {dispatch, navigator} = this.props const {groupName} = parseGitterGroupUrl(url) - const navigateOnSuccess = groupId => navigator.push({screen: 'gm.Group', passProps: {groupId, groupName}}) + const navigateOnSuccess = groupId => navigator.push({screen: 'gm.Group', passProps: {groupId}}) dispatch(getGroupIdByName(groupName, navigateOnSuccess)) } diff --git a/app/utils/parseUrl.js b/app/utils/parseUrl.js index c204d77..b05703f 100644 --- a/app/utils/parseUrl.js +++ b/app/utils/parseUrl.js @@ -1,6 +1,6 @@ import { GITTER_REGEXPS } from '../constants' -const { baseUrl, groupParamsExp, messageParamsExp, roomParamsExp } = GITTER_REGEXPS +const {baseUrl, groupParamsExp, messageParamsExp, roomParamsExp} = GITTER_REGEXPS export const parseGitterRoomUrl = (url) => { const uri = url.replace(baseUrl, '') @@ -22,7 +22,7 @@ export const parseGitterGroupUrl = (url) => { } export const parseGitterMessageUrl = (url) => { - const [, atParam, roomName] = messageParamsExp.exec(url.replace(baseUrl, '')) + const [, roomName, atParam] = messageParamsExp.exec(url.replace(baseUrl, '')) return { atParam, From 281752ee3fd8a73b06ea2beddfd35ce2af2c847b Mon Sep 17 00:00:00 2001 From: AndreyKogut Date: Fri, 14 Jul 2017 17:45:34 +0300 Subject: [PATCH 8/8] Toasts for linking errors --- app/modules/groups.js | 21 +++++++++++++++------ app/modules/messages.js | 10 ++++++++-- app/modules/rooms.js | 8 +++++++- app/screens/Message/index.js | 16 ++++++++++++++-- app/screens/Room/index.js | 23 ++++++++++++++++------- app/screens/RoomInfo/index.js | 4 +++- package.json | 2 ++ 7 files changed, 65 insertions(+), 19 deletions(-) diff --git a/app/modules/groups.js b/app/modules/groups.js index 58778d0..d93a532 100644 --- a/app/modules/groups.js +++ b/app/modules/groups.js @@ -10,26 +10,35 @@ export const GROUP_ROOMS = 'groups/GROUPS_ROOMS' export const GROUP_ROOMS_RECEIVED = 'groups/GROUP_ROOMS_RECEIVED' export const GROUP_FOUND = 'groups/GROUP_FOUND' export const GROUP_ROOMS_FAILED = 'groups/GROUP_ROOMS_FAILED' +const func = () => {} -export function getGroupIdByName(groupName, navigateOnSuccess = () => {}) { +export function getGroupIdByName(groupName, navigateOnSuccess = func, handleError = func) { return async (dispatch, getState) => { try { const {token} = getState().auth const {results: rooms} = await Api.roomsByUri(token, groupName) const groupRegExp = new RegExp(`\\b${groupName}\/`) + if (!rooms) { + throw new Error('Probably such group is not exist.') + } + // Get group id - const {groupId} = _.find(rooms, ({uri}) => groupRegExp.test(uri)) + const matchedGroup = _.find(rooms, ({uri}) => groupRegExp.test(uri)) + + if (!matchedGroup) { + throw new Error(`Group '${groupName}' not found.`) + } + const payload = { - id: groupId, + id: matchedGroup.groupId, name: groupName } dispatch({type: GROUP_FOUND, payload}) - navigateOnSuccess(groupId) + navigateOnSuccess(matchedGroup.groupId) } catch (error) { - console.log(error) - // TODO: Handle error + handleError(error) } } } diff --git a/app/modules/messages.js b/app/modules/messages.js index 8532c7f..ee045a3 100644 --- a/app/modules/messages.js +++ b/app/modules/messages.js @@ -358,6 +358,7 @@ const initialState = { // [id]: bool }, isLoadingMessage: false, + singleMessageError: false, messages: {} } @@ -754,10 +755,10 @@ export default function messages(state = initialState, action) { isLoadingMessage: false, entities: {...state.entities, [action.messageId]: action.payload - } + }, + singleMessageError: false } - case SINGLE_MESSAGE_ERROR: case ROOM_MESSAGES_BEFORE_FAILED: case ROOM_MESSAGES_FAILED: return {...state, @@ -768,6 +769,11 @@ export default function messages(state = initialState, action) { errors: action.error } + case SINGLE_MESSAGE_ERROR: + return {...state, + singleMessageError: true + } + default: return state } diff --git a/app/modules/rooms.js b/app/modules/rooms.js index fb13857..37eb734 100644 --- a/app/modules/rooms.js +++ b/app/modules/rooms.js @@ -46,6 +46,7 @@ export const GET_NOTIFICATION_SETTINGS_OK = 'rooms/GET_NOTIFICATION_SETTINGS_OK' export const GET_NOTIFICATION_SETTINGS_ERROR = 'rooms/GET_NOTIFICATION_SETTINGS_ERROR' export const CHANGE_NOTIFICATION_SETTINGS_OK = 'rooms/CHANGE_NOTIFICATION_SETTINGS_OK' export const CHANGE_NOTIFICATION_SETTINGS_ERROR = 'rooms/CHANGE_NOTIFICATION_SETTINGS_ERROR' +const func = () => {} /** * Action Creators @@ -97,16 +98,21 @@ export function getRoom(id) { * Return room by uri */ -export function getRoomByUrl(url, navigateOnSuccess = () => {}) { +export function getRoomByUrl(url, navigateOnSuccess = func, handleError = func) { return async (dispatch, getState) => { const {token} = getState().auth dispatch({type: ROOM}) try { const {results: [room]} = await Api.roomsByUri(token, url) + if (!room) { + throw new Error('Room not found.') + } + dispatch({type: ROOM_RECEIVED, payload: room}) navigateOnSuccess(room.id) } catch (error) { + handleError(error) dispatch({type: ROOM_FAILED, error}) } } diff --git a/app/screens/Message/index.js b/app/screens/Message/index.js index 4461869..69d8f47 100644 --- a/app/screens/Message/index.js +++ b/app/screens/Message/index.js @@ -1,6 +1,6 @@ import PropTypes from 'prop-types' import React, {Component} from 'react' -import {View, ScrollView, Platform} from 'react-native'; +import {View, ScrollView, Platform} from 'react-native' import {connect} from 'react-redux' import s from './styles' import navigationStyles from '../../styles/common/navigationStyles' @@ -8,6 +8,7 @@ import Loading from '../../components/Loading' import {THEMES} from '../../constants' import {getSingleMessage} from '../../modules/messages' import {subscribeToReadBy, unsubscribeFromReadBy} from '../../modules/realtime' +import FailedToLoad from '../../components/FailedToLoad' import ReadBy from './ReadBy' import Msg from './Message' @@ -21,6 +22,7 @@ class Message extends Component { this.renderMessage = this.renderMessage.bind(this) this.renderReadBy = this.renderReadBy.bind(this) this.handleAvatarPress = this.handleAvatarPress.bind(this) + this.retryToLoadMessage = this.componentWillMount.bind(this) this.props.navigator.setTitle({title: 'Message'}) this.props.navigator.setOnNavigatorEvent(this.onNavigatorEvent.bind(this)) @@ -108,6 +110,14 @@ class Message extends Component { ) } + if (this.props.error) { + return ( + + ) + } + return ( @@ -129,6 +139,7 @@ Message.propTypes = { roomId: PropTypes.string, messageId: PropTypes.string }), + error: PropTypes.oneOfType([PropTypes.object, PropTypes.array]), isLoadingMessage: PropTypes.bool, messages: PropTypes.object, readBy: PropTypes.object, @@ -149,7 +160,8 @@ function mapStateToProps(state, props) { viewer: state.viewer.user, roomMessagesResult: state.search.roomMessagesResult, roomId: props.roomId || props.route.roomId, - messageId: props.messageId || props.route.messageId + messageId: props.messageId || props.route.messageId, + error: state.messages.singleMessageError ? state.messages.errors : null } } diff --git a/app/screens/Room/index.js b/app/screens/Room/index.js index 4611b1f..7e820d8 100644 --- a/app/screens/Room/index.js +++ b/app/screens/Room/index.js @@ -3,6 +3,7 @@ import React, { Component } from 'react' import {Linking, Keyboard, ActionSheetIOS, DrawerLayoutAndroid, ToastAndroid, Clipboard, Alert, ListView, View, Platform, KeyboardAvoidingView} from 'react-native'; import {connect} from 'react-redux' import Share from 'react-native-share' +import Toast, {DURATION} from 'react-native-easy-toast' import navigationStyles from '../../styles/common/navigationStyles' import _ from 'lodash' import {roomUsers} from '../../modules/users' @@ -88,6 +89,7 @@ class Room extends Component { this.handleGitterRoomUrlClick = this.handleGitterRoomUrlClick.bind(this) this.handleGitterGroupUrlClick = this.handleGitterGroupUrlClick.bind(this) this.handleGitterMessageUrlClick = this.handleGitterMessageUrlClick.bind(this) + this.showToast = _.curry(this.showToast.bind(this)) this.props.navigator.setOnNavigatorEvent(this.onNavigatorEvent.bind(this)) @@ -420,7 +422,7 @@ class Room extends Component { const {uri} = parseGitterRoomUrl(url) const navigateOnSuccess = roomId => navigator.push({screen: 'gm.Room', passProps: {roomId}}) - dispatch(getRoomByUrl(uri, navigateOnSuccess)) + dispatch(getRoomByUrl(uri, navigateOnSuccess, this.showToast('Unable to access room data.'))) } handleGitterMessageUrlClick(url) { @@ -429,7 +431,7 @@ class Room extends Component { const navigateOnSuccess = roomId => navigator.push({screen: 'gm.Message', passProps: {route: {roomId, messageId}}}) - dispatch(getRoomByUrl(roomName, navigateOnSuccess)) + dispatch(getRoomByUrl(roomName, navigateOnSuccess, this.showToast('Unable to access room data.'))) } handleGitterGroupUrlClick(url) { @@ -437,7 +439,7 @@ class Room extends Component { const {groupName} = parseGitterGroupUrl(url) const navigateOnSuccess = groupId => navigator.push({screen: 'gm.Group', passProps: {groupId}}) - dispatch(getGroupIdByName(groupName, navigateOnSuccess)) + dispatch(getGroupIdByName(groupName, navigateOnSuccess, this.showToast('Unable to access group data.'))) } handleOverflowClick() { @@ -561,7 +563,7 @@ class Room extends Component { case 'roomInfo': { dispatch(roomUsers(roomId)) return iOS - ? navigator.push({screen: 'gm.RoomInfo', passProps: {roomId}}) + ? navigator.push({screen: 'gm.RoomInfo', passProps: {route: {roomId}}}) : this.toggleDrawerState() } case 'toggleFavorite': return dispatch(changeFavoriteStatus(roomId)) @@ -684,6 +686,9 @@ class Room extends Component { } } + showToast(errorTitle, {message}) { + this.toast.show(`${errorTitle}${'\n'}${message}`, DURATION.LENGTH_SHORT) + } renderBottom() { const {rooms, route: {roomId}} = this.props @@ -788,9 +793,13 @@ class Room extends Component { drawerPosition={!iOS && DrawerLayoutAndroid.positions.Right} renderNavigationView={this.renderRoomInfo} keyboardDismissMode="on-drag"> - {this.renderListView()} - {getMessagesError || isLoadingMessages || _.has(listView, 'data') && - listView.data.length === 0 ? null : this.renderBottom()} + {this.renderListView()} + {getMessagesError || isLoadingMessages || _.has(listView, 'data') && + listView.data.length === 0 ? null : this.renderBottom()} + this.toast = toast} + style={{backgroundColor: colors.red}} + position="top" /> ) } diff --git a/app/screens/RoomInfo/index.js b/app/screens/RoomInfo/index.js index 453b8de..a637aa5 100644 --- a/app/screens/RoomInfo/index.js +++ b/app/screens/RoomInfo/index.js @@ -197,7 +197,9 @@ class RoomInfoScreen extends Component { RoomInfoScreen.propTypes = { dispatch: PropTypes.func, drawer: PropTypes.element, - route: PropTypes.object, + route: PropTypes.shape({ + roomId: PropTypes.string + }), roomInfo: PropTypes.object, rooms: PropTypes.object, roomInfoDrawerState: PropTypes.string, diff --git a/package.json b/package.json index 06ddc1e..f0e64be 100644 --- a/package.json +++ b/package.json @@ -17,6 +17,7 @@ "test": "jest", "android:check": "adb devices && adb reverse tcp:8081 tcp:8081", "android": "node node_modules/react-native/local-cli/cli.js run-android", + "ios": "node node_modules/react-native/local-cli/cli.js run-ios", "lint": "node node_modules/eslint/bin/eslint -c .eslintrc ./", "release": "cd android && ./gradlew assembleRelease", "android-release": "node node_modules/react-native/local-cli/cli.js run-android --variant=release" @@ -30,6 +31,7 @@ "react-native-device-info": "^0.10.2", "react-native-dialogs": "0.0.19", "react-native-drawer-layout": "^1.3.0", + "react-native-easy-toast": "^1.0.6", "react-native-invertible-scroll-view": "^1.0.0", "react-native-navigation": "^1.1.24", "react-native-parsed-text": "0.0.16",