Compare commits

..

9 Commits

Author SHA1 Message Date
Elias Nahum
6e08d69b10 Bump app version to 1.6.1 2018-02-12 16:15:53 -03:00
Elias Nahum
a8a95a98e7 Bump app build to 86 2018-02-12 15:21:22 -03:00
Stephen Kiers
b4b23d4d46 ICU-639- Fixes bug with Samsung keyboard (#1427)
* Internalized value to local state so that there is less ‘delay’ and less surface area for bugs to happen

* removed toLowercase on comparison and fixed parent components instead

* Update more_channels.js
2018-02-12 14:04:17 -03:00
enahum
99cff03193 Fix Android stuck when opening from push notification (#1429) 2018-02-12 14:04:02 -03:00
enahum
b9864bf26f Fix Android app stuck in splash screen when killed by the OS (#1403) 2018-02-12 09:09:07 -03:00
enahum
e73b9017fc Fix race condition preventing the app opened from a PN to go to the correct channel (#1398)
* Fix race condition preventing the app opened from a PN to go to the correct channel

* Feedback review
2018-02-12 09:08:23 -03:00
enahum
f65820dd0c Bump Android build number to 85 (#1417) 2018-02-05 21:54:11 -03:00
enahum
7564ac023c Bump iOS build number to 85 (#1416) 2018-02-05 21:46:49 -03:00
enahum
5ef83639e9 Remove iOS share extension (#1415) 2018-02-05 21:14:09 -03:00
473 changed files with 6172 additions and 12759 deletions

View File

@@ -40,7 +40,7 @@
"brace-style": [2, "1tbs", { "allowSingleLine": false }],
"camelcase": [2, {"properties": "never"}],
"class-methods-use-this": 0,
"comma-dangle": [2, "always-multiline"],
"comma-dangle": [2, "never"],
"comma-spacing": [2, {"before": false, "after": true}],
"comma-style": [2, "last"],
"complexity": [1, 10],
@@ -54,7 +54,7 @@
"eqeqeq": [2, "smart"],
"func-call-spacing": [2, "never"],
"func-names": 2,
"func-style": [2, "declaration", { "allowArrowFunctions": true }],
"func-style": [2, "declaration"],
"generator-star-spacing": [0, {"before": false, "after": true}],
"global-require": 2,
"guard-for-in": 2,

View File

@@ -1,44 +1,5 @@
# Mattermost Mobile Apps Changelog
## v1.6.1 Release
- Release Date: February 13, 2018
- Server Versions Supported: Server v4.0+ is required, Self-Signed SSL Certificates are not supported
### Bug Fixes
- Fixed an issue preventing the app from going to the correct channel when opened from a push notification
- Fixed an issue on Android devices where the app could sometimes freeze on the launch screen
- Fixed an issue on Samsung devices causing extra letters to be insterted when typing to filter user lists
## v1.6.0 Release
- Release Date: February 6, 2018
- Server Versions Supported: Server v4.0+ is required, Self-Signed SSL Certificates are not supported
### Highlights
#### Android File Sharing
- Share files and images from other applications as attached files in Mattermost
### Improvements
- Added a right drawer to access settings, edit profile information, change online status and logout
- Added support for opening a Direct Message channel with yourself
### Bugs
- Fixed a number of issues causing crashes on Android devices
- Fixed an issue with auto capitalization on Android keyboards
- Fixed an issue where the GitLab SSO login button sometimes didn't appear
- Fixed an issue with link previews not appearing on some accounts
- Fixed an issue where logging out of the app didn't clear the notification badge on the homescreen icon
- Fixed an issue where interactive message buttons would not wrap to a new line
- Fixed an issue where the keyboard would sometimes overlap the text input box
- Fixed an issue where the Direct Message channel wouldn't open from the profile page
- Fixed an issue where posts would sometimes overlap
- Fixed an issue where the app sometimes hangs on logout
## v1.5.3 Release
- Release Date: February 1, 2018
- Server Versions Supported: Server v4.0+ is required, Self-Signed SSL Certificates are not supported
- Fixed a login issue when connecting to servers running a Data Retention policy
## v1.5.2 Release
- Release Date: January 12, 2018
- Server Versions Supported: Server v4.0+ is required, Self-Signed SSL Certificates are not supported

View File

@@ -5,14 +5,14 @@
.PHONY: build-ios build-android unsigned-ios unsigned-android
.PHONY: test help
POD := $(shell which pod 2> /dev/null)
POD := $(shell command -v pod 2> /dev/null)
OS := $(shell sh -c 'uname -s 2>/dev/null')
BASE_ASSETS = $(shell find assets/base -type d) $(shell find assets/base -type f -name '*')
OVERRIDE_ASSETS = $(shell find assets/override -type d 2> /dev/null) $(shell find assets/override -type f -name '*' 2> /dev/null)
.yarninstall: package.json
@if ! [ $(shell which yarn 2> /dev/null) ]; then \
echo "yarn is not installed https://yarnpkg.com"; \
@if ! [ $(shell command -v yarn 2> /dev/null) ]; then \
@echo "yarn is not installed https://yarnpkg.com"; \
exit 1; \
fi
@@ -47,7 +47,7 @@ pre-run: | .yarninstall .podinstall dist/assets ## Installs dependencies and ass
check-style: .yarninstall ## Runs eslint
@echo Checking for style guide compliance
@yarn run check
@node_modules/.bin/eslint --ext \".js\" --ignore-pattern node_modules --quiet .
clean: ## Cleans dependencies, previous builds and temp files
@echo Cleaning started
@@ -75,11 +75,13 @@ post-install:
@sed -i'' -e 's|"./lib/locales": false|"./lib/locales": "./lib/locales"|g' node_modules/intl-relativeformat/package.json
@sed -i'' -e 's|"./locale-data/complete.js": false|"./locale-data/complete.js": "./locale-data/complete.js"|g' node_modules/intl/package.json
@sed -i'' -e 's|auto("auto", Configuration.ORIENTATION_UNDEFINED, ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED);|auto("auto", Configuration.ORIENTATION_UNDEFINED, ActivityInfo.SCREEN_ORIENTATION_FULL_USER);|g' node_modules/react-native-navigation/android/app/src/main/java/com/reactnativenavigation/params/Orientation.java
@sed -i'' -e "s|var AndroidTextInput = requireNativeComponent('AndroidTextInput', null);|var AndroidTextInput = requireNativeComponent('CustomTextInput', null);|g" node_modules/react-native/Libraries/Components/TextInput/TextInput.js
@sed -i'' -e "s|super.onBackPressed();|this.moveTaskToBack(true);|g" node_modules/react-native-navigation/android/app/src/main/java/com/reactnativenavigation/controllers/NavigationActivity.java
@if [ $(shell grep "const Platform" node_modules/react-native/Libraries/Lists/VirtualizedList.js | grep -civ grep) -eq 0 ]; then \
sed $ -i'' -e "s|const ReactNative = require('ReactNative');|const ReactNative = require('ReactNative');`echo $\\\\\\r;`const Platform = require('Platform');|g" node_modules/react-native/Libraries/Lists/VirtualizedList.js; \
fi
@sed -i'' -e 's|transform: \[{scaleY: -1}\],|...Platform.select({android: {transform: \[{perspective: 1}, {scaleY: -1}\]}, ios: {transform: \[{scaleY: -1}\]}}),|g' node_modules/react-native/Libraries/Lists/VirtualizedList.js
@cd ./node_modules/react-native-svg/ios && rm -rf PerformanceBezier QuartzBookPack && yarn run postinstall
@cd ./node_modules/mattermost-redux && yarn run build
start: | pre-run ## Starts the React Native packager server
@@ -93,39 +95,39 @@ start: | pre-run ## Starts the React Native packager server
stop: ## Stops the React Native packager server
@echo Stopping React Native packager server
@if [ $(shell ps -ef | grep -i "cli.js start" | grep -civ grep) -eq 1 ]; then \
ps -ef | grep -i "cli.js start" | grep -iv grep | awk '{print $$2}' | xargs kill -9; \
@if [ $(shell ps -e | grep -i "cli.js start" | grep -civ grep) -eq 1 ]; then \
ps -e | grep -i "cli.js start" | grep -iv grep | awk '{print $$1}' | xargs kill -9; \
echo React Native packager server stopped; \
else \
echo No React Native packager server running; \
fi
check-device-ios:
@if ! [ $(shell which xcodebuild) ]; then \
@if ! [ $(shell command -v xcodebuild) ]; then \
echo "xcode is not installed"; \
exit 1; \
fi
@if ! [ $(shell which watchman) ]; then \
@if ! [ $(shell command -v watchman) ]; then \
echo "watchman is not installed"; \
exit 1; \
fi
check-device-android:
@if ! [ $(ANDROID_HOME) ]; then \
echo "ANDROID_HOME is not set"; \
exit 1; \
@echo "ANDROID_HOME is not set"; \
@exit 1; \
fi
@if ! [ $(shell which adb 2> /dev/null) ]; then \
echo "adb is not installed"; \
exit 1; \
@if ! [ $(shell command -v adb 2> /dev/null) ]; then \
@echo "adb is not installed"; \
@exit 1; \
fi
@echo "Connect your Android device or open the emulator"
@adb wait-for-device
@if ! [ $(shell which watchman 2> /dev/null) ]; then \
echo "watchman is not installed"; \
exit 1; \
@if ! [ $(shell command -v watchman 2> /dev/null) ]; then \
@echo "watchman is not installed"; \
@exit 1; \
fi
prepare-android-build:

View File

@@ -2623,38 +2623,3 @@ 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

@@ -80,11 +80,6 @@ apply from: "../../node_modules/react-native/react.gradle"
apply from: "../../node_modules/react-native-vector-icons/fonts.gradle"
if (System.getenv("SENTRY_ENABLED") == "true") {
project.ext.sentryCli = [
logLevel: "debug",
flavorAware: true
]
apply from: "../../node_modules/react-native-sentry/sentry.gradle"
}
@@ -111,8 +106,8 @@ android {
applicationId "com.mattermost.rnbeta"
minSdkVersion 21
targetSdkVersion 23
versionCode 92
versionName "1.7.1"
versionCode 86
versionName "1.6.1"
multiDexEnabled true
ndk {
abiFilters "armeabi-v7a", "x86"

View File

@@ -0,0 +1,30 @@
package com.mattermost.components;
import android.content.Context;
import android.text.InputType;
import com.facebook.react.views.textinput.ReactEditText;
public class CustomTextInput extends ReactEditText {
private boolean autoScroll = false;
public CustomTextInput(Context context) {
super(context);
}
private boolean isMultiline() {
return (getInputType() & InputType.TYPE_TEXT_FLAG_MULTI_LINE) != 0;
}
@Override
public boolean isLayoutRequested() {
if (isMultiline() && !autoScroll) {
return true;
}
return false;
}
public void setAutoScroll(boolean autoScroll) {
this.autoScroll = autoScroll;
}
}

View File

@@ -0,0 +1,35 @@
package com.mattermost.components;
import android.text.InputType;
import android.util.TypedValue;
import com.facebook.react.views.textinput.ReactTextInputManager;
import com.facebook.react.uimanager.PixelUtil;
import com.facebook.react.uimanager.ThemedReactContext;
import com.facebook.react.uimanager.ViewDefaults;
import com.facebook.react.uimanager.annotations.ReactProp;
public class CustomTextInputManager extends ReactTextInputManager {
@Override
public String getName() {
return "CustomTextInput";
}
@Override
public CustomTextInput createViewInstance(ThemedReactContext context) {
CustomTextInput editText = new CustomTextInput(context);
int inputType = editText.getInputType();
editText.setInputType(inputType & (~InputType.TYPE_TEXT_FLAG_MULTI_LINE));
editText.setReturnKeyType("done");
editText.setTextSize(
TypedValue.COMPLEX_UNIT_PX,
(int) Math.ceil(PixelUtil.toPixelFromSP(ViewDefaults.FONT_SIZE_SP)));
return editText;
}
@ReactProp(name = "autoScroll", defaultBoolean = false)
public void setAutoScroll(CustomTextInput view, boolean autoScroll) {
view.setAutoScroll(autoScroll);
}
}

View File

@@ -10,6 +10,8 @@ import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.uimanager.ViewManager;
import com.facebook.react.bridge.JavaScriptModule;
import com.mattermost.components.CustomTextInputManager;
public class MattermostPackage implements ReactPackage {
private final MainApplication mApplication;
@@ -27,6 +29,8 @@ public class MattermostPackage implements ReactPackage {
@Override
public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) {
return Arrays.<ViewManager>asList();
return Arrays.<ViewManager>asList(
new CustomTextInputManager()
);
}
}

View File

@@ -11,8 +11,8 @@ export function calculateDeviceDimensions() {
type: DeviceTypes.DEVICE_DIMENSIONS_CHANGED,
data: {
deviceHeight: height,
deviceWidth: width,
},
deviceWidth: width
}
};
}
@@ -20,7 +20,7 @@ export function connection(isOnline) {
return async (dispatch, getState) => {
dispatch({
type: DeviceTypes.CONNECTION_CHANGED,
data: isOnline,
data: isOnline
}, getState);
};
}
@@ -28,21 +28,21 @@ export function connection(isOnline) {
export function setStatusBarHeight(height = 20) {
return {
type: DeviceTypes.STATUSBAR_HEIGHT_CHANGED,
data: height,
data: height
};
}
export function setDeviceOrientation(orientation) {
return {
type: DeviceTypes.DEVICE_ORIENTATION_CHANGED,
data: orientation,
data: orientation
};
}
export function setDeviceAsTablet() {
return {
type: DeviceTypes.DEVICE_TYPE_CHANGED,
data: true,
data: true
};
}
@@ -51,5 +51,5 @@ export default {
connection,
setDeviceOrientation,
setDeviceAsTablet,
setStatusBarHeight,
setStatusBarHeight
};

View File

@@ -11,15 +11,15 @@ export function handleUpdateUserNotifyProps(notifyProps) {
const config = state.entities.general.config;
const {currentUserId} = state.entities.users;
const {interval, user_id: userId, ...otherProps} = notifyProps;
const {interval, user_id, ...otherProps} = notifyProps;
const email = notifyProps.email;
if (config.EnableEmailBatching === 'true' && email !== 'false') {
const emailInterval = [{
user_id: userId,
user_id,
category: Preferences.CATEGORY_NOTIFICATIONS,
name: Preferences.EMAIL_INTERVAL,
value: interval,
value: interval
}];
savePreferences(currentUserId, emailInterval)(dispatch, getState);

View File

@@ -1,11 +0,0 @@
// Copyright (c) 2018-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import {ViewTypes} from 'app/constants';
export function dismissBanner(text) {
return {
type: ViewTypes.ANNOUNCEMENT_BANNER,
data: text,
};
}

View File

@@ -10,7 +10,7 @@ import {
fetchMyChannelsAndMembers,
markChannelAsRead,
selectChannel,
leaveChannel as serviceLeaveChannel,
leaveChannel as serviceLeaveChannel
} from 'mattermost-redux/actions/channels';
import {getPosts, getPostsBefore, getPostsSince, getPostThread} from 'mattermost-redux/actions/posts';
import {getFilesForPost} from 'mattermost-redux/actions/files';
@@ -19,14 +19,13 @@ import {getTeamMembersByIds} from 'mattermost-redux/actions/teams';
import {getProfilesInChannel} from 'mattermost-redux/actions/users';
import {General, Preferences} from 'mattermost-redux/constants';
import {getCurrentChannelId} from 'mattermost-redux/selectors/entities/channels';
import {getTeamByName} from 'mattermost-redux/selectors/entities/teams';
import {
getChannelByName,
getDirectChannelName,
getUserIdFromChannelName,
isDirectChannel,
isGroupChannel,
isGroupChannel
} from 'mattermost-redux/utils/channel_utils';
import {getLastCreateAt} from 'mattermost-redux/utils/post_utils';
import {getPreferencesByCategory} from 'mattermost-redux/utils/preference_utils';
@@ -41,18 +40,6 @@ export function loadChannelsIfNecessary(teamId) {
};
}
export function loadChannelsByTeamName(teamName) {
return async (dispatch, getState) => {
const state = getState();
const {currentTeamId} = state.entities.teams;
const team = getTeamByName(state, teamName);
if (team && team.id !== currentTeamId) {
await dispatch(fetchMyChannelsAndMembers(team.id));
}
};
}
export function loadProfilesAndTeamMembersForDMSidebar(teamId) {
return async (dispatch, getState) => {
const state = getState();
@@ -71,7 +58,7 @@ export function loadProfilesAndTeamMembersForDMSidebar(teamId) {
user_id: currentUserId,
category: Preferences.CATEGORY_DIRECT_CHANNEL_SHOW,
name,
value: 'true',
value: 'true'
};
}
@@ -143,7 +130,7 @@ export function loadProfilesAndTeamMembersForDMSidebar(teamId) {
actions.push({
type: UserTypes.RECEIVED_PROFILE_IN_CHANNEL,
data: {user_id: members[i]},
id: channel.id,
id: channel.id
});
}
}
@@ -162,15 +149,10 @@ export function loadPostsIfNecessaryWithRetry(channelId) {
const time = Date.now();
let loadMorePostsVisible = true;
let received;
if (!postsIds || postsIds.length < ViewTypes.POST_VISIBILITY_CHUNK_SIZE) {
// Get the first page of posts if it appears we haven't gotten it yet, like the webapp
received = await retryGetPostsAction(getPosts(channelId), dispatch, getState);
if (received) {
loadMorePostsVisible = received.order.length >= ViewTypes.POST_VISIBILITY_CHUNK_SIZE;
}
} else {
const {lastConnectAt} = state.device.websocket;
const lastGetPosts = state.views.channel.lastGetPosts[channelId];
@@ -187,21 +169,15 @@ export function loadPostsIfNecessaryWithRetry(channelId) {
}
received = await retryGetPostsAction(getPostsSince(channelId, since), dispatch, getState);
if (received) {
loadMorePostsVisible = postsIds.length + received.order.length >= ViewTypes.POST_VISIBILITY_CHUNK_SIZE;
}
}
if (received) {
dispatch({
type: ViewTypes.RECEIVED_POSTS_FOR_CHANNEL_AT_TIME,
channelId,
time,
time
});
}
dispatch(setLoadMorePostsVisible(loadMorePostsVisible));
};
}
@@ -290,23 +266,20 @@ export function handleSelectChannel(channelId) {
return async (dispatch, getState) => {
const {currentTeamId} = getState().entities.teams;
dispatch(setLoadMorePostsVisible(true));
loadPostsIfNecessaryWithRetry(channelId)(dispatch, getState);
selectChannel(channelId)(dispatch, getState);
dispatch(batchActions([
{
type: ViewTypes.SET_INITIAL_POST_VISIBILITY,
data: channelId,
data: channelId
},
setChannelLoading(false),
{
type: ViewTypes.SET_LAST_CHANNEL_FOR_TEAM,
teamId: currentTeamId,
channelId,
},
]));
channelId
}
]), 'BATCH_CHANNEL_LOADED');
};
}
@@ -315,7 +288,7 @@ export function handlePostDraftChanged(channelId, draft) {
dispatch({
type: ViewTypes.POST_DRAFT_CHANGED,
channelId,
draft,
draft
}, getState);
};
}
@@ -324,7 +297,7 @@ export function handlePostDraftSelectionChanged(channelId, cursorPosition) {
return {
type: ViewTypes.POST_DRAFT_SELECTION_CHANGED,
channelId,
cursorPosition,
cursorPosition
};
}
@@ -343,7 +316,7 @@ export function insertToDraft(value) {
cursorPosition = threadDraft.cursorPosition;
action = {
type: ViewTypes.COMMENT_DRAFT_CHANGED,
rootId: threadId,
rootId: threadId
};
} else if (state.views.channel.drafts[channelId]) {
const channelDraft = state.views.channel.drafts[channelId];
@@ -351,7 +324,7 @@ export function insertToDraft(value) {
cursorPosition = channelDraft.cursorPosition;
action = {
type: ViewTypes.POST_DRAFT_CHANGED,
channelId,
channelId
};
}
@@ -365,7 +338,7 @@ export function insertToDraft(value) {
if (action && nextDraft !== draft) {
dispatch({
...action,
draft: nextDraft,
draft: nextDraft
});
}
};
@@ -380,7 +353,7 @@ export function toggleDMChannel(otherUserId, visible) {
user_id: currentUserId,
category: Preferences.CATEGORY_DIRECT_CHANNEL_SHOW,
name: otherUserId,
value: visible,
value: visible
}];
savePreferences(currentUserId, dm)(dispatch, getState);
@@ -396,7 +369,7 @@ export function toggleGMChannel(channelId, visible) {
user_id: currentUserId,
category: Preferences.CATEGORY_GROUP_CHANNEL_SHOW,
name: channelId,
value: visible,
value: visible
}];
savePreferences(currentUserId, gm)(dispatch, getState);
@@ -449,28 +422,28 @@ export function leaveChannel(channel, reset = false) {
export function setChannelLoading(loading = true) {
return {
type: ViewTypes.SET_CHANNEL_LOADER,
loading,
loading
};
}
export function setChannelRefreshing(loading = true) {
return {
type: ViewTypes.SET_CHANNEL_REFRESHING,
loading,
loading
};
}
export function setChannelRetryFailed(failed = true) {
return {
type: ViewTypes.SET_CHANNEL_RETRY_FAILED,
failed,
failed
};
}
export function setChannelDisplayName(displayName) {
return {
type: ViewTypes.SET_CHANNEL_DISPLAY_NAME,
displayName,
displayName
};
}
@@ -493,10 +466,11 @@ export function increasePostVisibility(channelId, focusedPostId) {
if (loadedPostCount >= desiredPostVisibility) {
// We already have the posts, so we just need to show them
dispatch(batchActions([
doIncreasePostVisibility(channelId),
setLoadMorePostsVisible(true),
]));
dispatch({
type: ViewTypes.INCREASE_POST_VISIBILITY,
data: channelId,
amount: ViewTypes.POST_VISIBILITY_CHUNK_SIZE
});
return;
}
@@ -505,49 +479,33 @@ export function increasePostVisibility(channelId, focusedPostId) {
dispatch({
type: ViewTypes.LOADING_POSTS,
data: true,
channelId,
channelId
});
const pageSize = ViewTypes.POST_VISIBILITY_CHUNK_SIZE;
const page = Math.floor(currentPostVisibility / pageSize);
const page = Math.floor(currentPostVisibility / ViewTypes.POST_VISIBILITY_CHUNK_SIZE);
let result;
if (focusedPostId) {
result = await getPostsBefore(channelId, focusedPostId, page, pageSize)(dispatch, getState);
result = await getPostsBefore(channelId, focusedPostId, page, ViewTypes.POST_VISIBILITY_CHUNK_SIZE)(dispatch, getState);
} else {
result = await getPosts(channelId, page, pageSize)(dispatch, getState);
result = await getPosts(channelId, page, ViewTypes.POST_VISIBILITY_CHUNK_SIZE)(dispatch, getState);
}
const actions = [{
type: ViewTypes.LOADING_POSTS,
data: false,
channelId,
}];
const posts = result.data;
if (posts) {
// make sure to increment the posts visibility
// only if we got results
actions.push(doIncreasePostVisibility(channelId));
actions.push(setLoadMorePostsVisible(posts.order.length >= pageSize));
dispatch({
type: ViewTypes.INCREASE_POST_VISIBILITY,
data: channelId,
amount: ViewTypes.POST_VISIBILITY_CHUNK_SIZE
});
}
dispatch(batchActions(actions));
};
}
function doIncreasePostVisibility(channelId) {
return {
type: ViewTypes.INCREASE_POST_VISIBILITY,
data: channelId,
amount: ViewTypes.POST_VISIBILITY_CHUNK_SIZE,
};
}
function setLoadMorePostsVisible(visible) {
return {
type: ViewTypes.SET_LOAD_MORE_POSTS_VISIBLE,
data: visible,
dispatch({
type: ViewTypes.LOADING_POSTS,
data: false,
channelId
});
};
}

View File

@@ -5,6 +5,6 @@ import {ViewTypes} from 'app/constants';
export function setLastUpgradeCheck() {
return {
type: ViewTypes.SET_LAST_UPGRADE_CHECK,
type: ViewTypes.SET_LAST_UPGRADE_CHECK
};
}

View File

@@ -14,7 +14,7 @@ export function executeCommand(message, channelId, rootId) {
channel_id: channelId,
team_id: teamId,
root_id: rootId,
parent_id: rootId,
parent_id: rootId
};
let msg = message;

View File

@@ -18,7 +18,7 @@ export function handleCreateChannel(displayName, purpose, header, type) {
display_name: displayName,
purpose,
header,
type,
type
};
const {data} = await createChannel(channel, currentUserId)(dispatch, getState);

View File

@@ -29,16 +29,6 @@ export function addReactionToLatestPost(emoji, rootId) {
export function addRecentEmoji(emoji) {
return {
type: ViewTypes.ADD_RECENT_EMOJI,
emoji,
};
}
export function incrementEmojiPickerPage() {
return async (dispatch) => {
dispatch({
type: ViewTypes.INCREMENT_EMOJI_PICKER_PAGE,
});
return {data: true};
emoji
};
}

View File

@@ -6,6 +6,6 @@ import {ViewTypes} from 'app/constants';
export function addFileToFetchCache(url) {
return {
type: ViewTypes.ADD_FILE_TO_FETCH_CACHE,
url,
url
};
}

View File

@@ -1,14 +1,12 @@
// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import FormData from 'form-data';
import {Platform} from 'react-native';
import {uploadFile} from 'mattermost-redux/actions/files';
import {parseClientIdsFromFormData} from 'mattermost-redux/utils/file_utils';
import {
buildFileUploadData,
encodeHeaderURIStringToUTF8,
generateId,
} from 'app/utils/file';
import {buildFileUploadData, generateId} from 'app/utils/file';
import {ViewTypes} from 'app/constants';
export function handleUploadFiles(files, rootId) {
@@ -28,10 +26,9 @@ export function handleUploadFiles(files, rootId) {
localPath: fileData.uri,
name: fileData.name,
type: fileData.mimeType,
extension: fileData.extension,
extension: fileData.extension
});
fileData.name = encodeHeaderURIStringToUTF8(fileData.name);
formData.append('files', fileData);
formData.append('channel_id', channelId);
formData.append('client_ids', clientId);
@@ -46,11 +43,10 @@ export function handleUploadFiles(files, rootId) {
type: ViewTypes.SET_TEMP_UPLOAD_FILES_FOR_POST_DRAFT,
clientIds,
channelId,
rootId,
rootId
});
const clientIdsArray = clientIds.map((c) => c.clientId);
await uploadFile(channelId, rootId, clientIdsArray, formData, formBoundary)(dispatch, getState);
await uploadFile(channelId, rootId, parseClientIdsFromFormData(formData), formData, formBoundary)(dispatch, getState);
};
}
@@ -60,11 +56,13 @@ export function retryFileUpload(file, rootId) {
const channelId = state.entities.channels.currentChannelId;
const formData = new FormData();
const fileData = buildFileUploadData(file);
fileData.uri = file.localPath;
const fileData = {
uri: file.localPath,
name: file.name,
type: file.type
};
fileData.name = encodeHeaderURIStringToUTF8(fileData.name);
formData.append('files', fileData);
formData.append('channel_id', channelId);
formData.append('client_ids', file.clientId);
@@ -78,7 +76,7 @@ export function retryFileUpload(file, rootId) {
type: ViewTypes.RETRY_UPLOAD_FILE_FOR_POST,
clientId: file.clientId,
channelId,
rootId,
rootId
});
await uploadFile(channelId, rootId, [file.clientId], formData, formBoundary)(dispatch, getState);
@@ -89,7 +87,7 @@ export function handleClearFiles(channelId, rootId) {
return {
type: ViewTypes.CLEAR_FILES_FOR_POST_DRAFT,
channelId,
rootId,
rootId
};
}
@@ -97,7 +95,7 @@ export function handleClearFailedFiles(channelId, rootId) {
return {
type: ViewTypes.CLEAR_FAILED_FILES_FOR_POST_DRAFT,
channelId,
rootId,
rootId
};
}
@@ -106,7 +104,7 @@ export function handleRemoveFile(clientId, channelId, rootId) {
type: ViewTypes.REMOVE_FILE_FROM_POST_DRAFT,
clientId,
channelId,
rootId,
rootId
};
}
@@ -114,6 +112,6 @@ export function handleRemoveLastFile(channelId, rootId) {
return {
type: ViewTypes.REMOVE_LAST_FILE_FROM_POST_DRAFT,
channelId,
rootId,
rootId
};
}

View File

@@ -11,7 +11,7 @@ export function handleLoginIdChanged(loginId) {
return async (dispatch, getState) => {
dispatch({
type: ViewTypes.LOGIN_ID_CHANGED,
loginId,
loginId
}, getState);
};
}
@@ -20,7 +20,7 @@ export function handlePasswordChanged(password) {
return async (dispatch, getState) => {
dispatch({
type: ViewTypes.PASSWORD_CHANGED,
password,
password
}, getState);
};
}
@@ -35,8 +35,8 @@ export function handleSuccessfulLogin() {
type: GeneralTypes.RECEIVED_APP_CREDENTIALS,
data: {
url,
token,
},
token
}
}, getState);
if (config.DataRetentionEnableMessageDeletion && config.DataRetentionEnableMessageDeletion === 'true' &&
@@ -73,5 +73,5 @@ export default {
handleLoginIdChanged,
handlePasswordChanged,
handleSuccessfulLogin,
getSession,
getSession
};

View File

@@ -14,7 +14,7 @@ import {recordTime} from 'app/utils/segment';
import {
handleSelectChannel,
setChannelDisplayName,
retryGetPostsAction,
retryGetPostsAction
} from 'app/actions/views/channel';
export function loadConfigAndLicense() {
@@ -22,7 +22,7 @@ export function loadConfigAndLicense() {
const {currentUserId} = getState().entities.users;
const [configData, licenseData] = await Promise.all([
getClientConfig()(dispatch, getState),
getLicenseConfig()(dispatch, getState),
getLicenseConfig()(dispatch, getState)
]);
const config = configData.data || {};
@@ -56,7 +56,7 @@ export function loadFromPushNotification(notification) {
if (teamId && (!teams[teamId] || !myTeamMembers[teamId])) {
await Promise.all([
getMyTeams()(dispatch, getState),
getMyTeamMembers()(dispatch, getState),
getMyTeamMembers()(dispatch, getState)
]);
}
@@ -95,7 +95,7 @@ export function createPost(post) {
...post,
pending_post_id: pendingPostId,
create_at: timestamp,
update_at: timestamp,
update_at: timestamp
};
return Client4.createPost({...newPost, create_at: 0}).then((payload) => {
@@ -104,10 +104,10 @@ export function createPost(post) {
data: {
order: [],
posts: {
[payload.id]: payload,
},
[payload.id]: payload
}
},
channelId: payload.channel_id,
channelId: payload.channel_id
});
});
};
@@ -124,5 +124,5 @@ export function recordLoadTime(screenName, category) {
export default {
loadConfigAndLicense,
loadFromPushNotification,
purgeOfflineStore,
purgeOfflineStore
};

View File

@@ -7,7 +7,7 @@ export function handleSearchDraftChanged(text) {
return async (dispatch, getState) => {
dispatch({
type: ViewTypes.SEARCH_DRAFT_CHANGED,
text,
text
}, getState);
};
}

View File

@@ -11,11 +11,11 @@ export function handleServerUrlChanged(serverUrl) {
dispatch(batchActions([
{type: GeneralTypes.CLIENT_CONFIG_RESET},
{type: GeneralTypes.CLIENT_LICENSE_RESET},
{type: ViewTypes.SERVER_URL_CHANGED, serverUrl},
{type: ViewTypes.SERVER_URL_CHANGED, serverUrl}
]), getState);
};
}
export default {
handleServerUrlChanged,
handleServerUrlChanged
};

View File

@@ -22,7 +22,7 @@ export function handleTeamChange(teamId, selectChannel = true) {
const actions = [
setChannelDisplayName(''),
{type: TeamTypes.SELECT_TEAM, data: teamId},
{type: TeamTypes.SELECT_TEAM, data: teamId}
];
if (selectChannel) {
@@ -55,5 +55,5 @@ export function selectFirstAvailableTeam() {
export default {
handleTeamChange,
selectFirstAvailableTeam,
selectFirstAvailableTeam
};

View File

@@ -8,7 +8,7 @@ export function handleCommentDraftChanged(rootId, draft) {
dispatch({
type: ViewTypes.COMMENT_DRAFT_CHANGED,
rootId,
draft,
draft
}, getState);
};
}
@@ -17,6 +17,6 @@ export function handleCommentDraftSelectionChanged(rootId, cursorPosition) {
return {
type: ViewTypes.COMMENT_DRAFT_SELECTION_CHANGED,
rootId,
cursorPosition,
cursorPosition
};
}

View File

@@ -1,144 +0,0 @@
// Copyright (c) 2018-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import React, {PureComponent} from 'react';
import PropTypes from 'prop-types';
import {
Alert,
Animated,
StyleSheet,
Text,
TouchableOpacity,
} from 'react-native';
import {intlShape} from 'react-intl';
import MaterialIcons from 'react-native-vector-icons/MaterialIcons';
const {View: AnimatedView} = Animated;
export default class AnnouncementBanner extends PureComponent {
static propTypes = {
actions: PropTypes.shape({
dismissBanner: PropTypes.func.isRequired,
}).isRequired,
allowDismissal: PropTypes.bool,
bannerColor: PropTypes.string,
bannerDismissed: PropTypes.bool,
bannerEnabled: PropTypes.bool,
bannerText: PropTypes.string,
bannerTextColor: PropTypes.string,
};
static contextTypes = {
intl: intlShape,
};
state = {
bannerHeight: new Animated.Value(0),
};
componentWillMount() {
const {bannerDismissed, bannerEnabled, bannerText} = this.props;
const showBanner = bannerEnabled && !bannerDismissed && Boolean(bannerText);
this.toggleBanner(showBanner);
}
componentWillReceiveProps(nextProps) {
if (this.props.bannerText !== nextProps.bannerText ||
this.props.bannerEnabled !== nextProps.bannerEnabled ||
this.props.bannerDismissed !== nextProps.bannerDismissed) {
const showBanner = nextProps.bannerEnabled && !nextProps.bannerDismissed && Boolean(nextProps.bannerText);
this.toggleBanner(showBanner);
}
}
handleDismiss = () => {
const {actions, bannerText} = this.props;
actions.dismissBanner(bannerText);
};
handlePress = () => {
const {formatMessage} = this.context.intl;
const options = [{
text: formatMessage({id: 'mobile.announcement_banner.ok', defaultMessage: 'OK'}),
}];
if (this.props.allowDismissal) {
options.push({
text: formatMessage({id: 'mobile.announcement_banner.dismiss', defaultMessage: 'Dismiss'}),
onPress: this.handleDismiss,
});
}
Alert.alert(
formatMessage({id: 'mobile.announcement_banner.title', defaultMessage: 'Announcement'}),
this.props.bannerText,
options,
{cancelable: false}
);
};
toggleBanner = (show = true) => {
const value = show ? 38 : 0;
Animated.timing(this.state.bannerHeight, {
toValue: value,
duration: 350,
}).start();
};
render() {
const {bannerHeight} = this.state;
const bannerStyle = {
backgroundColor: this.props.bannerColor,
height: bannerHeight,
};
const bannerTextStyle = {
color: this.props.bannerTextColor,
};
return (
<AnimatedView
style={[style.bannerContainer, bannerStyle]}
>
<TouchableOpacity
onPress={this.handlePress}
style={style.wrapper}
>
<Text
ellipsizeMode='tail'
numberOfLines={1}
style={[style.bannerText, bannerTextStyle]}
>
{this.props.bannerText}
</Text>
<MaterialIcons
color={this.props.bannerTextColor}
name='info'
size={16}
/>
</TouchableOpacity>
</AnimatedView>
);
}
}
const style = StyleSheet.create({
bannerContainer: {
paddingHorizontal: 10,
position: 'absolute',
top: 0,
overflow: 'hidden',
width: '100%',
},
wrapper: {
alignItems: 'center',
flex: 1,
flexDirection: 'row',
},
bannerText: {
flex: 1,
fontSize: 14,
marginRight: 5,
},
});

View File

@@ -1,36 +0,0 @@
// Copyright (c) 2018-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import {bindActionCreators} from 'redux';
import {connect} from 'react-redux';
import {getConfig, getLicense} from 'mattermost-redux/selectors/entities/general';
import {dismissBanner} from 'app/actions/views/announcement';
import AnnouncementBanner from './announcement_banner';
function mapStateToProps(state) {
const config = getConfig(state);
const license = getLicense(state);
const {announcement} = state.views;
return {
allowDismissal: config.AllowBannerDismissal === 'true',
bannerColor: config.BannerColor,
bannerDismissed: config.BannerText === announcement,
bannerEnabled: config.EnableBanner === 'true' && license.IsLicensed === 'true',
bannerText: config.BannerText,
bannerTextColor: config.BannerTextColor,
};
}
function mapDispatchToProps(dispatch) {
return {
actions: bindActionCreators({
dismissBanner,
}, dispatch),
};
}
export default connect(mapStateToProps, mapDispatchToProps)(AnnouncementBanner);

View File

@@ -5,14 +5,14 @@ import React, {PureComponent} from 'react';
import PropTypes from 'prop-types';
import Svg, {
G,
Path,
Path
} from 'react-native-svg';
export default class AwayStatus extends PureComponent {
static propTypes = {
width: PropTypes.number.isRequired,
height: PropTypes.number.isRequired,
color: PropTypes.string.isRequired,
color: PropTypes.string.isRequired
};
render() {

View File

@@ -3,7 +3,7 @@
import React from 'react';
import PropTypes from 'prop-types';
import {Clipboard, Platform, Text} from 'react-native';
import {Clipboard, Text} from 'react-native';
import {intlShape} from 'react-intl';
import CustomPropTypes from 'app/constants/custom_prop_types';
@@ -19,11 +19,11 @@ export default class AtMention extends React.PureComponent {
onPostPress: PropTypes.func,
textStyle: CustomPropTypes.Style,
theme: PropTypes.object.isRequired,
usersByUsername: PropTypes.object.isRequired,
usersByUsername: PropTypes.object.isRequired
};
static contextTypes = {
intl: intlShape,
intl: intlShape
}
constructor(props) {
@@ -32,7 +32,7 @@ export default class AtMention extends React.PureComponent {
const userDetails = this.getUserDetailsFromMentionName(props);
this.state = {
username: userDetails.username,
id: userDetails.id,
id: userDetails.id
};
}
@@ -41,7 +41,7 @@ export default class AtMention extends React.PureComponent {
const userDetails = this.getUserDetailsFromMentionName(nextProps);
this.setState({
username: userDetails.username,
id: userDetails.id,
id: userDetails.id
});
}
}
@@ -49,27 +49,22 @@ export default class AtMention extends React.PureComponent {
goToUserProfile = () => {
const {navigator, theme} = this.props;
const {intl} = this.context;
const options = {
navigator.push({
screen: 'UserProfile',
title: intl.formatMessage({id: 'mobile.routes.user_profile', defaultMessage: 'Profile'}),
animated: true,
backButtonTitle: '',
passProps: {
userId: this.state.id,
userId: this.state.id
},
navigatorStyle: {
navBarTextColor: theme.sidebarHeaderTextColor,
navBarBackgroundColor: theme.sidebarHeaderBg,
navBarButtonColor: theme.sidebarHeaderTextColor,
screenBackgroundColor: theme.centerChannelBg,
},
};
if (Platform.OS === 'ios') {
navigator.push(options);
} else {
navigator.showModal(options);
}
screenBackgroundColor: theme.centerChannelBg
}
});
};
getUserDetailsFromMentionName(props) {
@@ -80,7 +75,7 @@ export default class AtMention extends React.PureComponent {
const user = props.usersByUsername[mentionName];
return {
username: user.username,
id: user.id,
id: user.id
};
}
@@ -93,7 +88,7 @@ export default class AtMention extends React.PureComponent {
}
return {
username: '',
username: ''
};
}
@@ -107,9 +102,9 @@ export default class AtMention extends React.PureComponent {
action = {
text: intl.formatMessage({
id: 'mobile.mention.copy_mention',
defaultMessage: 'Copy Mention',
defaultMessage: 'Copy Mention'
}),
onPress: this.handleCopyMention,
onPress: this.handleCopyMention
};
}

View File

@@ -12,7 +12,7 @@ import AtMention from './at_mention';
function mapStateToProps(state) {
return {
theme: getTheme(state),
usersByUsername: getUsersByUsername(state),
usersByUsername: getUsersByUsername(state)
};
}

View File

@@ -2,16 +2,13 @@ import React, {PureComponent} from 'react';
import PropTypes from 'prop-types';
import {injectIntl, intlShape} from 'react-intl';
import {
Alert,
Platform,
StyleSheet,
TouchableOpacity,
TouchableOpacity
} from 'react-native';
import Icon from 'react-native-vector-icons/Ionicons';
import ImagePicker from 'react-native-image-picker';
import Permissions from 'react-native-permissions';
import {PermissionTypes} from 'app/constants';
import {changeOpacity} from 'app/utils/theme';
class AttachmentButton extends PureComponent {
@@ -25,50 +22,46 @@ class AttachmentButton extends PureComponent {
onShowFileMaxWarning: PropTypes.func,
theme: PropTypes.object.isRequired,
uploadFiles: PropTypes.func.isRequired,
wrapper: PropTypes.bool,
wrapper: PropTypes.bool
};
static defaultProps = {
maxFileCount: 5,
maxFileCount: 5
};
attachFileFromCamera = async () => {
attachFileFromCamera = () => {
const {formatMessage} = this.props.intl;
const options = {
quality: 1.0,
noData: true,
storageOptions: {
cameraRoll: true,
waitUntilSaved: true,
waitUntilSaved: true
},
permissionDenied: {
title: formatMessage({
id: 'mobile.android.camera_permission_denied_title',
defaultMessage: 'Camera access is required',
defaultMessage: 'Camera access is required'
}),
text: formatMessage({
id: 'mobile.android.camera_permission_denied_description',
defaultMessage: 'To take photos and videos with your camera, please change your permission settings.',
defaultMessage: 'To take photos and videos with your camera, please change your permission settings.'
}),
reTryTitle: formatMessage({
id: 'mobile.android.permission_denied_retry',
defaultMessage: 'Set Permission',
defaultMessage: 'Set Permission'
}),
okTitle: formatMessage({id: 'mobile.android.permission_denied_dismiss', defaultMessage: 'Dismiss'}),
},
okTitle: formatMessage({id: 'mobile.android.permission_denied_dismiss', defaultMessage: 'Dismiss'})
}
};
const hasPhotoPermission = await this.hasPhotoPermission();
ImagePicker.launchCamera(options, (response) => {
if (response.error || response.didCancel) {
return;
}
if (hasPhotoPermission) {
ImagePicker.launchCamera(options, (response) => {
if (response.error || response.didCancel) {
return;
}
this.uploadFiles([response]);
});
}
this.uploadFiles([response]);
});
};
attachFileFromLibrary = () => {
@@ -79,18 +72,18 @@ class AttachmentButton extends PureComponent {
permissionDenied: {
title: formatMessage({
id: 'mobile.android.photos_permission_denied_title',
defaultMessage: 'Photo library access is required',
defaultMessage: 'Photo library access is required'
}),
text: formatMessage({
id: 'mobile.android.photos_permission_denied_description',
defaultMessage: 'To upload images from your library, please change your permission settings.',
defaultMessage: 'To upload images from your library, please change your permission settings.'
}),
reTryTitle: formatMessage({
id: 'mobile.android.permission_denied_retry',
defaultMessage: 'Set Permission',
defaultMessage: 'Set Permission'
}),
okTitle: formatMessage({id: 'mobile.android.permission_denied_dismiss', defaultMessage: 'Dismiss'}),
},
okTitle: formatMessage({id: 'mobile.android.permission_denied_dismiss', defaultMessage: 'Dismiss'})
}
};
if (Platform.OS === 'ios') {
@@ -115,18 +108,18 @@ class AttachmentButton extends PureComponent {
permissionDenied: {
title: formatMessage({
id: 'mobile.android.videos_permission_denied_title',
defaultMessage: 'Video library access is required',
defaultMessage: 'Video library access is required'
}),
text: formatMessage({
id: 'mobile.android.videos_permission_denied_description',
defaultMessage: 'To upload videos from your library, please change your permission settings.',
defaultMessage: 'To upload videos from your library, please change your permission settings.'
}),
reTryTitle: formatMessage({
id: 'mobile.android.permission_denied_retry',
defaultMessage: 'Set Permission',
defaultMessage: 'Set Permission'
}),
okTitle: formatMessage({id: 'mobile.android.permission_denied_dismiss', defaultMessage: 'Dismiss'}),
},
okTitle: formatMessage({id: 'mobile.android.permission_denied_dismiss', defaultMessage: 'Dismiss'})
}
};
ImagePicker.launchImageLibrary(options, (response) => {
@@ -138,66 +131,13 @@ class AttachmentButton extends PureComponent {
});
};
hasPhotoPermission = async () => {
if (Platform.OS === 'ios') {
const {formatMessage} = this.props.intl;
let permissionRequest;
const hasPermissionToStorage = await Permissions.check('photo');
switch (hasPermissionToStorage) {
case PermissionTypes.UNDETERMINED:
permissionRequest = await Permissions.request('photo');
if (permissionRequest !== PermissionTypes.AUTHORIZED) {
return false;
}
break;
case PermissionTypes.DENIED: {
const canOpenSettings = await Permissions.canOpenSettings();
let grantOption = null;
if (canOpenSettings) {
grantOption = {
text: formatMessage({
id: 'mobile.android.permission_denied_retry',
defaultMessage: 'Set permission',
}),
onPress: () => Permissions.openSettings(),
};
}
Alert.alert(
formatMessage({
id: 'mobile.android.photos_permission_denied_title',
defaultMessage: 'Photo library access is required',
}),
formatMessage({
id: 'mobile.android.photos_permission_denied_description',
defaultMessage: 'To upload images from your library, please change your permission settings.',
}),
[
grantOption,
{
text: formatMessage({
id: 'mobile.android.permission_denied_dismiss',
defaultMessage: 'Dismiss',
}),
},
]
);
return false;
}
}
}
return true;
};
uploadFiles = (images) => {
this.props.uploadFiles(images);
};
handleFileAttachmentOption = (action) => {
this.props.navigator.dismissModal({
animationType: 'none',
animationType: 'none'
});
// Have to wait to launch the library attachment action.
@@ -224,17 +164,17 @@ class AttachmentButton extends PureComponent {
action: () => this.handleFileAttachmentOption(this.attachFileFromCamera),
text: {
id: 'mobile.file_upload.camera',
defaultMessage: 'Take Photo or Video',
defaultMessage: 'Take Photo or Video'
},
icon: 'camera',
icon: 'camera'
}, {
action: () => this.handleFileAttachmentOption(this.attachFileFromLibrary),
text: {
id: 'mobile.file_upload.library',
defaultMessage: 'Photo Library',
defaultMessage: 'Photo Library'
},
icon: 'photo',
}],
icon: 'photo'
}]
};
if (Platform.OS === 'android') {
@@ -242,9 +182,9 @@ class AttachmentButton extends PureComponent {
action: () => this.handleFileAttachmentOption(this.attachVideoFromLibraryAndroid),
text: {
id: 'mobile.file_upload.video',
defaultMessage: 'Video Library',
defaultMessage: 'Video Library'
},
icon: 'file-video-o',
icon: 'file-video-o'
});
}
@@ -253,15 +193,15 @@ class AttachmentButton extends PureComponent {
title: '',
animationType: 'none',
passProps: {
items: options.items,
items: options.items
},
navigatorStyle: {
navBarHidden: true,
statusBarHidden: false,
statusBarHideWithNavBar: false,
screenBackgroundColor: 'transparent',
modalPresentationStyle: 'overCurrentContext',
},
modalPresentationStyle: 'overCurrentContext'
}
});
};
@@ -298,18 +238,18 @@ const style = StyleSheet.create({
attachIcon: {
marginTop: Platform.select({
ios: 2,
android: 0,
}),
android: 0
})
},
buttonContainer: {
height: Platform.select({
ios: 34,
android: 36,
android: 36
}),
width: 45,
alignItems: 'center',
justifyContent: 'center',
},
justifyContent: 'center'
}
});
export default injectIntl(AttachmentButton);

View File

@@ -17,7 +17,7 @@ import {makeStyleSheetFromTheme} from 'app/utils/theme';
export default class AtMention extends PureComponent {
static propTypes = {
actions: PropTypes.shape({
autocompleteUsers: PropTypes.func.isRequired,
autocompleteUsers: PropTypes.func.isRequired
}).isRequired,
currentChannelId: PropTypes.string,
currentTeamId: PropTypes.string.isRequired,
@@ -33,20 +33,20 @@ export default class AtMention extends PureComponent {
requestStatus: PropTypes.string.isRequired,
teamMembers: PropTypes.array,
theme: PropTypes.object.isRequired,
value: PropTypes.string,
value: PropTypes.string
};
static defaultProps = {
defaultChannel: {},
isSearch: false,
value: '',
value: ''
};
constructor(props) {
super(props);
this.state = {
sections: [],
sections: []
};
}
@@ -56,7 +56,7 @@ export default class AtMention extends PureComponent {
// if the term changes but is null or the mention has been completed we render this component as null
this.setState({
mentionComplete: false,
sections: [],
sections: []
});
this.props.onResultCountChange(0);
@@ -84,7 +84,7 @@ export default class AtMention extends PureComponent {
id: 'mobile.suggestion.members',
defaultMessage: 'Members',
data: teamMembers,
key: 'teamMembers',
key: 'teamMembers'
});
} else {
if (inChannel.length) {
@@ -92,7 +92,7 @@ export default class AtMention extends PureComponent {
id: 'suggestion.mention.members',
defaultMessage: 'Channel Members',
data: inChannel,
key: 'inChannel',
key: 'inChannel'
});
}
@@ -102,7 +102,7 @@ export default class AtMention extends PureComponent {
defaultMessage: 'Special Mentions',
data: this.getSpecialMentions(),
key: 'special',
renderItem: this.renderSpecialMentions,
renderItem: this.renderSpecialMentions
});
}
@@ -111,13 +111,13 @@ export default class AtMention extends PureComponent {
id: 'suggestion.mention.nonmembers',
defaultMessage: 'Not in Channel',
data: outChannel,
key: 'outChannel',
key: 'outChannel'
});
}
}
this.setState({
sections,
sections
});
this.props.onResultCountChange(sections.reduce((total, section) => total + section.data.length, 0));
@@ -134,16 +134,16 @@ export default class AtMention extends PureComponent {
id: 'suggestion.mention.all',
defaultMessage: 'Notifies everyone in the channel, use in {townsquare} to notify the whole team',
values: {
townsquare: this.props.defaultChannel.display_name,
},
townsquare: this.props.defaultChannel.display_name
}
}, {
completeHandle: 'channel',
id: 'suggestion.mention.channel',
defaultMessage: 'Notifies everyone in the channel',
defaultMessage: 'Notifies everyone in the channel'
}, {
completeHandle: 'here',
id: 'suggestion.mention.here',
defaultMessage: 'Notifies everyone in the channel and online',
defaultMessage: 'Notifies everyone in the channel and online'
}];
};
@@ -232,10 +232,10 @@ export default class AtMention extends PureComponent {
const getStyleFromTheme = makeStyleSheetFromTheme((theme) => {
return {
listView: {
backgroundColor: theme.centerChannelBg,
backgroundColor: theme.centerChannelBg
},
search: {
minHeight: 125,
},
minHeight: 125
}
};
});

View File

@@ -12,7 +12,7 @@ import {
filterMembersInChannel,
filterMembersNotInChannel,
filterMembersInCurrentTeam,
getMatchTermForAtMention,
getMatchTermForAtMention
} from 'app/selectors/autocomplete';
import {getTheme} from 'mattermost-redux/selectors/entities/preferences';
@@ -44,15 +44,15 @@ function mapStateToProps(state, ownProps) {
inChannel,
outChannel,
requestStatus: state.requests.users.autocompleteUsers.status,
theme: getTheme(state),
theme: getTheme(state)
};
}
function mapDispatchToProps(dispatch) {
return {
actions: bindActionCreators({
autocompleteUsers,
}, dispatch),
autocompleteUsers
}, dispatch)
};
}

View File

@@ -6,7 +6,7 @@ import PropTypes from 'prop-types';
import {
Text,
TouchableOpacity,
View,
View
} from 'react-native';
import ProfilePicture from 'app/components/profile_picture';
@@ -19,7 +19,7 @@ export default class AtMentionItem extends PureComponent {
onPress: PropTypes.func.isRequired,
userId: PropTypes.string.isRequired,
username: PropTypes.string,
theme: PropTypes.object.isRequired,
theme: PropTypes.object.isRequired
};
completeMention = () => {
@@ -33,7 +33,7 @@ export default class AtMentionItem extends PureComponent {
lastName,
userId,
username,
theme,
theme
} = this.props;
const style = getStyleFromTheme(theme);
@@ -67,21 +67,21 @@ const getStyleFromTheme = makeStyleSheetFromTheme((theme) => {
paddingVertical: 8,
flexDirection: 'row',
alignItems: 'center',
backgroundColor: theme.centerChannelBg,
backgroundColor: theme.centerChannelBg
},
rowPicture: {
marginHorizontal: 8,
width: 20,
alignItems: 'center',
justifyContent: 'center',
justifyContent: 'center'
},
rowUsername: {
fontSize: 13,
color: theme.centerChannelColor,
color: theme.centerChannelColor
},
rowFullname: {
color: theme.centerChannelColor,
opacity: 0.6,
},
opacity: 0.6
}
};
});

View File

@@ -16,7 +16,7 @@ function mapStateToProps(state, ownProps) {
firstName: user.first_name,
lastName: user.last_name,
username: user.username,
theme: getTheme(state),
theme: getTheme(state)
};
}

View File

@@ -6,7 +6,7 @@ import PropTypes from 'prop-types';
import {
Keyboard,
Platform,
View,
View
} from 'react-native';
import DeviceInfo from 'react-native-device-info';
@@ -24,11 +24,11 @@ export default class Autocomplete extends PureComponent {
rootId: PropTypes.string,
isSearch: PropTypes.bool,
theme: PropTypes.object.isRequired,
value: PropTypes.string,
value: PropTypes.string
};
static defaultProps = {
isSearch: false,
isSearch: false
};
state = {
@@ -37,12 +37,12 @@ export default class Autocomplete extends PureComponent {
channelMentionCount: 0,
emojiCount: 0,
commandCount: 0,
keyboardOffset: 0,
keyboardOffset: 0
};
handleSelectionChange = (event) => {
this.setState({
cursorPosition: event.nativeEvent.selection.end,
cursorPosition: event.nativeEvent.selection.end
});
};
@@ -147,34 +147,34 @@ const getStyleFromTheme = makeStyleSheetFromTheme((theme) => {
left: 0,
overflow: 'hidden',
position: 'absolute',
right: 0,
right: 0
},
borders: {
borderWidth: 1,
borderColor: changeOpacity(theme.centerChannelColor, 0.2),
borderBottomWidth: 0,
borderBottomWidth: 0
},
bordersSearch: {
borderWidth: 1,
borderColor: changeOpacity(theme.centerChannelColor, 0.2),
borderColor: changeOpacity(theme.centerChannelColor, 0.2)
},
container: {
bottom: 0,
maxHeight: 200,
maxHeight: 200
},
content: {
flex: 1,
flex: 1
},
searchContainer: {
flex: 1,
...Platform.select({
android: {
top: 46,
top: 46
},
ios: {
top: 44,
},
}),
},
top: 44
}
})
}
};
});

View File

@@ -9,7 +9,7 @@ import {makeStyleSheetFromTheme, changeOpacity} from 'app/utils/theme';
export default class AutocompleteDivider extends PureComponent {
static propTypes = {
theme: PropTypes.object.isRequired,
theme: PropTypes.object.isRequired
};
render() {
@@ -26,7 +26,7 @@ const getStyleFromTheme = makeStyleSheetFromTheme((theme) => {
return {
divider: {
height: 1,
backgroundColor: changeOpacity(theme.centerChannelColor, 0.2),
},
backgroundColor: changeOpacity(theme.centerChannelColor, 0.2)
}
};
});

View File

@@ -9,7 +9,7 @@ import AutocompleteDivider from './autocomplete_divider';
function mapStateToProps(state) {
return {
theme: getTheme(state),
theme: getTheme(state)
};
}

View File

@@ -12,7 +12,7 @@ export default class AutocompleteSectionHeader extends PureComponent {
static propTypes = {
defaultMessage: PropTypes.string.isRequired,
id: PropTypes.string.isRequired,
theme: PropTypes.object.isRequired,
theme: PropTypes.object.isRequired
};
render() {
@@ -40,15 +40,15 @@ const getStyleFromTheme = makeStyleSheetFromTheme((theme) => {
paddingLeft: 8,
backgroundColor: changeOpacity(theme.centerChannelColor, 0.1),
borderTopWidth: 1,
borderTopColor: changeOpacity(theme.centerChannelColor, 0.2),
borderTopColor: changeOpacity(theme.centerChannelColor, 0.2)
},
sectionText: {
fontSize: 12,
color: changeOpacity(theme.centerChannelColor, 0.7),
paddingVertical: 7,
paddingVertical: 7
},
sectionWrapper: {
backgroundColor: theme.centerChannelBg,
},
backgroundColor: theme.centerChannelBg
}
};
});

View File

@@ -16,7 +16,7 @@ import {makeStyleSheetFromTheme} from 'app/utils/theme';
export default class ChannelMention extends PureComponent {
static propTypes = {
actions: PropTypes.shape({
searchChannels: PropTypes.func.isRequired,
searchChannels: PropTypes.func.isRequired
}).isRequired,
currentTeamId: PropTypes.string.isRequired,
cursorPosition: PropTypes.number.isRequired,
@@ -31,19 +31,19 @@ export default class ChannelMention extends PureComponent {
publicChannels: PropTypes.array,
requestStatus: PropTypes.string.isRequired,
theme: PropTypes.object.isRequired,
value: PropTypes.string,
value: PropTypes.string
};
static defaultProps = {
isSearch: false,
value: '',
value: ''
};
constructor(props) {
super(props);
this.state = {
sections: [],
sections: []
};
}
@@ -54,7 +54,7 @@ export default class ChannelMention extends PureComponent {
// if the term changes but is null or the mention has been completed we render this component as null
this.setState({
mentionComplete: false,
sections: [],
sections: []
});
this.props.onResultCountChange(0);
@@ -83,7 +83,7 @@ export default class ChannelMention extends PureComponent {
id: 'suggestion.search.public',
defaultMessage: 'Public Channels',
data: publicChannels,
key: 'publicChannels',
key: 'publicChannels'
});
}
@@ -92,7 +92,7 @@ export default class ChannelMention extends PureComponent {
id: 'suggestion.search.private',
defaultMessage: 'Private Channels',
data: privateChannels,
key: 'privateChannels',
key: 'privateChannels'
});
}
} else {
@@ -101,7 +101,7 @@ export default class ChannelMention extends PureComponent {
id: 'suggestion.mention.channels',
defaultMessage: 'My Channels',
data: myChannels,
key: 'myChannels',
key: 'myChannels'
});
}
@@ -110,13 +110,13 @@ export default class ChannelMention extends PureComponent {
id: 'suggestion.mention.morechannels',
defaultMessage: 'Other Channels',
data: otherChannels,
key: 'otherChannels',
key: 'otherChannels'
});
}
}
this.setState({
sections,
sections
});
this.props.onResultCountChange(sections.reduce((total, section) => total + section.data.length, 0));
@@ -196,10 +196,10 @@ export default class ChannelMention extends PureComponent {
const getStyleFromTheme = makeStyleSheetFromTheme((theme) => {
return {
listView: {
backgroundColor: theme.centerChannelBg,
backgroundColor: theme.centerChannelBg
},
search: {
minHeight: 125,
},
minHeight: 125
}
};
});

View File

@@ -12,7 +12,7 @@ import {
filterOtherChannels,
filterPublicChannels,
filterPrivateChannels,
getMatchTermForChannelMention,
getMatchTermForChannelMention
} from 'app/selectors/autocomplete';
import {getTheme} from 'mattermost-redux/selectors/entities/preferences';
@@ -44,15 +44,15 @@ function mapStateToProps(state, ownProps) {
currentTeamId: getCurrentTeamId(state),
matchTerm,
requestStatus: state.requests.channels.getChannels.status,
theme: getTheme(state),
theme: getTheme(state)
};
}
function mapDispatchToProps(dispatch) {
return {
actions: bindActionCreators({
searchChannels,
}, dispatch),
searchChannels
}, dispatch)
};
}

View File

@@ -5,7 +5,7 @@ import React, {PureComponent} from 'react';
import PropTypes from 'prop-types';
import {
Text,
TouchableOpacity,
TouchableOpacity
} from 'react-native';
import {makeStyleSheetFromTheme} from 'app/utils/theme';
@@ -16,7 +16,7 @@ export default class ChannelMentionItem extends PureComponent {
displayName: PropTypes.string,
name: PropTypes.string,
onPress: PropTypes.func.isRequired,
theme: PropTypes.object.isRequired,
theme: PropTypes.object.isRequired
};
completeMention = () => {
@@ -29,7 +29,7 @@ export default class ChannelMentionItem extends PureComponent {
channelId,
displayName,
name,
theme,
theme
} = this.props;
const style = getStyleFromTheme(theme);
@@ -53,15 +53,15 @@ const getStyleFromTheme = makeStyleSheetFromTheme((theme) => {
padding: 8,
flexDirection: 'row',
alignItems: 'center',
backgroundColor: theme.centerChannelBg,
backgroundColor: theme.centerChannelBg
},
rowDisplayName: {
fontSize: 13,
color: theme.centerChannelColor,
color: theme.centerChannelColor
},
rowName: {
color: theme.centerChannelColor,
opacity: 0.6,
},
opacity: 0.6
}
};
});

View File

@@ -15,7 +15,7 @@ function mapStateToProps(state, ownProps) {
return {
displayName: channel.display_name,
name: channel.name,
theme: getTheme(state),
theme: getTheme(state)
};
}

View File

@@ -7,11 +7,9 @@ import {
FlatList,
Text,
TouchableOpacity,
View,
View
} from 'react-native';
import {isMinimumServerVersion} from 'mattermost-redux/utils/helpers';
import AutocompleteDivider from 'app/components/autocomplete/autocomplete_divider';
import Emoji from 'app/components/emoji';
import {makeStyleSheetFromTheme} from 'app/utils/theme';
@@ -22,8 +20,7 @@ const EMOJI_REGEX_WITHOUT_PREFIX = /\B(:([^:\s]*))$/i;
export default class EmojiSuggestion extends Component {
static propTypes = {
actions: PropTypes.shape({
addReactionToLatestPost: PropTypes.func.isRequired,
autocompleteCustomEmojis: PropTypes.func.isRequired,
addReactionToLatestPost: PropTypes.func.isRequired
}).isRequired,
cursorPosition: PropTypes.number,
emojis: PropTypes.array.isRequired,
@@ -33,26 +30,19 @@ export default class EmojiSuggestion extends Component {
onChangeText: PropTypes.func.isRequired,
onResultCountChange: PropTypes.func.isRequired,
rootId: PropTypes.string,
value: PropTypes.string,
serverVersion: PropTypes.string,
value: PropTypes.string
};
static defaultProps = {
defaultChannel: {},
value: '',
value: ''
};
state = {
active: false,
dataSource: [],
dataSource: []
};
constructor(props) {
super(props);
this.matchTerm = '';
}
componentWillReceiveProps(nextProps) {
if (nextProps.isSearch) {
return;
@@ -64,7 +54,8 @@ export default class EmojiSuggestion extends Component {
if (!match || this.state.emojiComplete) {
this.setState({
active: false,
emojiComplete: false,
matchTerm: null,
emojiComplete: false
});
this.props.onResultCountChange(0);
@@ -72,33 +63,18 @@ export default class EmojiSuggestion extends Component {
return;
}
const oldMatchTerm = this.matchTerm;
this.matchTerm = match[3] || '';
const matchTerm = match[3];
// If we're server version 4.7 or higher
if (isMinimumServerVersion(this.props.serverVersion, 4, 7)) {
if (this.matchTerm !== oldMatchTerm && this.matchTerm.length) {
this.props.actions.autocompleteCustomEmojis(this.matchTerm);
return;
}
if (this.props.emojis !== nextProps.emojis) {
this.handleFuzzySearch(this.matchTerm, nextProps);
} else if (!this.matchTerm.length) {
const initialEmojis = [...nextProps.emojis];
initialEmojis.splice(0, 300);
const data = initialEmojis.sort();
this.setEmojiData(data);
}
return;
const matchTermChanged = matchTerm !== this.state.matchTerm;
if (matchTermChanged) {
this.setState({
matchTerm
});
}
// If we're server version 4.6 or lower
if (this.matchTerm !== oldMatchTerm) {
this.handleFuzzySearch(this.matchTerm, nextProps);
} else if (!this.matchTerm.length) {
if (matchTermChanged) {
this.handleFuzzySearch(matchTerm, nextProps);
} else if (!matchTerm.length) {
const initialEmojis = [...nextProps.emojis];
initialEmojis.splice(0, 300);
const data = initialEmojis.sort();
@@ -118,7 +94,7 @@ export default class EmojiSuggestion extends Component {
setEmojiData = (data) => {
this.setState({
active: data.length > 0,
dataSource: data,
dataSource: data
});
this.props.onResultCountChange(data.length);
@@ -143,7 +119,7 @@ export default class EmojiSuggestion extends Component {
this.setState({
active: false,
emojiComplete: true,
emojiComplete: true
});
};
@@ -198,22 +174,22 @@ export default class EmojiSuggestion extends Component {
const getStyleFromTheme = makeStyleSheetFromTheme((theme) => {
return {
emoji: {
marginRight: 5,
marginRight: 5
},
emojiName: {
fontSize: 13,
color: theme.centerChannelColor,
color: theme.centerChannelColor
},
listView: {
flex: 1,
backgroundColor: theme.centerChannelBg,
backgroundColor: theme.centerChannelBg
},
row: {
height: 40,
flexDirection: 'row',
alignItems: 'center',
paddingHorizontal: 8,
backgroundColor: theme.centerChannelBg,
},
backgroundColor: theme.centerChannelBg
}
};
});

View File

@@ -6,8 +6,6 @@ import {createSelector} from 'reselect';
import {bindActionCreators} from 'redux';
import {getCustomEmojisByName} from 'mattermost-redux/selectors/entities/emojis';
import {autocompleteCustomEmojis} from 'mattermost-redux/actions/emojis';
import {Client4} from 'mattermost-redux/client';
import {addReactionToLatestPost} from 'app/actions/views/emoji';
import {getTheme} from 'mattermost-redux/selectors/entities/preferences';
@@ -35,7 +33,7 @@ function mapStateToProps(state) {
location: 0,
distance: 100,
minMatchCharLength: 2,
maxPatternLength: 32,
maxPatternLength: 32
};
const emojis = getEmojisByName(state);
@@ -45,17 +43,15 @@ function mapStateToProps(state) {
return {
fuse,
emojis,
theme: getTheme(state),
serverVersion: state.entities.general.serverVersion || Client4.getServerVersion(),
theme: getTheme(state)
};
}
function mapDispatchToProps(dispatch) {
return {
actions: bindActionCreators({
addReactionToLatestPost,
autocompleteCustomEmojis,
}, dispatch),
addReactionToLatestPost
}, dispatch)
};
}

View File

@@ -13,7 +13,7 @@ function mapStateToProps(state) {
const {deviceHeight} = getDimensions(state);
return {
deviceHeight,
theme: getTheme(state),
theme: getTheme(state)
};
}

View File

@@ -30,15 +30,15 @@ function mapStateToProps(state) {
commands: mobileCommandsSelector(state),
commandsRequest: state.requests.integrations.getAutocompleteCommands,
currentTeamId: getCurrentTeamId(state),
theme: getTheme(state),
theme: getTheme(state)
};
}
function mapDispatchToProps(dispatch) {
return {
actions: bindActionCreators({
getAutocompleteCommands,
}, dispatch),
getAutocompleteCommands
}, dispatch)
};
}

View File

@@ -4,7 +4,7 @@
import React, {Component} from 'react';
import PropTypes from 'prop-types';
import {
FlatList,
FlatList
} from 'react-native';
import {RequestStatus} from 'mattermost-redux/constants';
@@ -19,7 +19,7 @@ const TIME_BEFORE_NEXT_COMMAND_REQUEST = 1000 * 60 * 5;
export default class SlashSuggestion extends Component {
static propTypes = {
actions: PropTypes.shape({
getAutocompleteCommands: PropTypes.func.isRequired,
getAutocompleteCommands: PropTypes.func.isRequired
}).isRequired,
currentTeamId: PropTypes.string.isRequired,
commands: PropTypes.array,
@@ -28,19 +28,19 @@ export default class SlashSuggestion extends Component {
theme: PropTypes.object.isRequired,
onChangeText: PropTypes.func.isRequired,
onResultCountChange: PropTypes.func.isRequired,
value: PropTypes.string,
value: PropTypes.string
};
static defaultProps = {
defaultChannel: {},
value: '',
value: ''
};
state = {
active: false,
suggestionComplete: false,
dataSource: [],
lastCommandRequest: 0,
lastCommandRequest: 0
};
componentWillReceiveProps(nextProps) {
@@ -53,12 +53,12 @@ export default class SlashSuggestion extends Component {
commands: nextCommands,
commandsRequest: nextCommandsRequest,
currentTeamId: nextTeamId,
value: nextValue,
value: nextValue
} = nextProps;
if (currentTeamId !== nextTeamId) {
this.setState({
lastCommandRequest: 0,
lastCommandRequest: 0
});
}
@@ -68,7 +68,7 @@ export default class SlashSuggestion extends Component {
this.setState({
active: false,
matchTerm: null,
suggestionComplete: false,
suggestionComplete: false
});
this.props.onResultCountChange(0);
return;
@@ -79,7 +79,7 @@ export default class SlashSuggestion extends Component {
if ((!nextCommands.length || dataIsStale) && nextCommandsRequest.status !== RequestStatus.STARTED) {
this.props.actions.getAutocompleteCommands(nextProps.currentTeamId);
this.setState({
lastCommandRequest: Date.now(),
lastCommandRequest: Date.now()
});
}
@@ -89,7 +89,7 @@ export default class SlashSuggestion extends Component {
this.setState({
active: data.length,
dataSource: data,
dataSource: data
});
this.props.onResultCountChange(data.length);
@@ -116,7 +116,7 @@ export default class SlashSuggestion extends Component {
this.setState({
active: false,
suggestionComplete: true,
suggestionComplete: true
});
};
@@ -161,7 +161,7 @@ const getStyleFromTheme = makeStyleSheetFromTheme((theme) => {
return {
listView: {
flex: 1,
backgroundColor: theme.centerChannelBg,
},
backgroundColor: theme.centerChannelBg
}
};
});

View File

@@ -5,7 +5,7 @@ import React, {PureComponent} from 'react';
import PropTypes from 'prop-types';
import {
Text,
TouchableOpacity,
TouchableOpacity
} from 'react-native';
import {changeOpacity, makeStyleSheetFromTheme} from 'app/utils/theme';
@@ -17,7 +17,7 @@ export default class SlashSuggestionItem extends PureComponent {
hint: PropTypes.string,
onPress: PropTypes.func.isRequired,
theme: PropTypes.object.isRequired,
trigger: PropTypes.string,
trigger: PropTypes.string
};
completeSuggestion = () => {
@@ -31,7 +31,7 @@ export default class SlashSuggestionItem extends PureComponent {
description,
hint,
theme,
trigger,
trigger
} = this.props;
const style = getStyleFromTheme(theme);
@@ -60,24 +60,24 @@ const getStyleFromTheme = makeStyleSheetFromTheme((theme) => {
borderLeftWidth: 1,
borderLeftColor: changeOpacity(theme.centerChannelColor, 0.2),
borderRightWidth: 1,
borderRightColor: changeOpacity(theme.centerChannelColor, 0.2),
borderRightColor: changeOpacity(theme.centerChannelColor, 0.2)
},
rowDisplayName: {
fontSize: 13,
color: theme.centerChannelColor,
color: theme.centerChannelColor
},
rowName: {
color: theme.centerChannelColor,
opacity: 0.6,
opacity: 0.6
},
suggestionDescription: {
fontSize: 11,
color: changeOpacity(theme.centerChannelColor, 0.6),
color: changeOpacity(theme.centerChannelColor, 0.6)
},
suggestionName: {
fontSize: 13,
color: theme.centerChannelColor,
marginBottom: 5,
},
marginBottom: 5
}
};
});

View File

@@ -6,7 +6,7 @@ import PropTypes from 'prop-types';
import {
Text,
TouchableOpacity,
View,
View
} from 'react-native';
import Icon from 'react-native-vector-icons/FontAwesome';
@@ -20,7 +20,7 @@ export default class SpecialMentionItem extends PureComponent {
id: PropTypes.string.isRequired,
onPress: PropTypes.func.isRequired,
theme: PropTypes.object.isRequired,
values: PropTypes.object,
values: PropTypes.object
};
completeMention = () => {
@@ -34,7 +34,7 @@ export default class SpecialMentionItem extends PureComponent {
id,
completeHandle,
theme,
values,
values
} = this.props;
const style = getStyleFromTheme(theme);
@@ -70,31 +70,31 @@ const getStyleFromTheme = makeStyleSheetFromTheme((theme) => {
paddingVertical: 8,
flexDirection: 'row',
alignItems: 'center',
backgroundColor: theme.centerChannelBg,
backgroundColor: theme.centerChannelBg
},
rowPicture: {
marginHorizontal: 8,
width: 20,
alignItems: 'center',
justifyContent: 'center',
justifyContent: 'center'
},
rowIcon: {
color: changeOpacity(theme.centerChannelColor, 0.7),
fontSize: 14,
fontSize: 14
},
rowUsername: {
fontSize: 13,
color: theme.centerChannelColor,
color: theme.centerChannelColor
},
rowFullname: {
color: theme.centerChannelColor,
flex: 1,
opacity: 0.6,
opacity: 0.6
},
textWrapper: {
flex: 1,
flexWrap: 'wrap',
paddingRight: 8,
},
paddingRight: 8
}
};
});

View File

@@ -9,14 +9,14 @@ import {
Text,
TouchableWithoutFeedback,
View,
ViewPropTypes,
ViewPropTypes
} from 'react-native';
export default class Badge extends PureComponent {
static defaultProps = {
extraPaddingHorizontal: 10,
minHeight: 0,
minWidth: 0,
minWidth: 0
};
static propTypes = {
@@ -26,7 +26,7 @@ export default class Badge extends PureComponent {
countStyle: Text.propTypes.style,
minHeight: PropTypes.number,
minWidth: PropTypes.number,
onPress: PropTypes.func,
onPress: PropTypes.func
};
constructor(props) {
@@ -42,7 +42,7 @@ export default class Badge extends PureComponent {
onMoveShouldSetPanResponder: () => true,
onStartShouldSetResponderCapture: () => true,
onMoveShouldSetResponderCapture: () => true,
onResponderMove: () => false,
onResponderMove: () => false
});
}
@@ -74,6 +74,8 @@ export default class Badge extends PureComponent {
onLayout = (e) => {
if (!this.layoutReady) {
const height = Math.max(e.nativeEvent.layout.height, this.props.minHeight);
const borderRadius = height / 2;
let width;
if (e.nativeEvent.layout.width <= e.nativeEvent.layout.height) {
@@ -81,14 +83,14 @@ export default class Badge extends PureComponent {
} else {
width = e.nativeEvent.layout.width + this.props.extraPaddingHorizontal;
}
width = Math.max(width + 10, this.props.minWidth);
const borderRadius = width / 2;
width = Math.max(width, this.props.minWidth);
this.setNativeProps({
style: {
width,
height,
borderRadius,
opacity: 1,
},
opacity: 1
}
});
this.layoutReady = true;
}
@@ -139,23 +141,22 @@ export default class Badge extends PureComponent {
const styles = StyleSheet.create({
badge: {
backgroundColor: '#444',
borderRadius: 20,
height: 20,
top: 2,
padding: 12,
paddingTop: 3,
paddingBottom: 3,
backgroundColor: '#444',
borderRadius: 20,
position: 'absolute',
right: 30,
top: 2,
right: 30
},
wrapper: {
alignItems: 'center',
flex: 1,
justifyContent: 'center',
justifyContent: 'center'
},
text: {
fontSize: 14,
color: 'white',
},
color: 'white'
}
});

View File

@@ -11,19 +11,19 @@ import {GlobalStyles} from 'app/styles';
const styles = StyleSheet.create({
container: {
flex: 1,
flexDirection: 'row',
flexDirection: 'row'
},
loading: {
marginLeft: 3,
},
marginLeft: 3
}
});
export default class Button extends PureComponent {
static propTypes = {
children: PropTypes.node,
loading: PropTypes.bool,
onPress: PropTypes.func.isRequired,
onPress: PropTypes.func.isRequired
};
onPress = () => {

View File

@@ -9,7 +9,7 @@ import {
Keyboard,
Platform,
StyleSheet,
View,
View
} from 'react-native';
import {General, WebsocketEvents} from 'mattermost-redux/constants';
@@ -29,7 +29,7 @@ const {
ANDROID_TOP_LANDSCAPE,
ANDROID_TOP_PORTRAIT,
IOS_TOP_LANDSCAPE,
IOS_TOP_PORTRAIT,
IOS_TOP_PORTRAIT
} = ViewTypes;
const DRAWER_INITIAL_OFFSET = 40;
const DRAWER_LANDSCAPE_OFFSET = 150;
@@ -43,7 +43,7 @@ export default class ChannelDrawer extends Component {
makeDirectChannel: PropTypes.func.isRequired,
markChannelAsRead: PropTypes.func.isRequired,
setChannelDisplayName: PropTypes.func.isRequired,
setChannelLoading: PropTypes.func.isRequired,
setChannelLoading: PropTypes.func.isRequired
}).isRequired,
blurPostTextBox: PropTypes.func.isRequired,
children: PropTypes.node,
@@ -54,7 +54,7 @@ export default class ChannelDrawer extends Component {
intl: PropTypes.object.isRequired,
navigator: PropTypes.object,
teamsCount: PropTypes.number.isRequired,
theme: PropTypes.object.isRequired,
theme: PropTypes.object.isRequired
};
closeHandle = null;
@@ -69,7 +69,7 @@ export default class ChannelDrawer extends Component {
openDrawerOffset = DRAWER_LANDSCAPE_OFFSET;
}
this.state = {
openDrawerOffset,
openDrawerOffset
};
}
@@ -175,12 +175,12 @@ export default class ChannelDrawer extends Component {
mainOverlay: {
backgroundColor: this.props.theme.centerChannelBg,
elevation: 3,
opacity,
opacity
},
drawerOverlay: {
backgroundColor: ratio ? '#000' : '#FFF',
opacity: ratio ? (1 - ratio) / 2 : 1,
},
opacity: ratio ? (1 - ratio) / 2 : 1
}
};
};
@@ -202,7 +202,7 @@ export default class ChannelDrawer extends Component {
selectChannel = (channel, currentChannelId) => {
const {
actions,
actions
} = this.props;
const {
@@ -210,17 +210,16 @@ export default class ChannelDrawer extends Component {
markChannelAsRead,
setChannelLoading,
setChannelDisplayName,
markChannelAsViewed,
markChannelAsViewed
} = actions;
tracker.channelSwitch = Date.now();
setChannelLoading(channel.id !== currentChannelId);
setChannelDisplayName(channel.display_name);
this.closeChannelDrawer();
InteractionManager.runAfterInteractions(() => {
setChannelLoading(channel.id !== currentChannelId);
setChannelDisplayName(channel.display_name);
handleSelectChannel(channel.id);
requestAnimationFrame(() => {
// mark the channel as viewed after all the frame has flushed
@@ -237,12 +236,12 @@ export default class ChannelDrawer extends Component {
actions,
currentTeamId,
currentUserId,
intl,
intl
} = this.props;
const {
joinChannel,
makeDirectChannel,
makeDirectChannel
} = actions;
const displayValue = {displayName: channel.display_name};
@@ -254,7 +253,7 @@ export default class ChannelDrawer extends Component {
if (result.error) {
const dmFailedMessage = {
id: 'mobile.open_dm.error',
defaultMessage: "We couldn't open a direct message with {displayName}. Please check your connection and try again.",
defaultMessage: "We couldn't open a direct message with {displayName}. Please check your connection and try again."
};
alertErrorWithFallback(intl, result.error, dmFailedMessage, displayValue);
}
@@ -264,7 +263,7 @@ export default class ChannelDrawer extends Component {
if (result.error) {
const joinFailedMessage = {
id: 'mobile.join_channel.error',
defaultMessage: "We couldn't join the channel {displayName}. Please check your connection and try again.",
defaultMessage: "We couldn't join the channel {displayName}. Please check your connection and try again."
};
alertErrorWithFallback(intl, result.error, joinFailedMessage, displayValue);
}
@@ -320,11 +319,11 @@ export default class ChannelDrawer extends Component {
const {
navigator,
teamsCount,
theme,
theme
} = this.props;
const {
openDrawerOffset,
openDrawerOffset
} = this.state;
const multipleTeams = teamsCount > 1;
@@ -429,9 +428,9 @@ export default class ChannelDrawer extends Component {
shadowRadius: 12,
shadowOffset: {
width: -4,
height: 0,
},
},
height: 0
}
}
}}
>
{children}
@@ -443,6 +442,6 @@ export default class ChannelDrawer extends Component {
const style = StyleSheet.create({
swiperContent: {
flex: 1,
marginBottom: 10,
},
marginBottom: 10
}
});

View File

@@ -8,13 +8,13 @@ import {
Platform,
TouchableHighlight,
Text,
View,
View
} from 'react-native';
import {intlShape} from 'react-intl';
import Badge from 'app/components/badge';
import ChannelIcon from 'app/components/channel_icon';
import {preventDoubleTap} from 'app/utils/tap';
import {wrapWithPreventDoubleTap} from 'app/utils/tap';
import {changeOpacity, makeStyleSheetFromTheme} from 'app/utils/theme';
const {View: AnimatedView} = Animated;
@@ -31,16 +31,15 @@ export default class ChannelItem extends PureComponent {
navigator: PropTypes.object,
onSelectChannel: PropTypes.func.isRequired,
status: PropTypes.string,
teammateDeletedAt: PropTypes.number,
type: PropTypes.string.isRequired,
theme: PropTypes.object.isRequired,
theme: PropTypes.object.isRequired
};
static contextTypes = {
intl: intlShape,
intl: intlShape
};
onPress = preventDoubleTap(() => {
onPress = wrapWithPreventDoubleTap(() => {
const {channelId, currentChannelId, displayName, fake, onSelectChannel, type} = this.props;
requestAnimationFrame(() => {
onSelectChannel({id: channelId, display_name: displayName, fake, type}, currentChannelId);
@@ -58,11 +57,11 @@ export default class ChannelItem extends PureComponent {
previewView: this.previewRef,
previewActions: [{
id: 'action-mark-as-read',
title: intl.formatMessage({id: 'mobile.channel.markAsRead', defaultMessage: 'Mark As Read'}),
title: intl.formatMessage({id: 'mobile.channel.markAsRead', defaultMessage: 'Mark As Read'})
}],
passProps: {
channelId,
},
channelId
}
});
}
};
@@ -80,9 +79,8 @@ export default class ChannelItem extends PureComponent {
isUnread,
mentions,
status,
teammateDeletedAt,
theme,
type,
type
} = this.props;
const {intl} = this.context;
@@ -91,7 +89,7 @@ export default class ChannelItem extends PureComponent {
if (isMyUser) {
channelDisplayName = intl.formatMessage({
id: 'channel_header.directchannel.you',
defaultMessage: '{displayName} (you)',
defaultMessage: '{displayName} (you)'
}, {displayname: displayName});
}
@@ -135,7 +133,6 @@ export default class ChannelItem extends PureComponent {
membersCount={displayName.split(',').length}
size={16}
status={status}
teammateDeletedAt={teammateDeletedAt}
theme={theme}
type={type}
/>
@@ -173,22 +170,22 @@ const getStyleSheet = makeStyleSheetFromTheme((theme) => {
container: {
flex: 1,
flexDirection: 'row',
height: 44,
height: 44
},
borderActive: {
backgroundColor: theme.sidebarTextActiveBorder,
width: 5,
width: 5
},
item: {
alignItems: 'center',
height: 44,
flex: 1,
flexDirection: 'row',
paddingLeft: 16,
paddingLeft: 16
},
itemActive: {
backgroundColor: changeOpacity(theme.sidebarTextActiveColor, 0.1),
paddingLeft: 11,
paddingLeft: 11
},
text: {
color: changeOpacity(theme.sidebarText, 0.4),
@@ -196,13 +193,13 @@ const getStyleSheet = makeStyleSheetFromTheme((theme) => {
fontSize: 14,
fontWeight: '600',
lineHeight: 16,
paddingRight: 40,
paddingRight: 40
},
textActive: {
color: theme.sidebarTextActiveColor,
color: theme.sidebarTextActiveColor
},
textUnread: {
color: theme.sidebarUnreadText,
color: theme.sidebarUnreadText
},
badge: {
backgroundColor: theme.mentionBj,
@@ -211,11 +208,11 @@ const getStyleSheet = makeStyleSheetFromTheme((theme) => {
borderWidth: 1,
padding: 3,
position: 'relative',
right: 16,
right: 16
},
mention: {
color: theme.mentionColor,
fontSize: 10,
},
fontSize: 10
}
};
});

View File

@@ -6,7 +6,7 @@ import {connect} from 'react-redux';
import {General} from 'mattermost-redux/constants';
import {getCurrentChannelId, makeGetChannel, getMyChannelMember} from 'mattermost-redux/selectors/entities/channels';
import {getTheme} from 'mattermost-redux/selectors/entities/preferences';
import {getCurrentUserId, getUser} from 'mattermost-redux/selectors/entities/users';
import {getCurrentUserId} from 'mattermost-redux/selectors/entities/users';
import ChannelItem from './channel_item';
@@ -22,13 +22,8 @@ function makeMapStateToProps() {
const currentUserId = getCurrentUserId(state);
let isMyUser = false;
let teammateDeletedAt = 0;
if (channel.type === General.DM_CHANNEL && channel.teammate_id) {
isMyUser = channel.teammate_id === currentUserId;
const teammate = getUser(state, channel.teammate_id);
if (teammate && teammate.delete_at) {
teammateDeletedAt = teammate.delete_at;
}
}
return {
@@ -38,9 +33,8 @@ function makeMapStateToProps() {
isMyUser,
mentions: member ? member.mention_count : 0,
status: channel.status,
teammateDeletedAt,
theme: getTheme(state),
type: channel.type,
type: channel.type
};
};
}

View File

@@ -5,7 +5,7 @@ import React from 'react';
import PropTypes from 'prop-types';
import {
Platform,
View,
View
} from 'react-native';
import {injectIntl, intlShape} from 'react-intl';
import MaterialIcon from 'react-native-vector-icons/MaterialIcons';
@@ -29,7 +29,7 @@ class ChannelsList extends React.PureComponent {
onSearchStart: PropTypes.func.isRequired,
onSelectChannel: PropTypes.func.isRequired,
onShowTeams: PropTypes.func.isRequired,
theme: PropTypes.object.isRequired,
theme: PropTypes.object.isRequired
};
constructor(props) {
@@ -37,7 +37,7 @@ class ChannelsList extends React.PureComponent {
this.state = {
searching: false,
term: '',
term: ''
};
MaterialIcon.getImageSource('close', 20, this.props.theme.sidebarHeaderTextColor).then((source) => {
@@ -77,7 +77,7 @@ class ChannelsList extends React.PureComponent {
intl,
navigator,
onShowTeams,
theme,
theme
} = this.props;
const {searching, term} = this.state;
@@ -106,11 +106,7 @@ class ChannelsList extends React.PureComponent {
backgroundColor: changeOpacity(theme.sidebarHeaderTextColor, 0.2),
color: theme.sidebarHeaderTextColor,
fontSize: 15,
...Platform.select({
android: {
marginBottom: -5,
},
}),
lineHeight: 66
};
const title = (
@@ -120,7 +116,7 @@ class ChannelsList extends React.PureComponent {
placeholder={intl.formatMessage({id: 'mobile.channel_drawer.search', defaultMessage: 'Jump to...'})}
cancelTitle={intl.formatMessage({id: 'mobile.post.cancel', defaultMessage: 'Cancel'})}
backgroundColor='transparent'
inputHeight={34}
inputHeight={33}
inputStyle={searchBarInput}
placeholderTextColor={changeOpacity(theme.sidebarHeaderTextColor, 0.5)}
tintColorSearch={changeOpacity(theme.sidebarHeaderTextColor, 0.5)}
@@ -161,10 +157,10 @@ const getStyleSheet = makeStyleSheetFromTheme((theme) => {
return {
container: {
backgroundColor: theme.sidebarBg,
flex: 1,
flex: 1
},
statusBar: {
backgroundColor: theme.sidebarHeaderBg,
backgroundColor: theme.sidebarHeaderBg
},
headerContainer: {
alignItems: 'center',
@@ -175,30 +171,30 @@ const getStyleSheet = makeStyleSheetFromTheme((theme) => {
borderBottomColor: changeOpacity(theme.sidebarHeaderTextColor, 0.10),
...Platform.select({
android: {
height: ANDROID_TOP_PORTRAIT,
height: ANDROID_TOP_PORTRAIT
},
ios: {
height: 44,
},
}),
height: 44
}
})
},
header: {
color: theme.sidebarHeaderTextColor,
flex: 1,
fontSize: 17,
fontWeight: 'normal',
paddingLeft: 16,
paddingLeft: 16
},
switchContainer: {
position: 'relative',
top: -1,
top: -1
},
titleContainer: { // These aren't used by this component, but they are passed down to the list component
alignItems: 'center',
flex: 1,
flexDirection: 'row',
height: 48,
marginLeft: 16,
marginLeft: 16
},
title: {
flex: 1,
@@ -207,40 +203,40 @@ const getStyleSheet = makeStyleSheetFromTheme((theme) => {
fontSize: 15,
fontWeight: '400',
letterSpacing: 0.8,
lineHeight: 18,
lineHeight: 18
},
searchContainer: {
flex: 1,
paddingRight: 10,
...Platform.select({
android: {
marginBottom: 1,
marginBottom: 1
},
ios: {
marginBottom: 3,
},
}),
marginBottom: 3
}
})
},
divider: {
backgroundColor: changeOpacity(theme.sidebarText, 0.1),
height: 1,
height: 1
},
actionContainer: {
alignItems: 'center',
height: 48,
justifyContent: 'center',
width: 50,
width: 50
},
action: {
color: theme.sidebarText,
fontSize: 20,
fontWeight: '500',
lineHeight: 18,
lineHeight: 18
},
above: {
backgroundColor: theme.mentionBj,
top: 9,
},
top: 9
}
};
});

View File

@@ -8,7 +8,7 @@ import {
FlatList,
Text,
TouchableHighlight,
View,
View
} from 'react-native';
import {injectIntl, intlShape} from 'react-intl';
import MaterialIcon from 'react-native-vector-icons/MaterialIcons';
@@ -30,7 +30,7 @@ class FilteredList extends Component {
getProfilesInTeam: PropTypes.func.isRequired,
makeGroupMessageVisibleIfNecessary: PropTypes.func.isRequired,
searchChannels: PropTypes.func.isRequired,
searchProfiles: PropTypes.func.isRequired,
searchProfiles: PropTypes.func.isRequired
}).isRequired,
channels: PropTypes.object.isRequired,
currentTeam: PropTypes.object.isRequired,
@@ -49,18 +49,19 @@ class FilteredList extends Component {
statuses: PropTypes.object,
styles: PropTypes.object.isRequired,
term: PropTypes.string,
theme: PropTypes.object.isRequired,
theme: PropTypes.object.isRequired
};
static defaultProps = {
currentTeam: {},
currentChannel: {},
pastDirectMessages: [],
pastDirectMessages: []
};
constructor(props) {
super(props);
this.state = {
dataSource: this.buildData(props),
dataSource: this.buildData(props)
};
}
@@ -145,28 +146,28 @@ class FilteredList extends Component {
unreads: {
builder: this.buildUnreadChannelsForSearch,
id: 'mobile.channel_list.unreads',
defaultMessage: 'UNREADS',
defaultMessage: 'UNREADS'
},
channels: {
builder: this.buildChannelsForSearch,
id: 'mobile.channel_list.channels',
defaultMessage: 'CHANNELS',
defaultMessage: 'CHANNELS'
},
dms: {
builder: this.buildCurrentDMSForSearch,
id: 'sidebar.direct',
defaultMessage: 'DIRECT MESSAGES',
defaultMessage: 'DIRECT MESSAGES'
},
members: {
builder: this.buildMembersForSearch,
id: 'mobile.channel_list.members',
defaultMessage: 'MEMBERS',
defaultMessage: 'MEMBERS'
},
nonmembers: {
builder: this.buildOtherMembersForSearch,
id: 'mobile.channel_list.not_member',
defaultMessage: 'NOT A MEMBER',
},
defaultMessage: 'NOT A MEMBER'
}
});
buildUnreadChannelsForSearch = (props, term) => {
@@ -211,14 +212,14 @@ class FilteredList extends Component {
type: General.DM_CHANNEL,
fake: true,
nickname: u.nickname,
fullname: `${u.first_name} ${u.last_name}`,
fullname: `${u.first_name} ${u.last_name}`
};
});
groupChannels = groupChannels.map((channel) => {
return {
...channel,
...groupChannelMemberDetails[channel.id],
...groupChannelMemberDetails[channel.id]
};
});
@@ -252,7 +253,7 @@ class FilteredList extends Component {
type: General.DM_CHANNEL,
fake: true,
nickname: u.nickname,
fullname: `${u.first_name} ${u.last_name}`,
fullname: `${u.first_name} ${u.last_name}`
};
});
@@ -265,7 +266,7 @@ class FilteredList extends Component {
const {
favoriteChannels,
publicChannels,
privateChannels,
privateChannels
} = props.channels;
const favorites = favoriteChannels.filter((c) => {
@@ -282,7 +283,7 @@ class FilteredList extends Component {
const notMemberOf = otherChannels.map((o) => {
return {
...o,
fake: true,
fake: true
};
});
@@ -368,7 +369,7 @@ class FilteredList extends Component {
</View>
{bottomDivider && this.renderDivider(styles, 16)}
</View>
),
)
};
};

View File

@@ -13,10 +13,9 @@ import {
getChannelsWithUnreadSection,
getCurrentChannel,
getGroupChannels,
getOtherChannels,
getOtherChannels
} from 'mattermost-redux/selectors/entities/channels';
import {getConfig} from 'mattermost-redux/selectors/entities/general';
import {getCurrentTeam} from 'mattermost-redux/selectors/entities/teams';
import {getCurrentUserId, getProfilesInCurrentTeam, getUsers, getUserIdsInChannels, getUserStatuses} from 'mattermost-redux/selectors/entities/users';
import {getDirectShowPreferences, getTeammateNameDisplaySetting, getTheme} from 'mattermost-redux/selectors/entities/preferences';
@@ -73,14 +72,14 @@ function getGroupDetails(currentUserId, userIdsInChannels, profiles, groupChanne
email: [],
fullname: [],
nickname: [],
username: [],
username: []
});
groupMemberDetails[channel.id] = {
email: members.email.join(','),
fullname: members.fullname.join(','),
nickname: members.nickname.join(','),
username: members.username.join(','),
username: members.username.join(',')
};
return groupMemberDetails;
@@ -110,7 +109,6 @@ function mapStateToProps(state) {
return {
channels: getChannelsWithUnreadSection(state),
currentChannel: getCurrentChannel(state),
currentTeam: getCurrentTeam(state),
currentUserId,
otherChannels: getOtherChannels(state),
groupChannelMemberDetails: getGroupChannelMemberDetails(state),
@@ -121,7 +119,7 @@ function mapStateToProps(state) {
searchOrder,
pastDirectMessages: pastDirectMessages(state),
restrictDms,
theme: getTheme(state),
theme: getTheme(state)
};
}
@@ -131,8 +129,8 @@ function mapDispatchToProps(dispatch) {
getProfilesInTeam,
makeGroupMessageVisibleIfNecessary,
searchChannels,
searchProfiles,
}, dispatch),
searchProfiles
}, dispatch)
};
}

View File

@@ -9,7 +9,7 @@ import ChannelsList from './channels_list';
function mapStateToProps(state) {
return {
theme: getTheme(state),
theme: getTheme(state)
};
}

View File

@@ -9,7 +9,7 @@ import {
getSortedFavoriteChannelIds,
getSortedPublicChannelIds,
getSortedPrivateChannelIds,
getSortedDirectChannelIds,
getSortedDirectChannelIds
} from 'mattermost-redux/selectors/entities/channels';
import {getCurrentUserId, getCurrentUserRoles} from 'mattermost-redux/selectors/entities/users';
import {getTheme, getFavoritesPreferences} from 'mattermost-redux/selectors/entities/preferences';
@@ -34,7 +34,7 @@ function mapStateToProps(state) {
publicChannelIds,
privateChannelIds,
directChannelIds,
theme: getTheme(state),
theme: getTheme(state)
};
}

View File

@@ -8,7 +8,7 @@ import {
SectionList,
Text,
TouchableHighlight,
View,
View
} from 'react-native';
import {intlShape} from 'react-intl';
import MaterialIcon from 'react-native-vector-icons/MaterialIcons';
@@ -19,12 +19,12 @@ import {debounce} from 'mattermost-redux/actions/helpers';
import ChannelItem from 'app/components/channel_drawer/channels_list/channel_item';
import UnreadIndicator from 'app/components/channel_drawer/channels_list/unread_indicator';
import {ListTypes} from 'app/constants';
import {preventDoubleTap} from 'app/utils/tap';
import {wrapWithPreventDoubleTap} from 'app/utils/tap';
import {changeOpacity} from 'app/utils/theme';
const VIEWABILITY_CONFIG = {
...ListTypes.VISIBILITY_CONFIG_DEFAULTS,
waitForInteraction: true,
waitForInteraction: true
};
export default class List extends PureComponent {
@@ -38,11 +38,11 @@ export default class List extends PureComponent {
privateChannelIds: PropTypes.array.isRequired,
styles: PropTypes.object.isRequired,
theme: PropTypes.object.isRequired,
unreadChannelIds: PropTypes.array.isRequired,
unreadChannelIds: PropTypes.array.isRequired
};
static contextTypes = {
intl: intlShape,
intl: intlShape
};
constructor(props) {
@@ -51,7 +51,7 @@ export default class List extends PureComponent {
this.state = {
sections: this.buildSections(props),
showIndicator: false,
width: 0,
width: 0
};
MaterialIcon.getImageSource('close', 20, this.props.theme.sidebarHeaderTextColor).then((source) => {
@@ -66,7 +66,7 @@ export default class List extends PureComponent {
favoriteChannelIds,
publicChannelIds,
privateChannelIds,
unreadChannelIds,
unreadChannelIds
} = this.props;
if (nextProps.canCreatePrivateChannels !== canCreatePrivateChannels ||
@@ -93,7 +93,7 @@ export default class List extends PureComponent {
favoriteChannelIds,
publicChannelIds,
privateChannelIds,
unreadChannelIds,
unreadChannelIds
} = props;
const sections = [];
@@ -104,7 +104,7 @@ export default class List extends PureComponent {
data: unreadChannelIds,
renderItem: this.renderUnreadItem,
topSeparator: false,
bottomSeparator: true,
bottomSeparator: true
});
}
@@ -114,7 +114,7 @@ export default class List extends PureComponent {
defaultMessage: 'FAVORITES',
data: favoriteChannelIds,
topSeparator: unreadChannelIds.length > 0,
bottomSeparator: true,
bottomSeparator: true
});
}
@@ -124,7 +124,7 @@ export default class List extends PureComponent {
defaultMessage: 'PUBLIC CHANNELS',
data: publicChannelIds,
topSeparator: favoriteChannelIds.length > 0 || unreadChannelIds.length > 0,
bottomSeparator: publicChannelIds.length > 0,
bottomSeparator: publicChannelIds.length > 0
});
sections.push({
@@ -133,7 +133,7 @@ export default class List extends PureComponent {
defaultMessage: 'PRIVATE CHANNELS',
data: privateChannelIds,
topSeparator: true,
bottomSeparator: privateChannelIds.length > 0,
bottomSeparator: privateChannelIds.length > 0
});
sections.push({
@@ -142,13 +142,13 @@ export default class List extends PureComponent {
defaultMessage: 'DIRECT MESSAGES',
data: directChannelIds,
topSeparator: true,
bottomSeparator: directChannelIds.length > 0,
bottomSeparator: directChannelIds.length > 0
});
return sections;
};
goToCreatePrivateChannel = preventDoubleTap(() => {
goToCreatePrivateChannel = wrapWithPreventDoubleTap(() => {
const {navigator, theme} = this.props;
const {intl} = this.context;
@@ -162,16 +162,16 @@ export default class List extends PureComponent {
navBarTextColor: theme.sidebarHeaderTextColor,
navBarBackgroundColor: theme.sidebarHeaderBg,
navBarButtonColor: theme.sidebarHeaderTextColor,
screenBackgroundColor: theme.centerChannelBg,
screenBackgroundColor: theme.centerChannelBg
},
passProps: {
channelType: General.PRIVATE_CHANNEL,
closeButton: this.closeButton,
},
closeButton: this.closeButton
}
});
});
goToDirectMessages = preventDoubleTap(() => {
goToDirectMessages = wrapWithPreventDoubleTap(() => {
const {navigator, theme} = this.props;
const {intl} = this.context;
@@ -185,18 +185,18 @@ export default class List extends PureComponent {
navBarTextColor: theme.sidebarHeaderTextColor,
navBarBackgroundColor: theme.sidebarHeaderBg,
navBarButtonColor: theme.sidebarHeaderTextColor,
screenBackgroundColor: theme.centerChannelBg,
screenBackgroundColor: theme.centerChannelBg
},
navigatorButtons: {
leftButtons: [{
id: 'close-dms',
icon: this.closeButton,
}],
},
icon: this.closeButton
}]
}
});
});
goToMoreChannels = preventDoubleTap(() => {
goToMoreChannels = wrapWithPreventDoubleTap(() => {
const {navigator, theme} = this.props;
const {intl} = this.context;
@@ -210,11 +210,11 @@ export default class List extends PureComponent {
navBarTextColor: theme.sidebarHeaderTextColor,
navBarBackgroundColor: theme.sidebarHeaderBg,
navBarButtonColor: theme.sidebarHeaderTextColor,
screenBackgroundColor: theme.centerChannelBg,
screenBackgroundColor: theme.centerChannelBg
},
passProps: {
closeButton: this.closeButton,
},
closeButton: this.closeButton
}
});
});
@@ -282,7 +282,7 @@ export default class List extends PureComponent {
bottomSeparator,
defaultMessage,
id,
topSeparator,
topSeparator
} = section;
return (
@@ -304,7 +304,7 @@ export default class List extends PureComponent {
this.refs.list._wrapperListRef.getListRef().scrollToOffset({ //eslint-disable-line no-underscore-dangle
x: 0,
y: 0,
animated: true,
animated: true
});
}
};

View File

@@ -13,9 +13,10 @@ function mapStateToProps(state) {
return {
currentTeamId: team.id,
displayName: team.display_name,
mentionCount: getChannelDrawerBadgeCount(state),
teamsCount: getMyTeamsCount(state),
theme: getTheme(state),
theme: getTheme(state)
};
}

View File

@@ -4,38 +4,39 @@
import PropTypes from 'prop-types';
import React from 'react';
import {
Text,
TouchableHighlight,
View,
View
} from 'react-native';
import AwesomeIcon from 'react-native-vector-icons/FontAwesome';
import Badge from 'app/components/badge';
import {preventDoubleTap} from 'app/utils/tap';
import {wrapWithPreventDoubleTap} from 'app/utils/tap';
import {changeOpacity, makeStyleSheetFromTheme} from 'app/utils/theme';
import TeamIcon from 'app/components/team_icon';
export default class SwitchTeamsButton extends React.PureComponent {
static propTypes = {
currentTeamId: PropTypes.string,
displayName: PropTypes.string,
searching: PropTypes.bool.isRequired,
onShowTeams: PropTypes.func.isRequired,
mentionCount: PropTypes.number.isRequired,
teamsCount: PropTypes.number.isRequired,
theme: PropTypes.object.isRequired,
theme: PropTypes.object.isRequired
};
showTeams = preventDoubleTap(() => {
showTeams = wrapWithPreventDoubleTap(() => {
this.props.onShowTeams();
});
render() {
const {
currentTeamId,
displayName,
mentionCount,
searching,
teamsCount,
theme,
theme
} = this.props;
if (!currentTeamId) {
@@ -68,14 +69,12 @@ export default class SwitchTeamsButton extends React.PureComponent {
<AwesomeIcon
name='chevron-left'
size={12}
style={styles.switcherArrow}
color={theme.sidebarHeaderBg}
/>
<View style={styles.switcherDivider}/>
<TeamIcon
teamId={currentTeamId}
styleContainer={styles.teamIconContainer}
styleText={styles.teamIconText}
/>
<Text style={styles.switcherTeam}>
{displayName.substr(0, 2).toUpperCase()}
</Text>
</View>
</TouchableHighlight>
{badge}
@@ -87,33 +86,26 @@ export default class SwitchTeamsButton extends React.PureComponent {
const getStyleSheet = makeStyleSheetFromTheme((theme) => {
return {
switcherContainer: {
backgroundColor: theme.sidebarHeaderTextColor,
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'center',
height: 32,
backgroundColor: theme.sidebarHeaderTextColor,
borderRadius: 2,
flexDirection: 'row',
height: 32,
justifyContent: 'center',
marginLeft: 6,
marginRight: 6,
paddingHorizontal: 3,
},
switcherArrow: {
color: theme.sidebarHeaderBg,
marginRight: 3,
marginRight: 5,
paddingHorizontal: 6
},
switcherDivider: {
backgroundColor: theme.sidebarHeaderBg,
height: 15,
marginHorizontal: 6,
width: 1,
width: 1
},
teamIconContainer: {
width: 26,
height: 26,
marginLeft: 3,
},
teamIconText: {
fontSize: 14,
switcherTeam: {
color: theme.sidebarHeaderBg,
fontFamily: 'OpenSans',
fontSize: 14
},
badge: {
backgroundColor: theme.mentionBj,
@@ -124,11 +116,11 @@ const getStyleSheet = makeStyleSheetFromTheme((theme) => {
padding: 3,
position: 'absolute',
left: -5,
top: -5,
top: -5
},
mention: {
color: theme.mentionColor,
fontSize: 10,
},
fontSize: 10
}
};
});

View File

@@ -6,7 +6,7 @@ import PropTypes from 'prop-types';
import {
TouchableWithoutFeedback,
View,
ViewPropTypes,
ViewPropTypes
} from 'react-native';
import IonIcon from 'react-native-vector-icons/Ionicons';
@@ -18,11 +18,11 @@ export default class UnreadIndicator extends PureComponent {
show: PropTypes.bool,
style: ViewPropTypes.style,
onPress: PropTypes.func,
theme: PropTypes.object.isRequired,
theme: PropTypes.object.isRequired
};
static defaultProps = {
onPress: () => true,
onPress: () => true
};
render() {
@@ -65,7 +65,7 @@ const getStyleSheet = makeStyleSheetFromTheme((theme) => {
position: 'absolute',
borderRadius: 15,
marginHorizontal: 15,
height: 25,
height: 25
},
indicatorText: {
backgroundColor: 'transparent',
@@ -74,11 +74,11 @@ const getStyleSheet = makeStyleSheetFromTheme((theme) => {
paddingVertical: 2,
paddingHorizontal: 4,
textAlign: 'center',
textAlignVertical: 'center',
textAlignVertical: 'center'
},
arrow: {
position: 'relative',
bottom: -1,
},
bottom: -1
}
};
});

View File

@@ -16,12 +16,12 @@ export default class DrawerSwiper extends Component {
onPageSelected: PropTypes.func,
openDrawerOffset: PropTypes.number,
showTeams: PropTypes.bool.isRequired,
theme: PropTypes.object.isRequired,
theme: PropTypes.object.isRequired
};
static defaultProps = {
onPageSelected: () => true,
openDrawerOffset: 0,
openDrawerOffset: 0
};
shouldComponentUpdate(nextProps) {
@@ -64,7 +64,7 @@ export default class DrawerSwiper extends Component {
deviceWidth,
openDrawerOffset,
showTeams,
theme,
theme
} = this.props;
const initialPage = React.Children.count(children) - 1;
@@ -92,6 +92,6 @@ export default class DrawerSwiper extends Component {
const style = StyleSheet.create({
pagination: {
bottom: 0,
position: 'absolute',
},
position: 'absolute'
}
});

View File

@@ -12,7 +12,7 @@ import DraweSwiper from './drawer_swiper';
function mapStateToProps(state) {
return {
...getDimensions(state),
theme: getTheme(state),
theme: getTheme(state)
};
}

View File

@@ -24,7 +24,7 @@ function mapStateToProps(state) {
isLandscape: isLandscape(state),
isTablet: isTablet(state),
teamsCount: getMyTeamsCount(state),
theme: getTheme(state),
theme: getTheme(state)
};
}
@@ -38,8 +38,8 @@ function mapDispatchToProps(dispatch) {
makeDirectChannel,
markChannelAsRead,
setChannelDisplayName,
setChannelLoading,
}, dispatch),
setChannelLoading
}, dispatch)
};
}

View File

@@ -5,7 +5,7 @@ import {bindActionCreators} from 'redux';
import {connect} from 'react-redux';
import {getCurrentUrl} from 'mattermost-redux/selectors/entities/general';
import {getCurrentTeamId, getMySortedTeamIds} from 'mattermost-redux/selectors/entities/teams';
import {getCurrentTeamId, getJoinableTeamIds, getMySortedTeamIds} from 'mattermost-redux/selectors/entities/teams';
import {getTheme} from 'mattermost-redux/selectors/entities/preferences';
import {handleTeamChange} from 'app/actions/views/select_team';
@@ -18,18 +18,19 @@ function mapStateToProps(state) {
const locale = getCurrentLocale(state);
return {
canJoinOtherTeams: getJoinableTeamIds(state).length > 0,
currentTeamId: getCurrentTeamId(state),
currentUrl: removeProtocol(getCurrentUrl(state)),
teamIds: getMySortedTeamIds(state, locale),
theme: getTheme(state),
theme: getTheme(state)
};
}
function mapDispatchToProps(dispatch) {
return {
actions: bindActionCreators({
handleTeamChange,
}, dispatch),
handleTeamChange
}, dispatch)
};
}

View File

@@ -9,14 +9,14 @@ import {
StatusBar,
Text,
TouchableHighlight,
View,
View
} from 'react-native';
import {injectIntl, intlShape} from 'react-intl';
import MaterialIcon from 'react-native-vector-icons/MaterialIcons';
import FormattedText from 'app/components/formatted_text';
import {ListTypes, ViewTypes} from 'app/constants';
import {preventDoubleTap} from 'app/utils/tap';
import {wrapWithPreventDoubleTap} from 'app/utils/tap';
import {changeOpacity, makeStyleSheetFromTheme} from 'app/utils/theme';
import tracker from 'app/utils/time_tracker';
@@ -25,21 +25,22 @@ import TeamsListItem from './teams_list_item';
const {ANDROID_TOP_PORTRAIT} = ViewTypes;
const VIEWABILITY_CONFIG = {
...ListTypes.VISIBILITY_CONFIG_DEFAULTS,
waitForInteraction: true,
waitForInteraction: true
};
class TeamsList extends PureComponent {
static propTypes = {
actions: PropTypes.shape({
handleTeamChange: PropTypes.func.isRequired,
handleTeamChange: PropTypes.func.isRequired
}).isRequired,
canJoinOtherTeams: PropTypes.bool.isRequired,
closeChannelDrawer: PropTypes.func.isRequired,
currentTeamId: PropTypes.string.isRequired,
currentUrl: PropTypes.string.isRequired,
intl: intlShape.isRequired,
navigator: PropTypes.object.isRequired,
teamIds: PropTypes.array.isRequired,
theme: PropTypes.object.isRequired,
theme: PropTypes.object.isRequired
};
constructor(props) {
@@ -63,7 +64,7 @@ class TeamsList extends PureComponent {
});
};
goToSelectTeam = preventDoubleTap(() => {
goToSelectTeam = wrapWithPreventDoubleTap(() => {
const {currentUrl, intl, navigator, theme} = this.props;
navigator.showModal({
@@ -76,18 +77,18 @@ class TeamsList extends PureComponent {
navBarTextColor: theme.sidebarHeaderTextColor,
navBarBackgroundColor: theme.sidebarHeaderBg,
navBarButtonColor: theme.sidebarHeaderTextColor,
screenBackgroundColor: theme.centerChannelBg,
screenBackgroundColor: theme.centerChannelBg
},
navigatorButtons: {
leftButtons: [{
id: 'close-teams',
icon: this.closeButton,
}],
icon: this.closeButton
}]
},
passProps: {
currentUrl,
theme,
},
theme
}
});
});
@@ -105,22 +106,25 @@ class TeamsList extends PureComponent {
};
render() {
const {teamIds, theme} = this.props;
const {canJoinOtherTeams, teamIds, theme} = this.props;
const styles = getStyleSheet(theme);
const moreAction = (
<TouchableHighlight
style={styles.moreActionContainer}
onPress={this.goToSelectTeam}
underlayColor={changeOpacity(theme.sidebarHeaderBg, 0.5)}
>
<Text
style={styles.moreAction}
let moreAction;
if (canJoinOtherTeams) {
moreAction = (
<TouchableHighlight
style={styles.moreActionContainer}
onPress={this.goToSelectTeam}
underlayColor={changeOpacity(theme.sidebarHeaderBg, 0.5)}
>
{'+'}
</Text>
</TouchableHighlight>
);
<Text
style={styles.moreAction}
>
{'+'}
</Text>
</TouchableHighlight>
);
}
return (
<View style={styles.container}>
@@ -149,10 +153,10 @@ const getStyleSheet = makeStyleSheetFromTheme((theme) => {
return {
container: {
backgroundColor: theme.sidebarBg,
flex: 1,
flex: 1
},
statusBar: {
backgroundColor: theme.sidebarHeaderBg,
backgroundColor: theme.sidebarHeaderBg
},
headerContainer: {
alignItems: 'center',
@@ -162,19 +166,19 @@ const getStyleSheet = makeStyleSheetFromTheme((theme) => {
borderBottomColor: changeOpacity(theme.sidebarHeaderTextColor, 0.10),
...Platform.select({
android: {
height: ANDROID_TOP_PORTRAIT,
height: ANDROID_TOP_PORTRAIT
},
ios: {
height: 44,
},
}),
height: 44
}
})
},
header: {
color: theme.sidebarHeaderTextColor,
flex: 1,
fontSize: 17,
textAlign: 'center',
fontWeight: '600',
fontWeight: '600'
},
moreActionContainer: {
alignItems: 'center',
@@ -182,17 +186,17 @@ const getStyleSheet = makeStyleSheetFromTheme((theme) => {
width: 50,
...Platform.select({
android: {
height: ANDROID_TOP_PORTRAIT,
height: ANDROID_TOP_PORTRAIT
},
ios: {
height: 44,
},
}),
height: 44
}
})
},
moreAction: {
color: theme.sidebarHeaderTextColor,
fontSize: 30,
},
fontSize: 30
}
};
});

View File

@@ -23,7 +23,7 @@ function makeMapStateToProps() {
displayName: team.display_name,
mentionCount: getMentionCount(state, ownProps.teamId),
name: team.name,
theme: getTheme(state),
theme: getTheme(state)
};
};
}

View File

@@ -6,14 +6,12 @@ import PropTypes from 'prop-types';
import {
Text,
TouchableHighlight,
View,
View
} from 'react-native';
import IonIcon from 'react-native-vector-icons/Ionicons';
import Badge from 'app/components/badge';
import TeamIcon from 'app/components/team_icon';
import {preventDoubleTap} from 'app/utils/tap';
import {wrapWithPreventDoubleTap} from 'app/utils/tap';
import {changeOpacity, makeStyleSheetFromTheme} from 'app/utils/theme';
export default class TeamsListItem extends React.PureComponent {
@@ -25,10 +23,10 @@ export default class TeamsListItem extends React.PureComponent {
name: PropTypes.string.isRequired,
selectTeam: PropTypes.func.isRequired,
teamId: PropTypes.string.isRequired,
theme: PropTypes.object.isRequired,
theme: PropTypes.object.isRequired
};
selectTeam = preventDoubleTap(() => {
selectTeam = wrapWithPreventDoubleTap(() => {
this.props.selectTeam(this.props.teamId);
});
@@ -40,7 +38,7 @@ export default class TeamsListItem extends React.PureComponent {
mentionCount,
name,
teamId,
theme,
theme
} = this.props;
const styles = getStyleSheet(theme);
@@ -73,11 +71,11 @@ export default class TeamsListItem extends React.PureComponent {
onPress={this.selectTeam}
>
<View style={styles.teamContainer}>
<TeamIcon
teamId={teamId}
styleContainer={styles.teamIconContainer}
styleText={styles.teamIconText}
/>
<View style={styles.teamIconContainer}>
<Text style={styles.teamIcon}>
{displayName.substr(0, 2).toUpperCase()}
</Text>
</View>
<View style={styles.teamNameContainer}>
<Text
numberOfLines={1}
@@ -106,40 +104,47 @@ export default class TeamsListItem extends React.PureComponent {
const getStyleSheet = makeStyleSheetFromTheme((theme) => {
return {
teamWrapper: {
marginTop: 20,
marginTop: 20
},
teamContainer: {
alignItems: 'center',
flex: 1,
flexDirection: 'row',
marginHorizontal: 16,
marginHorizontal: 16
},
teamIconContainer: {
alignItems: 'center',
backgroundColor: theme.sidebarText,
borderRadius: 2,
height: 40,
justifyContent: 'center',
width: 40
},
teamIcon: {
color: theme.sidebarBg,
fontFamily: 'OpenSans',
fontSize: 18,
fontWeight: '600'
},
teamNameContainer: {
flex: 1,
flexDirection: 'column',
marginLeft: 10,
marginLeft: 10
},
teamName: {
color: theme.sidebarText,
fontSize: 18,
},
teamIconContainer: {
width: 40,
height: 40,
},
teamIconText: {
fontSize: 18,
fontSize: 18
},
teamUrl: {
color: changeOpacity(theme.sidebarText, 0.5),
fontSize: 12,
fontSize: 12
},
checkmarkContainer: {
alignItems: 'flex-end',
alignItems: 'flex-end'
},
checkmark: {
color: theme.sidebarText,
fontSize: 20,
fontSize: 20
},
badge: {
backgroundColor: theme.mentionBj,
@@ -150,11 +155,11 @@ const getStyleSheet = makeStyleSheetFromTheme((theme) => {
padding: 3,
position: 'absolute',
left: 45,
top: -7.5,
top: -7.5
},
mention: {
color: theme.mentionColor,
fontSize: 10,
},
fontSize: 10
}
};
});

View File

@@ -5,17 +5,11 @@ import React from 'react';
import PropTypes from 'prop-types';
import {
Text,
View,
View
} from 'react-native';
import Icon from 'react-native-vector-icons/FontAwesome';
import {
ArchiveIcon,
AwayAvatar,
DndAvatar,
OfflineAvatar,
OnlineAvatar,
} from 'app/components/status_icons';
import {AwayAvatar, DndAvatar, OfflineAvatar, OnlineAvatar} from 'app/components/status_icons';
import {General} from 'mattermost-redux/constants';
@@ -29,30 +23,19 @@ export default class ChannelIcon extends React.PureComponent {
membersCount: PropTypes.number,
size: PropTypes.number,
status: PropTypes.string,
teammateDeletedAt: PropTypes.number,
theme: PropTypes.object.isRequired,
type: PropTypes.string.isRequired,
type: PropTypes.string.isRequired
};
static defaultProps = {
isActive: false,
isInfo: false,
isUnread: false,
size: 12,
size: 12
};
render() {
const {
isActive,
isUnread,
isInfo,
membersCount,
size,
status,
teammateDeletedAt,
theme,
type,
} = this.props;
const {isActive, isUnread, isInfo, membersCount, size, status, theme, type} = this.props;
const style = getStyleSheet(theme);
let activeIcon;
@@ -106,14 +89,6 @@ export default class ChannelIcon extends React.PureComponent {
</Text>
</View>
);
} else if (type === General.DM_CHANNEL && teammateDeletedAt) {
icon = (
<ArchiveIcon
width={size}
height={size}
color={offlineColor}
/>
);
} else if (type === General.DM_CHANNEL) {
switch (status) {
case General.AWAY:
@@ -167,49 +142,49 @@ const getStyleSheet = makeStyleSheetFromTheme((theme) => {
return {
container: {
marginRight: 12,
alignItems: 'center',
alignItems: 'center'
},
icon: {
color: changeOpacity(theme.sidebarText, 0.4),
color: changeOpacity(theme.sidebarText, 0.4)
},
iconActive: {
color: theme.sidebarTextActiveColor,
color: theme.sidebarTextActiveColor
},
iconUnread: {
color: theme.sidebarUnreadText,
color: theme.sidebarUnreadText
},
iconInfo: {
color: theme.centerChannelColor,
color: theme.centerChannelColor
},
groupBox: {
alignSelf: 'flex-start',
alignItems: 'center',
borderWidth: 1,
borderColor: changeOpacity(theme.sidebarText, 0.4),
justifyContent: 'center',
justifyContent: 'center'
},
groupBoxActive: {
borderColor: theme.sidebarTextActiveColor,
borderColor: theme.sidebarTextActiveColor
},
groupBoxUnread: {
borderColor: theme.sidebarUnreadText,
borderColor: theme.sidebarUnreadText
},
groupBoxInfo: {
borderColor: theme.centerChannelColor,
borderColor: theme.centerChannelColor
},
group: {
color: changeOpacity(theme.sidebarText, 0.4),
fontSize: 10,
fontWeight: '600',
fontWeight: '600'
},
groupActive: {
color: theme.sidebarTextActiveColor,
color: theme.sidebarTextActiveColor
},
groupUnread: {
color: theme.sidebarUnreadText,
color: theme.sidebarUnreadText
},
groupInfo: {
color: theme.centerChannelColor,
},
color: theme.centerChannelColor
}
};
});

View File

@@ -4,10 +4,9 @@
import React, {PureComponent} from 'react';
import PropTypes from 'prop-types';
import {
Platform,
Text,
TouchableOpacity,
View,
View
} from 'react-native';
import {getFullName} from 'mattermost-redux/utils/user_utils';
import {General} from 'mattermost-redux/constants';
@@ -26,32 +25,27 @@ class ChannelIntro extends PureComponent {
intl: intlShape.isRequired,
isLoadingPosts: PropTypes.bool,
navigator: PropTypes.object.isRequired,
theme: PropTypes.object.isRequired,
theme: PropTypes.object.isRequired
};
goToUserProfile = (userId) => {
const {intl, navigator, theme} = this.props;
const options = {
navigator.push({
screen: 'UserProfile',
title: intl.formatMessage({id: 'mobile.routes.user_profile', defaultMessage: 'Profile'}),
animated: true,
backButtonTitle: '',
passProps: {
userId,
userId
},
navigatorStyle: {
navBarTextColor: theme.sidebarHeaderTextColor,
navBarBackgroundColor: theme.sidebarHeaderBg,
navBarButtonColor: theme.sidebarHeaderTextColor,
screenBackgroundColor: theme.centerChannelBg,
},
};
if (Platform.OS === 'ios') {
navigator.push(options);
} else {
navigator.showModal(options);
}
screenBackgroundColor: theme.centerChannelBg
}
});
};
getDisplayName = (member) => {
@@ -75,7 +69,7 @@ class ChannelIntro extends PureComponent {
return currentChannelMembers.map((member) => (
<TouchableOpacity
key={member.id}
onPress={preventDoubleTap(() => this.goToUserProfile(member.id))}
onPress={() => preventDoubleTap(this.goToUserProfile, this, member.id)}
style={style.profile}
>
<ProfilePicture
@@ -95,7 +89,7 @@ class ChannelIntro extends PureComponent {
return currentChannelMembers.map((member, index) => (
<TouchableOpacity
key={member.id}
onPress={preventDoubleTap(() => this.goToUserProfile(member.id))}
onPress={() => preventDoubleTap(this.goToUserProfile, this, member.id)}
>
<Text style={style.displayName}>
{index === currentChannelMembers.length - 1 ? this.getDisplayName(member) : `${this.getDisplayName(member)}, `}
@@ -115,9 +109,9 @@ class ChannelIntro extends PureComponent {
<Text style={style.message}>
{intl.formatMessage({
id: 'mobile.intro_messages.DM',
defaultMessage: 'This is the start of your direct message history with {teammate}. Direct messages and files shared here are not shown to people outside this area.',
defaultMessage: 'This is the start of your direct message history with {teammate}. Direct messages and files shared here are not shown to people outside this area.'
}, {
teammate,
teammate
})}
</Text>
);
@@ -134,7 +128,7 @@ class ChannelIntro extends PureComponent {
<Text style={style.message}>
{intl.formatMessage({
id: 'intro_messages.group_message',
defaultMessage: 'This is the start of your group message history with these teammates. Messages and files shared here are not shown to people outside this area.',
defaultMessage: 'This is the start of your group message history with these teammates. Messages and files shared here are not shown to people outside this area.'
})}
</Text>
);
@@ -147,7 +141,7 @@ class ChannelIntro extends PureComponent {
const date = intl.formatDate(currentChannel.create_at, {
year: 'numeric',
month: 'long',
day: 'numeric',
day: 'numeric'
});
let mainMessageIntl;
@@ -162,9 +156,9 @@ class ChannelIntro extends PureComponent {
date,
type: intl.formatMessage({
id: 'intro_messages.channel',
defaultMessage: 'channel',
}),
},
defaultMessage: 'channel'
})
}
};
} else {
mainMessageIntl = {
@@ -175,20 +169,20 @@ class ChannelIntro extends PureComponent {
date,
type: intl.formatMessage({
id: 'intro_messages.channel',
defaultMessage: 'channel',
}),
},
defaultMessage: 'channel'
})
}
};
}
const mainMessage = intl.formatMessage({
id: mainMessageIntl.id,
defaultMessage: mainMessageIntl.defaultMessage,
defaultMessage: mainMessageIntl.defaultMessage
}, mainMessageIntl.values);
const anyMemberMessage = intl.formatMessage({
id: 'intro_messages.anyMember',
defaultMessage: ' Any member can join and read this channel.',
defaultMessage: ' Any member can join and read this channel.'
});
return (
@@ -196,9 +190,9 @@ class ChannelIntro extends PureComponent {
<Text style={style.channelTitle}>
{intl.formatMessage({
id: 'intro_messages.beginning',
defaultMessage: 'Beginning of {name}',
defaultMessage: 'Beginning of {name}'
}, {
name: currentChannel.display_name,
name: currentChannel.display_name
})}
</Text>
<Text style={style.message}>
@@ -216,25 +210,25 @@ class ChannelIntro extends PureComponent {
const date = intl.formatDate(currentChannel.create_at, {
year: 'numeric',
month: 'long',
day: 'numeric',
day: 'numeric'
});
const mainMessage = intl.formatMessage({
id: 'intro_messages.creator',
defaultMessage: 'This is the start of the {name} {type}, created by {creator} on {date}.',
defaultMessage: 'This is the start of the {name} {type}, created by {creator} on {date}.'
}, {
name: currentChannel.display_name,
creator: creatorName,
date,
type: intl.formatMessage({
id: 'intro_messages.group',
defaultMessage: 'private channel',
}),
defaultMessage: 'private channel'
})
});
const onlyInvitedMessage = intl.formatMessage({
id: 'intro_messages.onlyInvited',
defaultMessage: ' Only invited members can see this private channel.',
defaultMessage: ' Only invited members can see this private channel.'
});
return (
@@ -242,9 +236,9 @@ class ChannelIntro extends PureComponent {
<Text style={style.channelTitle}>
{intl.formatMessage({
id: 'intro_messages.beginning',
defaultMessage: 'Beginning of {name}',
defaultMessage: 'Beginning of {name}'
}, {
name: currentChannel.display_name,
name: currentChannel.display_name
})}
</Text>
<Text style={style.message}>
@@ -263,23 +257,23 @@ class ChannelIntro extends PureComponent {
<Text style={style.channelTitle}>
{intl.formatMessage({
id: 'intro_messages.beginning',
defaultMessage: 'Beginning of {name}',
defaultMessage: 'Beginning of {name}'
}, {
name: currentChannel.display_name,
name: currentChannel.display_name
})}
</Text>
<Text style={style.channelWelcome}>
{intl.formatMessage({
id: 'mobile.intro_messages.default_welcome',
defaultMessage: 'Welcome to {name}!',
defaultMessage: 'Welcome to {name}!'
}, {
name: currentChannel.display_name,
name: currentChannel.display_name
})}
</Text>
<Text style={style.message}>
{intl.formatMessage({
id: 'mobile.intro_messages.default_message',
defaultMessage: 'This is the first channel teammates see when they sign up - use it for posting updates everyone needs to know.',
defaultMessage: 'This is the first channel teammates see when they sign up - use it for posting updates everyone needs to know.'
})}
</Text>
</View>
@@ -354,42 +348,42 @@ const getStyleSheet = makeStyleSheetFromTheme((theme) => {
color: theme.centerChannelColor,
fontSize: 19,
fontWeight: '600',
marginBottom: 12,
marginBottom: 12
},
channelWelcome: {
color: theme.centerChannelColor,
marginBottom: 12,
marginBottom: 12
},
container: {
marginTop: 60,
marginHorizontal: 12,
marginBottom: 12,
marginBottom: 12
},
displayName: {
color: theme.centerChannelColor,
fontSize: 15,
fontWeight: '600',
fontWeight: '600'
},
message: {
color: changeOpacity(theme.centerChannelColor, 0.8),
fontSize: 15,
lineHeight: 22,
lineHeight: 22
},
namesContainer: {
flexDirection: 'row',
flexWrap: 'wrap',
marginBottom: 12,
marginBottom: 12
},
profile: {
height: 67,
marginBottom: 12,
marginRight: 12,
marginRight: 12
},
profilesContainer: {
flexDirection: 'row',
flexWrap: 'wrap',
justifyContent: 'flex-start',
},
justifyContent: 'flex-start'
}
};
});

View File

@@ -10,14 +10,24 @@ import {getCurrentUserId, getUser, makeGetProfilesInChannel} from 'mattermost-re
import {getTheme} from 'mattermost-redux/selectors/entities/preferences';
import {getChannelMembersForDm} from 'app/selectors/channel';
import ChannelIntro from './channel_intro';
function makeMapStateToProps() {
const getChannel = makeGetChannel();
const getProfilesInChannel = makeGetProfilesInChannel();
const getOtherUserIdForDm = createSelector(
(state, channel) => channel,
getCurrentUserId,
(channel, currentUserId) => {
if (!channel) {
return '';
}
return channel.name.split('__').find((m) => m !== currentUserId) || currentUserId;
}
);
const getChannelMembers = createSelector(
getCurrentUserId,
(state, channel) => getProfilesInChannel(state, channel.id),
@@ -27,6 +37,17 @@ function makeMapStateToProps() {
}
);
const getChannelMembersForDm = createSelector(
(state, channel) => getUser(state, getOtherUserIdForDm(state, channel)),
(otherUser) => {
if (!otherUser) {
return [];
}
return [otherUser];
}
);
return function mapStateToProps(state, ownProps) {
const currentChannel = getChannel(state, {id: ownProps.channelId}) || {};
const {status: getPostsRequestStatus} = state.requests.posts.getPosts;
@@ -51,7 +72,7 @@ function makeMapStateToProps() {
currentChannel,
currentChannelMembers,
isLoadingPosts: (!postsInChannel || postsInChannel.length === 0) && getPostsRequestStatus === RequestStatus.STARTED,
theme: getTheme(state),
theme: getTheme(state)
};
};
}

View File

@@ -15,22 +15,22 @@ export default class ChannelLink extends React.PureComponent {
channelsByName: PropTypes.object.isRequired,
actions: PropTypes.shape({
handleSelectChannel: PropTypes.func.isRequired,
setChannelDisplayName: PropTypes.func.isRequired,
}).isRequired,
setChannelDisplayName: PropTypes.func.isRequired
}).isRequired
};
constructor(props) {
super(props);
this.state = {
channel: this.getChannelFromChannelName(props),
channel: this.getChannelFromChannelName(props)
};
}
componentWillReceiveProps(nextProps) {
if (nextProps.channelName !== this.props.channelName || nextProps.channelsByName !== this.props.channelsByName) {
this.setState({
channel: this.getChannelFromChannelName(nextProps),
channel: this.getChannelFromChannelName(nextProps)
});
}
}
@@ -63,7 +63,7 @@ export default class ChannelLink extends React.PureComponent {
const channel = this.state.channel;
if (!channel) {
return <Text style={this.props.textStyle}>{`~${this.props.channelName}`}</Text>;
return <Text style={this.props.textStyle}>{'~' + this.props.channelName}</Text>;
}
const suffix = this.props.channelName.substring(channel.name.length);
@@ -74,7 +74,7 @@ export default class ChannelLink extends React.PureComponent {
style={this.props.linkStyle}
onPress={this.handlePress}
>
{`~${channel.display_name}`}
{channel.display_name}
</Text>
{suffix}
</Text>

View File

@@ -12,7 +12,7 @@ import ChannelLink from './channel_link';
function mapStateToProps(state) {
return {
channelsByName: getChannelsNameMapInCurrentTeam(state),
channelsByName: getChannelsNameMapInCurrentTeam(state)
};
}
@@ -20,8 +20,8 @@ function mapDispatchToProps(dispatch) {
return {
actions: bindActionCreators({
handleSelectChannel,
setChannelDisplayName,
}, dispatch),
setChannelDisplayName
}, dispatch)
};
}

View File

@@ -5,7 +5,7 @@ import React, {PureComponent} from 'react';
import PropTypes from 'prop-types';
import {
Platform,
View,
View
} from 'react-native';
import LinearGradient from 'react-native-linear-gradient';
@@ -19,7 +19,7 @@ export default class ChannelLoader extends PureComponent {
static propTypes = {
channelIsLoading: PropTypes.bool.isRequired,
deviceWidth: PropTypes.number.isRequired,
theme: PropTypes.object.isRequired,
theme: PropTypes.object.isRequired
};
buildSections(key, style, top) {
@@ -36,7 +36,7 @@ export default class ChannelLoader extends PureComponent {
colors={[
changeOpacity('#e5e5e4', GRADIENT_START),
changeOpacity('#d6d6d5', GRADIENT_MIDDLE),
changeOpacity('#e5e5e4', GRADIENT_END),
changeOpacity('#e5e5e4', GRADIENT_END)
]}
locations={[0.1, 0.3, 0.7]}
style={[style.messageText, {width: 106}]}
@@ -47,7 +47,7 @@ export default class ChannelLoader extends PureComponent {
colors={[
changeOpacity('#e5e5e4', GRADIENT_START),
changeOpacity('#d6d6d5', GRADIENT_MIDDLE),
changeOpacity('#e5e5e4', GRADIENT_END),
changeOpacity('#e5e5e4', GRADIENT_END)
]}
locations={[0.1, 0.3, 0.7]}
style={[style.messageText, {alignSelf: 'stretch'}]}
@@ -58,7 +58,7 @@ export default class ChannelLoader extends PureComponent {
colors={[
changeOpacity('#e5e5e4', GRADIENT_START),
changeOpacity('#d6d6d5', GRADIENT_MIDDLE),
changeOpacity('#e5e5e4', GRADIENT_END),
changeOpacity('#e5e5e4', GRADIENT_END)
]}
locations={[0.1, 0.3, 0.7]}
style={[style.messageText, {alignSelf: 'stretch'}]}
@@ -93,35 +93,35 @@ const getStyleSheet = makeStyleSheetFromTheme((theme) => {
position: 'absolute',
...Platform.select({
android: {
top: 0,
top: 0
},
ios: {
top: 15,
},
}),
top: 15
}
})
},
avatar: {
backgroundColor: changeOpacity(theme.centerChannelColor, 0.1),
borderRadius: 16,
height: 32,
width: 32,
width: 32
},
messageText: {
backgroundColor: changeOpacity(theme.centerChannelColor, 0.1),
height: 10,
marginBottom: 10,
marginBottom: 10
},
section: {
backgroundColor: theme.centerChannelBg,
flexDirection: 'row',
paddingLeft: 12,
paddingRight: 20,
marginVertical: 10,
marginVertical: 10
},
sectionMessage: {
marginLeft: 12,
flex: 1,
},
flex: 1
}
};
});

View File

@@ -11,7 +11,7 @@ function mapStateToProps(state) {
return {
channelIsLoading: state.views.channel.loading,
deviceWidth,
theme: getTheme(state),
theme: getTheme(state)
};
}

View File

@@ -9,7 +9,7 @@ export default class CheckMark extends PureComponent {
static propTypes = {
width: PropTypes.number.isRequired,
height: PropTypes.number.isRequired,
color: PropTypes.string.isRequired,
color: PropTypes.string.isRequired
};
render() {

View File

@@ -8,7 +8,7 @@ import {
Animated,
Linking,
TouchableOpacity,
View,
View
} from 'react-native';
import {intlShape} from 'react-intl';
import DeviceInfo from 'react-native-device-info';
@@ -27,7 +27,7 @@ export default class ClientUpgradeListener extends PureComponent {
static propTypes = {
actions: PropTypes.shape({
logError: PropTypes.func.isRequired,
setLastUpgradeCheck: PropTypes.func.isRequired,
setLastUpgradeCheck: PropTypes.func.isRequired
}).isRequired,
currentVersion: PropTypes.string,
downloadLink: PropTypes.string,
@@ -37,11 +37,11 @@ export default class ClientUpgradeListener extends PureComponent {
latestVersion: PropTypes.string,
minVersion: PropTypes.string,
navigator: PropTypes.object,
theme: PropTypes.object.isRequired,
theme: PropTypes.object.isRequired
};
static contextTypes = {
intl: intlShape,
intl: intlShape
};
constructor(props) {
@@ -54,7 +54,7 @@ export default class ClientUpgradeListener extends PureComponent {
});
this.state = {
top: new Animated.Value(-100),
top: new Animated.Value(-100)
};
}
@@ -108,7 +108,7 @@ export default class ClientUpgradeListener extends PureComponent {
}
Animated.timing(this.state.top, {
toValue,
duration: 300,
duration: 300
}).start();
};
@@ -128,11 +128,11 @@ export default class ClientUpgradeListener extends PureComponent {
Alert.alert(
intl.formatMessage({
id: 'mobile.client_upgrade.download_error.title',
defaultMessage: 'Upgrade Error',
defaultMessage: 'Upgrade Error'
}),
intl.formatMessage({
id: 'mobile.client_upgrade.download_error.message',
defaultMessage: 'An error occurred while trying to open the download link.',
defaultMessage: 'An error occurred while trying to open the download link.'
})
);
@@ -152,17 +152,17 @@ export default class ClientUpgradeListener extends PureComponent {
navigatorStyle: {
navBarHidden: false,
statusBarHidden: false,
statusBarHideWithNavBar: false,
statusBarHideWithNavBar: false
},
navigatorButtons: {
leftButtons: [{
id: 'close-upgrade',
icon: this.closeButton,
}],
icon: this.closeButton
}]
},
passProps: {
upgradeType: this.state.upgradeType,
},
upgradeType: this.state.upgradeType
}
});
this.toggleUpgradeMessage(false);
@@ -226,28 +226,28 @@ const getStyleSheet = makeStyleSheetFromTheme((theme) => {
justifyContent: 'space-around',
borderTopColor: changeOpacity(theme.centerChannelColor, 0.2),
backgroundColor: changeOpacity(theme.centerChannelColor, 0.06),
borderTopWidth: 1,
borderTopWidth: 1
},
button: {
color: theme.linkColor,
fontSize: 13,
paddingHorizontal: 5,
paddingVertical: 5,
paddingVertical: 5
},
container: {
flex: 1,
backgroundColor: changeOpacity(theme.centerChannelBg, 0.8),
borderRadius: 5,
borderRadius: 5
},
message: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
alignItems: 'center'
},
messageText: {
fontSize: 16,
color: changeOpacity(theme.centerChannelColor, 0.8),
fontWeight: '600',
fontWeight: '600'
},
wrapper: {
position: 'absolute',
@@ -262,10 +262,10 @@ const getStyleSheet = makeStyleSheetFromTheme((theme) => {
shadowColor: theme.centerChannelColor,
shadowOffset: {
width: 0,
height: 3,
height: 3
},
shadowOpacity: 0.2,
shadowRadius: 2,
},
shadowRadius: 2
}
};
});

View File

@@ -21,7 +21,7 @@ function mapStateToProps(state) {
lastUpgradeCheck: state.views.clientUpgrade.lastUpdateCheck,
latestVersion,
minVersion,
theme: getTheme(state),
theme: getTheme(state)
};
}
@@ -29,8 +29,8 @@ function mapDispatchToProps(dispatch) {
return {
actions: bindActionCreators({
logError,
setLastUpgradeCheck,
}, dispatch),
setLastUpgradeCheck
}, dispatch)
};
}

View File

@@ -10,7 +10,7 @@ import CustomPropTypes from 'app/constants/custom_prop_types';
export default class ConditionalTouchable extends React.PureComponent {
static propTypes = {
touchable: PropTypes.bool,
children: CustomPropTypes.Children.isRequired,
children: CustomPropTypes.Children.isRequired
};
render() {

View File

@@ -5,7 +5,7 @@ import React from 'react';
import PropTypes from 'prop-types';
import {
Text,
View,
View
} from 'react-native';
import Icon from 'react-native-vector-icons/FontAwesome';
import {makeStyleSheetFromTheme, changeOpacity} from 'app/utils/theme';
@@ -17,7 +17,7 @@ export default class ChannelListRow extends React.PureComponent {
id: PropTypes.string.isRequired,
theme: PropTypes.object.isRequired,
channel: PropTypes.object.isRequired,
...CustomListRow.propTypes,
...CustomListRow.propTypes
};
onPress = () => {
@@ -70,25 +70,25 @@ const getStyleFromTheme = makeStyleSheetFromTheme((theme) => {
return {
titleContainer: {
alignItems: 'center',
flexDirection: 'row',
flexDirection: 'row'
},
displayName: {
fontSize: 16,
color: theme.centerChannelColor,
marginLeft: 5,
marginLeft: 5
},
icon: {
fontSize: 16,
color: theme.centerChannelColor,
color: theme.centerChannelColor
},
container: {
flex: 1,
flexDirection: 'column',
flexDirection: 'column'
},
purpose: {
marginTop: 7,
fontSize: 13,
color: changeOpacity(theme.centerChannelColor, 0.5),
},
color: changeOpacity(theme.centerChannelColor, 0.5)
}
};
});

View File

@@ -15,7 +15,7 @@ function makeMapStateToProps() {
return (state, ownProps) => {
return {
theme: getTheme(state),
channel: getChannel(state, ownProps),
channel: getChannel(state, ownProps)
};
};
}

View File

@@ -4,7 +4,7 @@
import PropTypes from 'prop-types';
import React from 'react';
import {
View,
View
} from 'react-native';
import Icon from 'react-native-vector-icons/FontAwesome';
@@ -19,11 +19,11 @@ export default class CustomListRow extends React.PureComponent {
enabled: PropTypes.bool,
selectable: PropTypes.bool,
selected: PropTypes.bool,
children: CustomPropTypes.Children,
children: CustomPropTypes.Children
};
static defaultProps = {
enabled: true,
enabled: true
};
render() {
@@ -62,11 +62,11 @@ const getStyleFromTheme = makeStyleSheetFromTheme((theme) => {
height: 65,
paddingHorizontal: 15,
alignItems: 'center',
backgroundColor: theme.centerChannelBg,
backgroundColor: theme.centerChannelBg
},
displayName: {
fontSize: 15,
color: theme.centerChannelColor,
color: theme.centerChannelColor
},
selector: {
height: 28,
@@ -75,20 +75,20 @@ const getStyleFromTheme = makeStyleSheetFromTheme((theme) => {
borderWidth: 1,
borderColor: '#888',
alignItems: 'center',
justifyContent: 'center',
justifyContent: 'center'
},
selectorContainer: {
height: 50,
paddingRight: 15,
alignItems: 'center',
justifyContent: 'center',
justifyContent: 'center'
},
selectorDisabled: {
backgroundColor: '#888',
backgroundColor: '#888'
},
selectorFilled: {
backgroundColor: '#378FD2',
borderWidth: 0,
},
borderWidth: 0
}
};
});

View File

@@ -26,7 +26,7 @@ export default class CustomList extends PureComponent {
onRowSelect: PropTypes.func,
renderRow: PropTypes.func.isRequired,
createSections: PropTypes.func,
showNoResults: PropTypes.bool,
showNoResults: PropTypes.bool
};
static defaultProps = {
@@ -40,7 +40,7 @@ export default class CustomList extends PureComponent {
selectable: false,
createSections: () => true,
showSections: true,
showNoResults: true,
showNoResults: true
};
constructor(props) {
@@ -64,7 +64,7 @@ export default class CustomList extends PureComponent {
const dataSource = showSections ? this.state.dataSource.cloneWithRowsAndSections(mergedData) : this.state.dataSource.cloneWithRows(mergedData);
this.setState({
data: mergedData,
dataSource,
dataSource
});
}
}
@@ -72,7 +72,7 @@ export default class CustomList extends PureComponent {
buildDataSource = (props) => {
const ds = new ListView.DataSource({
rowHasChanged: (r1, r2) => r1 !== r2,
sectionHeaderHasChanged: (s1, s2) => s1 !== s2,
sectionHeaderHasChanged: (s1, s2) => s1 !== s2
});
let newData = props.data;
if (props.showSections) {
@@ -81,7 +81,7 @@ export default class CustomList extends PureComponent {
const dataSource = props.showSections ? ds.cloneWithRowsAndSections(newData) : ds.cloneWithRows(newData);
return {
data: newData,
dataSource,
dataSource
};
};
@@ -97,7 +97,7 @@ export default class CustomList extends PureComponent {
const dataSource = this.state.dataSource.cloneWithRowsAndSections(mergedData);
this.setState({
data: mergedData,
dataSource,
dataSource
}, () => this.props.onRowSelect(id));
};
@@ -118,7 +118,7 @@ export default class CustomList extends PureComponent {
item,
selected: item.selected,
selectable: this.props.selectable,
onPress: this.props.onRowPress,
onPress: this.props.onRowPress
};
if ('disableSelect' in item) {
@@ -181,7 +181,7 @@ export default class CustomList extends PureComponent {
searching,
showNoResults,
showSections,
theme,
theme
} = this.props;
const {dataSource} = this.state;
const style = getStyleFromTheme(theme);
@@ -245,45 +245,45 @@ const getStyleFromTheme = makeStyleSheetFromTheme((theme) => {
backgroundColor: theme.centerChannelBg,
...Platform.select({
android: {
marginBottom: 20,
},
}),
marginBottom: 20
}
})
},
loadingText: {
color: changeOpacity(theme.centerChannelColor, 0.6),
color: changeOpacity(theme.centerChannelColor, 0.6)
},
searching: {
backgroundColor: theme.centerChannelBg,
height: '100%',
position: 'absolute',
width: '100%',
width: '100%'
},
sectionContainer: {
backgroundColor: changeOpacity(theme.centerChannelColor, 0.07),
paddingLeft: 10,
paddingVertical: 2,
paddingVertical: 2
},
sectionWrapper: {
backgroundColor: theme.centerChannelBg,
backgroundColor: theme.centerChannelBg
},
sectionText: {
fontWeight: '600',
color: theme.centerChannelColor,
color: theme.centerChannelColor
},
separator: {
height: 1,
flex: 1,
backgroundColor: changeOpacity(theme.centerChannelColor, 0.1),
backgroundColor: changeOpacity(theme.centerChannelColor, 0.1)
},
noResultContainer: {
flex: 1,
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'center',
justifyContent: 'center'
},
noResultText: {
fontSize: 26,
color: changeOpacity(theme.centerChannelColor, 0.5),
},
color: changeOpacity(theme.centerChannelColor, 0.5)
}
};
});

View File

@@ -13,7 +13,7 @@ function mapStateToProps(state, ownProps) {
isMyUser: getCurrentUserId(state) === ownProps.id,
theme: getTheme(state),
user: getUser(state, ownProps.id),
teammateNameDisplay: getTeammateNameDisplaySetting(state),
teammateNameDisplay: getTeammateNameDisplaySetting(state)
};
}

View File

@@ -6,7 +6,7 @@ import PropTypes from 'prop-types';
import {intlShape} from 'react-intl';
import {
Text,
View,
View
} from 'react-native';
import ProfilePicture from 'app/components/profile_picture';
import {makeStyleSheetFromTheme, changeOpacity} from 'app/utils/theme';
@@ -22,11 +22,11 @@ export default class UserListRow extends React.PureComponent {
theme: PropTypes.object.isRequired,
user: PropTypes.object.isRequired,
teammateNameDisplay: PropTypes.string.isRequired,
...CustomListRow.propTypes,
...CustomListRow.propTypes
};
static contextTypes = {
intl: intlShape,
intl: intlShape
};
onPress = () => {
@@ -44,7 +44,7 @@ export default class UserListRow extends React.PureComponent {
selected,
teammateNameDisplay,
theme,
user,
user
} = this.props;
const {id, username} = user;
@@ -54,7 +54,7 @@ export default class UserListRow extends React.PureComponent {
if (isMyUser) {
usernameDisplay = formatMessage({
id: 'mobile.more_dms.you',
defaultMessage: '(@{username} - you)',
defaultMessage: '(@{username} - you)'
}, {username});
}
@@ -99,24 +99,24 @@ const getStyleFromTheme = makeStyleSheetFromTheme((theme) => {
height: 65,
paddingHorizontal: 15,
alignItems: 'center',
backgroundColor: theme.centerChannelBg,
backgroundColor: theme.centerChannelBg
},
displayName: {
fontSize: 15,
color: theme.centerChannelColor,
color: theme.centerChannelColor
},
icon: {
fontSize: 20,
color: theme.centerChannelColor,
color: theme.centerChannelColor
},
textContainer: {
flexDirection: 'row',
marginLeft: 5,
marginLeft: 5
},
username: {
marginLeft: 5,
fontSize: 15,
color: changeOpacity(theme.centerChannelColor, 0.5),
color: changeOpacity(theme.centerChannelColor, 0.5)
},
selector: {
height: 28,
@@ -125,20 +125,20 @@ const getStyleFromTheme = makeStyleSheetFromTheme((theme) => {
borderWidth: 1,
borderColor: '#888',
alignItems: 'center',
justifyContent: 'center',
justifyContent: 'center'
},
selectorContainer: {
height: 50,
paddingRight: 15,
alignItems: 'center',
justifyContent: 'center',
justifyContent: 'center'
},
selectorDisabled: {
backgroundColor: '#888',
backgroundColor: '#888'
},
selectorFilled: {
backgroundColor: '#378FD2',
borderWidth: 0,
},
borderWidth: 0
}
};
});

View File

@@ -6,7 +6,7 @@ import {
Platform,
SectionList,
Text,
View,
View
} from 'react-native';
import Loading from 'app/components/loading';
@@ -85,7 +85,7 @@ export default class CustomSectionList extends React.PureComponent {
/*
* How many items to render when the list is first rendered.
*/
initialNumToRender: PropTypes.number,
initialNumToRender: PropTypes.number
};
static defaultProps = {
@@ -96,21 +96,21 @@ export default class CustomSectionList extends React.PureComponent {
onListEndReached: () => true,
onListEndReachedThreshold: 50,
loadingText: null,
initialNumToRender: 10,
initialNumToRender: 10
};
constructor(props) {
super(props);
this.state = {
sections: this.extractSections(props.items),
sections: this.extractSections(props.items)
};
}
componentWillReceiveProps(nextProps) {
if (nextProps.items !== this.props.items) {
this.setState({
sections: this.extractSections(nextProps.items),
sections: this.extractSections(nextProps.items)
});
}
}
@@ -134,7 +134,7 @@ export default class CustomSectionList extends React.PureComponent {
return sectionKeys.map((sectionKey) => {
return {
key: sectionKey,
data: sections[sectionKey].sort(this.props.compareItems),
data: sections[sectionKey].sort(this.props.compareItems)
};
});
}
@@ -156,7 +156,7 @@ export default class CustomSectionList extends React.PureComponent {
const props = {
id: item.id,
item,
onPress: this.props.onRowPress,
onPress: this.props.onRowPress
};
// Allow passing in a component like UserListRow or ChannelListRow
@@ -258,51 +258,51 @@ const getStyleFromTheme = makeStyleSheetFromTheme((theme) => {
backgroundColor: theme.centerChannelBg,
...Platform.select({
android: {
marginBottom: 20,
},
}),
marginBottom: 20
}
})
},
loading: {
height: 70,
backgroundColor: theme.centerChannelBg,
alignItems: 'center',
justifyContent: 'center',
justifyContent: 'center'
},
loadingText: {
color: changeOpacity(theme.centerChannelColor, 0.6),
color: changeOpacity(theme.centerChannelColor, 0.6)
},
searching: {
backgroundColor: theme.centerChannelBg,
height: '100%',
position: 'absolute',
width: '100%',
width: '100%'
},
sectionContainer: {
backgroundColor: changeOpacity(theme.centerChannelColor, 0.07),
paddingLeft: 10,
paddingVertical: 2,
paddingVertical: 2
},
sectionWrapper: {
backgroundColor: theme.centerChannelBg,
backgroundColor: theme.centerChannelBg
},
sectionText: {
fontWeight: '600',
color: theme.centerChannelColor,
color: theme.centerChannelColor
},
separator: {
height: 1,
flex: 1,
backgroundColor: changeOpacity(theme.centerChannelColor, 0.1),
backgroundColor: changeOpacity(theme.centerChannelColor, 0.1)
},
noResultContainer: {
flex: 1,
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'center',
justifyContent: 'center'
},
noResultText: {
fontSize: 26,
color: changeOpacity(theme.centerChannelColor, 0.5),
},
color: changeOpacity(theme.centerChannelColor, 0.5)
}
};
});

View File

@@ -11,7 +11,7 @@ export default class Drawer extends BaseDrawer {
...BaseDrawer.propTypes,
onRequestClose: PropTypes.func.isRequired,
bottomPanOffset: PropTypes.number,
topPanOffset: PropTypes.number,
topPanOffset: PropTypes.number
};
constructor(props) {

View File

@@ -8,7 +8,7 @@ import {
TouchableWithoutFeedback,
View,
Text,
findNodeHandle,
findNodeHandle
} from 'react-native';
import {KeyboardAwareScrollView} from 'react-native-keyboard-aware-scroll-view';
@@ -47,11 +47,11 @@ export default class EditChannelInfo extends PureComponent {
oldDisplayName: PropTypes.string,
oldChannelURL: PropTypes.string,
oldHeader: PropTypes.string,
oldPurpose: PropTypes.string,
oldPurpose: PropTypes.string
};
static defaultProps = {
editing: false,
editing: false
};
blur = () => {
@@ -96,7 +96,7 @@ export default class EditChannelInfo extends PureComponent {
this.props.navigator.pop({animated: true});
} else {
this.props.navigator.dismissModal({
animationType: 'slide-down',
animationType: 'slide-down'
});
}
};
@@ -110,7 +110,7 @@ export default class EditChannelInfo extends PureComponent {
oldDisplayName,
oldChannelURL,
oldPurpose,
oldHeader,
oldHeader
} = this.props;
return displayName !== oldDisplayName || channelURL !== oldChannelURL ||
@@ -190,7 +190,7 @@ export default class EditChannelInfo extends PureComponent {
displayName,
channelURL,
header,
purpose,
purpose
} = this.props;
const {error, saving} = this.props;
const fullUrl = currentTeamUrl + '/channels';
@@ -213,7 +213,7 @@ export default class EditChannelInfo extends PureComponent {
let displayError;
if (error) {
displayError = (
<View style={[style.errorContainer, {width: deviceWidth}]}>
<View style={[style.errorContainer, {deviceWidth}]}>
<View style={style.errorWrapper}>
<ErrorText error={error}/>
</View>
@@ -374,54 +374,54 @@ const getStyleSheet = makeStyleSheetFromTheme((theme) => {
return {
container: {
flex: 1,
backgroundColor: theme.centerChannelBg,
backgroundColor: theme.centerChannelBg
},
scrollView: {
flex: 1,
backgroundColor: changeOpacity(theme.centerChannelColor, 0.03),
paddingTop: 10,
paddingTop: 10
},
errorContainer: {
backgroundColor: changeOpacity(theme.centerChannelColor, 0.03),
backgroundColor: changeOpacity(theme.centerChannelColor, 0.03)
},
errorWrapper: {
justifyContent: 'center',
alignItems: 'center',
alignItems: 'center'
},
inputContainer: {
marginTop: 10,
backgroundColor: '#fff',
backgroundColor: '#fff'
},
input: {
color: '#333',
fontSize: 14,
height: 40,
paddingHorizontal: 15,
paddingHorizontal: 15
},
titleContainer30: {
flexDirection: 'row',
marginTop: 30,
marginTop: 30
},
titleContainer15: {
flexDirection: 'row',
marginTop: 15,
marginTop: 15
},
title: {
fontSize: 14,
color: theme.centerChannelColor,
marginLeft: 15,
marginLeft: 15
},
optional: {
color: changeOpacity(theme.centerChannelColor, 0.5),
fontSize: 14,
marginLeft: 5,
marginLeft: 5
},
helpText: {
fontSize: 14,
color: changeOpacity(theme.centerChannelColor, 0.5),
marginTop: 10,
marginHorizontal: 15,
},
marginHorizontal: 15
}
};
});

View File

@@ -3,86 +3,60 @@
import React from 'react';
import PropTypes from 'prop-types';
import {
Image,
PixelRatio,
Platform,
StyleSheet,
Text,
} from 'react-native';
import {Image, Platform, Text} from 'react-native';
import FastImage from 'react-native-fast-image';
import CustomPropTypes from 'app/constants/custom_prop_types';
import {EmojiIndicesByAlias, Emojis} from 'app/utils/emojis';
const scaleEmojiBasedOnDevice = (size) => {
if (Platform.OS === 'ios') {
return size * 1.1; // slightly larger emojis look better on ios
}
return size * PixelRatio.get();
};
import {Client4} from 'mattermost-redux/client';
export default class Emoji extends React.PureComponent {
static propTypes = {
/*
* Emoji text name.
*/
customEmojis: PropTypes.object,
emojiName: PropTypes.string.isRequired,
/*
* Image URL for the emoji.
*/
imageUrl: PropTypes.string.isRequired,
/*
* Set if this is a custom emoji.
*/
isCustomEmoji: PropTypes.bool.isRequired,
/*
* Set to render only the text and no image.
*/
displayTextOnly: PropTypes.bool,
fontSize: PropTypes.number,
literal: PropTypes.string,
size: PropTypes.number,
size: PropTypes.number.isRequired,
textStyle: CustomPropTypes.Style,
token: PropTypes.string.isRequired,
token: PropTypes.string.isRequired
};
static defaultProps = {
customEmojis: new Map(),
literal: '',
imageUrl: '',
isCustomEmoji: false,
literal: ''
};
constructor(props) {
super(props);
this.state = {
...this.getImageUrl(props),
originalWidth: 0,
originalHeight: 0,
originalHeight: 0
};
}
componentWillMount() {
this.mounted = true;
if (!this.props.displayTextOnly && this.props.imageUrl && this.props.isCustomEmoji) {
this.updateImageHeight(this.props.imageUrl);
if (this.state.imageUrl && this.state.isCustomEmoji) {
this.updateImageHeight(this.state.imageUrl);
}
}
componentWillReceiveProps(nextProps) {
if (nextProps.emojiName !== this.props.emojiName) {
if (nextProps.customEmojis !== this.props.customEmojis || nextProps.emojiName !== this.props.emojiName) {
this.setState({
...this.getImageUrl(nextProps),
originalWidth: 0,
originalHeight: 0,
originalHeight: 0
});
}
}
if (!nextProps.displayTextOnly && nextProps.imageUrl && nextProps.isCustomEmoji &&
nextProps.imageUrl !== this.props.imageUrl) {
this.updateImageHeight(nextProps.imageUrl);
componentWillUpdate(nextProps, nextState) {
if (nextState.imageUrl !== this.state.imageUrl && nextState.imageUrl && nextState.isCustomEmoji) {
this.updateImageHeight(nextState.imageUrl);
}
}
@@ -90,12 +64,34 @@ export default class Emoji extends React.PureComponent {
this.mounted = false;
}
getImageUrl = (props = this.props) => {
const emojiName = props.emojiName;
let imageUrl = '';
let isCustomEmoji = false;
if (EmojiIndicesByAlias.has(emojiName)) {
const emoji = Emojis[EmojiIndicesByAlias.get(emojiName)];
imageUrl = Client4.getSystemEmojiImageUrl(emoji.filename);
} else if (props.customEmojis.has(emojiName)) {
const emoji = props.customEmojis.get(emojiName);
imageUrl = Client4.getCustomEmojiImageUrl(emoji.id);
isCustomEmoji = true;
}
return {
imageUrl,
isCustomEmoji
};
}
updateImageHeight = (imageUrl) => {
Image.getSize(imageUrl, (originalWidth, originalHeight) => {
if (this.mounted) {
this.setState({
originalWidth,
originalHeight,
originalHeight
});
}
});
@@ -103,60 +99,17 @@ export default class Emoji extends React.PureComponent {
render() {
const {
fontSize,
literal,
size,
textStyle,
token,
imageUrl,
displayTextOnly,
token
} = this.props;
let size = this.props.size;
let fontSize = size;
if (!size && textStyle) {
const flatten = StyleSheet.flatten(textStyle);
fontSize = flatten.fontSize;
size = scaleEmojiBasedOnDevice(fontSize);
}
if (displayTextOnly) {
if (!this.state.imageUrl) {
return <Text style={textStyle}>{literal}</Text>;
}
const source = {
uri: imageUrl,
headers: {
Authorization: `Bearer ${token}`,
},
};
let width = size;
let height = size;
let {originalHeight, originalWidth} = this.state;
originalHeight = scaleEmojiBasedOnDevice(originalHeight);
originalWidth = scaleEmojiBasedOnDevice(originalWidth);
if (originalHeight && originalWidth) {
if (originalWidth > originalHeight) {
height = (size * originalHeight) / originalWidth;
} else if (originalWidth < originalHeight) {
// This may cause text to reflow, but its impossible to add a horizontal margin
width = (size * originalWidth) / originalHeight;
}
}
let marginTop = 0;
if (textStyle) {
// hack to get the vertical alignment looking better
if (fontSize > 16) {
marginTop -= 2;
} else if (fontSize <= 16) {
marginTop += 1;
}
}
// Android can't change the size of an image after its first render, so
// force a new image to be rendered when the size changes
const key = Platform.OS === 'android' ? (height + '-' + width) : null;
let ImageComponent;
if (Platform.OS === 'android') {
ImageComponent = Image;
@@ -164,15 +117,41 @@ export default class Emoji extends React.PureComponent {
ImageComponent = FastImage;
}
if (!imageUrl) {
return (
<ImageComponent
key={key}
style={{width, height, marginTop}}
/>
);
const source = {
uri: this.state.imageUrl,
headers: {
Authorization: `Bearer ${token}`
}
};
let width = size;
let height = size;
if (this.state.originalHeight && this.state.originalWidth) {
if (this.state.originalWidth > this.state.originalHeight) {
height = (size * this.state.originalHeight) / this.state.originalWidth;
} else if (this.state.originalWidth < this.state.originalHeight) {
// This may cause text to reflow, but its impossible to add a horizontal margin
width = (size * this.state.originalWidth) / this.state.originalHeight;
}
}
let marginTop = 0;
if (fontSize) {
// Center the image vertically on iOS (does nothing on Android)
marginTop = (height - 16) / 2;
// hack to get the vertical alignment looking better
if (fontSize === 17) {
marginTop -= 2;
} else if (fontSize === 15) {
marginTop += 1;
}
}
// Android can't change the size of an image after its first render, so
// force a new image to be rendered when the size changes
const key = Platform.OS === 'android' ? (height + '-' + width) : null;
return (
<ImageComponent
key={key}

View File

@@ -4,41 +4,13 @@
import {connect} from 'react-redux';
import {getCustomEmojisByName} from 'mattermost-redux/selectors/entities/emojis';
import {getCurrentUserId} from 'mattermost-redux/selectors/entities/users';
import {getConfig} from 'mattermost-redux/selectors/entities/general';
import {Client4} from 'mattermost-redux/client';
import {isMinimumServerVersion} from 'mattermost-redux/utils/helpers';
import {EmojiIndicesByAlias, Emojis} from 'app/utils/emojis';
import Emoji from './emoji';
function mapStateToProps(state, ownProps) {
const emojiName = ownProps.emojiName;
const customEmojis = getCustomEmojisByName(state);
let imageUrl = '';
let isCustomEmoji = false;
let displayTextOnly = false;
if (EmojiIndicesByAlias.has(emojiName)) {
const emoji = Emojis[EmojiIndicesByAlias.get(emojiName)];
imageUrl = Client4.getSystemEmojiImageUrl(emoji.filename);
} else if (customEmojis.has(emojiName)) {
const emoji = customEmojis.get(emojiName);
imageUrl = Client4.getCustomEmojiImageUrl(emoji.id);
isCustomEmoji = true;
} else {
displayTextOnly = state.entities.emojis.nonExistentEmoji.has(emojiName) ||
getConfig(state).EnableCustomEmoji !== 'true' ||
getCurrentUserId(state) === '' ||
!isMinimumServerVersion(Client4.getServerVersion(), 4, 7);
}
function mapStateToProps(state) {
return {
imageUrl,
isCustomEmoji,
displayTextOnly,
token: state.entities.general.credentials.token,
customEmojis: getCustomEmojisByName(state),
token: state.entities.general.credentials.token
};
}

View File

@@ -5,21 +5,18 @@ import React, {PureComponent} from 'react';
import PropTypes from 'prop-types';
import {intlShape} from 'react-intl';
import {
ActivityIndicator,
FlatList,
KeyboardAvoidingView,
Platform,
SectionList,
Text,
TouchableOpacity,
View,
View
} from 'react-native';
import DeviceInfo from 'react-native-device-info';
import FontAwesomeIcon from 'react-native-vector-icons/FontAwesome';
import sectionListGetItemLayout from 'react-native-section-list-get-item-layout';
import {isMinimumServerVersion} from 'mattermost-redux/utils/helpers';
import Emoji from 'app/components/emoji';
import FormattedText from 'app/components/formatted_text';
import SafeAreaView from 'app/components/safe_area_view';
@@ -33,33 +30,24 @@ const EMOJI_SIZE = 30;
const EMOJI_GUTTER = 7.5;
const SECTION_MARGIN = 15;
const SECTION_HEADER_HEIGHT = 28;
const EMOJIS_PER_PAGE = 200;
export default class EmojiPicker extends PureComponent {
static propTypes = {
customEmojisEnabled: PropTypes.bool.isRequired,
customEmojiPage: PropTypes.number.isRequired,
deviceWidth: PropTypes.number.isRequired,
fuse: PropTypes.object.isRequired,
emojis: PropTypes.array.isRequired,
emojisBySection: PropTypes.array.isRequired,
fuse: PropTypes.object.isRequired,
deviceWidth: PropTypes.number.isRequired,
isLandscape: PropTypes.bool.isRequired,
onEmojiPress: PropTypes.func,
serverVersion: PropTypes.string,
theme: PropTypes.object.isRequired,
actions: PropTypes.shape({
getCustomEmojis: PropTypes.func.isRequired,
incrementEmojiPickerPage: PropTypes.func.isRequired,
searchCustomEmojis: PropTypes.func.isRequired,
}).isRequired,
theme: PropTypes.object.isRequired
};
static defaultProps = {
onEmojiPress: emptyFunction,
onEmojiPress: emptyFunction
};
static contextTypes = {
intl: intlShape.isRequired,
intl: intlShape.isRequired
};
constructor(props) {
@@ -69,7 +57,7 @@ export default class EmojiPicker extends PureComponent {
getItemHeight: () => {
return EMOJI_SIZE + (EMOJI_GUTTER * 2);
},
getSectionHeaderHeight: () => SECTION_HEADER_HEIGHT,
getSectionHeaderHeight: () => SECTION_HEADER_HEIGHT
});
const emojis = this.renderableEmojis(props.emojisBySection, props.deviceWidth);
@@ -82,31 +70,20 @@ export default class EmojiPicker extends PureComponent {
emojiSectionIndexByOffset,
filteredEmojis: [],
searchTerm: '',
currentSectionIndex: 0,
missingPages: isMinimumServerVersion(this.props.serverVersion, 4, 7),
currentSectionIndex: 0
};
}
componentWillReceiveProps(nextProps) {
let rebuildEmojis = false;
if (this.props.deviceWidth !== nextProps.deviceWidth) {
rebuildEmojis = true;
this.setState({
emojis: this.renderableEmojis(this.props.emojisBySection, nextProps.deviceWidth)
});
if (this.refs.search_bar) {
this.refs.search_bar.blur();
}
}
if (this.props.emojis !== nextProps.emojis) {
rebuildEmojis = true;
}
if (rebuildEmojis) {
const emojis = this.renderableEmojis(this.props.emojisBySection, nextProps.deviceWidth);
this.setState({
emojis,
});
}
}
renderableEmojis = (emojis, deviceWidth) => {
@@ -116,7 +93,7 @@ export default class EmojiPicker extends PureComponent {
const data = [];
let row = {
key: `${section.key}-0`,
items: [],
items: []
};
section.data.forEach((emoji, index) => {
@@ -124,7 +101,7 @@ export default class EmojiPicker extends PureComponent {
data.push(row);
row = {
key: `${section.key}-${index}`,
items: [],
items: []
};
}
@@ -143,7 +120,7 @@ export default class EmojiPicker extends PureComponent {
return {
...section,
data,
data
};
});
@@ -162,34 +139,24 @@ export default class EmojiPicker extends PureComponent {
};
changeSearchTerm = (text) => {
const nextState = {
searchTerm: text,
};
if (!text) {
nextState.currentSectionIndex = 0;
}
this.setState(nextState);
this.setState({
searchTerm: text
});
clearTimeout(this.searchTermTimeout);
const timeout = text ? 100 : 0;
this.searchTermTimeout = setTimeout(async () => {
if (isMinimumServerVersion(this.props.serverVersion, 4, 7)) {
await this.props.actions.searchCustomEmojis(text);
}
this.searchTermTimeout = setTimeout(() => {
const filteredEmojis = this.searchEmojis(text);
this.setState({
filteredEmojis,
filteredEmojis
});
}, timeout);
};
cancelSearch = () => {
this.setState({
currentSectionIndex: 0,
filteredEmojis: [],
searchTerm: '',
searchTerm: ''
});
};
@@ -247,26 +214,6 @@ export default class EmojiPicker extends PureComponent {
);
};
loadMoreCustomEmojis = async () => {
if (!this.props.customEmojisEnabled || !isMinimumServerVersion(this.props.serverVersion, 4, 7)) {
return;
}
const {data} = await this.props.actions.getCustomEmojis(this.props.customEmojiPage, EMOJIS_PER_PAGE);
this.setState({loadingMore: false});
if (!data) {
return;
}
if (data.length < EMOJIS_PER_PAGE) {
this.setState({missingPages: false});
return;
}
this.props.actions.incrementEmojiPickerPage();
}
onScroll = (e) => {
if (this.state.jumpToSection) {
return;
@@ -285,7 +232,7 @@ export default class EmojiPicker extends PureComponent {
if (nextIndex !== this.state.currentSectionIndex) {
this.setState({
currentSectionIndex: nextIndex,
currentSectionIndex: nextIndex
});
}
};
@@ -293,7 +240,7 @@ export default class EmojiPicker extends PureComponent {
onMomentumScrollEnd = () => {
if (this.state.jumpToSection) {
this.setState({
jumpToSection: false,
jumpToSection: false
});
}
};
@@ -301,12 +248,12 @@ export default class EmojiPicker extends PureComponent {
scrollToSection = (index) => {
this.setState({
jumpToSection: true,
currentSectionIndex: index,
currentSectionIndex: index
}, () => {
this.sectionList.scrollToLocation({
sectionIndex: index,
itemIndex: 0,
viewOffset: 25,
viewOffset: 25
});
});
};
@@ -338,13 +285,9 @@ export default class EmojiPicker extends PureComponent {
);
};
handleSectionIconPress = (index, isCustomSection = false) => {
handleSectionIconPress = (index) => {
this.scrollToSectionTries = 0;
this.scrollToSection(index);
if (isCustomSection && this.props.customEmojiPage === 0) {
this.loadMoreCustomEmojis();
}
}
renderSectionIcons = () => {
@@ -352,7 +295,7 @@ export default class EmojiPicker extends PureComponent {
const styles = getStyleSheetFromTheme(theme);
return this.state.emojis.map((section, index) => {
const onPress = () => this.handleSectionIconPress(index, section.key === 'custom');
const onPress = () => this.handleSectionIconPress(index);
return (
<TouchableOpacity
@@ -374,21 +317,6 @@ export default class EmojiPicker extends PureComponent {
this.sectionList = c;
};
renderFooter = () => {
if (!this.state.missingPages) {
return null;
}
const {theme} = this.props;
const styles = getStyleSheetFromTheme(theme);
return (
<View style={styles.loading}>
<ActivityIndicator/>
</View>
);
}
render() {
const {deviceWidth, isLandscape, theme} = this.props;
const {emojis, filteredEmojis, searchTerm} = this.state;
@@ -425,9 +353,6 @@ export default class EmojiPicker extends PureComponent {
onScrollToIndexFailed={this.handleScrollToSectionFailed}
onMomentumScrollEnd={this.onMomentumScrollEnd}
pageSize={30}
ListFooterComponent={this.renderFooter}
onEndReached={this.loadMoreCustomEmojis}
onEndReachedThreshold={Platform.OS === 'ios' ? 0 : 1}
/>
);
}
@@ -441,17 +366,6 @@ export default class EmojiPicker extends PureComponent {
keyboardOffset = 52;
}
const searchBarInput = {
backgroundColor: theme.centerChannelBg,
color: theme.centerChannelColor,
fontSize: 13,
...Platform.select({
android: {
marginBottom: -3,
},
}),
};
return (
<SafeAreaView excludeHeader={true}>
<KeyboardAvoidingView
@@ -466,7 +380,11 @@ export default class EmojiPicker extends PureComponent {
cancelTitle={formatMessage({id: 'mobile.post.cancel', defaultMessage: 'Cancel'})}
backgroundColor='transparent'
inputHeight={33}
inputStyle={searchBarInput}
inputStyle={{
backgroundColor: theme.centerChannelBg,
color: theme.centerChannelColor,
fontSize: 13
}}
placeholderTextColor={changeOpacity(theme.centerChannelColor, 0.5)}
tintColorSearch={changeOpacity(theme.centerChannelColor, 0.8)}
tintColorDelete={changeOpacity(theme.centerChannelColor, 0.5)}
@@ -499,7 +417,7 @@ const getStyleSheetFromTheme = makeStyleSheetFromTheme((theme) => {
borderTopColor: changeOpacity(theme.centerChannelColor, 0.3),
borderTopWidth: 1,
flexDirection: 'row',
justifyContent: 'space-between',
justifyContent: 'space-between'
},
bottomContentWrapper: {
position: 'absolute',
@@ -507,43 +425,43 @@ const getStyleSheetFromTheme = makeStyleSheetFromTheme((theme) => {
left: 0,
right: 0,
height: 35,
width: '100%',
width: '100%'
},
columnStyle: {
alignSelf: 'stretch',
flexDirection: 'row',
marginVertical: EMOJI_GUTTER,
justifyContent: 'flex-start',
justifyContent: 'flex-start'
},
container: {
alignItems: 'center',
backgroundColor: theme.centerChannelBg,
flex: 1,
flex: 1
},
emoji: {
width: EMOJI_SIZE,
height: EMOJI_SIZE,
marginHorizontal: EMOJI_GUTTER,
alignItems: 'center',
justifyContent: 'center',
justifyContent: 'center'
},
emojiLeft: {
marginLeft: 0,
marginLeft: 0
},
emojiRight: {
marginRight: 0,
marginRight: 0
},
flatList: {
flex: 1,
backgroundColor: theme.centerChannelBg,
alignSelf: 'stretch',
alignSelf: 'stretch'
},
flatListEmoji: {
marginRight: 5,
marginRight: 5
},
flatListEmojiName: {
fontSize: 13,
color: theme.centerChannelColor,
color: theme.centerChannelColor
},
flatListRow: {
height: 40,
@@ -556,47 +474,43 @@ const getStyleSheetFromTheme = makeStyleSheetFromTheme((theme) => {
borderLeftWidth: 1,
borderLeftColor: changeOpacity(theme.centerChannelColor, 0.2),
borderRightWidth: 1,
borderRightColor: changeOpacity(theme.centerChannelColor, 0.2),
borderRightColor: changeOpacity(theme.centerChannelColor, 0.2)
},
listView: {
backgroundColor: theme.centerChannelBg,
marginBottom: 35,
marginBottom: 35
},
searchBar: {
backgroundColor: changeOpacity(theme.centerChannelColor, 0.2),
paddingVertical: 5,
paddingVertical: 5
},
section: {
alignItems: 'center',
alignItems: 'center'
},
sectionIcon: {
color: changeOpacity(theme.centerChannelColor, 0.3),
color: changeOpacity(theme.centerChannelColor, 0.3)
},
sectionIconContainer: {
flex: 1,
height: 35,
alignItems: 'center',
justifyContent: 'center',
justifyContent: 'center'
},
sectionIconHighlight: {
color: theme.centerChannelColor,
color: theme.centerChannelColor
},
sectionTitle: {
color: changeOpacity(theme.centerChannelColor, 0.2),
fontSize: 15,
fontWeight: '700',
fontWeight: '700'
},
sectionTitleContainer: {
height: SECTION_HEADER_HEIGHT,
justifyContent: 'center',
backgroundColor: theme.centerChannelBg,
backgroundColor: theme.centerChannelBg
},
wrapper: {
flex: 1,
},
loading: {
flex: 1,
alignItems: 'center',
},
flex: 1
}
};
});

View File

@@ -6,9 +6,8 @@ import PropTypes from 'prop-types';
import {
StyleSheet,
TouchableOpacity,
View,
View
} from 'react-native';
import shallowEqual from 'shallow-equals';
import Emoji from 'app/components/emoji';
@@ -17,11 +16,11 @@ export default class EmojiPickerRow extends Component {
emojiGutter: PropTypes.number.isRequired,
emojiSize: PropTypes.number.isRequired,
items: PropTypes.array.isRequired,
onEmojiPress: PropTypes.func.isRequired,
onEmojiPress: PropTypes.func.isRequired
}
shouldComponentUpdate(nextProps) {
return !shallowEqual(this.props.items, nextProps.items);
return this.props.items.length !== nextProps.items.length;
}
renderEmojis = (emoji, index, emojis) => {
@@ -32,8 +31,8 @@ export default class EmojiPickerRow extends Component {
{
width: emojiSize,
height: emojiSize,
marginHorizontal: emojiGutter,
},
marginHorizontal: emojiGutter
}
];
if (index === 0) {
style.push(styles.emojiLeft);
@@ -81,16 +80,16 @@ const styles = StyleSheet.create({
columnStyle: {
alignSelf: 'stretch',
flexDirection: 'row',
justifyContent: 'space-between',
justifyContent: 'space-between'
},
emoji: {
alignItems: 'center',
justifyContent: 'center',
justifyContent: 'center'
},
emojiLeft: {
marginLeft: 0,
marginLeft: 0
},
emojiRight: {
marginRight: 0,
},
});
marginRight: 0
}
});

View File

@@ -3,16 +3,11 @@
import {connect} from 'react-redux';
import {createSelector} from 'reselect';
import {bindActionCreators} from 'redux';
import {getTheme} from 'mattermost-redux/selectors/entities/preferences';
import {getCustomEmojisByName} from 'mattermost-redux/selectors/entities/emojis';
import {getConfig} from 'mattermost-redux/selectors/entities/general';
import {getCustomEmojis, searchCustomEmojis} from 'mattermost-redux/actions/emojis';
import {Client4} from 'mattermost-redux/client';
import {incrementEmojiPickerPage} from 'app/actions/views/emoji';
import {getDimensions, isLandscape} from 'app/selectors/device';
import {getTheme} from 'mattermost-redux/selectors/entities/preferences';
import {CategoryNames, Emojis, EmojiIndicesByAlias, EmojiIndicesByCategory} from 'app/utils/emojis';
import EmojiPicker from './emoji_picker';
@@ -22,60 +17,60 @@ const categoryToI18n = {
activity: {
id: 'mobile.emoji_picker.activity',
defaultMessage: 'ACTIVITY',
icon: 'futbol-o',
icon: 'futbol-o'
},
custom: {
id: 'mobile.emoji_picker.custom',
defaultMessage: 'CUSTOM',
icon: 'at',
icon: 'at'
},
flags: {
id: 'mobile.emoji_picker.flags',
defaultMessage: 'FLAGS',
icon: 'flag-o',
icon: 'flag-o'
},
foods: {
id: 'mobile.emoji_picker.foods',
defaultMessage: 'FOODS',
icon: 'cutlery',
icon: 'cutlery'
},
nature: {
id: 'mobile.emoji_picker.nature',
defaultMessage: 'NATURE',
icon: 'leaf',
icon: 'leaf'
},
objects: {
id: 'mobile.emoji_picker.objects',
defaultMessage: 'OBJECTS',
icon: 'lightbulb-o',
icon: 'lightbulb-o'
},
people: {
id: 'mobile.emoji_picker.people',
defaultMessage: 'PEOPLE',
icon: 'smile-o',
icon: 'smile-o'
},
places: {
id: 'mobile.emoji_picker.places',
defaultMessage: 'PLACES',
icon: 'plane',
icon: 'plane'
},
recent: {
id: 'mobile.emoji_picker.recent',
defaultMessage: 'RECENTLY USED',
icon: 'clock-o',
icon: 'clock-o'
},
symbols: {
id: 'mobile.emoji_picker.symbols',
defaultMessage: 'SYMBOLS',
icon: 'heart-o',
},
icon: 'heart-o'
}
};
function fillEmoji(indice) {
const emoji = Emojis[indice];
return {
name: emoji.aliases[0],
aliases: emoji.aliases,
aliases: emoji.aliases
};
}
@@ -89,7 +84,7 @@ const getEmojisBySection = createSelector(
const section = {
...categoryToI18n[category],
key: category,
data: items,
data: items
};
return section;
@@ -99,14 +94,14 @@ const getEmojisBySection = createSelector(
for (const [key] of customEmojis) {
customEmojiItems.push({
name: key,
name: key
});
}
emoticons.push({
...categoryToI18n.custom,
key: 'custom',
data: customEmojiItems,
data: customEmojiItems
});
if (recentEmojis.length) {
@@ -115,7 +110,7 @@ const getEmojisBySection = createSelector(
emoticons.unshift({
...categoryToI18n.recent,
key: 'recent',
data: items,
data: items
});
}
@@ -145,7 +140,7 @@ function mapStateToProps(state) {
location: 0,
distance: 100,
minMatchCharLength: 2,
maxPatternLength: 32,
maxPatternLength: 32
};
const list = emojis.length ? emojis : [];
@@ -157,21 +152,8 @@ function mapStateToProps(state) {
emojisBySection,
deviceWidth,
isLandscape: isLandscape(state),
theme: getTheme(state),
customEmojisEnabled: getConfig(state).EnableCustomEmoji === 'true',
customEmojiPage: state.views.emoji.emojiPickerCustomPage,
serverVersion: state.entities.general.serverVersion || Client4.getServerVersion(),
theme: getTheme(state)
};
}
function mapDispatchToProps(dispatch) {
return {
actions: bindActionCreators({
getCustomEmojis,
incrementEmojiPickerPage,
searchCustomEmojis,
}, dispatch),
};
}
export default connect(mapStateToProps, mapDispatchToProps)(EmojiPicker);
export default connect(mapStateToProps)(EmojiPicker);

View File

@@ -7,7 +7,7 @@ import {
Dimensions,
StyleSheet,
View,
TouchableOpacity,
TouchableOpacity
} from 'react-native';
import Icon from 'react-native-vector-icons/FontAwesome';
import FormattedText from 'app/components/formatted_text';
@@ -25,22 +25,22 @@ const style = StyleSheet.create({
borderColor: '#fff',
borderWidth: 1,
alignItems: 'center',
justifyContent: 'center',
justifyContent: 'center'
},
closeButtonContainer: {
alignItems: 'center',
justifyContent: 'center',
marginTop: 5,
marginTop: 5
},
closeButtonText: {
color: '#fff',
color: '#fff'
},
container: {
paddingTop: 15,
paddingBottom: 15,
alignItems: 'center',
justifyContent: 'center',
minHeight: 75,
minHeight: 75
},
wrapper: {
position: 'absolute',
@@ -49,17 +49,17 @@ const style = StyleSheet.create({
width: deviceWidth,
overflow: 'hidden',
backgroundColor: 'rgba(255, 116, 92, 1)',
zIndex: 99999,
},
zIndex: 99999
}
});
export default class ErrorList extends PureComponent {
static propTypes = {
actions: PropTypes.shape({
dismissError: PropTypes.func.isRequired,
clearErrors: PropTypes.func.isRequired,
clearErrors: PropTypes.func.isRequired
}).isRequired,
errors: PropTypes.array.isRequired,
errors: PropTypes.array.isRequired
}
renderErrorsList() {

View File

@@ -7,7 +7,7 @@ import {
StyleSheet,
View,
Text,
TouchableOpacity,
TouchableOpacity
} from 'react-native';
import Icon from 'react-native-vector-icons/FontAwesome';
@@ -16,22 +16,22 @@ const style = StyleSheet.create({
width: 25,
height: 25,
alignItems: 'center',
justifyContent: 'center',
justifyContent: 'center'
},
buttons: {
marginHorizontal: 15,
marginHorizontal: 15
},
container: {
alignSelf: 'stretch',
paddingHorizontal: 15,
paddingVertical: 8,
flexDirection: 'row',
alignItems: 'center',
alignItems: 'center'
},
message: {
flex: 1,
color: '#fff',
},
color: '#fff'
}
});
function GeneralError(props) {
@@ -66,7 +66,7 @@ function GeneralError(props) {
GeneralError.propTypes = {
dismiss: PropTypes.func.isRequired,
error: PropTypes.object.isRequired,
error: PropTypes.object.isRequired
};
export default GeneralError;

View File

@@ -11,7 +11,7 @@ import ErrorList from './error_list';
function mapStateToProps(state) {
return {
errors: getDisplayableErrors(state),
errors: getDisplayableErrors(state)
};
}
@@ -19,8 +19,8 @@ function mapDispatchToProps(dispatch) {
return {
actions: bindActionCreators({
dismissError,
clearErrors,
}, dispatch),
clearErrors
}, dispatch)
};
}

View File

@@ -16,7 +16,7 @@ class ErrorText extends PureComponent {
static propTypes = {
error: PropTypes.oneOfType([PropTypes.string, PropTypes.object]),
textStyle: CustomPropTypes.Style,
theme: PropTypes.object.isRequired,
theme: PropTypes.object.isRequired
};
render() {
@@ -50,15 +50,15 @@ class ErrorText extends PureComponent {
const getStyleSheet = makeStyleSheetFromTheme((theme) => {
return {
errorLabel: {
color: (theme.errorTextColor || '#DA4A4A'),
},
color: (theme.errorTextColor || '#DA4A4A')
}
};
});
function mapStateToProps(state, ownProps) {
return {
...ownProps,
theme: getTheme(state),
theme: getTheme(state)
};
}

View File

@@ -6,7 +6,7 @@ import PropTypes from 'prop-types';
import {
Text,
TouchableOpacity,
View,
View
} from 'react-native';
import {changeOpacity, makeStyleSheetFromTheme} from 'app/utils/theme';
@@ -23,13 +23,12 @@ export default class FileAttachment extends PureComponent {
file: PropTypes.object.isRequired,
onInfoPress: PropTypes.func,
onPreviewPress: PropTypes.func,
theme: PropTypes.object.isRequired,
navigator: PropTypes.object,
theme: PropTypes.object.isRequired
};
static defaultProps = {
onInfoPress: () => true,
onPreviewPress: () => true,
onPreviewPress: () => true
};
handlePreviewPress = () => {
@@ -62,7 +61,7 @@ export default class FileAttachment extends PureComponent {
}
render() {
const {file, onInfoPress, theme, navigator} = this.props;
const {file, onInfoPress, theme} = this.props;
const style = getStyleSheet(theme);
let mime = file.mime_type;
@@ -87,7 +86,6 @@ export default class FileAttachment extends PureComponent {
<FileAttachmentDocument
file={file}
theme={theme}
navigator={navigator}
/>
);
} else {
@@ -119,43 +117,43 @@ const getStyleSheet = makeStyleSheetFromTheme((theme) => {
return {
downloadIcon: {
color: changeOpacity(theme.centerChannelColor, 0.7),
marginRight: 5,
marginRight: 5
},
fileDownloadContainer: {
flexDirection: 'row',
marginTop: 3,
marginTop: 3
},
fileInfo: {
marginLeft: 2,
fontSize: 14,
color: changeOpacity(theme.centerChannelColor, 0.5),
color: changeOpacity(theme.centerChannelColor, 0.5)
},
fileInfoContainer: {
flex: 1,
paddingHorizontal: 8,
paddingVertical: 5,
borderLeftWidth: 1,
borderLeftColor: changeOpacity(theme.centerChannelColor, 0.2),
borderLeftColor: changeOpacity(theme.centerChannelColor, 0.2)
},
fileName: {
flexDirection: 'column',
flexWrap: 'wrap',
marginLeft: 2,
fontSize: 14,
color: theme.centerChannelColor,
color: theme.centerChannelColor
},
fileWrapper: {
flex: 1,
flexDirection: 'row',
marginTop: 10,
borderWidth: 1,
borderColor: changeOpacity(theme.centerChannelColor, 0.2),
borderColor: changeOpacity(theme.centerChannelColor, 0.2)
},
circularProgress: {
width: '100%',
height: '100%',
alignItems: 'center',
justifyContent: 'center',
justifyContent: 'center'
},
circularProgressContent: {
position: 'absolute',
@@ -164,7 +162,7 @@ const getStyleSheet = makeStyleSheetFromTheme((theme) => {
top: 0,
left: 0,
alignItems: 'center',
justifyContent: 'center',
},
justifyContent: 'center'
}
};
});

Some files were not shown because too many files have changed in this diff Show More