Enable iOS Share Extension with TableView (#1418)

* Enable iOS Share Extension with TableView

* show channel display_name when main app channel is dm or gm

* feedback review
This commit is contained in:
enahum 2018-02-08 14:14:40 -03:00 committed by Harrison Healey
parent 94af2d5725
commit 9a0a43ae35
8 changed files with 168 additions and 69 deletions

View file

@ -2623,3 +2623,38 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
---
## react-native-tableview
Native iOS UITableView for React Native with JSON support.
* HOMEPAGE:
* https://github.com/aksonov/react-native-tableview
* LICENSE:
Copyright (c) 2015, aksonov
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
---

View file

@ -70,6 +70,7 @@
7FC200E81EBB65370099331B /* libReactNativeNavigation.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 7FC200DF1EBB65100099331B /* libReactNativeNavigation.a */; };
7FC649EE1FE983660074E4C7 /* EvilIcons.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 349FBA7338E74D9BBD709528 /* EvilIcons.ttf */; };
7FC649F01FE9B5D90074E4C7 /* OpenSans-Bold.ttf in Resources */ = {isa = PBXBuildFile; fileRef = D4B1B363C2414DA19C1AC521 /* OpenSans-Bold.ttf */; };
7FD8DEDE2029EDB7001AAC5E /* libRNTableView.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 7FD8DEDA2029ECDF001AAC5E /* libRNTableView.a */; };
7FDB92B11F706F58006CDFD1 /* libRNImagePicker.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 7FDB92A71F706F45006CDFD1 /* libRNImagePicker.a */; };
7FEB10981F6101710039A015 /* BlurAppScreen.m in Sources */ = {isa = PBXBuildFile; fileRef = 7FEB10971F6101710039A015 /* BlurAppScreen.m */; };
7FEB109D1F61019C0039A015 /* MattermostManaged.m in Sources */ = {isa = PBXBuildFile; fileRef = 7FEB109A1F61019C0039A015 /* MattermostManaged.m */; };
@ -531,6 +532,13 @@
remoteGlobalIDString = D8AFADBD1BEE6F3F00A4592D;
remoteInfo = ReactNativeNavigation;
};
7FD8DED92029ECDF001AAC5E /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 7FD8DE972029ECDE001AAC5E /* RNTableView.xcodeproj */;
proxyType = 2;
remoteGlobalIDString = 873769951B7CD6E900F7C3C2;
remoteInfo = RNTableView;
};
7FDB92A61F706F45006CDFD1 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 7FDB92751F706F45006CDFD1 /* RNImagePicker.xcodeproj */;
@ -678,6 +686,7 @@
7F6C47A41FE87E8C00F5A912 /* PerformRequests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = PerformRequests.m; sourceTree = "<group>"; };
7F7D7F52201645D300D31155 /* ReactNativePermissions.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = ReactNativePermissions.xcodeproj; path = "../node_modules/react-native-permissions/ios/ReactNativePermissions.xcodeproj"; sourceTree = "<group>"; };
7FC200BC1EBB65100099331B /* ReactNativeNavigation.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = ReactNativeNavigation.xcodeproj; path = "../node_modules/react-native-navigation/ios/ReactNativeNavigation.xcodeproj"; sourceTree = "<group>"; };
7FD8DE972029ECDE001AAC5E /* RNTableView.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RNTableView.xcodeproj; path = "../node_modules/react-native-tableview/RNTableView.xcodeproj"; sourceTree = "<group>"; };
7FDB92751F706F45006CDFD1 /* RNImagePicker.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RNImagePicker.xcodeproj; path = "../node_modules/react-native-image-picker/ios/RNImagePicker.xcodeproj"; sourceTree = "<group>"; };
7FEB10961F6101710039A015 /* BlurAppScreen.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = BlurAppScreen.h; path = Mattermost/BlurAppScreen.h; sourceTree = "<group>"; };
7FEB10971F6101710039A015 /* BlurAppScreen.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = BlurAppScreen.m; path = Mattermost/BlurAppScreen.m; sourceTree = "<group>"; };
@ -806,6 +815,7 @@
7FFE32EA1FD9CD050038C7A0 /* libReact.a in Frameworks */,
7FFE32E91FD9CCF40038C7A0 /* libRCTAnimation.a in Frameworks */,
7FFE32E81FD9CCDE0038C7A0 /* libRCTCameraRoll.a in Frameworks */,
7FD8DEDE2029EDB7001AAC5E /* libRNTableView.a in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@ -1201,6 +1211,14 @@
name = Products;
sourceTree = "<group>";
};
7FD8DE982029ECDE001AAC5E /* Products */ = {
isa = PBXGroup;
children = (
7FD8DEDA2029ECDF001AAC5E /* libRNTableView.a */,
);
name = Products;
sourceTree = "<group>";
};
7FDB92761F706F45006CDFD1 /* Products */ = {
isa = PBXGroup;
children = (
@ -1259,6 +1277,7 @@
832341AE1AAA6A7D00B99B32 /* Libraries */ = {
isa = PBXGroup;
children = (
7FD8DE972029ECDE001AAC5E /* RNTableView.xcodeproj */,
7F7D7F52201645D300D31155 /* ReactNativePermissions.xcodeproj */,
7FDB92751F706F45006CDFD1 /* RNImagePicker.xcodeproj */,
37ABD3971F4CE13B001FDE6B /* ART.xcodeproj */,
@ -1592,6 +1611,10 @@
ProductGroup = 7FDF28C41E1F4B1F00DBBE56 /* Products */;
ProjectRef = 2CBE9C0FB56E4FDA96C30792 /* RNSVG.xcodeproj */;
},
{
ProductGroup = 7FD8DE982029ECDE001AAC5E /* Products */;
ProjectRef = 7FD8DE972029ECDE001AAC5E /* RNTableView.xcodeproj */;
},
{
ProductGroup = 7FDF28EE1E1F4B4E00DBBE56 /* Products */;
ProjectRef = 2B25899FDAC149EB96ED3305 /* RNVectorIcons.xcodeproj */;
@ -2024,6 +2047,13 @@
remoteRef = 7FC200DE1EBB65100099331B /* PBXContainerItemProxy */;
sourceTree = BUILT_PRODUCTS_DIR;
};
7FD8DEDA2029ECDF001AAC5E /* libRNTableView.a */ = {
isa = PBXReferenceProxy;
fileType = archive.ar;
path = libRNTableView.a;
remoteRef = 7FD8DED92029ECDF001AAC5E /* PBXContainerItemProxy */;
sourceTree = BUILT_PRODUCTS_DIR;
};
7FDB92A71F706F45006CDFD1 /* libRNImagePicker.a */ = {
isa = PBXReferenceProxy;
fileType = archive.ar;
@ -2530,6 +2560,7 @@
"$(SRCROOT)/../node_modules/react-native-video/ios",
"$(SRCROOT)/../node_modules/react-native-doc-viewer/ios/**",
"$(SRCROOT)/../node_modules/react-native-safe-area/ios/RNSafeArea",
"$(SRCROOT)/../node_modules/react-native-tableview/**",
);
INFOPLIST_FILE = MattermostShare/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 9.3;
@ -2589,6 +2620,7 @@
"$(SRCROOT)/../node_modules/react-native-video/ios",
"$(SRCROOT)/../node_modules/react-native-doc-viewer/ios/**",
"$(SRCROOT)/../node_modules/react-native-safe-area/ios/RNSafeArea",
"$(SRCROOT)/../node_modules/react-native-tableview/**",
);
INFOPLIST_FILE = MattermostShare/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 9.3;

View file

@ -46,6 +46,7 @@
"react-native-slider": "0.11.0",
"react-native-status-bar-size": "0.3.3",
"react-native-svg": "6.1.3",
"react-native-tableview": "2.1.0",
"react-native-tooltip": "enahum/react-native-tooltip",
"react-native-vector-icons": "4.5.0",
"react-native-video": "2.0.0",

View file

@ -17,7 +17,7 @@ import {Preferences} from 'mattermost-redux/constants';
import SearchBar from 'app/components/search_bar';
import {changeOpacity, makeStyleSheetFromTheme} from 'app/utils/theme';
import ExtensionChannelItem from 'share_extension/common/extension_channel_item';
import ExtensionChannelItem from './extension_channel_item';
const defaultTheme = Preferences.THEMES.default;

View file

@ -5,12 +5,13 @@ import React, {PureComponent} from 'react';
import PropTypes from 'prop-types';
import {
ActivityIndicator,
SectionList,
Text,
View
} from 'react-native';
import DeviceInfo from 'react-native-device-info';
import {intlShape} from 'react-intl';
import TableView from 'react-native-tableview';
import Icon from 'react-native-vector-icons/FontAwesome';
import {General} from 'mattermost-redux/constants';
import {getChannelsInTeam, getDirectChannels} from 'mattermost-redux/selectors/entities/channels';
@ -18,8 +19,6 @@ import {getChannelsInTeam, getDirectChannels} from 'mattermost-redux/selectors/e
import SearchBar from 'app/components/search_bar';
import {changeOpacity, makeStyleSheetFromTheme} from 'app/utils/theme';
import ExtensionChannelItem from 'share_extension/common/extension_channel_item';
import ExtensionNavBar from './extension_nav_bar';
export default class ExtensionChannels extends PureComponent {
@ -112,6 +111,18 @@ export default class ExtensionChannels extends PureComponent {
const direct = getDirectChannels({entities});
const channels = channelIds.map((id) => this.props.entities.channels.channels[id]).concat(direct);
const icons = await Promise.all([
Icon.getImageSource('globe', 16, this.props.theme.centerChannelColor),
Icon.getImageSource('lock', 16, this.props.theme.centerChannelColor),
Icon.getImageSource('user', 16, this.props.theme.centerChannelColor),
Icon.getImageSource('users', 16, this.props.theme.centerChannelColor)
]);
this.publicChannelIcon = icons[0];
this.privateChannelIcon = icons[1];
this.dmChannelIcon = icons[2];
this.gmChannelIcon = icons[3];
this.setState({
channels
}, () => {
@ -134,12 +145,20 @@ export default class ExtensionChannels extends PureComponent {
});
};
handleSelectChannel = (channel) => {
this.props.onSelectChannel(channel);
this.goBack();
handleSelectChannel = (selected) => {
const {sections} = this.state;
const section = sections.find((s) => s.id === selected.detail);
if (section) {
const channel = section.data.find((c) => c.id === selected.value);
if (channel) {
this.props.onSelectChannel(channel);
this.goBack();
}
}
};
renderBody = (styles) => {
const {theme} = this.props;
const {error, sections} = this.state;
if (error) {
@ -161,45 +180,22 @@ export default class ExtensionChannels extends PureComponent {
}
return (
<SectionList
sections={sections}
ListHeaderComponent={this.renderSearchBar(styles)}
ItemSeparatorComponent={this.renderItemSeparator}
renderItem={this.renderItem}
renderSectionHeader={this.renderSectionHeader}
keyExtractor={this.keyExtractor}
keyboardShouldPersistTaps='always'
keyboardDismissMode='on-drag'
initialNumToRender={10}
maxToRenderPerBatch={10}
stickySectionHeadersEnabled={true}
scrollEventThrottle={100}
windowSize={5}
/>
);
};
renderItem = ({item}) => {
const {currentChannelId, theme} = this.props;
return (
<ExtensionChannelItem
channel={item}
currentChannelId={currentChannelId}
onSelectChannel={this.handleSelectChannel}
theme={theme}
/>
);
};
renderItemSeparator = () => {
const {theme} = this.props;
const styles = getStyleSheet(theme);
return (
<View style={styles.separatorContainer}>
<View style={styles.separator}/>
</View>
<TableView
tableViewStyle={TableView.Consts.Style.Plain}
tableViewCellStyle={TableView.Consts.CellStyle.Default}
separatorColor={changeOpacity(theme.centerChannelColor, 0.5)}
tintColor={theme.linkColor}
detailFontSize={16}
detailTextColor={theme.centerChannelColor}
headerFontSize={15}
headerTextColor={changeOpacity(theme.centerChannelColor, 0.6)}
style={styles.flex}
>
<TableView.Section>
<TableView.Cell>{this.renderSearchBar(styles)}</TableView.Cell>
</TableView.Section>
{this.renderSections()}
</TableView>
);
};
@ -227,29 +223,53 @@ export default class ExtensionChannels extends PureComponent {
);
};
renderSectionHeader = ({section}) => {
const {intl} = this.context;
const {theme} = this.props;
const styles = getStyleSheet(theme);
const {
defaultMessage,
id
} = section;
renderSections = () => {
const {formatMessage} = this.context.intl;
const {currentChannelId} = this.props;
const {sections} = this.state;
return (
<View style={[styles.titleContainer, {backgroundColor: theme.centerChannelBg}]}>
<View style={{backgroundColor: changeOpacity(theme.centerChannelColor, 0.1), justifyContent: 'center'}}>
<Text style={styles.title}>
{intl.formatMessage({id, defaultMessage}).toUpperCase()}
</Text>
</View>
</View>
);
};
return sections.map((s) => {
const items = s.data.map((c) => {
let icon;
switch (c.type) {
case General.OPEN_CHANNEL:
icon = this.publicChannelIcon.uri;
break;
case General.PRIVATE_CHANNEL:
icon = this.privateChannelIcon.uri;
break;
case General.DM_CHANNEL:
icon = this.dmChannelIcon.uri;
break;
case General.GM_CHANNEL:
icon = this.gmChannelIcon.uri;
break;
}
sort = (a, b) => {
const locale = DeviceInfo.getDeviceLocale().split('-')[0];
return a.localeCompare(b, locale, {numeric: true});
return (
<TableView.Item
key={c.id}
value={c.id}
detail={s.id}
selected={c.id === currentChannelId}
onPress={this.handleSelectChannel}
image={icon}
>
{c.display_name}
</TableView.Item>
);
});
return (
<TableView.Section
key={s.id}
label={formatMessage({id: s.id, defaultMessage: s.defaultMessage})}
headerHeight={30}
>
{items}
</TableView.Section>
);
});
};
sortDisplayName = (a, b) => {

View file

@ -21,6 +21,8 @@ import LocalAuth from 'react-native-local-auth';
import RNFetchBlob from 'react-native-fetch-blob';
import {Client4} from 'mattermost-redux/client';
import {General} from 'mattermost-redux/constants';
import {getChannel} from 'mattermost-redux/selectors/entities/channels';
import {getFormattedFileSize, lookupMimeType} from 'mattermost-redux/utils/file_utils';
import mattermostBucket from 'app/mattermost_bucket';
@ -40,7 +42,6 @@ import {
import ExtensionChannels from './extension_channels';
import ExtensionNavBar from './extension_nav_bar';
import ExtensionTeams from './extension_teams';
import {General} from 'mattermost-redux/constants/index';
const ShareExtension = NativeModules.MattermostShare;
const MAX_INPUT_HEIGHT = 95;
@ -189,13 +190,17 @@ export default class ExtensionPost extends PureComponent {
const {credentials} = entities.general;
const {currentUserId} = entities.users;
const team = entities.teams.teams[entities.teams.currentTeamId];
const channel = entities.channels.channels[entities.channels.currentChannelId];
let channel = entities.channels.channels[entities.channels.currentChannelId];
const items = await ShareExtension.data(Config.AppGroupId);
const text = [];
const urls = [];
const files = [];
let totalSize = 0;
if (channel.type === General.GM_CHANNEL || channel.type === General.DM_CHANNEL) {
channel = getChannel({entities}, channel.id);
}
for (let i = 0; i < items.length; i++) {
const item = items[i];
switch (item.type) {

View file

@ -5311,6 +5311,12 @@ react-native-tab-view@^0.0.74:
dependencies:
prop-types "^15.6.0"
react-native-tableview@2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/react-native-tableview/-/react-native-tableview-2.1.0.tgz#d98b8303728b5ff4860612f05163028735bb3b07"
dependencies:
prop-types "^15.6.0"
react-native-tooltip@enahum/react-native-tooltip:
version "5.1.1"
resolved "https://codeload.github.com/enahum/react-native-tooltip/tar.gz/eb47f93a728813b58c01f23ec84f0b8b1bd70bd0"