forked from Ivasoft/mattermost-mobile
Compare commits
42 Commits
v1.4.0
...
release-1.
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
56e36b0468 | ||
|
|
b5b57085e5 | ||
|
|
e082b42947 | ||
|
|
77fdaa9058 | ||
|
|
774f1a1b47 | ||
|
|
192e5093c1 | ||
|
|
52ba404b8e | ||
|
|
8249080304 | ||
|
|
b6c0d47d18 | ||
|
|
031876fb77 | ||
|
|
ac0ac22f39 | ||
|
|
bed81ad514 | ||
|
|
642dd299c6 | ||
|
|
2633060a7f | ||
|
|
50369d0c28 | ||
|
|
4ef308469d | ||
|
|
f2394ba8df | ||
|
|
cc55b03e75 | ||
|
|
82b3dcc1f6 | ||
|
|
13922e3764 | ||
|
|
0df3c7428a | ||
|
|
8e526b61ed | ||
|
|
c93f04a708 | ||
|
|
e3761fc529 | ||
|
|
72fef11496 | ||
|
|
6fdd58b481 | ||
|
|
ad2d126ec0 | ||
|
|
76eb5d06fd | ||
|
|
1e434346ae | ||
|
|
a694122ffd | ||
|
|
0c3bb89832 | ||
|
|
78e6b8d5a3 | ||
|
|
ae7c566375 | ||
|
|
6f260bf4c7 | ||
|
|
ff65b52618 | ||
|
|
2f47d7db2e | ||
|
|
b8e450ba85 | ||
|
|
f2533bd650 | ||
|
|
f9419a7746 | ||
|
|
978c80bef1 | ||
|
|
6e1d8471f7 | ||
|
|
fa9110d9d7 |
5
.babelrc
5
.babelrc
@@ -1,10 +1,5 @@
|
||||
{
|
||||
"presets": [ "react-native" ],
|
||||
"env": {
|
||||
"production": {
|
||||
"plugins": ["transform-remove-console"]
|
||||
}
|
||||
},
|
||||
"plugins": [
|
||||
["module-resolver", {
|
||||
"root": ["./src", "."],
|
||||
|
||||
@@ -6,10 +6,8 @@ import android.content.ActivityNotFoundException;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.ResolveInfo;
|
||||
import android.graphics.BitmapFactory;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.provider.MediaStore;
|
||||
import android.provider.Settings;
|
||||
import android.support.annotation.NonNull;
|
||||
@@ -46,7 +44,6 @@ import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.util.List;
|
||||
|
||||
import com.facebook.react.modules.core.PermissionListener;
|
||||
import com.facebook.react.modules.core.PermissionAwareActivity;
|
||||
@@ -199,9 +196,7 @@ public class ImagePickerModule extends ReactContextBaseJavaModule
|
||||
|
||||
public void doOnCancel()
|
||||
{
|
||||
if (this.callback != null) {
|
||||
responseHelper.invokeCancel(this.callback);
|
||||
}
|
||||
responseHelper.invokeCancel(callback);
|
||||
}
|
||||
|
||||
public void launchCamera()
|
||||
@@ -226,7 +221,6 @@ public class ImagePickerModule extends ReactContextBaseJavaModule
|
||||
return;
|
||||
}
|
||||
|
||||
this.callback = callback;
|
||||
this.options = options;
|
||||
|
||||
if (!permissionsCheck(currentActivity, callback, REQUEST_PERMISSIONS_FOR_CAMERA))
|
||||
@@ -257,12 +251,7 @@ public class ImagePickerModule extends ReactContextBaseJavaModule
|
||||
final File original = createNewFile(reactContext, this.options, false);
|
||||
imageConfig = imageConfig.withOriginalFile(original);
|
||||
|
||||
if (imageConfig.original != null) {
|
||||
cameraCaptureURI = RealPathUtil.compatUriFromFile(reactContext, imageConfig.original);
|
||||
}else {
|
||||
responseHelper.invokeError(callback, "Couldn't get file path for photo");
|
||||
return;
|
||||
}
|
||||
cameraCaptureURI = RealPathUtil.compatUriFromFile(reactContext, imageConfig.original);
|
||||
if (cameraCaptureURI == null)
|
||||
{
|
||||
responseHelper.invokeError(callback, "Couldn't get file path for photo");
|
||||
@@ -277,16 +266,7 @@ public class ImagePickerModule extends ReactContextBaseJavaModule
|
||||
return;
|
||||
}
|
||||
|
||||
// Workaround for Android bug.
|
||||
// grantUriPermission also needed for KITKAT,
|
||||
// see https://code.google.com/p/android/issues/detail?id=76683
|
||||
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.KITKAT) {
|
||||
List<ResolveInfo> resInfoList = reactContext.getPackageManager().queryIntentActivities(cameraIntent, PackageManager.MATCH_DEFAULT_ONLY);
|
||||
for (ResolveInfo resolveInfo : resInfoList) {
|
||||
String packageName = resolveInfo.activityInfo.packageName;
|
||||
reactContext.grantUriPermission(packageName, cameraCaptureURI, Intent.FLAG_GRANT_WRITE_URI_PERMISSION | Intent.FLAG_GRANT_READ_URI_PERMISSION);
|
||||
}
|
||||
}
|
||||
this.callback = callback;
|
||||
|
||||
try
|
||||
{
|
||||
@@ -314,7 +294,6 @@ public class ImagePickerModule extends ReactContextBaseJavaModule
|
||||
}
|
||||
|
||||
this.options = options;
|
||||
this.callback = callback;
|
||||
|
||||
if (!permissionsCheck(currentActivity, callback, REQUEST_PERMISSIONS_FOR_LIBRARY))
|
||||
{
|
||||
@@ -335,7 +314,7 @@ public class ImagePickerModule extends ReactContextBaseJavaModule
|
||||
{
|
||||
requestCode = REQUEST_LAUNCH_IMAGE_LIBRARY;
|
||||
libraryIntent = new Intent(Intent.ACTION_PICK,
|
||||
MediaStore.Images.Media.EXTERNAL_CONTENT_URI);
|
||||
MediaStore.Images.Media.EXTERNAL_CONTENT_URI);
|
||||
}
|
||||
|
||||
if (libraryIntent.resolveActivity(reactContext.getPackageManager()) == null)
|
||||
@@ -344,6 +323,8 @@ public class ImagePickerModule extends ReactContextBaseJavaModule
|
||||
return;
|
||||
}
|
||||
|
||||
this.callback = callback;
|
||||
|
||||
try
|
||||
{
|
||||
currentActivity.startActivityForResult(libraryIntent, requestCode);
|
||||
@@ -590,9 +571,7 @@ public class ImagePickerModule extends ReactContextBaseJavaModule
|
||||
innerActivity.startActivityForResult(intent, 1);
|
||||
}
|
||||
});
|
||||
if (dialog != null) {
|
||||
dialog.show();
|
||||
}
|
||||
dialog.show();
|
||||
return false;
|
||||
}
|
||||
else
|
||||
@@ -627,7 +606,7 @@ public class ImagePickerModule extends ReactContextBaseJavaModule
|
||||
|
||||
private boolean isCameraAvailable() {
|
||||
return reactContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_CAMERA)
|
||||
|| reactContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_CAMERA_ANY);
|
||||
|| reactContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_CAMERA_ANY);
|
||||
}
|
||||
|
||||
private @NonNull String getRealPathFromURI(@NonNull final Uri uri) {
|
||||
|
||||
10
Makefile
10
Makefile
@@ -86,7 +86,7 @@ run-android: | check-device-android start prepare-android-build
|
||||
@echo Running Android app in development
|
||||
@react-native run-android --no-packager
|
||||
|
||||
test: | pre-run check-style
|
||||
test: pre-run
|
||||
@yarn test
|
||||
|
||||
check-style: .yarninstall
|
||||
@@ -120,9 +120,7 @@ post-install:
|
||||
@sed -i'' -e 's|"./lib/locales": false|"./lib/locales": "./lib/locales"|g' node_modules/intl-messageformat/package.json
|
||||
@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
|
||||
@cd ./node_modules/react-native-svg/ios && rm -rf PerformanceBezier && git clone https://github.com/adamwulf/PerformanceBezier.git
|
||||
@cd ./node_modules/mattermost-redux && yarn run build
|
||||
@sed -i'' -e 's|auto("auto", Configuration.ORIENTATION_UNDEFINED, ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED);|auto("auto", Configuration.ORIENTATION_UNDEFINED, ActivityInfo.SCREEN_ORIENTATION_SENSOR);|g' node_modules/react-native-navigation/android/app/src/main/java/com/reactnativenavigation/params/Orientation.java
|
||||
|
||||
start-packager:
|
||||
@if [ $(shell ps -e | grep -i "cli.js start" | grep -civ grep) -eq 0 ]; then \
|
||||
@@ -156,7 +154,7 @@ endif
|
||||
|
||||
do-build-ios:
|
||||
@echo "Building ios $(ios_target) app"
|
||||
@cd fastlane && BABEL_ENV=production NODE_ENV=production bundle exec fastlane ios $(ios_target)
|
||||
@cd fastlane && NODE_ENV=production bundle exec fastlane ios $(ios_target)
|
||||
|
||||
|
||||
build-ios: | check-ios-target pre-run check-style start-packager do-build-ios stop-packager
|
||||
@@ -180,7 +178,7 @@ prepare-android-build:
|
||||
|
||||
do-build-android:
|
||||
@echo "Building android $(android_target) app"
|
||||
@cd fastlane && BABEL_ENV=production NODE_ENV=production bundle exec fastlane android $(android_target)
|
||||
@cd fastlane && NODE_ENV=production bundle exec fastlane android $(android_target)
|
||||
|
||||
build-android: | check-android-target pre-run check-style start-packager prepare-android-build do-build-android stop-packager
|
||||
|
||||
|
||||
@@ -84,8 +84,7 @@ Follow the [React Native Getting Started Guide](https://facebook.github.io/react
|
||||
$ cd watchman
|
||||
$ git checkout master
|
||||
$ ./autogen.sh
|
||||
$ ./configure
|
||||
$ make
|
||||
$ ./configure make
|
||||
$ sudo make install
|
||||
```
|
||||
Configure your kernel to accept a lot of file watches, using a command like:
|
||||
|
||||
@@ -95,8 +95,8 @@ android {
|
||||
applicationId "com.mattermost.rnbeta"
|
||||
minSdkVersion 21
|
||||
targetSdkVersion 23
|
||||
versionCode 63
|
||||
versionName "1.4.0"
|
||||
versionCode 57
|
||||
versionName "1.3.0"
|
||||
multiDexEnabled true
|
||||
ndk {
|
||||
abiFilters "armeabi-v7a", "x86"
|
||||
@@ -154,7 +154,6 @@ android {
|
||||
dependencies {
|
||||
compile fileTree(dir: "libs", include: ["*.jar"])
|
||||
compile "com.android.support:appcompat-v7:25.0.1"
|
||||
compile 'com.android.support:percent:25.3.1'
|
||||
compile "com.facebook.react:react-native:+" // From node_modules
|
||||
compile project(':react-native-navigation')
|
||||
compile project(':react-native-image-picker')
|
||||
|
||||
@@ -16,11 +16,8 @@ import android.os.Build;
|
||||
import android.app.Notification;
|
||||
import android.app.NotificationManager;
|
||||
import android.app.RemoteInput;
|
||||
import android.provider.Settings.System;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Collections;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.lang.reflect.Field;
|
||||
|
||||
import com.wix.reactnativenotifications.core.notification.PushNotification;
|
||||
@@ -43,7 +40,7 @@ public class CustomPushNotification extends PushNotification {
|
||||
public static final String NOTIFICATION_REPLIED_EVENT_NAME = "notificationReplied";
|
||||
|
||||
private static LinkedHashMap<String,Integer> channelIdToNotificationCount = new LinkedHashMap<String,Integer>();
|
||||
private static LinkedHashMap<String,List<Bundle>> channelIdToNotification = new LinkedHashMap<String,List<Bundle>>();
|
||||
private static LinkedHashMap<String,ArrayList<Bundle>> channelIdToNotification = new LinkedHashMap<String,ArrayList<Bundle>>();
|
||||
private static AppLifecycleFacade lifecycleFacade;
|
||||
private static Context context;
|
||||
|
||||
@@ -77,16 +74,14 @@ public class CustomPushNotification extends PushNotification {
|
||||
channelIdToNotificationCount.put(channelId, count);
|
||||
|
||||
Object bundleArray = channelIdToNotification.get(channelId);
|
||||
List list = null;
|
||||
ArrayList list = null;
|
||||
if (bundleArray == null) {
|
||||
list = Collections.synchronizedList(new ArrayList(0));
|
||||
list = new ArrayList(0);
|
||||
} else {
|
||||
list = Collections.synchronizedList((List)bundleArray);
|
||||
}
|
||||
synchronized (list) {
|
||||
list.add(0, data);
|
||||
channelIdToNotification.put(channelId, list);
|
||||
list = (ArrayList)bundleArray;
|
||||
}
|
||||
list.add(0, data);
|
||||
channelIdToNotification.put(channelId, list);
|
||||
}
|
||||
|
||||
if ("clear".equals(type)) {
|
||||
@@ -197,7 +192,7 @@ public class CustomPushNotification extends PushNotification {
|
||||
String summaryTitle = String.format("%s (%d)", title, numMessages);
|
||||
|
||||
Notification.InboxStyle style = new Notification.InboxStyle();
|
||||
List<Bundle> list = new ArrayList<Bundle>(channelIdToNotification.get(channelId));
|
||||
ArrayList<Bundle> list = (ArrayList<Bundle>) channelIdToNotification.get(channelId);
|
||||
|
||||
for (Bundle data : list){
|
||||
String msg = data.getString("message");
|
||||
@@ -259,7 +254,7 @@ public class CustomPushNotification extends PushNotification {
|
||||
notification.setSound(Uri.parse(soundUri), AudioManager.STREAM_NOTIFICATION);
|
||||
}
|
||||
} else {
|
||||
Uri defaultUri = System.DEFAULT_NOTIFICATION_URI;
|
||||
Uri defaultUri = RingtoneManager.getActualDefaultRingtoneUri(mContext, RingtoneManager.TYPE_NOTIFICATION);
|
||||
notification.setSound(defaultUri, AudioManager.STREAM_NOTIFICATION);
|
||||
}
|
||||
|
||||
|
||||
@@ -2,9 +2,62 @@ package com.mattermost.rnbeta;
|
||||
|
||||
import com.reactnativenavigation.controllers.SplashActivity;
|
||||
|
||||
import java.lang.ref.WeakReference;
|
||||
|
||||
import android.content.Context;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.LinearLayout;
|
||||
import android.graphics.Color;
|
||||
import android.widget.TextView;
|
||||
import android.view.ViewGroup.LayoutParams;
|
||||
import android.view.Gravity;
|
||||
import android.util.TypedValue;
|
||||
|
||||
public class MainActivity extends SplashActivity {
|
||||
@Override
|
||||
public int getSplashLayout() {
|
||||
return R.layout.launch_screen;
|
||||
}
|
||||
|
||||
private static ImageView imageView;
|
||||
private static WeakReference<MainActivity> wr_activity;
|
||||
protected static MainActivity getActivity() {
|
||||
return wr_activity.get();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the name of the main component registered from JavaScript.
|
||||
* This is used to schedule rendering of the component.
|
||||
*/
|
||||
// @Override
|
||||
// protected String getMainComponentName() {
|
||||
// return "Mattermost";
|
||||
// }
|
||||
|
||||
@Override
|
||||
public LinearLayout createSplashLayout() {
|
||||
wr_activity = new WeakReference<>(this);
|
||||
LayoutParams layoutParams = new LinearLayout.LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
|
||||
Context context = getActivity();
|
||||
final int drawableId = getImageId();
|
||||
|
||||
NotificationsLifecycleFacade.getInstance().LoadManagedConfig(getActivity());
|
||||
|
||||
imageView = new ImageView(context);
|
||||
imageView.setImageResource(drawableId);
|
||||
|
||||
imageView.setLayoutParams(layoutParams);
|
||||
imageView.setScaleType(ImageView.ScaleType.CENTER);
|
||||
|
||||
LinearLayout view = new LinearLayout(this);
|
||||
view.setBackgroundColor(Color.parseColor("#FFFFFF"));
|
||||
view.setGravity(Gravity.CENTER);
|
||||
view.addView(imageView);
|
||||
|
||||
return view;
|
||||
}
|
||||
|
||||
private static int getImageId() {
|
||||
int drawableId = getActivity().getResources().getIdentifier("splash", "drawable", getActivity().getClass().getPackage().getName());
|
||||
if (drawableId == 0) {
|
||||
drawableId = getActivity().getResources().getIdentifier("splash", "drawable", getActivity().getPackageName());
|
||||
}
|
||||
return drawableId;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,6 @@ import android.content.Context;
|
||||
import android.os.Bundle;
|
||||
|
||||
import com.facebook.react.ReactApplication;
|
||||
import com.horcrux.svg.SvgPackage;
|
||||
import com.inprogress.reactnativeyoutube.ReactNativeYouTube;
|
||||
import io.sentry.RNSentryPackage;
|
||||
import com.masteratul.exceptionhandler.ReactNativeExceptionHandlerPackage;
|
||||
@@ -24,6 +23,7 @@ import com.gnet.bottomsheet.RNBottomSheetPackage;
|
||||
import com.learnium.RNDeviceInfo.RNDeviceInfo;
|
||||
import com.psykar.cookiemanager.CookieManagerPackage;
|
||||
import com.oblador.vectoricons.VectorIconsPackage;
|
||||
import com.horcrux.svg.SvgPackage;
|
||||
import com.BV.LinearGradient.LinearGradientPackage;
|
||||
import com.github.yamill.orientation.OrientationPackage;
|
||||
import com.reactnativenavigation.NavigationApplication;
|
||||
|
||||
@@ -1,19 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<android.support.percent.PercentRelativeLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="#ffffff"
|
||||
android:gravity="center_horizontal"
|
||||
tools:context=".SplashScreenActivity">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/imgLogo"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:adjustViewBounds="true"
|
||||
android:src="@drawable/splash" />
|
||||
|
||||
</android.support.percent.PercentRelativeLayout>
|
||||
@@ -1,4 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<color name="white">#FFFFFF</color>
|
||||
</resources>
|
||||
@@ -3,9 +3,7 @@
|
||||
<!-- Base application theme. -->
|
||||
<style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar">
|
||||
<!-- Customize your theme here. -->
|
||||
<item name="android:windowIsTranslucent">false</item>
|
||||
<item name="android:windowBackground">@color/white</item>
|
||||
<item name="android:colorBackground">@color/white</item>
|
||||
<item name="android:windowIsTranslucent">true</item>
|
||||
</style>
|
||||
|
||||
</resources>
|
||||
|
||||
@@ -27,9 +27,9 @@ include ':reactnativenotifications'
|
||||
project(':reactnativenotifications').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-notifications/android')
|
||||
|
||||
include ':app'
|
||||
include ':react-native-svg'
|
||||
project(':react-native-svg').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-svg/android')
|
||||
include ':react-native-orientation'
|
||||
project(':react-native-orientation').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-orientation/android')
|
||||
include ':react-native-linear-gradient'
|
||||
project(':react-native-linear-gradient').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-linear-gradient/android')
|
||||
include ':react-native-svg'
|
||||
project(':react-native-svg').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-svg/android')
|
||||
|
||||
@@ -8,6 +8,7 @@ import {ViewTypes} from 'app/constants';
|
||||
import {UserTypes} from 'mattermost-redux/action_types';
|
||||
import {
|
||||
fetchMyChannelsAndMembers,
|
||||
getChannelStats,
|
||||
selectChannel,
|
||||
leaveChannel as serviceLeaveChannel,
|
||||
unfavoriteChannel
|
||||
@@ -18,19 +19,18 @@ import {savePreferences} from 'mattermost-redux/actions/preferences';
|
||||
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 {
|
||||
getChannelByName,
|
||||
getDirectChannelName,
|
||||
getUserIdFromChannelName,
|
||||
isDirectChannelVisible,
|
||||
isGroupChannelVisible,
|
||||
isDirectChannel,
|
||||
isGroupChannel
|
||||
} from 'mattermost-redux/utils/channel_utils';
|
||||
import {getLastCreateAt} from 'mattermost-redux/utils/post_utils';
|
||||
import {getPreferencesByCategory} from 'mattermost-redux/utils/preference_utils';
|
||||
|
||||
import {isDirectChannelVisible, isGroupChannelVisible} from 'app/utils/channels';
|
||||
|
||||
const MAX_POST_TRIES = 3;
|
||||
|
||||
export function loadChannelsIfNecessary(teamId) {
|
||||
@@ -180,17 +180,15 @@ export function loadPostsIfNecessaryWithRetry(channelId) {
|
||||
};
|
||||
}
|
||||
|
||||
export async function retryGetPostsAction(action, dispatch, getState, maxTries = MAX_POST_TRIES) {
|
||||
async function retryGetPostsAction(action, dispatch, getState, maxTries = MAX_POST_TRIES) {
|
||||
for (let i = 0; i < maxTries; i++) {
|
||||
const {data} = await action(dispatch, getState);
|
||||
const posts = await action(dispatch, getState);
|
||||
|
||||
if (data) {
|
||||
dispatch(setChannelRetryFailed(false));
|
||||
return data;
|
||||
if (posts) {
|
||||
return posts;
|
||||
}
|
||||
}
|
||||
|
||||
dispatch(setChannelRetryFailed(true));
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -223,8 +221,7 @@ export function selectInitialChannel(teamId) {
|
||||
const {channels, myMembers} = state.entities.channels;
|
||||
const {currentUserId} = state.entities.users;
|
||||
const {myPreferences} = state.entities.preferences;
|
||||
const lastChannelForTeam = state.views.team.lastChannelForTeam[teamId];
|
||||
const lastChannelId = lastChannelForTeam && lastChannelForTeam.length ? lastChannelForTeam[0] : '';
|
||||
const lastChannelId = state.views.team.lastChannelForTeam[teamId] || '';
|
||||
const lastChannel = channels[lastChannelId];
|
||||
|
||||
const isDMVisible = lastChannel && lastChannel.type === General.DM_CHANNEL &&
|
||||
@@ -258,84 +255,28 @@ export function handleSelectChannel(channelId) {
|
||||
return async (dispatch, getState) => {
|
||||
const {currentTeamId} = getState().entities.teams;
|
||||
|
||||
loadPostsIfNecessaryWithRetry(channelId)(dispatch, getState);
|
||||
selectChannel(channelId)(dispatch, getState);
|
||||
dispatch(batchActions([
|
||||
{
|
||||
type: ViewTypes.SET_INITIAL_POST_VISIBILITY,
|
||||
data: channelId
|
||||
},
|
||||
setChannelLoading(false),
|
||||
{
|
||||
type: ViewTypes.SET_LAST_CHANNEL_FOR_TEAM,
|
||||
teamId: currentTeamId,
|
||||
channelId
|
||||
}
|
||||
]), 'BATCH_CHANNEL_LOADED');
|
||||
dispatch(setChannelLoading(false));
|
||||
|
||||
dispatch({
|
||||
type: ViewTypes.SET_LAST_CHANNEL_FOR_TEAM,
|
||||
teamId: currentTeamId,
|
||||
channelId
|
||||
});
|
||||
getChannelStats(channelId)(dispatch, getState);
|
||||
};
|
||||
}
|
||||
|
||||
export function handlePostDraftChanged(channelId, draft) {
|
||||
export function handlePostDraftChanged(channelId, postDraft) {
|
||||
return async (dispatch, getState) => {
|
||||
dispatch({
|
||||
type: ViewTypes.POST_DRAFT_CHANGED,
|
||||
channelId,
|
||||
draft
|
||||
postDraft
|
||||
}, getState);
|
||||
};
|
||||
}
|
||||
|
||||
export function handlePostDraftSelectionChanged(channelId, cursorPosition) {
|
||||
return {
|
||||
type: ViewTypes.POST_DRAFT_SELECTION_CHANGED,
|
||||
channelId,
|
||||
cursorPosition
|
||||
};
|
||||
}
|
||||
|
||||
export function insertToDraft(value) {
|
||||
return (dispatch, getState) => {
|
||||
const state = getState();
|
||||
const channelId = getCurrentChannelId(state);
|
||||
const threadId = state.entities.posts.selectedPostId;
|
||||
|
||||
let draft;
|
||||
let cursorPosition;
|
||||
let action;
|
||||
if (state.views.thread.drafts[threadId]) {
|
||||
const threadDraft = state.views.thread.drafts[threadId];
|
||||
draft = threadDraft.draft;
|
||||
cursorPosition = threadDraft.cursorPosition;
|
||||
action = {
|
||||
type: ViewTypes.COMMENT_DRAFT_CHANGED,
|
||||
rootId: threadId
|
||||
};
|
||||
} else if (state.views.channel.drafts[channelId]) {
|
||||
const channelDraft = state.views.channel.drafts[channelId];
|
||||
draft = channelDraft.draft;
|
||||
cursorPosition = channelDraft.cursorPosition;
|
||||
action = {
|
||||
type: ViewTypes.POST_DRAFT_CHANGED,
|
||||
channelId
|
||||
};
|
||||
}
|
||||
|
||||
let nextDraft = `${value}`;
|
||||
if (cursorPosition > 0) {
|
||||
const beginning = draft.slice(0, cursorPosition);
|
||||
const end = draft.slice(cursorPosition);
|
||||
nextDraft = `${beginning}${value}${end}`;
|
||||
}
|
||||
|
||||
if (action && nextDraft !== draft) {
|
||||
dispatch({
|
||||
...action,
|
||||
draft: nextDraft
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export function toggleDMChannel(otherUserId, visible) {
|
||||
return async (dispatch, getState) => {
|
||||
const state = getState();
|
||||
@@ -399,11 +340,8 @@ export function closeGMChannel(channel) {
|
||||
}
|
||||
|
||||
export function refreshChannelWithRetry(channelId) {
|
||||
return async (dispatch, getState) => {
|
||||
dispatch(setChannelRefreshing(true));
|
||||
const posts = await retryGetPostsAction(getPosts(channelId), dispatch, getState);
|
||||
dispatch(setChannelRefreshing(false));
|
||||
return posts;
|
||||
return (dispatch, getState) => {
|
||||
return retryGetPostsAction(getPosts(channelId), dispatch, getState);
|
||||
};
|
||||
}
|
||||
|
||||
@@ -431,10 +369,10 @@ export function setChannelRefreshing(loading = true) {
|
||||
};
|
||||
}
|
||||
|
||||
export function setChannelRetryFailed(failed = true) {
|
||||
export function setPostTooltipVisible(visible = true) {
|
||||
return {
|
||||
type: ViewTypes.SET_CHANNEL_RETRY_FAILED,
|
||||
failed
|
||||
type: ViewTypes.POST_TOOLTIP_VISIBLE,
|
||||
visible
|
||||
};
|
||||
}
|
||||
|
||||
@@ -466,14 +404,13 @@ export function increasePostVisibility(channelId, focusedPostId) {
|
||||
|
||||
const page = Math.floor(currentPostVisibility / ViewTypes.POST_VISIBILITY_CHUNK_SIZE);
|
||||
|
||||
let result;
|
||||
let posts;
|
||||
if (focusedPostId) {
|
||||
result = await getPostsBefore(channelId, focusedPostId, page, ViewTypes.POST_VISIBILITY_CHUNK_SIZE)(dispatch, getState);
|
||||
posts = await getPostsBefore(channelId, focusedPostId, page, ViewTypes.POST_VISIBILITY_CHUNK_SIZE)(dispatch, getState);
|
||||
} else {
|
||||
result = await getPosts(channelId, page, ViewTypes.POST_VISIBILITY_CHUNK_SIZE)(dispatch, getState);
|
||||
posts = await getPosts(channelId, page, ViewTypes.POST_VISIBILITY_CHUNK_SIZE)(dispatch, getState);
|
||||
}
|
||||
|
||||
const posts = result.data;
|
||||
if (posts) {
|
||||
// make sure to increment the posts visibility
|
||||
// only if we got results
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
import {ViewTypes} from 'app/constants';
|
||||
|
||||
export function setLastUpgradeCheck() {
|
||||
return {
|
||||
type: ViewTypes.SET_LAST_UPGRADE_CHECK
|
||||
};
|
||||
}
|
||||
@@ -12,7 +12,7 @@ export function handleCreateChannel(displayName, purpose, header, type) {
|
||||
const state = getState();
|
||||
const currentUserId = getCurrentUserId(state);
|
||||
const teamId = getCurrentTeamId(state);
|
||||
const channel = {
|
||||
let channel = {
|
||||
team_id: teamId,
|
||||
name: cleanUpUrlable(displayName),
|
||||
display_name: displayName,
|
||||
@@ -21,10 +21,10 @@ export function handleCreateChannel(displayName, purpose, header, type) {
|
||||
type
|
||||
};
|
||||
|
||||
const {data} = await createChannel(channel, currentUserId)(dispatch, getState);
|
||||
if (data && data.id) {
|
||||
channel = await createChannel(channel, currentUserId)(dispatch, getState);
|
||||
if (channel && channel.id) {
|
||||
dispatch(setChannelDisplayName(displayName));
|
||||
handleSelectChannel(data.id)(dispatch, getState);
|
||||
handleSelectChannel(channel.id)(dispatch, getState);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -2,16 +2,16 @@
|
||||
// See License.txt for license information.
|
||||
|
||||
import {addReaction} from 'mattermost-redux/actions/posts';
|
||||
import {getPostIdsInCurrentChannel, makeGetPostIdsForThread} from 'mattermost-redux/selectors/entities/posts';
|
||||
import {getPostsInCurrentChannel, makeGetPostsForThread} from 'mattermost-redux/selectors/entities/posts';
|
||||
|
||||
const getPostIdsForThread = makeGetPostIdsForThread();
|
||||
const getPostsForThread = makeGetPostsForThread();
|
||||
|
||||
export function addReactionToLatestPost(emoji, rootId) {
|
||||
return async (dispatch, getState) => {
|
||||
const state = getState();
|
||||
const postIds = rootId ? getPostIdsForThread(state, rootId) : getPostIdsInCurrentChannel(state);
|
||||
const lastPostId = postIds[0];
|
||||
const posts = rootId ? getPostsForThread(state, {rootId}) : getPostsInCurrentChannel(state);
|
||||
const lastPost = posts[0];
|
||||
|
||||
dispatch(addReaction(lastPostId, emoji));
|
||||
dispatch(addReaction(lastPost.id, emoji));
|
||||
};
|
||||
}
|
||||
|
||||
14
app/actions/views/load_team.js
Normal file
14
app/actions/views/load_team.js
Normal file
@@ -0,0 +1,14 @@
|
||||
// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
import {ViewTypes} from 'app/constants';
|
||||
|
||||
export function initialize() {
|
||||
return async (dispatch, getState) => {
|
||||
setTimeout(() => {
|
||||
dispatch({
|
||||
type: ViewTypes.APPLICATION_INITIALIZED
|
||||
}, getState);
|
||||
}, 400);
|
||||
};
|
||||
}
|
||||
@@ -1,16 +1,16 @@
|
||||
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
import {General} from 'mattermost-redux/constants';
|
||||
import {getClientConfig, getLicenseConfig} from 'mattermost-redux/actions/general';
|
||||
import {getPosts} from 'mattermost-redux/actions/posts';
|
||||
import {getMyTeams, getMyTeamMembers, selectTeam} from 'mattermost-redux/actions/teams';
|
||||
|
||||
import {ViewTypes} from 'app/constants';
|
||||
import {
|
||||
handleSelectChannel,
|
||||
setChannelDisplayName,
|
||||
retryGetPostsAction
|
||||
setChannelDisplayName
|
||||
} from 'app/actions/views/channel';
|
||||
import {handleTeamChange, selectFirstAvailableTeam} from 'app/actions/views/select_team';
|
||||
|
||||
import {General} from 'mattermost-redux/constants';
|
||||
import {getClientConfig, getLicenseConfig} from 'mattermost-redux/actions/general';
|
||||
import {getChannelAndMyMember, markChannelAsRead, viewChannel} from 'mattermost-redux/actions/channels';
|
||||
|
||||
export function loadConfigAndLicense() {
|
||||
return async (dispatch, getState) => {
|
||||
@@ -23,39 +23,48 @@ export function loadConfigAndLicense() {
|
||||
};
|
||||
}
|
||||
|
||||
export function loadFromPushNotification(notification) {
|
||||
export function queueNotification(notification) {
|
||||
return async (dispatch, getState) => {
|
||||
dispatch({type: ViewTypes.NOTIFICATION_CHANGED, data: notification}, getState);
|
||||
};
|
||||
}
|
||||
|
||||
export function clearNotification() {
|
||||
return async (dispatch, getState) => {
|
||||
dispatch({type: ViewTypes.NOTIFICATION_CHANGED, data: null}, getState);
|
||||
};
|
||||
}
|
||||
|
||||
export function goToNotification(notification) {
|
||||
return async (dispatch, getState) => {
|
||||
const state = getState();
|
||||
const {data} = notification;
|
||||
const {currentTeamId, teams, myMembers: myTeamMembers} = state.entities.teams;
|
||||
const {currentChannelId} = state.entities.channels;
|
||||
const {currentTeamId, teams} = state.entities.teams;
|
||||
const {channels, currentChannelId, myMembers} = state.entities.channels;
|
||||
const channelId = data.channel_id;
|
||||
|
||||
// when the notification does not have a team id is because its from a DM or GM
|
||||
// if the notification does not have a team id is because its from a DM or GM
|
||||
const teamId = data.team_id || currentTeamId;
|
||||
|
||||
//verify that we have the team loaded
|
||||
if (teamId && (!teams[teamId] || !myTeamMembers[teamId])) {
|
||||
await Promise.all([
|
||||
getMyTeams()(dispatch, getState),
|
||||
getMyTeamMembers()(dispatch, getState)
|
||||
]);
|
||||
dispatch(setChannelDisplayName(''));
|
||||
|
||||
if (teamId && teamId !== currentTeamId) {
|
||||
handleTeamChange(teams[teamId], false)(dispatch, getState);
|
||||
} else if (!teamId) {
|
||||
await selectFirstAvailableTeam()(dispatch, getState);
|
||||
}
|
||||
|
||||
// when the notification is from a team other than the current team
|
||||
if (teamId !== currentTeamId) {
|
||||
selectTeam({id: teamId})(dispatch, getState);
|
||||
if (!channels[channelId] || !myMembers[channelId]) {
|
||||
getChannelAndMyMember(channelId)(dispatch, getState);
|
||||
}
|
||||
|
||||
// when the notification is from the same channel as the current channel
|
||||
// we should get the posts
|
||||
if (channelId === currentChannelId) {
|
||||
await retryGetPostsAction(getPosts(channelId), dispatch, getState);
|
||||
} else {
|
||||
// when the notification is from a channel other than the current channel
|
||||
dispatch(setChannelDisplayName(''));
|
||||
if (channelId !== currentChannelId) {
|
||||
handleSelectChannel(channelId)(dispatch, getState);
|
||||
}
|
||||
|
||||
viewChannel(channelId)(dispatch, getState);
|
||||
|
||||
markChannelAsRead(channelId, currentChannelId)(dispatch, getState);
|
||||
};
|
||||
}
|
||||
|
||||
@@ -65,6 +74,8 @@ export function purgeOfflineStore() {
|
||||
|
||||
export default {
|
||||
loadConfigAndLicense,
|
||||
loadFromPushNotification,
|
||||
queueNotification,
|
||||
clearNotification,
|
||||
goToNotification,
|
||||
purgeOfflineStore
|
||||
};
|
||||
|
||||
@@ -12,23 +12,23 @@ import {NavigationTypes} from 'app/constants';
|
||||
|
||||
import {setChannelDisplayName} from './channel';
|
||||
|
||||
export function handleTeamChange(teamId, selectChannel = true) {
|
||||
export function handleTeamChange(team, selectChannel = true) {
|
||||
return async (dispatch, getState) => {
|
||||
const state = getState();
|
||||
const {currentTeamId} = state.entities.teams;
|
||||
if (currentTeamId === teamId) {
|
||||
if (currentTeamId === team.id) {
|
||||
return;
|
||||
}
|
||||
|
||||
const actions = [
|
||||
setChannelDisplayName(''),
|
||||
{type: TeamTypes.SELECT_TEAM, data: teamId}
|
||||
{type: TeamTypes.SELECT_TEAM, data: team.id}
|
||||
];
|
||||
|
||||
if (selectChannel) {
|
||||
actions.push({type: ChannelTypes.SELECT_CHANNEL, data: ''});
|
||||
|
||||
const lastChannelId = state.views.team.lastChannelForTeam[teamId] || '';
|
||||
const lastChannelId = state.views.team.lastChannelForTeam[team.id] || '';
|
||||
const currentChannelId = getCurrentChannelId(state);
|
||||
viewChannel(lastChannelId, currentChannelId)(dispatch, getState);
|
||||
markChannelAsRead(lastChannelId, currentChannelId)(dispatch, getState);
|
||||
@@ -45,7 +45,7 @@ export function selectFirstAvailableTeam() {
|
||||
const firstTeam = Object.values(teams).sort((a, b) => a.display_name.localeCompare(b.display_name))[0];
|
||||
|
||||
if (firstTeam) {
|
||||
handleTeamChange(firstTeam.id)(dispatch, getState);
|
||||
handleTeamChange(firstTeam)(dispatch, getState);
|
||||
} else {
|
||||
EventEmitter.emit(NavigationTypes.NAVIGATION_NO_TEAMS);
|
||||
}
|
||||
|
||||
@@ -12,11 +12,3 @@ export function handleCommentDraftChanged(rootId, draft) {
|
||||
}, getState);
|
||||
};
|
||||
}
|
||||
|
||||
export function handleCommentDraftSelectionChanged(rootId, cursorPosition) {
|
||||
return {
|
||||
type: ViewTypes.COMMENT_DRAFT_SELECTION_CHANGED,
|
||||
rootId,
|
||||
cursorPosition
|
||||
};
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ import {connect} from 'react-redux';
|
||||
|
||||
import {getUsersByUsername} from 'mattermost-redux/selectors/entities/users';
|
||||
|
||||
import {getTheme} from 'mattermost-redux/selectors/entities/preferences';
|
||||
import {getTheme} from 'app/selectors/preferences';
|
||||
|
||||
import AtMention from './at_mention';
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@ import {
|
||||
filterMembersInCurrentTeam,
|
||||
getMatchTermForAtMention
|
||||
} from 'app/selectors/autocomplete';
|
||||
import {getTheme} from 'mattermost-redux/selectors/entities/preferences';
|
||||
import {getTheme} from 'app/selectors/preferences';
|
||||
|
||||
import AtMention from './at_mention';
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ import {connect} from 'react-redux';
|
||||
|
||||
import {getUser} from 'mattermost-redux/selectors/entities/users';
|
||||
|
||||
import {getTheme} from 'mattermost-redux/selectors/entities/preferences';
|
||||
import {getTheme} from 'app/selectors/preferences';
|
||||
|
||||
import AtMentionItem from './at_mention_item';
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ import {
|
||||
filterPrivateChannels,
|
||||
getMatchTermForChannelMention
|
||||
} from 'app/selectors/autocomplete';
|
||||
import {getTheme} from 'mattermost-redux/selectors/entities/preferences';
|
||||
import {getTheme} from 'app/selectors/preferences';
|
||||
|
||||
import ChannelMention from './channel_mention';
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ import {connect} from 'react-redux';
|
||||
|
||||
import {getChannel} from 'mattermost-redux/selectors/entities/channels';
|
||||
|
||||
import {getTheme} from 'mattermost-redux/selectors/entities/preferences';
|
||||
import {getTheme} from 'app/selectors/preferences';
|
||||
|
||||
import ChannelMentionItem from './channel_mention_item';
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ import {bindActionCreators} from 'redux';
|
||||
import {getCustomEmojisByName} from 'mattermost-redux/selectors/entities/emojis';
|
||||
|
||||
import {addReactionToLatestPost} from 'app/actions/views/emoji';
|
||||
import {getTheme} from 'mattermost-redux/selectors/entities/preferences';
|
||||
import {getTheme} from 'app/selectors/preferences';
|
||||
import {EmojiIndicesByAlias} from 'app/utils/emojis';
|
||||
|
||||
import EmojiSuggestion from './emoji_suggestion';
|
||||
|
||||
@@ -118,10 +118,6 @@ export default class Badge extends PureComponent {
|
||||
};
|
||||
|
||||
render() {
|
||||
if (!this.props.count) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<TouchableWithoutFeedback
|
||||
{...this.panResponder.panHandlers}
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
import React, {Component} from 'react';
|
||||
import React, {PureComponent} from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import {
|
||||
BackHandler,
|
||||
InteractionManager,
|
||||
Keyboard,
|
||||
Platform,
|
||||
StyleSheet,
|
||||
View
|
||||
} from 'react-native';
|
||||
@@ -25,7 +24,7 @@ import EventEmitter from 'mattermost-redux/utils/event_emitter';
|
||||
const DRAWER_INITIAL_OFFSET = 40;
|
||||
const DRAWER_LANDSCAPE_OFFSET = 150;
|
||||
|
||||
export default class ChannelDrawer extends Component {
|
||||
export default class ChannelDrawer extends PureComponent {
|
||||
static propTypes = {
|
||||
actions: PropTypes.shape({
|
||||
getTeams: PropTypes.func.isRequired,
|
||||
@@ -38,6 +37,7 @@ export default class ChannelDrawer extends Component {
|
||||
}).isRequired,
|
||||
blurPostTextBox: PropTypes.func.isRequired,
|
||||
children: PropTypes.node,
|
||||
currentChannelId: PropTypes.string.isRequired,
|
||||
currentTeamId: PropTypes.string.isRequired,
|
||||
currentUserId: PropTypes.string.isRequired,
|
||||
isLandscape: PropTypes.bool.isRequired,
|
||||
@@ -60,6 +60,7 @@ export default class ChannelDrawer extends Component {
|
||||
openDrawerOffset = DRAWER_LANDSCAPE_OFFSET;
|
||||
}
|
||||
this.state = {
|
||||
openDrawer: false,
|
||||
openDrawerOffset
|
||||
};
|
||||
}
|
||||
@@ -73,14 +74,15 @@ export default class ChannelDrawer extends Component {
|
||||
EventEmitter.on('close_channel_drawer', this.closeChannelDrawer);
|
||||
EventEmitter.on(WebsocketEvents.CHANNEL_UPDATED, this.handleUpdateTitle);
|
||||
BackHandler.addEventListener('hardwareBackPress', this.handleAndroidBack);
|
||||
this.mounted = true;
|
||||
}
|
||||
|
||||
componentWillReceiveProps(nextProps) {
|
||||
const {isLandscape} = this.props;
|
||||
if (nextProps.isLandscape !== isLandscape) {
|
||||
const {isLandscape, isTablet} = this.props;
|
||||
if (nextProps.isLandscape !== isLandscape || nextProps.isTablet || isTablet) {
|
||||
if (this.state.openDrawerOffset !== 0) {
|
||||
let openDrawerOffset = DRAWER_INITIAL_OFFSET;
|
||||
if (nextProps.isLandscape || this.props.isTablet) {
|
||||
if (nextProps.isLandscape || nextProps.isTablet) {
|
||||
openDrawerOffset = DRAWER_LANDSCAPE_OFFSET;
|
||||
}
|
||||
this.setState({openDrawerOffset});
|
||||
@@ -88,29 +90,17 @@ export default class ChannelDrawer extends Component {
|
||||
}
|
||||
}
|
||||
|
||||
shouldComponentUpdate(nextProps, nextState) {
|
||||
const {currentTeamId, isLandscape, teamsCount} = this.props;
|
||||
const {openDrawerOffset} = this.state;
|
||||
|
||||
if (nextState.openDrawerOffset !== openDrawerOffset) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return nextProps.currentTeamId !== currentTeamId ||
|
||||
nextProps.isLandscape !== isLandscape ||
|
||||
nextProps.teamsCount !== teamsCount;
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
EventEmitter.off('open_channel_drawer', this.openChannelDrawer);
|
||||
EventEmitter.off('close_channel_drawer', this.closeChannelDrawer);
|
||||
EventEmitter.off(WebsocketEvents.CHANNEL_UPDATED, this.handleUpdateTitle);
|
||||
BackHandler.removeEventListener('hardwareBackPress', this.handleAndroidBack);
|
||||
this.mounted = false;
|
||||
}
|
||||
|
||||
handleAndroidBack = () => {
|
||||
if (this.refs.drawer && this.refs.drawer.isOpened()) {
|
||||
this.refs.drawer.close();
|
||||
if (this.state.openDrawer) {
|
||||
this.setState({openDrawer: false});
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -118,8 +108,8 @@ export default class ChannelDrawer extends Component {
|
||||
};
|
||||
|
||||
closeChannelDrawer = () => {
|
||||
if (this.refs.drawer && this.refs.drawer.isOpened()) {
|
||||
this.refs.drawer.close();
|
||||
if (this.mounted) {
|
||||
this.setState({openDrawer: false});
|
||||
}
|
||||
};
|
||||
|
||||
@@ -134,6 +124,13 @@ export default class ChannelDrawer extends Component {
|
||||
InteractionManager.clearInteractionHandle(this.closeLeftHandle);
|
||||
this.closeLeftHandle = null;
|
||||
}
|
||||
|
||||
if (this.state.openDrawer && this.mounted) {
|
||||
// The state doesn't get updated if you swipe to close
|
||||
this.setState({
|
||||
openDrawer: false
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
handleDrawerCloseStart = () => {
|
||||
@@ -157,6 +154,13 @@ export default class ChannelDrawer extends Component {
|
||||
if (!this.openLeftHandle) {
|
||||
this.openLeftHandle = InteractionManager.createInteractionHandle();
|
||||
}
|
||||
|
||||
if (!this.state.openDrawer && this.mounted) {
|
||||
// The state doesn't get updated if you swipe to open
|
||||
this.setState({
|
||||
openDrawer: true
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
handleDrawerTween = (ratio) => {
|
||||
@@ -188,14 +192,17 @@ export default class ChannelDrawer extends Component {
|
||||
openChannelDrawer = () => {
|
||||
this.props.blurPostTextBox();
|
||||
|
||||
if (this.refs.drawer && !this.refs.drawer.isOpened()) {
|
||||
this.refs.drawer.open();
|
||||
if (this.mounted) {
|
||||
this.setState({
|
||||
openDrawer: true
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
selectChannel = (channel, currentChannelId) => {
|
||||
selectChannel = (channel) => {
|
||||
const {
|
||||
actions
|
||||
actions,
|
||||
currentChannelId
|
||||
} = this.props;
|
||||
|
||||
const {
|
||||
@@ -206,24 +213,21 @@ export default class ChannelDrawer extends Component {
|
||||
viewChannel
|
||||
} = actions;
|
||||
|
||||
setChannelLoading(channel.id !== currentChannelId);
|
||||
setChannelLoading();
|
||||
setChannelDisplayName(channel.display_name);
|
||||
|
||||
this.closeChannelDrawer();
|
||||
|
||||
InteractionManager.runAfterInteractions(() => {
|
||||
handleSelectChannel(channel.id);
|
||||
requestAnimationFrame(() => {
|
||||
// mark the channel as viewed after all the frame has flushed
|
||||
markChannelAsRead(channel.id, currentChannelId);
|
||||
if (channel.id !== currentChannelId) {
|
||||
viewChannel(currentChannelId);
|
||||
}
|
||||
});
|
||||
markChannelAsRead(channel.id, currentChannelId);
|
||||
if (channel.id !== currentChannelId) {
|
||||
viewChannel(currentChannelId);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
joinChannel = async (channel, currentChannelId) => {
|
||||
joinChannel = async (channel) => {
|
||||
const {
|
||||
actions,
|
||||
currentTeamId,
|
||||
@@ -265,7 +269,7 @@ export default class ChannelDrawer extends Component {
|
||||
return;
|
||||
}
|
||||
|
||||
this.selectChannel(result.data, currentChannelId);
|
||||
this.selectChannel(result.data);
|
||||
};
|
||||
|
||||
onPageSelected = (index) => {
|
||||
@@ -275,11 +279,7 @@ export default class ChannelDrawer extends Component {
|
||||
onSearchEnds = () => {
|
||||
//hack to update the drawer when the offset changes
|
||||
const {isLandscape, isTablet} = this.props;
|
||||
|
||||
if (this.refs.drawer) {
|
||||
this.refs.drawer._syncAfterUpdate = true; //eslint-disable-line no-underscore-dangle
|
||||
}
|
||||
|
||||
this.refs.drawer._syncAfterUpdate = true; //eslint-disable-line no-underscore-dangle
|
||||
let openDrawerOffset = DRAWER_INITIAL_OFFSET;
|
||||
if (isLandscape || isTablet) {
|
||||
openDrawerOffset = DRAWER_LANDSCAPE_OFFSET;
|
||||
@@ -288,21 +288,18 @@ export default class ChannelDrawer extends Component {
|
||||
};
|
||||
|
||||
onSearchStart = () => {
|
||||
if (this.refs.drawer) {
|
||||
this.refs.drawer._syncAfterUpdate = true; //eslint-disable-line no-underscore-dangle
|
||||
}
|
||||
|
||||
this.refs.drawer._syncAfterUpdate = true; //eslint-disable-line no-underscore-dangle
|
||||
this.setState({openDrawerOffset: 0});
|
||||
};
|
||||
|
||||
showTeams = () => {
|
||||
if (this.drawerSwiper && this.swiperIndex === 1 && this.props.teamsCount > 1) {
|
||||
if (this.swiperIndex === 1 && this.props.teamsCount > 1) {
|
||||
this.drawerSwiper.getWrappedInstance().showTeamsPage();
|
||||
}
|
||||
};
|
||||
|
||||
resetDrawer = () => {
|
||||
if (this.drawerSwiper && this.swiperIndex !== 1) {
|
||||
if (this.swiperIndex !== 1) {
|
||||
this.drawerSwiper.getWrappedInstance().resetPage();
|
||||
}
|
||||
};
|
||||
@@ -356,7 +353,6 @@ export default class ChannelDrawer extends Component {
|
||||
onShowTeams={this.showTeams}
|
||||
onSearchStart={this.onSearchStart}
|
||||
onSearchEnds={this.onSearchEnds}
|
||||
theme={theme}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
@@ -367,6 +363,7 @@ export default class ChannelDrawer extends Component {
|
||||
onPageSelected={this.onPageSelected}
|
||||
openDrawerOffset={openDrawerOffset}
|
||||
showTeams={showTeams}
|
||||
theme={theme}
|
||||
>
|
||||
{lists}
|
||||
</DrawerSwiper>
|
||||
@@ -375,11 +372,12 @@ export default class ChannelDrawer extends Component {
|
||||
|
||||
render() {
|
||||
const {children} = this.props;
|
||||
const {openDrawerOffset} = this.state;
|
||||
const {openDrawer, openDrawerOffset} = this.state;
|
||||
|
||||
return (
|
||||
<Drawer
|
||||
ref='drawer'
|
||||
open={openDrawer}
|
||||
onOpenStart={this.handleDrawerOpenStart}
|
||||
onOpen={this.handleDrawerOpen}
|
||||
onClose={this.handleDrawerClose}
|
||||
@@ -402,8 +400,6 @@ export default class ChannelDrawer extends Component {
|
||||
tweenDuration={100}
|
||||
tweenHandler={this.handleDrawerTween}
|
||||
elevation={-5}
|
||||
bottomPanOffset={Platform.OS === 'ios' ? 46 : 64}
|
||||
topPanOffset={Platform.OS === 'ios' ? 64 : 46}
|
||||
styles={{
|
||||
main: {
|
||||
shadowColor: '#000000',
|
||||
|
||||
@@ -10,63 +10,45 @@ import {
|
||||
} from 'react-native';
|
||||
|
||||
import Badge from 'app/components/badge';
|
||||
import ChannelIcon from 'app/components/channel_icon';
|
||||
import {wrapWithPreventDoubleTap} from 'app/utils/tap';
|
||||
import ChanneIcon from 'app/components/channel_icon';
|
||||
import {preventDoubleTap} from 'app/utils/tap';
|
||||
import {changeOpacity, makeStyleSheetFromTheme} from 'app/utils/theme';
|
||||
|
||||
export default class ChannelItem extends PureComponent {
|
||||
static propTypes = {
|
||||
channelId: PropTypes.string.isRequired,
|
||||
currentChannelId: PropTypes.string.isRequired,
|
||||
displayName: PropTypes.string.isRequired,
|
||||
fake: PropTypes.bool,
|
||||
isUnread: PropTypes.bool,
|
||||
mentions: PropTypes.number.isRequired,
|
||||
channel: PropTypes.object.isRequired,
|
||||
onSelectChannel: PropTypes.func.isRequired,
|
||||
status: PropTypes.string,
|
||||
type: PropTypes.string.isRequired,
|
||||
isActive: PropTypes.bool.isRequired,
|
||||
hasUnread: PropTypes.bool.isRequired,
|
||||
mentions: PropTypes.number.isRequired,
|
||||
theme: PropTypes.object.isRequired
|
||||
};
|
||||
|
||||
onPress = wrapWithPreventDoubleTap(() => {
|
||||
const {channelId, currentChannelId, displayName, fake, onSelectChannel, type} = this.props;
|
||||
onPress = () => {
|
||||
const {channel, onSelectChannel} = this.props;
|
||||
requestAnimationFrame(() => {
|
||||
onSelectChannel({id: channelId, display_name: displayName, fake, type}, currentChannelId);
|
||||
preventDoubleTap(onSelectChannel, this, channel);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
render() {
|
||||
const {
|
||||
channelId,
|
||||
currentChannelId,
|
||||
displayName,
|
||||
isUnread,
|
||||
mentions,
|
||||
status,
|
||||
channel,
|
||||
theme,
|
||||
type
|
||||
mentions,
|
||||
hasUnread,
|
||||
isActive
|
||||
} = this.props;
|
||||
|
||||
const style = getStyleSheet(theme);
|
||||
const isActive = channelId === currentChannelId;
|
||||
|
||||
let extraItemStyle;
|
||||
let extraTextStyle;
|
||||
let extraBorder;
|
||||
|
||||
if (isActive) {
|
||||
extraItemStyle = style.itemActive;
|
||||
extraTextStyle = style.textActive;
|
||||
|
||||
extraBorder = (
|
||||
<View style={style.borderActive}/>
|
||||
);
|
||||
} else if (isUnread) {
|
||||
extraTextStyle = style.textUnread;
|
||||
}
|
||||
let activeItem;
|
||||
let activeText;
|
||||
let unreadText;
|
||||
|
||||
let activeBorder;
|
||||
let badge;
|
||||
if (mentions) {
|
||||
|
||||
if (mentions && !isActive) {
|
||||
badge = (
|
||||
<Badge
|
||||
style={style.badge}
|
||||
@@ -79,16 +61,28 @@ export default class ChannelItem extends PureComponent {
|
||||
);
|
||||
}
|
||||
|
||||
if (hasUnread) {
|
||||
unreadText = style.textUnread;
|
||||
}
|
||||
|
||||
if (isActive) {
|
||||
activeItem = style.itemActive;
|
||||
activeText = style.textActive;
|
||||
|
||||
activeBorder = (
|
||||
<View style={style.borderActive}/>
|
||||
);
|
||||
}
|
||||
|
||||
const icon = (
|
||||
<ChannelIcon
|
||||
<ChanneIcon
|
||||
isActive={isActive}
|
||||
channelId={channelId}
|
||||
isUnread={isUnread}
|
||||
membersCount={displayName.split(',').length}
|
||||
hasUnread={hasUnread}
|
||||
membersCount={channel.display_name.split(',').length}
|
||||
size={16}
|
||||
status={status}
|
||||
status={channel.status}
|
||||
theme={theme}
|
||||
type={type}
|
||||
type={channel.type}
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -98,15 +92,15 @@ export default class ChannelItem extends PureComponent {
|
||||
onPress={this.onPress}
|
||||
>
|
||||
<View style={style.container}>
|
||||
{extraBorder}
|
||||
<View style={[style.item, extraItemStyle]}>
|
||||
{activeBorder}
|
||||
<View style={[style.item, activeItem]}>
|
||||
{icon}
|
||||
<Text
|
||||
style={[style.text, extraTextStyle]}
|
||||
style={[style.text, unreadText, activeText]}
|
||||
ellipsizeMode='tail'
|
||||
numberOfLines={1}
|
||||
>
|
||||
{displayName}
|
||||
{channel.display_name}
|
||||
</Text>
|
||||
{badge}
|
||||
</View>
|
||||
@@ -1,33 +0,0 @@
|
||||
// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
import {connect} from 'react-redux';
|
||||
|
||||
import {getCurrentChannelId, makeGetChannel, getMyChannelMember} from 'mattermost-redux/selectors/entities/channels';
|
||||
import {getTheme} from 'mattermost-redux/selectors/entities/preferences';
|
||||
|
||||
import ChannelItem from './channel_item';
|
||||
|
||||
function makeMapStateToProps() {
|
||||
const getChannel = makeGetChannel();
|
||||
|
||||
return (state, ownProps) => {
|
||||
const channel = ownProps.channel || getChannel(state, {id: ownProps.channelId});
|
||||
let member;
|
||||
if (ownProps.isUnread) {
|
||||
member = getMyChannelMember(state, ownProps.channelId);
|
||||
}
|
||||
|
||||
return {
|
||||
currentChannelId: getCurrentChannelId(state),
|
||||
displayName: channel.display_name,
|
||||
fake: channel.fake,
|
||||
mentions: member ? member.mention_count : 0,
|
||||
status: channel.status,
|
||||
theme: getTheme(state),
|
||||
type: channel.type
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
export default connect(makeMapStateToProps)(ChannelItem);
|
||||
@@ -18,7 +18,7 @@ import {changeOpacity, makeStyleSheetFromTheme} from 'app/utils/theme';
|
||||
|
||||
import FilteredList from './filtered_list';
|
||||
import List from './list';
|
||||
import SwitchTeamsButton from './switch_teams_button';
|
||||
import SwitchTeams from './switch_teams';
|
||||
|
||||
class ChannelsList extends React.PureComponent {
|
||||
static propTypes = {
|
||||
@@ -45,16 +45,14 @@ class ChannelsList extends React.PureComponent {
|
||||
});
|
||||
}
|
||||
|
||||
onSelectChannel = (channel, currentChannelId) => {
|
||||
onSelectChannel = (channel) => {
|
||||
if (channel.fake) {
|
||||
this.props.onJoinChannel(channel, currentChannelId);
|
||||
this.props.onJoinChannel(channel);
|
||||
} else {
|
||||
this.props.onSelectChannel(channel, currentChannelId);
|
||||
this.props.onSelectChannel(channel);
|
||||
}
|
||||
|
||||
if (this.refs.search_bar) {
|
||||
this.refs.search_bar.cancel();
|
||||
}
|
||||
this.refs.search_bar.cancel();
|
||||
};
|
||||
|
||||
openSettingsModal = wrapWithPreventDoubleTap(() => {
|
||||
@@ -144,18 +142,17 @@ class ChannelsList extends React.PureComponent {
|
||||
<View style={styles.searchContainer}>
|
||||
<SearchBar
|
||||
ref='search_bar'
|
||||
placeholder={intl.formatMessage({id: 'mobile.channel_drawer.search', defaultMessage: 'Jump to...'})}
|
||||
placeholder={intl.formatMessage({id: 'mobile.channel_drawer.search', defaultMessage: 'Jump to a conversation'})}
|
||||
cancelTitle={intl.formatMessage({id: 'mobile.post.cancel', defaultMessage: 'Cancel'})}
|
||||
backgroundColor='transparent'
|
||||
inputHeight={33}
|
||||
inputStyle={{
|
||||
backgroundColor: changeOpacity(theme.sidebarHeaderTextColor, 0.2),
|
||||
color: theme.sidebarHeaderTextColor,
|
||||
fontSize: 15,
|
||||
lineHeight: 66
|
||||
fontSize: 13
|
||||
}}
|
||||
placeholderTextColor={changeOpacity(theme.sidebarHeaderTextColor, 0.5)}
|
||||
tintColorSearch={changeOpacity(theme.sidebarHeaderTextColor, 0.5)}
|
||||
tintColorSearch={changeOpacity(theme.sidebarHeaderTextColor, 0.8)}
|
||||
tintColorDelete={changeOpacity(theme.sidebarHeaderTextColor, 0.5)}
|
||||
titleCancelColor={theme.sidebarHeaderTextColor}
|
||||
selectionColor={changeOpacity(theme.sidebarHeaderTextColor, 0.5)}
|
||||
@@ -174,12 +171,10 @@ class ChannelsList extends React.PureComponent {
|
||||
>
|
||||
<View style={styles.statusBar}>
|
||||
<View style={styles.headerContainer}>
|
||||
<View style={styles.switchContainer}>
|
||||
<SwitchTeamsButton
|
||||
searching={searching}
|
||||
onShowTeams={onShowTeams}
|
||||
/>
|
||||
</View>
|
||||
<SwitchTeams
|
||||
searching={searching}
|
||||
showTeams={onShowTeams}
|
||||
/>
|
||||
{title}
|
||||
{settings}
|
||||
</View>
|
||||
@@ -227,10 +222,6 @@ const getStyleSheet = makeStyleSheetFromTheme((theme) => {
|
||||
fontWeight: 'normal',
|
||||
paddingLeft: 16
|
||||
},
|
||||
switchContainer: {
|
||||
position: 'relative',
|
||||
top: -1
|
||||
},
|
||||
settingsContainer: {
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
@@ -297,6 +288,15 @@ const getStyleSheet = makeStyleSheetFromTheme((theme) => {
|
||||
above: {
|
||||
backgroundColor: theme.mentionBj,
|
||||
top: 9
|
||||
},
|
||||
indicatorText: {
|
||||
backgroundColor: 'transparent',
|
||||
color: theme.mentionColor,
|
||||
fontSize: 14,
|
||||
paddingVertical: 2,
|
||||
paddingHorizontal: 4,
|
||||
textAlign: 'center',
|
||||
textAlignVertical: 'center'
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
@@ -95,25 +95,25 @@ class FilteredList extends Component {
|
||||
}
|
||||
|
||||
onSelectChannel = (channel) => {
|
||||
const {actions, currentChannel} = this.props;
|
||||
const {makeGroupMessageVisibleIfNecessary} = actions;
|
||||
const {makeGroupMessageVisibleIfNecessary} = this.props.actions;
|
||||
|
||||
if (channel.type === General.GM_CHANNEL) {
|
||||
makeGroupMessageVisibleIfNecessary(channel.id);
|
||||
}
|
||||
|
||||
this.props.onSelectChannel(channel, currentChannel.id);
|
||||
this.props.onSelectChannel(channel);
|
||||
};
|
||||
|
||||
createChannelElement = (channel) => {
|
||||
return (
|
||||
<ChannelDrawerItem
|
||||
ref={channel.id}
|
||||
channelId={channel.id}
|
||||
channel={channel}
|
||||
isUnread={false}
|
||||
hasUnread={false}
|
||||
mentions={0}
|
||||
onSelectChannel={this.onSelectChannel}
|
||||
isActive={channel.isCurrent || false}
|
||||
theme={this.props.theme}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -3,12 +3,13 @@
|
||||
|
||||
import {connect} from 'react-redux';
|
||||
|
||||
import {getTheme} from 'mattermost-redux/selectors/entities/preferences';
|
||||
import {getTheme} from 'app/selectors/preferences';
|
||||
|
||||
import ChannelsList from './channels_list';
|
||||
|
||||
function mapStateToProps(state) {
|
||||
function mapStateToProps(state, ownProps) {
|
||||
return {
|
||||
...ownProps,
|
||||
theme: getTheme(state)
|
||||
};
|
||||
}
|
||||
|
||||
@@ -4,48 +4,26 @@
|
||||
import {connect} from 'react-redux';
|
||||
|
||||
import {General} from 'mattermost-redux/constants';
|
||||
import {
|
||||
getSortedUnreadChannelIds,
|
||||
getSortedFavoriteChannelIds,
|
||||
getSortedPublicChannelIds,
|
||||
getSortedPrivateChannelIds,
|
||||
getSortedDirectChannelIds
|
||||
} from 'mattermost-redux/selectors/entities/channels';
|
||||
import {getChannelsWithUnreadSection, getCurrentChannel, getMyChannelMemberships} from 'mattermost-redux/selectors/entities/channels';
|
||||
import {getCurrentUserId, getCurrentUserRoles} from 'mattermost-redux/selectors/entities/users';
|
||||
import {getTheme, getFavoritesPreferences} from 'mattermost-redux/selectors/entities/preferences';
|
||||
import {getTheme} from 'mattermost-redux/selectors/entities/preferences';
|
||||
import {showCreateOption} from 'mattermost-redux/utils/channel_utils';
|
||||
import {isAdmin, isSystemAdmin} from 'mattermost-redux/utils/user_utils';
|
||||
|
||||
import List from './list';
|
||||
|
||||
function mapStateToProps(state) {
|
||||
function mapStateToProps(state, ownProps) {
|
||||
const {config, license} = state.entities.general;
|
||||
const roles = getCurrentUserId(state) ? getCurrentUserRoles(state) : '';
|
||||
const unreadChannelIds = getSortedUnreadChannelIds(state);
|
||||
const favoriteChannelIds = getSortedFavoriteChannelIds(state);
|
||||
const publicChannelIds = getSortedPublicChannelIds(state);
|
||||
const privateChannelIds = getSortedPrivateChannelIds(state);
|
||||
const directChannelIds = getSortedDirectChannelIds(state);
|
||||
|
||||
return {
|
||||
canCreatePrivateChannels: showCreateOption(config, license, General.PRIVATE_CHANNEL, isAdmin(roles), isSystemAdmin(roles)),
|
||||
unreadChannelIds,
|
||||
favoriteChannelIds,
|
||||
publicChannelIds,
|
||||
privateChannelIds,
|
||||
directChannelIds,
|
||||
theme: getTheme(state)
|
||||
channelMembers: getMyChannelMemberships(state),
|
||||
channels: getChannelsWithUnreadSection(state),
|
||||
currentChannel: getCurrentChannel(state),
|
||||
theme: getTheme(state),
|
||||
...ownProps
|
||||
};
|
||||
}
|
||||
|
||||
function areStatesEqual(next, prev) {
|
||||
const equalRoles = getCurrentUserRoles(prev) === getCurrentUserRoles(next);
|
||||
const equalChannels = next.entities.channels === prev.entities.channels;
|
||||
const equalConfig = next.entities.general.config === prev.entities.general.config;
|
||||
const equalUsers = next.entities.users.profiles === prev.entities.users.profiles;
|
||||
const equalFav = getFavoritesPreferences(next) === getFavoritesPreferences(prev);
|
||||
|
||||
return equalChannels && equalConfig && equalRoles && equalUsers && equalFav;
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps, null, null, {pure: true, areStatesEqual})(List);
|
||||
export default connect(mapStateToProps, null)(List);
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
import React, {PureComponent} from 'react';
|
||||
import deepEqual from 'deep-equal';
|
||||
import React, {Component} from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import {
|
||||
InteractionManager,
|
||||
SectionList,
|
||||
FlatList,
|
||||
Text,
|
||||
TouchableHighlight,
|
||||
View
|
||||
@@ -13,36 +13,38 @@ import {
|
||||
import {injectIntl, intlShape} from 'react-intl';
|
||||
import MaterialIcon from 'react-native-vector-icons/MaterialIcons';
|
||||
|
||||
import {General} from 'mattermost-redux/constants';
|
||||
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 FormattedText from 'app/components/formatted_text';
|
||||
import {wrapWithPreventDoubleTap} from 'app/utils/tap';
|
||||
import {changeOpacity} from 'app/utils/theme';
|
||||
|
||||
class List extends PureComponent {
|
||||
import {General} from 'mattermost-redux/constants';
|
||||
|
||||
import ChannelItem from 'app/components/channel_drawer/channels_list/channel_item';
|
||||
import UnreadIndicator from 'app/components/channel_drawer/channels_list/unread_indicator';
|
||||
|
||||
class List extends Component {
|
||||
static propTypes = {
|
||||
canCreatePrivateChannels: PropTypes.bool.isRequired,
|
||||
directChannelIds: PropTypes.array.isRequired,
|
||||
favoriteChannelIds: PropTypes.array.isRequired,
|
||||
channels: PropTypes.object.isRequired,
|
||||
channelMembers: PropTypes.object,
|
||||
currentChannel: PropTypes.object,
|
||||
intl: intlShape.isRequired,
|
||||
navigator: PropTypes.object,
|
||||
onSelectChannel: PropTypes.func.isRequired,
|
||||
publicChannelIds: PropTypes.array.isRequired,
|
||||
privateChannelIds: PropTypes.array.isRequired,
|
||||
styles: PropTypes.object.isRequired,
|
||||
theme: PropTypes.object.isRequired,
|
||||
unreadChannelIds: PropTypes.array.isRequired
|
||||
theme: PropTypes.object.isRequired
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
currentChannel: {}
|
||||
};
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.firstUnreadChannel = null;
|
||||
this.state = {
|
||||
sections: this.buildSections(props),
|
||||
showIndicator: false,
|
||||
width: 0
|
||||
dataSource: this.buildData(props),
|
||||
showAbove: false
|
||||
};
|
||||
|
||||
MaterialIcon.getImageSource('close', 20, this.props.theme.sidebarHeaderTextColor).then((source) => {
|
||||
@@ -50,96 +52,106 @@ class List extends PureComponent {
|
||||
});
|
||||
}
|
||||
|
||||
shouldComponentUpdate(nextProps, nextState) {
|
||||
return !deepEqual(this.props, nextProps, {strict: true}) || !deepEqual(this.state, nextState, {strict: true});
|
||||
}
|
||||
|
||||
componentWillReceiveProps(nextProps) {
|
||||
const {
|
||||
canCreatePrivateChannels,
|
||||
directChannelIds,
|
||||
favoriteChannelIds,
|
||||
publicChannelIds,
|
||||
privateChannelIds,
|
||||
unreadChannelIds
|
||||
} = this.props;
|
||||
|
||||
if (nextProps.canCreatePrivateChannels !== canCreatePrivateChannels ||
|
||||
nextProps.directChannelIds !== directChannelIds || nextProps.favoriteChannelIds !== favoriteChannelIds ||
|
||||
nextProps.publicChannelIds !== publicChannelIds || nextProps.privateChannelIds !== privateChannelIds ||
|
||||
nextProps.unreadChannelIds !== unreadChannelIds) {
|
||||
this.setState({sections: this.buildSections(nextProps)});
|
||||
}
|
||||
this.setState({
|
||||
dataSource: this.buildData(nextProps)
|
||||
}, () => {
|
||||
if (this.refs.list) {
|
||||
this.refs.list.recordInteraction();
|
||||
this.updateUnreadIndicators({
|
||||
viewableItems: Array.from(this.refs.list._listRef._viewabilityHelper._viewableItems.values()) //eslint-disable-line
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps, prevState) {
|
||||
if (prevState.sections !== this.state.sections && this.refs.list) {
|
||||
this.refs.list.recordInteraction();
|
||||
this.updateUnreadIndicators({
|
||||
viewableItems: Array.from(this.refs.list._wrapperListRef._listRef._viewabilityHelper._viewableItems.values()) //eslint-disable-line
|
||||
updateUnreadIndicators = ({viewableItems}) => {
|
||||
let showAbove = false;
|
||||
const visibleIndexes = viewableItems.map((v) => v.index);
|
||||
|
||||
if (visibleIndexes.length) {
|
||||
const {dataSource} = this.state;
|
||||
const firstVisible = parseInt(visibleIndexes[0], 10);
|
||||
|
||||
if (this.firstUnreadChannel) {
|
||||
const index = dataSource.findIndex((item) => {
|
||||
return item.display_name === this.firstUnreadChannel;
|
||||
});
|
||||
showAbove = index < firstVisible;
|
||||
}
|
||||
|
||||
this.setState({
|
||||
showAbove
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
buildSections = (props) => {
|
||||
const {
|
||||
canCreatePrivateChannels,
|
||||
directChannelIds,
|
||||
favoriteChannelIds,
|
||||
publicChannelIds,
|
||||
privateChannelIds,
|
||||
unreadChannelIds
|
||||
} = props;
|
||||
const sections = [];
|
||||
|
||||
if (unreadChannelIds.length) {
|
||||
sections.push({
|
||||
id: 'mobile.channel_list.unreads',
|
||||
defaultMessage: 'UNREADS',
|
||||
data: unreadChannelIds,
|
||||
renderItem: this.renderUnreadItem,
|
||||
topSeparator: false,
|
||||
bottomSeparator: true
|
||||
});
|
||||
}
|
||||
|
||||
if (favoriteChannelIds.length) {
|
||||
sections.push({
|
||||
id: 'sidebar.favorite',
|
||||
defaultMessage: 'FAVORITES',
|
||||
data: favoriteChannelIds,
|
||||
topSeparator: unreadChannelIds.length > 0,
|
||||
bottomSeparator: true
|
||||
});
|
||||
}
|
||||
|
||||
sections.push({
|
||||
action: this.goToMoreChannels,
|
||||
id: 'sidebar.channels',
|
||||
defaultMessage: 'PUBLIC CHANNELS',
|
||||
data: publicChannelIds,
|
||||
topSeparator: favoriteChannelIds.length > 0 || unreadChannelIds.length > 0,
|
||||
bottomSeparator: publicChannelIds.length > 0
|
||||
});
|
||||
|
||||
sections.push({
|
||||
action: canCreatePrivateChannels ? this.goToCreatePrivateChannel : null,
|
||||
id: 'sidebar.pg',
|
||||
defaultMessage: 'PRIVATE CHANNELS',
|
||||
data: privateChannelIds,
|
||||
topSeparator: true,
|
||||
bottomSeparator: privateChannelIds.length > 0
|
||||
});
|
||||
|
||||
sections.push({
|
||||
action: this.goToDirectMessages,
|
||||
id: 'sidebar.direct',
|
||||
defaultMessage: 'DIRECT MESSAGES',
|
||||
data: directChannelIds,
|
||||
topSeparator: true,
|
||||
bottomSeparator: directChannelIds.length > 0
|
||||
});
|
||||
|
||||
return sections;
|
||||
};
|
||||
|
||||
goToCreatePrivateChannel = wrapWithPreventDoubleTap(() => {
|
||||
onSelectChannel = (channel) => {
|
||||
this.props.onSelectChannel(channel);
|
||||
};
|
||||
|
||||
onLayout = (event) => {
|
||||
const {width} = event.nativeEvent.layout;
|
||||
this.width = width;
|
||||
};
|
||||
|
||||
getUnreadMessages = (channel) => {
|
||||
const member = this.props.channelMembers[channel.id];
|
||||
let mentions = 0;
|
||||
let unreadCount = 0;
|
||||
if (member && channel) {
|
||||
mentions = member.mention_count;
|
||||
unreadCount = channel.total_msg_count - member.msg_count;
|
||||
|
||||
if (member.notify_props && member.notify_props.mark_unread === General.MENTION) {
|
||||
unreadCount = 0;
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
mentions,
|
||||
unreadCount
|
||||
};
|
||||
};
|
||||
|
||||
findUnreadChannels = (data) => {
|
||||
data.forEach((c) => {
|
||||
if (c.id) {
|
||||
const {mentions, unreadCount} = this.getUnreadMessages(c);
|
||||
const unread = (mentions + unreadCount) > 0;
|
||||
|
||||
if (unread && c.id !== this.props.currentChannel.id) {
|
||||
if (!this.firstUnreadChannel) {
|
||||
this.firstUnreadChannel = c.display_name;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
createChannelElement = (channel) => {
|
||||
const {mentions, unreadCount} = this.getUnreadMessages(channel);
|
||||
const msgCount = mentions + unreadCount;
|
||||
const unread = msgCount > 0;
|
||||
|
||||
return (
|
||||
<ChannelItem
|
||||
ref={channel.id}
|
||||
channel={channel}
|
||||
hasUnread={unread}
|
||||
mentions={mentions}
|
||||
onSelectChannel={this.onSelectChannel}
|
||||
isActive={channel.isCurrent}
|
||||
theme={this.props.theme}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
createPrivateChannel = wrapWithPreventDoubleTap(() => {
|
||||
const {intl, navigator, theme} = this.props;
|
||||
|
||||
navigator.showModal({
|
||||
@@ -161,7 +173,75 @@ class List extends PureComponent {
|
||||
});
|
||||
});
|
||||
|
||||
goToDirectMessages = wrapWithPreventDoubleTap(() => {
|
||||
buildChannels = (props) => {
|
||||
const {canCreatePrivateChannels, styles} = props;
|
||||
const {
|
||||
unreadChannels,
|
||||
favoriteChannels,
|
||||
publicChannels,
|
||||
privateChannels,
|
||||
directAndGroupChannels
|
||||
} = props.channels;
|
||||
|
||||
const data = [];
|
||||
|
||||
if (unreadChannels.length) {
|
||||
data.push(
|
||||
this.renderTitle(styles, 'mobile.channel_list.unreads', 'UNREADS', null, false, true),
|
||||
...unreadChannels
|
||||
);
|
||||
}
|
||||
|
||||
if (favoriteChannels.length) {
|
||||
data.push(
|
||||
this.renderTitle(styles, 'sidebar.favorite', 'FAVORITES', null, unreadChannels.length > 0, true),
|
||||
...favoriteChannels
|
||||
);
|
||||
}
|
||||
|
||||
data.push(
|
||||
this.renderTitle(styles, 'sidebar.channels', 'CHANNELS', this.showMoreChannelsModal, favoriteChannels.length > 0, publicChannels.length > 0),
|
||||
...publicChannels
|
||||
);
|
||||
|
||||
let createPrivateChannel;
|
||||
if (canCreatePrivateChannels) {
|
||||
createPrivateChannel = this.createPrivateChannel;
|
||||
}
|
||||
data.push(
|
||||
this.renderTitle(styles, 'sidebar.pg', 'PRIVATE CHANNELS', createPrivateChannel, true, privateChannels.length > 0),
|
||||
...privateChannels
|
||||
);
|
||||
|
||||
data.push(
|
||||
this.renderTitle(styles, 'sidebar.direct', 'DIRECT MESSAGES', this.showDirectMessagesModal, true, directAndGroupChannels.length > 0),
|
||||
...directAndGroupChannels
|
||||
);
|
||||
|
||||
return data;
|
||||
};
|
||||
|
||||
buildData = (props) => {
|
||||
if (!props.currentChannel) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const data = this.buildChannels(props);
|
||||
this.firstUnreadChannel = null;
|
||||
this.findUnreadChannels(data);
|
||||
|
||||
return data;
|
||||
};
|
||||
|
||||
scrollToTop = () => {
|
||||
this.refs.list.scrollToOffset({
|
||||
x: 0,
|
||||
y: 0,
|
||||
animated: true
|
||||
});
|
||||
}
|
||||
|
||||
showDirectMessagesModal = wrapWithPreventDoubleTap(() => {
|
||||
const {intl, navigator, theme} = this.props;
|
||||
|
||||
navigator.showModal({
|
||||
@@ -185,7 +265,7 @@ class List extends PureComponent {
|
||||
});
|
||||
});
|
||||
|
||||
goToMoreChannels = wrapWithPreventDoubleTap(() => {
|
||||
showMoreChannelsModal = wrapWithPreventDoubleTap(() => {
|
||||
const {intl, navigator, theme} = this.props;
|
||||
|
||||
navigator.showModal({
|
||||
@@ -206,18 +286,6 @@ class List extends PureComponent {
|
||||
});
|
||||
});
|
||||
|
||||
keyExtractor = (item) => item.id || item;
|
||||
|
||||
onSelectChannel = (channel, currentChannelId) => {
|
||||
const {onSelectChannel} = this.props;
|
||||
onSelectChannel(channel, currentChannelId);
|
||||
};
|
||||
|
||||
onLayout = (event) => {
|
||||
const {width} = event.nativeEvent.layout;
|
||||
this.setState({width: width - 40});
|
||||
};
|
||||
|
||||
renderSectionAction = (styles, action) => {
|
||||
const {theme} = this.props;
|
||||
return (
|
||||
@@ -234,114 +302,83 @@ class List extends PureComponent {
|
||||
);
|
||||
};
|
||||
|
||||
renderSectionSeparator = () => {
|
||||
const {styles} = this.props;
|
||||
renderDivider = (styles, marginLeft) => {
|
||||
return (
|
||||
<View style={[styles.divider]}/>
|
||||
<View
|
||||
style={[styles.divider, {marginLeft}]}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
renderItem = ({item}) => {
|
||||
return (
|
||||
<ChannelItem
|
||||
channelId={item}
|
||||
onSelectChannel={this.onSelectChannel}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
renderUnreadItem = ({item}) => {
|
||||
return (
|
||||
<ChannelItem
|
||||
channelId={item}
|
||||
isUnread={true}
|
||||
onSelectChannel={this.onSelectChannel}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
renderSectionHeader = ({section}) => {
|
||||
const {intl, styles} = this.props;
|
||||
const {
|
||||
action,
|
||||
bottomSeparator,
|
||||
defaultMessage,
|
||||
id,
|
||||
topSeparator
|
||||
} = section;
|
||||
|
||||
return (
|
||||
<View>
|
||||
{topSeparator && this.renderSectionSeparator()}
|
||||
<View style={styles.titleContainer}>
|
||||
<Text style={styles.title}>
|
||||
{intl.formatMessage({id, defaultMessage}).toUpperCase()}
|
||||
</Text>
|
||||
{action && this.renderSectionAction(styles, action)}
|
||||
</View>
|
||||
{bottomSeparator && this.renderSectionSeparator()}
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
scrollToTop = () => {
|
||||
if (this.refs.list) {
|
||||
this.refs.list._wrapperListRef._listRef.scrollToOffset({ //eslint-disable-line no-underscore-dangle
|
||||
x: 0,
|
||||
y: 0,
|
||||
animated: true
|
||||
});
|
||||
if (!item.isTitle) {
|
||||
return this.createChannelElement(item);
|
||||
}
|
||||
return item.title;
|
||||
};
|
||||
|
||||
emitUnreadIndicatorChange = debounce((showIndicator) => {
|
||||
this.setState({showIndicator});
|
||||
}, 100);
|
||||
renderTitle = (styles, id, defaultMessage, action, topDivider, bottomDivider) => {
|
||||
const {formatMessage} = this.props.intl;
|
||||
|
||||
updateUnreadIndicators = ({viewableItems}) => {
|
||||
InteractionManager.runAfterInteractions(() => {
|
||||
const {unreadChannelIds} = this.props;
|
||||
const firstUnread = unreadChannelIds.length && unreadChannelIds[0];
|
||||
if (firstUnread && viewableItems.length) {
|
||||
const isVisible = viewableItems.find((v) => v.item === firstUnread);
|
||||
|
||||
return this.emitUnreadIndicatorChange(!isVisible);
|
||||
}
|
||||
|
||||
return this.emitUnreadIndicatorChange(false);
|
||||
});
|
||||
return {
|
||||
id,
|
||||
isTitle: true,
|
||||
title: (
|
||||
<View>
|
||||
{topDivider && this.renderDivider(styles, 0)}
|
||||
<View style={styles.titleContainer}>
|
||||
<Text style={styles.title}>
|
||||
{formatMessage({id, defaultMessage}).toUpperCase()}
|
||||
</Text>
|
||||
{action && this.renderSectionAction(styles, action)}
|
||||
</View>
|
||||
{bottomDivider && this.renderDivider(styles, 0)}
|
||||
</View>
|
||||
)
|
||||
};
|
||||
};
|
||||
|
||||
render() {
|
||||
const {styles, theme} = this.props;
|
||||
const {sections, width, showIndicator} = this.state;
|
||||
const {styles} = this.props;
|
||||
|
||||
const {dataSource, showAbove} = this.state;
|
||||
|
||||
let above;
|
||||
if (showAbove) {
|
||||
above = (
|
||||
<UnreadIndicator
|
||||
style={[styles.above, {width: (this.width - 40)}]}
|
||||
onPress={this.scrollToTop}
|
||||
text={(
|
||||
<FormattedText
|
||||
style={styles.indicatorText}
|
||||
id='sidebar.unreadAbove'
|
||||
defaultMessage='Unread post(s) above'
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<View
|
||||
style={styles.container}
|
||||
onLayout={this.onLayout}
|
||||
>
|
||||
<SectionList
|
||||
<FlatList
|
||||
ref='list'
|
||||
sections={sections}
|
||||
data={dataSource}
|
||||
renderItem={this.renderItem}
|
||||
renderSectionHeader={this.renderSectionHeader}
|
||||
keyExtractor={this.keyExtractor}
|
||||
keyExtractor={(item) => item.id}
|
||||
onViewableItemsChanged={this.updateUnreadIndicators}
|
||||
keyboardDismissMode='on-drag'
|
||||
maxToRenderPerBatch={10}
|
||||
stickySectionHeadersEnabled={false}
|
||||
viewabilityConfig={{
|
||||
viewAreaCoveragePercentThreshold: 3,
|
||||
waitForInteraction: true
|
||||
waitForInteraction: false
|
||||
}}
|
||||
/>
|
||||
<UnreadIndicator
|
||||
show={showIndicator}
|
||||
style={[styles.above, {width}]}
|
||||
onPress={this.scrollToTop}
|
||||
theme={theme}
|
||||
/>
|
||||
{above}
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
import {connect} from 'react-redux';
|
||||
|
||||
import {getTheme} from 'mattermost-redux/selectors/entities/preferences';
|
||||
import {getCurrentTeam, getTeamMemberships} from 'mattermost-redux/selectors/entities/teams';
|
||||
|
||||
import SwitchTeams from './switch_teams';
|
||||
|
||||
function mapStateToProps(state, ownProps) {
|
||||
return {
|
||||
currentTeam: getCurrentTeam(state),
|
||||
teamMembers: getTeamMemberships(state),
|
||||
theme: getTheme(state),
|
||||
...ownProps
|
||||
};
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps)(SwitchTeams);
|
||||
@@ -14,50 +14,96 @@ import Badge from 'app/components/badge';
|
||||
import {wrapWithPreventDoubleTap} from 'app/utils/tap';
|
||||
import {changeOpacity, makeStyleSheetFromTheme} from 'app/utils/theme';
|
||||
|
||||
export default class SwitchTeamsButton extends React.PureComponent {
|
||||
export default class SwitchTeams extends React.PureComponent {
|
||||
static propTypes = {
|
||||
currentTeamId: PropTypes.string,
|
||||
displayName: PropTypes.string,
|
||||
currentTeam: PropTypes.object,
|
||||
searching: PropTypes.bool.isRequired,
|
||||
onShowTeams: PropTypes.func.isRequired,
|
||||
mentionCount: PropTypes.number.isRequired,
|
||||
teamsCount: PropTypes.number.isRequired,
|
||||
showTeams: PropTypes.func.isRequired,
|
||||
teamMembers: PropTypes.object.isRequired,
|
||||
theme: PropTypes.object.isRequired
|
||||
};
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
badgeCount: this.getBadgeCount(props)
|
||||
};
|
||||
}
|
||||
|
||||
componentWillReceiveProps(nextProps) {
|
||||
if (nextProps.currentTeam !== this.props.currentTeam || nextProps.teamMembers !== this.props.teamMembers) {
|
||||
this.setState({
|
||||
badgeCount: this.getBadgeCount(nextProps)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
getBadgeCount = (props) => {
|
||||
const {
|
||||
currentTeam,
|
||||
teamMembers
|
||||
} = props;
|
||||
|
||||
let mentionCount = 0;
|
||||
let messageCount = 0;
|
||||
Object.values(teamMembers).forEach((m) => {
|
||||
if (m.team_id !== currentTeam.id) {
|
||||
mentionCount = mentionCount + (m.mention_count || 0);
|
||||
messageCount = messageCount + (m.msg_count || 0);
|
||||
}
|
||||
});
|
||||
|
||||
let badgeCount;
|
||||
if (mentionCount) {
|
||||
badgeCount = mentionCount;
|
||||
} else if (messageCount) {
|
||||
badgeCount = -1;
|
||||
} else {
|
||||
badgeCount = 0;
|
||||
}
|
||||
|
||||
return badgeCount;
|
||||
};
|
||||
|
||||
showTeams = wrapWithPreventDoubleTap(() => {
|
||||
this.props.onShowTeams();
|
||||
this.props.showTeams();
|
||||
});
|
||||
|
||||
render() {
|
||||
const {
|
||||
currentTeamId,
|
||||
displayName,
|
||||
mentionCount,
|
||||
currentTeam,
|
||||
searching,
|
||||
teamsCount,
|
||||
teamMembers,
|
||||
theme
|
||||
} = this.props;
|
||||
|
||||
if (!currentTeamId) {
|
||||
if (!currentTeam) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (searching || teamsCount < 2) {
|
||||
const {
|
||||
badgeCount
|
||||
} = this.state;
|
||||
|
||||
if (searching || Object.keys(teamMembers).length < 2) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const styles = getStyleSheet(theme);
|
||||
|
||||
const badge = (
|
||||
<Badge
|
||||
style={styles.badge}
|
||||
countStyle={styles.mention}
|
||||
count={mentionCount}
|
||||
minHeight={20}
|
||||
minWidth={20}
|
||||
/>
|
||||
);
|
||||
let badge;
|
||||
if (badgeCount) {
|
||||
badge = (
|
||||
<Badge
|
||||
style={styles.badge}
|
||||
countStyle={styles.mention}
|
||||
count={badgeCount}
|
||||
minHeight={20}
|
||||
minWidth={20}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<View>
|
||||
@@ -73,7 +119,7 @@ export default class SwitchTeamsButton extends React.PureComponent {
|
||||
/>
|
||||
<View style={styles.switcherDivider}/>
|
||||
<Text style={styles.switcherTeam}>
|
||||
{displayName.substr(0, 2).toUpperCase()}
|
||||
{currentTeam.display_name.substr(0, 2).toUpperCase()}
|
||||
</Text>
|
||||
</View>
|
||||
</TouchableHighlight>
|
||||
@@ -93,7 +139,7 @@ const getStyleSheet = makeStyleSheetFromTheme((theme) => {
|
||||
height: 32,
|
||||
justifyContent: 'center',
|
||||
marginLeft: 6,
|
||||
marginRight: 5,
|
||||
marginRight: 10,
|
||||
paddingHorizontal: 6
|
||||
},
|
||||
switcherDivider: {
|
||||
@@ -1,23 +0,0 @@
|
||||
// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
import {connect} from 'react-redux';
|
||||
|
||||
import {getTheme} from 'mattermost-redux/selectors/entities/preferences';
|
||||
import {getCurrentTeam, getMyTeamsCount, getChannelDrawerBadgeCount} from 'mattermost-redux/selectors/entities/teams';
|
||||
|
||||
import SwitchTeamsButton from './switch_teams_button';
|
||||
|
||||
function mapStateToProps(state) {
|
||||
const team = getCurrentTeam(state);
|
||||
|
||||
return {
|
||||
currentTeamId: team.id,
|
||||
displayName: team.display_name,
|
||||
mentionCount: getChannelDrawerBadgeCount(state),
|
||||
teamsCount: getMyTeamsCount(state),
|
||||
theme: getTheme(state)
|
||||
};
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps)(SwitchTeamsButton);
|
||||
@@ -0,0 +1,49 @@
|
||||
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
import React, {PureComponent} from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import {
|
||||
StyleSheet,
|
||||
Text,
|
||||
TouchableWithoutFeedback,
|
||||
View,
|
||||
ViewPropTypes
|
||||
} from 'react-native';
|
||||
|
||||
export default class UnreadIndicator extends PureComponent {
|
||||
static propTypes = {
|
||||
style: ViewPropTypes.style,
|
||||
textStyle: Text.propTypes.style,
|
||||
text: PropTypes.node.isRequired,
|
||||
onPress: PropTypes.func
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
onPress: () => true
|
||||
};
|
||||
|
||||
render() {
|
||||
return (
|
||||
<TouchableWithoutFeedback onPress={this.props.onPress}>
|
||||
<View
|
||||
style={[Styles.container, this.props.style]}
|
||||
>
|
||||
{this.props.text}
|
||||
</View>
|
||||
</TouchableWithoutFeedback>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const Styles = StyleSheet.create({
|
||||
container: {
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
flexDirection: 'row',
|
||||
position: 'absolute',
|
||||
borderRadius: 15,
|
||||
marginHorizontal: 15,
|
||||
height: 25
|
||||
}
|
||||
});
|
||||
@@ -1,48 +0,0 @@
|
||||
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
import React, {PureComponent} from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import {StyleSheet, View} from 'react-native';
|
||||
import Svg, {
|
||||
G,
|
||||
Path
|
||||
} from 'react-native-svg';
|
||||
|
||||
export default class AboveIcon extends PureComponent {
|
||||
static propTypes = {
|
||||
width: PropTypes.number.isRequired,
|
||||
height: PropTypes.number.isRequired,
|
||||
color: PropTypes.string.isRequired
|
||||
};
|
||||
|
||||
render() {
|
||||
const {color, height, width} = this.props;
|
||||
|
||||
return (
|
||||
<View style={[style.container, {height, width}]}>
|
||||
<Svg
|
||||
width={width}
|
||||
height={height}
|
||||
viewBox='0 0 10 10'
|
||||
>
|
||||
<G transform='matrix(1,0,0,1,-20,-18)'>
|
||||
<G transform='matrix(0.0330723,0,0,0.0322634,15.8132,12.3164)'>
|
||||
<Path
|
||||
d='M245.803,377.493C245.803,377.493 204.794,336.485 179.398,311.088C168.55,300.24 150.962,300.24 140.114,311.088C138.327,312.875 136.517,314.686 134.73,316.473C123.882,327.321 123.882,344.908 134.73,355.756C167.972,388.998 233.949,454.975 256.949,477.975C262.158,483.184 269.223,486.111 276.591,486.111C277.38,486.111 278.176,486.111 278.965,486.111C286.332,486.111 293.397,483.184 298.607,477.975C321.607,454.975 387.584,388.998 420.826,355.756C431.674,344.908 431.674,327.321 420.826,316.473C419.039,314.686 417.228,312.875 415.441,311.088C404.593,300.24 387.005,300.24 376.158,311.088C350.761,336.485 309.753,377.493 309.753,377.493C309.753,377.493 309.753,279.687 309.753,203.94C309.753,196.573 306.826,189.508 301.617,184.298C296.408,179.089 289.342,176.162 281.975,176.162C279.191,176.162 276.364,176.162 273.58,176.162C266.213,176.162 259.148,179.089 253.939,184.298C248.729,189.508 245.803,196.573 245.803,203.94L245.803,377.493Z'
|
||||
fill={color}
|
||||
/>
|
||||
</G>
|
||||
</G>
|
||||
</Svg>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const style = StyleSheet.create({
|
||||
container: {
|
||||
alignItems: 'flex-start',
|
||||
transform: [{rotate: '180deg'}]
|
||||
}
|
||||
});
|
||||
@@ -1,80 +0,0 @@
|
||||
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
import React, {PureComponent} from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import {
|
||||
TouchableWithoutFeedback,
|
||||
View,
|
||||
ViewPropTypes
|
||||
} from 'react-native';
|
||||
|
||||
import FormattedText from 'app/components/formatted_text';
|
||||
import {makeStyleSheetFromTheme} from 'app/utils/theme';
|
||||
|
||||
import AboveIcon from './above_icon';
|
||||
|
||||
export default class UnreadIndicator extends PureComponent {
|
||||
static propTypes = {
|
||||
show: PropTypes.bool,
|
||||
style: ViewPropTypes.style,
|
||||
onPress: PropTypes.func,
|
||||
theme: PropTypes.object.isRequired
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
onPress: () => true
|
||||
};
|
||||
|
||||
render() {
|
||||
const {onPress, show, theme} = this.props;
|
||||
|
||||
if (!show) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const style = getStyleSheet(theme);
|
||||
|
||||
return (
|
||||
<TouchableWithoutFeedback onPress={onPress}>
|
||||
<View
|
||||
style={[style.container, this.props.style]}
|
||||
>
|
||||
<FormattedText
|
||||
style={style.indicatorText}
|
||||
id='sidebar.unreads'
|
||||
defaultMessage='More unreads'
|
||||
/>
|
||||
<AboveIcon
|
||||
width={12}
|
||||
height={12}
|
||||
color={theme.mentionColor}
|
||||
/>
|
||||
</View>
|
||||
</TouchableWithoutFeedback>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const getStyleSheet = makeStyleSheetFromTheme((theme) => {
|
||||
return {
|
||||
container: {
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
flexDirection: 'row',
|
||||
position: 'absolute',
|
||||
borderRadius: 15,
|
||||
marginHorizontal: 15,
|
||||
height: 25
|
||||
},
|
||||
indicatorText: {
|
||||
backgroundColor: 'transparent',
|
||||
color: theme.mentionColor,
|
||||
fontSize: 14,
|
||||
paddingVertical: 2,
|
||||
paddingHorizontal: 4,
|
||||
textAlign: 'center',
|
||||
textAlignVertical: 'center'
|
||||
}
|
||||
};
|
||||
});
|
||||
@@ -1,7 +1,7 @@
|
||||
// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
import React, {Component} from 'react';
|
||||
import React, {PureComponent} from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import {StyleSheet} from 'react-native';
|
||||
|
||||
@@ -9,7 +9,7 @@ import {changeOpacity} from 'app/utils/theme';
|
||||
|
||||
import Swiper from 'app/components/swiper';
|
||||
|
||||
export default class DrawerSwiper extends Component {
|
||||
export default class DrawerSwiper extends PureComponent {
|
||||
static propTypes = {
|
||||
children: PropTypes.node.isRequired,
|
||||
deviceWidth: PropTypes.number.isRequired,
|
||||
@@ -24,28 +24,16 @@ export default class DrawerSwiper extends Component {
|
||||
openDrawerOffset: 0
|
||||
};
|
||||
|
||||
shouldComponentUpdate(nextProps) {
|
||||
const {deviceWidth, showTeams, theme} = this.props;
|
||||
return nextProps.deviceWidth !== deviceWidth ||
|
||||
nextProps.showTeams !== showTeams || nextProps.theme !== theme;
|
||||
}
|
||||
|
||||
runOnLayout = (shouldRun = true) => {
|
||||
if (this.refs.swiper) {
|
||||
this.refs.swiper.runOnLayout = shouldRun;
|
||||
}
|
||||
this.refs.swiper.runOnLayout = shouldRun;
|
||||
};
|
||||
|
||||
resetPage = () => {
|
||||
if (this.refs.swiper) {
|
||||
this.refs.swiper.scrollToIndex(1, false);
|
||||
}
|
||||
this.refs.swiper.scrollToIndex(1, false);
|
||||
};
|
||||
|
||||
scrollToStart = () => {
|
||||
if (this.refs.swiper) {
|
||||
this.refs.swiper.scrollToStart();
|
||||
}
|
||||
this.refs.swiper.scrollToStart();
|
||||
};
|
||||
|
||||
swiperPageSelected = (index) => {
|
||||
@@ -53,9 +41,7 @@ export default class DrawerSwiper extends Component {
|
||||
};
|
||||
|
||||
showTeamsPage = () => {
|
||||
if (this.refs.swiper) {
|
||||
this.refs.swiper.scrollToIndex(0, true);
|
||||
}
|
||||
this.refs.swiper.scrollToIndex(0, true);
|
||||
};
|
||||
|
||||
render() {
|
||||
|
||||
@@ -3,16 +3,15 @@
|
||||
|
||||
import {connect} from 'react-redux';
|
||||
|
||||
import {getTheme} from 'mattermost-redux/selectors/entities/preferences';
|
||||
|
||||
import {getDimensions} from 'app/selectors/device';
|
||||
import {getDimensions, isLandscape} from 'app/selectors/device';
|
||||
|
||||
import DraweSwiper from './drawer_swiper';
|
||||
|
||||
function mapStateToProps(state) {
|
||||
function mapStateToProps(state, ownProps) {
|
||||
return {
|
||||
...ownProps,
|
||||
...getDimensions(state),
|
||||
theme: getTheme(state)
|
||||
isLandscape: isLandscape(state)
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -6,24 +6,27 @@ import {connect} from 'react-redux';
|
||||
|
||||
import {joinChannel, viewChannel, markChannelAsRead} from 'mattermost-redux/actions/channels';
|
||||
import {getTeams} from 'mattermost-redux/actions/teams';
|
||||
import {getCurrentTeamId, getMyTeamsCount} from 'mattermost-redux/selectors/entities/teams';
|
||||
import {getCurrentChannelId} from 'mattermost-redux/selectors/entities/channels';
|
||||
import {getCurrentTeamId, getTeamMemberships} from 'mattermost-redux/selectors/entities/teams';
|
||||
|
||||
import {handleSelectChannel, setChannelDisplayName, setChannelLoading} from 'app/actions/views/channel';
|
||||
import {makeDirectChannel} from 'app/actions/views/more_dms';
|
||||
import {isLandscape, isTablet} from 'app/selectors/device';
|
||||
import {getTheme} from 'mattermost-redux/selectors/entities/preferences';
|
||||
import {getTheme} from 'app/selectors/preferences';
|
||||
|
||||
import ChannelDrawer from './channel_drawer.js';
|
||||
|
||||
function mapStateToProps(state) {
|
||||
function mapStateToProps(state, ownProps) {
|
||||
const {currentUserId} = state.entities.users;
|
||||
|
||||
return {
|
||||
...ownProps,
|
||||
currentTeamId: getCurrentTeamId(state),
|
||||
currentChannelId: getCurrentChannelId(state),
|
||||
currentUserId,
|
||||
isLandscape: isLandscape(state),
|
||||
isTablet: isTablet(state),
|
||||
teamsCount: getMyTeamsCount(state),
|
||||
teamsCount: Object.keys(getTeamMemberships(state)).length,
|
||||
theme: getTheme(state)
|
||||
};
|
||||
}
|
||||
|
||||
@@ -5,24 +5,23 @@ import {bindActionCreators} from 'redux';
|
||||
import {connect} from 'react-redux';
|
||||
|
||||
import {getCurrentUrl} from 'mattermost-redux/selectors/entities/general';
|
||||
import {getCurrentTeamId, getJoinableTeamIds, getMySortedTeamIds} from 'mattermost-redux/selectors/entities/teams';
|
||||
import {getTheme} from 'mattermost-redux/selectors/entities/preferences';
|
||||
import {getCurrentTeamId, getJoinableTeams} from 'mattermost-redux/selectors/entities/teams';
|
||||
|
||||
import {handleTeamChange} from 'app/actions/views/select_team';
|
||||
import {getCurrentLocale} from 'app/selectors/i18n';
|
||||
import {getTheme} from 'app/selectors/preferences';
|
||||
import {getMySortedTeams} from 'app/selectors/teams';
|
||||
import {removeProtocol} from 'app/utils/url';
|
||||
|
||||
import TeamsList from './teams_list';
|
||||
|
||||
function mapStateToProps(state) {
|
||||
const locale = getCurrentLocale(state);
|
||||
|
||||
function mapStateToProps(state, ownProps) {
|
||||
return {
|
||||
canJoinOtherTeams: getJoinableTeamIds(state).length > 0,
|
||||
joinableTeams: getJoinableTeams(state),
|
||||
currentTeamId: getCurrentTeamId(state),
|
||||
currentUrl: removeProtocol(getCurrentUrl(state)),
|
||||
teamIds: getMySortedTeamIds(state, locale),
|
||||
theme: getTheme(state)
|
||||
teams: getMySortedTeams(state),
|
||||
theme: getTheme(state),
|
||||
...ownProps
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -24,13 +24,13 @@ class TeamsList extends PureComponent {
|
||||
actions: PropTypes.shape({
|
||||
handleTeamChange: PropTypes.func.isRequired
|
||||
}).isRequired,
|
||||
canJoinOtherTeams: PropTypes.bool.isRequired,
|
||||
closeChannelDrawer: PropTypes.func.isRequired,
|
||||
currentTeamId: PropTypes.string.isRequired,
|
||||
currentUrl: PropTypes.string.isRequired,
|
||||
intl: intlShape.isRequired,
|
||||
joinableTeams: PropTypes.object.isRequired,
|
||||
navigator: PropTypes.object.isRequired,
|
||||
teamIds: PropTypes.array.isRequired,
|
||||
teams: PropTypes.array.isRequired,
|
||||
theme: PropTypes.object.isRequired
|
||||
};
|
||||
|
||||
@@ -42,11 +42,11 @@ class TeamsList extends PureComponent {
|
||||
});
|
||||
}
|
||||
|
||||
selectTeam = (teamId) => {
|
||||
selectTeam = (team) => {
|
||||
requestAnimationFrame(() => {
|
||||
const {actions, closeChannelDrawer, currentTeamId} = this.props;
|
||||
if (teamId !== currentTeamId) {
|
||||
actions.handleTeamChange(teamId);
|
||||
if (team.id !== currentTeamId) {
|
||||
actions.handleTeamChange(team);
|
||||
}
|
||||
|
||||
closeChannelDrawer();
|
||||
@@ -81,25 +81,25 @@ class TeamsList extends PureComponent {
|
||||
});
|
||||
});
|
||||
|
||||
keyExtractor = (item) => {
|
||||
return item;
|
||||
};
|
||||
keyExtractor = (team) => {
|
||||
return team.id;
|
||||
}
|
||||
|
||||
renderItem = ({item}) => {
|
||||
return (
|
||||
<TeamsListItem
|
||||
selectTeam={this.selectTeam}
|
||||
teamId={item}
|
||||
team={item}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
render() {
|
||||
const {canJoinOtherTeams, teamIds, theme} = this.props;
|
||||
const {joinableTeams, teams, theme} = this.props;
|
||||
const styles = getStyleSheet(theme);
|
||||
|
||||
let moreAction;
|
||||
if (canJoinOtherTeams) {
|
||||
if (Object.keys(joinableTeams).length) {
|
||||
moreAction = (
|
||||
<TouchableHighlight
|
||||
style={styles.moreActionContainer}
|
||||
@@ -128,7 +128,7 @@ class TeamsList extends PureComponent {
|
||||
</View>
|
||||
</View>
|
||||
<FlatList
|
||||
data={teamIds}
|
||||
data={teams}
|
||||
renderItem={this.renderItem}
|
||||
keyExtractor={this.keyExtractor}
|
||||
viewabilityConfig={{
|
||||
|
||||
@@ -5,23 +5,19 @@ import {connect} from 'react-redux';
|
||||
|
||||
import {getCurrentUrl} from 'mattermost-redux/selectors/entities/general';
|
||||
import {getTheme} from 'mattermost-redux/selectors/entities/preferences';
|
||||
import {getCurrentTeamId, getTeam, makeGetBadgeCountForTeamId} from 'mattermost-redux/selectors/entities/teams';
|
||||
import {getCurrentTeamId, getTeamMemberships} from 'mattermost-redux/selectors/entities/teams';
|
||||
|
||||
import {removeProtocol} from 'app/utils/url';
|
||||
|
||||
import TeamsListItem from './teams_list_item.js';
|
||||
|
||||
function mapStateToProps(state, ownProps) {
|
||||
const team = getTeam(state, ownProps.teamId);
|
||||
const getMentionCount = makeGetBadgeCountForTeamId();
|
||||
|
||||
return {
|
||||
currentTeamId: getCurrentTeamId(state),
|
||||
currentUrl: removeProtocol(getCurrentUrl(state)),
|
||||
displayName: team.display_name,
|
||||
mentionCount: getMentionCount(state, ownProps.teamId),
|
||||
name: team.name,
|
||||
theme: getTheme(state)
|
||||
teamMember: getTeamMemberships(state)[ownProps.team.id],
|
||||
theme: getTheme(state),
|
||||
...ownProps
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -18,32 +18,29 @@ export default class TeamsListItem extends React.PureComponent {
|
||||
static propTypes = {
|
||||
currentTeamId: PropTypes.string.isRequired,
|
||||
currentUrl: PropTypes.string.isRequired,
|
||||
displayName: PropTypes.string.isRequired,
|
||||
mentionCount: PropTypes.number.isRequired,
|
||||
name: PropTypes.string.isRequired,
|
||||
selectTeam: PropTypes.func.isRequired,
|
||||
teamId: PropTypes.string.isRequired,
|
||||
team: PropTypes.object.isRequired,
|
||||
teamMember: PropTypes.object.isRequired,
|
||||
theme: PropTypes.object.isRequired
|
||||
};
|
||||
|
||||
selectTeam = wrapWithPreventDoubleTap(() => {
|
||||
this.props.selectTeam(this.props.teamId);
|
||||
this.props.selectTeam(this.props.team);
|
||||
});
|
||||
|
||||
render() {
|
||||
const {
|
||||
currentTeamId,
|
||||
currentUrl,
|
||||
displayName,
|
||||
mentionCount,
|
||||
name,
|
||||
teamId,
|
||||
team,
|
||||
teamMember,
|
||||
theme
|
||||
} = this.props;
|
||||
const styles = getStyleSheet(theme);
|
||||
|
||||
let current;
|
||||
if (teamId === currentTeamId) {
|
||||
let badge;
|
||||
if (team.id === currentTeamId) {
|
||||
current = (
|
||||
<View style={styles.checkmarkContainer}>
|
||||
<IonIcon
|
||||
@@ -54,15 +51,24 @@ export default class TeamsListItem extends React.PureComponent {
|
||||
);
|
||||
}
|
||||
|
||||
const badge = (
|
||||
<Badge
|
||||
style={styles.badge}
|
||||
countStyle={styles.mention}
|
||||
count={mentionCount}
|
||||
minHeight={20}
|
||||
minWidth={20}
|
||||
/>
|
||||
);
|
||||
let badgeCount = 0;
|
||||
if (teamMember.mention_count) {
|
||||
badgeCount = teamMember.mention_count;
|
||||
} else if (teamMember.msg_count) {
|
||||
badgeCount = -1;
|
||||
}
|
||||
|
||||
if (badgeCount) {
|
||||
badge = (
|
||||
<Badge
|
||||
style={styles.badge}
|
||||
countStyle={styles.mention}
|
||||
count={badgeCount}
|
||||
minHeight={20}
|
||||
minWidth={20}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<View style={styles.teamWrapper}>
|
||||
@@ -73,7 +79,7 @@ export default class TeamsListItem extends React.PureComponent {
|
||||
<View style={styles.teamContainer}>
|
||||
<View style={styles.teamIconContainer}>
|
||||
<Text style={styles.teamIcon}>
|
||||
{displayName.substr(0, 2).toUpperCase()}
|
||||
{team.display_name.substr(0, 2).toUpperCase()}
|
||||
</Text>
|
||||
</View>
|
||||
<View style={styles.teamNameContainer}>
|
||||
@@ -82,14 +88,14 @@ export default class TeamsListItem extends React.PureComponent {
|
||||
ellipsizeMode='tail'
|
||||
style={styles.teamName}
|
||||
>
|
||||
{displayName}
|
||||
{team.display_name}
|
||||
</Text>
|
||||
<Text
|
||||
numberOfLines={1}
|
||||
ellipsizeMode='tail'
|
||||
style={styles.teamUrl}
|
||||
>
|
||||
{`${currentUrl}/${name}`}
|
||||
{`${currentUrl}/${team.name}`}
|
||||
</Text>
|
||||
</View>
|
||||
{current}
|
||||
|
||||
@@ -19,7 +19,7 @@ export default class ChannelIcon extends React.PureComponent {
|
||||
static propTypes = {
|
||||
isActive: PropTypes.bool,
|
||||
isInfo: PropTypes.bool,
|
||||
isUnread: PropTypes.bool,
|
||||
hasUnread: PropTypes.bool,
|
||||
membersCount: PropTypes.number,
|
||||
size: PropTypes.number,
|
||||
status: PropTypes.string,
|
||||
@@ -30,12 +30,12 @@ export default class ChannelIcon extends React.PureComponent {
|
||||
static defaultProps = {
|
||||
isActive: false,
|
||||
isInfo: false,
|
||||
isUnread: false,
|
||||
hasUnread: false,
|
||||
size: 12
|
||||
};
|
||||
|
||||
render() {
|
||||
const {isActive, isUnread, isInfo, membersCount, size, status, theme, type} = this.props;
|
||||
const {isActive, hasUnread, isInfo, membersCount, size, status, theme, type} = this.props;
|
||||
const style = getStyleSheet(theme);
|
||||
|
||||
let activeIcon;
|
||||
@@ -46,7 +46,7 @@ export default class ChannelIcon extends React.PureComponent {
|
||||
let unreadGroup;
|
||||
let offlineColor = changeOpacity(theme.sidebarText, 0.5);
|
||||
|
||||
if (isUnread) {
|
||||
if (hasUnread) {
|
||||
unreadIcon = style.iconUnread;
|
||||
unreadGroupBox = style.groupBoxUnread;
|
||||
unreadGroup = style.groupUnread;
|
||||
|
||||
@@ -12,7 +12,6 @@ import {getFullName} from 'mattermost-redux/utils/user_utils';
|
||||
import {General} from 'mattermost-redux/constants';
|
||||
import {injectIntl, intlShape} from 'react-intl';
|
||||
|
||||
import Loading from 'app/components/loading';
|
||||
import ProfilePicture from 'app/components/profile_picture';
|
||||
import {preventDoubleTap} from 'app/utils/tap';
|
||||
import {changeOpacity, makeStyleSheetFromTheme} from 'app/utils/theme';
|
||||
@@ -23,7 +22,6 @@ class ChannelIntro extends PureComponent {
|
||||
currentChannel: PropTypes.object.isRequired,
|
||||
currentChannelMembers: PropTypes.array.isRequired,
|
||||
intl: intlShape.isRequired,
|
||||
isLoadingPosts: PropTypes.bool,
|
||||
navigator: PropTypes.object.isRequired,
|
||||
theme: PropTypes.object.isRequired
|
||||
};
|
||||
@@ -306,35 +304,18 @@ class ChannelIntro extends PureComponent {
|
||||
};
|
||||
|
||||
render() {
|
||||
const {currentChannel, isLoadingPosts, theme} = this.props;
|
||||
const {theme} = this.props;
|
||||
|
||||
const style = getStyleSheet(theme);
|
||||
const channelType = currentChannel.type;
|
||||
|
||||
if (isLoadingPosts) {
|
||||
return (
|
||||
<View style={style.container}>
|
||||
<Loading/>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
let profiles;
|
||||
if (channelType === General.DM_CHANNEL || channelType === General.GM_CHANNEL) {
|
||||
profiles = (
|
||||
<View>
|
||||
<View style={style.profilesContainer}>
|
||||
{this.buildProfiles()}
|
||||
</View>
|
||||
<View style={style.namesContainer}>
|
||||
{this.buildNames()}
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<View style={style.container}>
|
||||
{profiles}
|
||||
<View style={style.profilesContainer}>
|
||||
{this.buildProfiles()}
|
||||
</View>
|
||||
<View style={style.namesContainer}>
|
||||
{this.buildNames()}
|
||||
</View>
|
||||
<View style={style.contentContainer}>
|
||||
{this.buildContent()}
|
||||
</View>
|
||||
|
||||
@@ -1,19 +1,19 @@
|
||||
// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
import {bindActionCreators} from 'redux';
|
||||
import {connect} from 'react-redux';
|
||||
import {General, RequestStatus} from 'mattermost-redux/constants';
|
||||
import {General} from 'mattermost-redux/constants';
|
||||
import {getCurrentChannel} from 'mattermost-redux/selectors/entities/channels';
|
||||
import {getCurrentUser, getProfilesInCurrentChannel} from 'mattermost-redux/selectors/entities/users';
|
||||
|
||||
import {getTheme} from 'mattermost-redux/selectors/entities/preferences';
|
||||
import {getTheme} from 'app/selectors/preferences';
|
||||
|
||||
import ChannelIntro from './channel_intro';
|
||||
|
||||
function mapStateToProps(state) {
|
||||
const currentChannel = getCurrentChannel(state) || {};
|
||||
const currentUser = getCurrentUser(state) || {};
|
||||
const {status: getPostsRequestStatus} = state.requests.posts.getPosts;
|
||||
|
||||
let currentChannelMembers = [];
|
||||
if (currentChannel.type === General.DM_CHANNEL) {
|
||||
@@ -28,15 +28,20 @@ function mapStateToProps(state) {
|
||||
}
|
||||
|
||||
const creator = currentChannel.creator_id === currentUser.id ? currentUser : state.entities.users.profiles[currentChannel.creator_id];
|
||||
const postsInChannel = state.entities.posts.postsInChannel[currentChannel.id] || [];
|
||||
|
||||
return {
|
||||
creator,
|
||||
currentChannel,
|
||||
currentChannelMembers,
|
||||
isLoadingPosts: !postsInChannel.length && getPostsRequestStatus === RequestStatus.STARTED,
|
||||
theme: getTheme(state)
|
||||
};
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps)(ChannelIntro);
|
||||
function mapDispatchToProps(dispatch) {
|
||||
// placeholder for invite and set header actions
|
||||
return {
|
||||
actions: bindActionCreators({}, dispatch)
|
||||
};
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(ChannelIntro);
|
||||
|
||||
@@ -2,13 +2,14 @@
|
||||
// See License.txt for license information.
|
||||
|
||||
import {connect} from 'react-redux';
|
||||
import {getTheme} from 'mattermost-redux/selectors/entities/preferences';
|
||||
import {getTheme} from 'app/selectors/preferences';
|
||||
|
||||
import ChannelLoader from './channel_loader';
|
||||
|
||||
function mapStateToProps(state) {
|
||||
function mapStateToProps(state, ownProps) {
|
||||
const {deviceWidth} = state.device.dimension;
|
||||
return {
|
||||
...ownProps,
|
||||
channelIsLoading: state.views.channel.loading,
|
||||
deviceWidth,
|
||||
theme: getTheme(state)
|
||||
|
||||
@@ -1,231 +0,0 @@
|
||||
// Copyright (c) 2017-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,
|
||||
Linking,
|
||||
TouchableOpacity,
|
||||
View
|
||||
} from 'react-native';
|
||||
import {injectIntl, intlShape} from 'react-intl';
|
||||
|
||||
import FormattedText from 'app/components/formatted_text';
|
||||
import {UpgradeTypes} from 'app/constants/view';
|
||||
import checkUpgradeType from 'app/utils/client_upgrade';
|
||||
import {changeOpacity, makeStyleSheetFromTheme} from 'app/utils/theme';
|
||||
|
||||
const {View: AnimatedView} = Animated;
|
||||
|
||||
const UPDATE_TIMEOUT = 60000;
|
||||
|
||||
class ClientUpgradeListener extends PureComponent {
|
||||
static propTypes = {
|
||||
actions: PropTypes.shape({
|
||||
logError: PropTypes.func.isRequired,
|
||||
setLastUpgradeCheck: PropTypes.func.isRequired
|
||||
}).isRequired,
|
||||
currentVersion: PropTypes.string,
|
||||
downloadLink: PropTypes.string,
|
||||
forceUpgrade: PropTypes.bool,
|
||||
intl: intlShape.isRequired,
|
||||
lastUpgradeCheck: PropTypes.number,
|
||||
latestVersion: PropTypes.string,
|
||||
minVersion: PropTypes.string,
|
||||
navigator: PropTypes.object,
|
||||
theme: PropTypes.object.isRequired
|
||||
};
|
||||
|
||||
state = {
|
||||
top: new Animated.Value(-100)
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
const {forceUpgrade, lastUpgradeCheck, latestVersion, minVersion} = this.props;
|
||||
if (forceUpgrade || Date.now() - lastUpgradeCheck > UPDATE_TIMEOUT) {
|
||||
this.checkUpgrade(minVersion, latestVersion);
|
||||
}
|
||||
}
|
||||
|
||||
componentWillReceiveProps(nextProps) {
|
||||
const {forceUpgrade, latestVersion, minVersion} = this.props;
|
||||
const {latestVersion: nextLatestVersion, minVersion: nextMinVersion, lastUpgradeCheck} = nextProps;
|
||||
|
||||
const versionMismatch = latestVersion !== nextLatestVersion || minVersion !== nextMinVersion;
|
||||
if (versionMismatch && (forceUpgrade || Date.now() - lastUpgradeCheck > UPDATE_TIMEOUT)) {
|
||||
this.checkUpgrade(minVersion, latestVersion);
|
||||
}
|
||||
}
|
||||
|
||||
checkUpgrade = (minVersion, latestVersion) => {
|
||||
const {actions, currentVersion} = this.props;
|
||||
|
||||
const upgradeType = checkUpgradeType(currentVersion, minVersion, latestVersion, actions.logError);
|
||||
|
||||
if (upgradeType === UpgradeTypes.NO_UPGRADE) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.setState({upgradeType});
|
||||
|
||||
setTimeout(this.toggleUpgradeMessage, 500);
|
||||
|
||||
actions.setLastUpgradeCheck();
|
||||
}
|
||||
|
||||
toggleUpgradeMessage = (show = true) => {
|
||||
const toValue = show ? 75 : -100;
|
||||
Animated.timing(this.state.top, {
|
||||
toValue,
|
||||
duration: 300
|
||||
}).start();
|
||||
}
|
||||
|
||||
handleDismiss = () => {
|
||||
this.toggleUpgradeMessage(false);
|
||||
}
|
||||
|
||||
handleDownload = () => {
|
||||
const {downloadLink, intl} = this.props;
|
||||
|
||||
Linking.canOpenURL(downloadLink).then((supported) => {
|
||||
if (supported) {
|
||||
return Linking.openURL(downloadLink);
|
||||
}
|
||||
|
||||
Alert.alert(
|
||||
intl.formatMessage({
|
||||
id: 'mobile.client_upgrade.download_error.title',
|
||||
defaultMessage: 'Upgrade Error'
|
||||
}),
|
||||
intl.formatMessage({
|
||||
id: 'mobile.client_upgrade.download_error.message',
|
||||
defaultMessage: 'An error occurred while trying to open the download link.'
|
||||
})
|
||||
);
|
||||
|
||||
return false;
|
||||
});
|
||||
|
||||
this.toggleUpgradeMessage(false);
|
||||
}
|
||||
|
||||
handleLearnMore = () => {
|
||||
this.props.navigator.dismissAllModals({animationType: 'none'});
|
||||
|
||||
this.props.navigator.showModal({
|
||||
screen: 'ClientUpgrade',
|
||||
navigatorStyle: {
|
||||
navBarHidden: true,
|
||||
statusBarHidden: true,
|
||||
statusBarHideWithNavBar: true
|
||||
},
|
||||
passProps: {
|
||||
upgradeType: this.state.upgradeType
|
||||
}
|
||||
});
|
||||
|
||||
this.toggleUpgradeMessage(false);
|
||||
}
|
||||
|
||||
render() {
|
||||
const {forceUpgrade, theme} = this.props;
|
||||
const styles = getStyleSheet(theme);
|
||||
|
||||
return (
|
||||
<AnimatedView
|
||||
style={[styles.wrapper, {top: this.state.top}]}
|
||||
>
|
||||
<View style={styles.container}>
|
||||
<View style={styles.message}>
|
||||
<FormattedText
|
||||
id='mobile.client_upgrade.listener.message'
|
||||
defaultMessage='A client upgrade is available!'
|
||||
style={styles.messageText}
|
||||
/>
|
||||
</View>
|
||||
<View style={styles.bottom}>
|
||||
<TouchableOpacity onPress={this.handleDownload}>
|
||||
<FormattedText
|
||||
style={styles.button}
|
||||
id='mobile.client_upgrade.listener.upgrade_button'
|
||||
defaultMessage='Upgrade'
|
||||
/>
|
||||
</TouchableOpacity>
|
||||
<TouchableOpacity onPress={this.handleLearnMore}>
|
||||
<FormattedText
|
||||
style={styles.button}
|
||||
id='mobile.client_upgrade.listener.learn_more_button'
|
||||
defaultMessage='Learn More'
|
||||
/>
|
||||
</TouchableOpacity>
|
||||
{!forceUpgrade &&
|
||||
<TouchableOpacity onPress={this.handleDismiss}>
|
||||
<FormattedText
|
||||
style={styles.button}
|
||||
id='mobile.client_upgrade.listener.dismiss_button'
|
||||
defaultMessage='Dismiss'
|
||||
/>
|
||||
</TouchableOpacity>
|
||||
}
|
||||
</View>
|
||||
</View>
|
||||
</AnimatedView>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const getStyleSheet = makeStyleSheetFromTheme((theme) => {
|
||||
return {
|
||||
bottom: {
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'space-around',
|
||||
borderTopColor: changeOpacity(theme.centerChannelColor, 0.2),
|
||||
backgroundColor: changeOpacity(theme.centerChannelColor, 0.06),
|
||||
borderTopWidth: 1
|
||||
},
|
||||
button: {
|
||||
color: theme.linkColor,
|
||||
fontSize: 13,
|
||||
paddingHorizontal: 5,
|
||||
paddingVertical: 5
|
||||
},
|
||||
container: {
|
||||
flex: 1,
|
||||
backgroundColor: changeOpacity(theme.centerChannelBg, 0.8),
|
||||
borderRadius: 5
|
||||
},
|
||||
message: {
|
||||
flex: 1,
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center'
|
||||
},
|
||||
messageText: {
|
||||
fontSize: 16,
|
||||
color: changeOpacity(theme.centerChannelColor, 0.8),
|
||||
fontWeight: '600'
|
||||
},
|
||||
wrapper: {
|
||||
position: 'absolute',
|
||||
elevation: 5,
|
||||
left: 30,
|
||||
right: 30,
|
||||
height: 75,
|
||||
backgroundColor: 'white',
|
||||
borderColor: changeOpacity(theme.centerChannelColor, 0.2),
|
||||
borderWidth: 2,
|
||||
borderRadius: 5,
|
||||
shadowColor: theme.centerChannelColor,
|
||||
shadowOffset: {
|
||||
width: 0,
|
||||
height: 3
|
||||
},
|
||||
shadowOpacity: 0.2,
|
||||
shadowRadius: 2
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
export default injectIntl(ClientUpgradeListener);
|
||||
@@ -1,35 +0,0 @@
|
||||
import {bindActionCreators} from 'redux';
|
||||
import {connect} from 'react-redux';
|
||||
|
||||
import {logError} from 'mattermost-redux/actions/errors';
|
||||
|
||||
import {setLastUpgradeCheck} from 'app/actions/views/client_upgrade';
|
||||
import getClientUpgrade from 'app/selectors/client_upgrade';
|
||||
import {getTheme} from 'mattermost-redux/selectors/entities/preferences';
|
||||
|
||||
import ClientUpgradeListener from './client_upgrade_listener';
|
||||
|
||||
function mapStateToProps(state) {
|
||||
const {currentVersion, downloadLink, forceUpgrade, latestVersion, minVersion} = getClientUpgrade(state);
|
||||
|
||||
return {
|
||||
currentVersion,
|
||||
downloadLink,
|
||||
forceUpgrade,
|
||||
lastUpgradeCheck: state.views.clientUpgrade.lastUpdateCheck,
|
||||
latestVersion,
|
||||
minVersion,
|
||||
theme: getTheme(state)
|
||||
};
|
||||
}
|
||||
|
||||
function mapDispatchToProps(dispatch) {
|
||||
return {
|
||||
actions: bindActionCreators({
|
||||
logError,
|
||||
setLastUpgradeCheck
|
||||
}, dispatch)
|
||||
};
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(ClientUpgradeListener);
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
import {connect} from 'react-redux';
|
||||
|
||||
import {getTheme} from 'mattermost-redux/selectors/entities/preferences';
|
||||
import {getTheme} from 'app/selectors/preferences';
|
||||
|
||||
import {makeGetChannel} from 'mattermost-redux/selectors/entities/channels';
|
||||
|
||||
|
||||
@@ -3,7 +3,9 @@
|
||||
|
||||
import {connect} from 'react-redux';
|
||||
|
||||
import {getTeammateNameDisplaySetting, getTheme} from 'mattermost-redux/selectors/entities/preferences';
|
||||
import {getTheme} from 'app/selectors/preferences';
|
||||
|
||||
import {getTeammateNameDisplaySetting} from 'mattermost-redux/selectors/entities/preferences';
|
||||
import {getUser} from 'mattermost-redux/selectors/entities/users';
|
||||
|
||||
import UserListRow from './user_list_row';
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
import {Keyboard, Dimensions} from 'react-native';
|
||||
import PropTypes from 'prop-types';
|
||||
import BaseDrawer from 'react-native-drawer';
|
||||
|
||||
@@ -9,49 +8,20 @@ import BaseDrawer from 'react-native-drawer';
|
||||
export default class Drawer extends BaseDrawer {
|
||||
static propTypes = {
|
||||
...BaseDrawer.propTypes,
|
||||
onRequestClose: PropTypes.func.isRequired,
|
||||
bottomPanOffset: PropTypes.number,
|
||||
topPanOffset: PropTypes.number
|
||||
onRequestClose: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.keyboardHeight = 0;
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
Keyboard.addListener('keyboardDidShow', this.keyboardDidShow);
|
||||
Keyboard.addListener('keyboardDidHide', this.keyboardDidHide);
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
Keyboard.removeListener('keyboardDidShow', this.keyboardDidShow);
|
||||
Keyboard.removeListener('keyboardDidHide', this.keyboardDidHide);
|
||||
}
|
||||
|
||||
// To fix the android onLayout issue give this a value of 100% as it does not need another one
|
||||
getMainHeight = () => '100%';
|
||||
|
||||
keyboardDidShow = (e) => {
|
||||
this.keyboardHeight = e.endCoordinates.height;
|
||||
};
|
||||
|
||||
keyboardDidHide = () => {
|
||||
this.keyboardHeight = 0;
|
||||
};
|
||||
|
||||
isOpened = () => {
|
||||
return this._open; //eslint-disable-line no-underscore-dangle
|
||||
};
|
||||
|
||||
processTapGestures = () => {
|
||||
// Note that we explicitly don't support tap to open or double tap because I didn't copy them over
|
||||
|
||||
if (this._activeTween) { //eslint-disable-line no-underscore-dangle
|
||||
if (this._activeTween) { // eslint-disable-line no-underscore-dangle
|
||||
return false;
|
||||
}
|
||||
|
||||
if (this.props.tapToClose && this._open) { //eslint-disable-line no-underscore-dangle
|
||||
if (this.props.tapToClose && this._open) { // eslint-disable-line no-underscore-dangle
|
||||
this.props.onRequestClose();
|
||||
|
||||
return true;
|
||||
@@ -59,41 +29,4 @@ export default class Drawer extends BaseDrawer {
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
testPanResponderMask = (e) => {
|
||||
if (this.props.disabled) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Disable if parent or child drawer exist and are open
|
||||
if (this.context.drawer && this.context.drawer._open) { //eslint-disable-line no-underscore-dangle
|
||||
return false;
|
||||
}
|
||||
|
||||
if (this._childDrawer && this._childDrawer._open) { //eslint-disable-line no-underscore-dangle
|
||||
return false;
|
||||
}
|
||||
|
||||
const topPanOffset = this.props.topPanOffset || 0;
|
||||
const bottomPanOffset = this.props.bottomPanOffset || 0;
|
||||
const height = Dimensions.get('window').height;
|
||||
if ((this.props.topPanOffset && e.nativeEvent.pageY < topPanOffset) ||
|
||||
(this.props.bottomPanOffset && e.nativeEvent.pageY > (height - (bottomPanOffset + this.keyboardHeight)))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const pos0 = this.isLeftOrRightSide() ? e.nativeEvent.pageX : e.nativeEvent.pageY;
|
||||
const deltaOpen = this.isLeftOrTopSide() ? this.getDeviceLength() - pos0 : pos0;
|
||||
const deltaClose = this.isLeftOrTopSide() ? pos0 : this.getDeviceLength() - pos0;
|
||||
|
||||
if (this._open && deltaOpen > this.getOpenMask()) { //eslint-disable-line no-underscore-dangle
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!this._open && deltaClose > this.getClosedMask()) { //eslint-disable-line no-underscore-dangle
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ import {createSelector} from 'reselect';
|
||||
import {getCustomEmojisByName} from 'mattermost-redux/selectors/entities/emojis';
|
||||
|
||||
import {getDimensions} from 'app/selectors/device';
|
||||
import {getTheme} from 'mattermost-redux/selectors/entities/preferences';
|
||||
import {getTheme} from 'app/selectors/preferences';
|
||||
import {CategoryNames, Emojis, EmojiIndicesByCategory} from 'app/utils/emojis';
|
||||
|
||||
import EmojiPicker from './emoji_picker';
|
||||
|
||||
@@ -7,7 +7,7 @@ import PropTypes from 'prop-types';
|
||||
import {Text} from 'react-native';
|
||||
|
||||
import FormattedText from 'app/components/formatted_text';
|
||||
import {getTheme} from 'mattermost-redux/selectors/entities/preferences';
|
||||
import {getTheme} from 'app/selectors/preferences';
|
||||
import {GlobalStyles} from 'app/styles';
|
||||
import {makeStyleSheetFromTheme} from 'app/utils/theme';
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ import {connect} from 'react-redux';
|
||||
import {makeGetFilesForPost} from 'mattermost-redux/selectors/entities/files';
|
||||
import {loadFilesForPostIfNecessary} from 'app/actions/views/channel';
|
||||
import {addFileToFetchCache} from 'app/actions/views/file_preview';
|
||||
import {getTheme} from 'mattermost-redux/selectors/entities/preferences';
|
||||
import {getTheme} from 'app/selectors/preferences';
|
||||
|
||||
import FileAttachmentList from './file_attachment_list';
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ import {createSelector} from 'reselect';
|
||||
import {handleRemoveFile, retryFileUpload} from 'app/actions/views/file_upload';
|
||||
import {addFileToFetchCache} from 'app/actions/views/file_preview';
|
||||
import {getDimensions} from 'app/selectors/device';
|
||||
import {getTheme} from 'mattermost-redux/selectors/entities/preferences';
|
||||
import {getTheme} from 'app/selectors/preferences';
|
||||
|
||||
import FileUploadPreview from './file_upload_preview';
|
||||
|
||||
|
||||
@@ -24,7 +24,7 @@ class FormattedTime extends React.PureComponent {
|
||||
|
||||
Reflect.deleteProperty(props, 'format');
|
||||
|
||||
const formattedTime = intl.formatDate(value, {...props, hour: 'numeric', minute: 'numeric'});
|
||||
const formattedTime = intl.formatTime(value, this.props);
|
||||
|
||||
if (typeof children === 'function') {
|
||||
return children(formattedTime);
|
||||
@@ -35,3 +35,4 @@ class FormattedTime extends React.PureComponent {
|
||||
}
|
||||
|
||||
export default injectIntl(FormattedTime);
|
||||
|
||||
|
||||
@@ -3,9 +3,7 @@
|
||||
|
||||
import React, {PureComponent} from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import {FlatList, Platform, ScrollView, StyleSheet, View} from 'react-native';
|
||||
|
||||
import RefreshList from 'app/components/refresh_list';
|
||||
import {FlatList, Platform, RefreshControl, ScrollView, StyleSheet, View} from 'react-native';
|
||||
|
||||
import VirtualList from './virtual_list';
|
||||
|
||||
@@ -66,7 +64,7 @@ export default class InvertibleFlatList extends PureComponent {
|
||||
<ScrollView
|
||||
{...props}
|
||||
refreshControl={
|
||||
<RefreshList
|
||||
<RefreshControl
|
||||
refreshing={props.refreshing}
|
||||
onRefresh={props.onRefresh}
|
||||
tintColor={theme.centerChannelColor}
|
||||
@@ -133,10 +131,7 @@ const styles = StyleSheet.create({
|
||||
},
|
||||
vertical: Platform.select({
|
||||
android: {
|
||||
transform: [
|
||||
{perspective: 1},
|
||||
{scaleY: -1}
|
||||
]
|
||||
scaleY: -1
|
||||
},
|
||||
ios: {
|
||||
transform: [{scaleY: -1}]
|
||||
@@ -144,10 +139,7 @@ const styles = StyleSheet.create({
|
||||
}),
|
||||
horizontal: Platform.select({
|
||||
android: {
|
||||
transform: [
|
||||
{perspective: 1},
|
||||
{scaleY: -1}
|
||||
]
|
||||
scaleX: -1
|
||||
},
|
||||
ios: {
|
||||
transform: [{scaleX: -1}]
|
||||
|
||||
@@ -1,24 +1,19 @@
|
||||
// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
import React, {PureComponent} from 'react';
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import {KeyboardAvoidingView, Platform, View} from 'react-native';
|
||||
|
||||
export default class KeyboardLayout extends PureComponent {
|
||||
export default class KeyboardLayout extends React.PureComponent {
|
||||
static propTypes = {
|
||||
behaviour: PropTypes.string,
|
||||
children: PropTypes.node,
|
||||
keyboardVerticalOffset: PropTypes.number,
|
||||
statusBarHeight: PropTypes.number
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
keyboardVerticalOffset: 0
|
||||
keyboardVerticalOffset: PropTypes.number
|
||||
};
|
||||
|
||||
render() {
|
||||
const {behaviour, children, keyboardVerticalOffset, statusBarHeight, ...otherProps} = this.props;
|
||||
const {behaviour, children, keyboardVerticalOffset, ...otherProps} = this.props;
|
||||
|
||||
if (Platform.OS === 'android') {
|
||||
return (
|
||||
@@ -28,17 +23,10 @@ export default class KeyboardLayout extends PureComponent {
|
||||
);
|
||||
}
|
||||
|
||||
let height = 0;
|
||||
if (statusBarHeight > 20) {
|
||||
height = (statusBarHeight - 20) + keyboardVerticalOffset;
|
||||
} else {
|
||||
height = keyboardVerticalOffset;
|
||||
}
|
||||
|
||||
return (
|
||||
<KeyboardAvoidingView
|
||||
behaviour={behaviour}
|
||||
keyboardVerticalOffset={height}
|
||||
keyboardVerticalOffset={keyboardVerticalOffset}
|
||||
{...otherProps}
|
||||
>
|
||||
{children}
|
||||
@@ -1,16 +0,0 @@
|
||||
// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
import {connect} from 'react-redux';
|
||||
|
||||
import {getStatusBarHeight} from 'app/selectors/device';
|
||||
|
||||
import KeyboardLayout from './keyboard_layout';
|
||||
|
||||
function mapStateToProps(state) {
|
||||
return {
|
||||
statusBarHeight: getStatusBarHeight(state)
|
||||
};
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps)(KeyboardLayout);
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
import {connect} from 'react-redux';
|
||||
|
||||
import {getTheme} from 'mattermost-redux/selectors/entities/preferences';
|
||||
import {getTheme} from 'app/selectors/preferences';
|
||||
|
||||
import MarkdownCodeBlock from './markdown_code_block';
|
||||
|
||||
|
||||
@@ -1,13 +1,11 @@
|
||||
// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
import React, {Children, PureComponent} from 'react';
|
||||
import React, {PureComponent} from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import {Linking, Text} from 'react-native';
|
||||
import urlParse from 'url-parse';
|
||||
|
||||
import CustomPropTypes from 'app/constants/custom_prop_types';
|
||||
import Config from 'assets/config';
|
||||
|
||||
export default class MarkdownLink extends PureComponent {
|
||||
static propTypes = {
|
||||
@@ -22,7 +20,13 @@ export default class MarkdownLink extends PureComponent {
|
||||
|
||||
handlePress = () => {
|
||||
// Android doesn't like the protocol being upper case
|
||||
const url = this.props.href;
|
||||
let url = this.props.href;
|
||||
|
||||
const index = url.indexOf(':');
|
||||
if (index !== -1) {
|
||||
const protocol = url.substring(0, index);
|
||||
url = protocol.toLowerCase() + url.substring(index);
|
||||
}
|
||||
|
||||
Linking.canOpenURL(url).then((supported) => {
|
||||
if (supported) {
|
||||
@@ -31,49 +35,13 @@ export default class MarkdownLink extends PureComponent {
|
||||
});
|
||||
};
|
||||
|
||||
parseLinkLiteral = (literal) => {
|
||||
let nextLiteral = literal;
|
||||
|
||||
const WWW_REGEX = /\b^(?:www.)/i;
|
||||
if (nextLiteral.match(WWW_REGEX)) {
|
||||
nextLiteral = literal.replace(WWW_REGEX, 'www.');
|
||||
}
|
||||
|
||||
const parsed = urlParse(nextLiteral, {});
|
||||
|
||||
return parsed.href;
|
||||
}
|
||||
|
||||
parseChildren = () => {
|
||||
return Children.map(this.props.children, (child) => {
|
||||
if (!child.props.literal || typeof child.props.literal !== 'string' || (child.props.context && child.props.context.length && !child.props.context.includes('link'))) {
|
||||
return child;
|
||||
}
|
||||
|
||||
const {props, ...otherChildProps} = child;
|
||||
const {literal, ...otherProps} = props;
|
||||
|
||||
const nextProps = {
|
||||
literal: this.parseLinkLiteral(literal),
|
||||
...otherProps
|
||||
};
|
||||
|
||||
return {
|
||||
props: nextProps,
|
||||
...otherChildProps
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
const children = Config.ExperimentalNormalizeMarkdownLinks ? this.parseChildren() : this.props.children;
|
||||
|
||||
return (
|
||||
<Text
|
||||
onPress={this.handlePress}
|
||||
onLongPress={this.props.onLongPress}
|
||||
>
|
||||
{children}
|
||||
{this.props.children}
|
||||
</Text>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,22 +1,37 @@
|
||||
// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
import {bindActionCreators} from 'redux';
|
||||
import {connect} from 'react-redux';
|
||||
|
||||
import {close as closeWebSocket, init as initWebSocket} from 'mattermost-redux/actions/websocket';
|
||||
|
||||
import {getConnection} from 'app/selectors/device';
|
||||
|
||||
import OfflineIndicator from './offline_indicator';
|
||||
|
||||
function mapStateToProps(state) {
|
||||
function mapStateToProps(state, ownProps) {
|
||||
const {websocket} = state.requests.general;
|
||||
const {appState} = state.entities.general;
|
||||
const webSocketStatus = websocket.status;
|
||||
const isConnecting = websocket.error > 1;
|
||||
const isConnecting = websocket.error >= 2;
|
||||
|
||||
return {
|
||||
appState,
|
||||
isConnecting,
|
||||
isOnline: getConnection(state),
|
||||
webSocketStatus
|
||||
webSocketStatus,
|
||||
...ownProps
|
||||
};
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps)(OfflineIndicator);
|
||||
function mapDispatchToProps(dispatch) {
|
||||
return {
|
||||
actions: bindActionCreators({
|
||||
closeWebSocket,
|
||||
initWebSocket
|
||||
}, dispatch)
|
||||
};
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(OfflineIndicator);
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
import React, {Component} from 'react';
|
||||
import React, {PureComponent} from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import {
|
||||
ActivityIndicator,
|
||||
Animated,
|
||||
Platform,
|
||||
StyleSheet,
|
||||
TouchableOpacity,
|
||||
View
|
||||
} from 'react-native';
|
||||
import IonIcon from 'react-native-vector-icons/Ionicons';
|
||||
@@ -23,14 +24,20 @@ const OFFLINE = 'offline';
|
||||
const CONNECTING = 'connecting';
|
||||
const CONNECTED = 'connected';
|
||||
|
||||
export default class OfflineIndicator extends Component {
|
||||
export default class OfflineIndicator extends PureComponent {
|
||||
static propTypes = {
|
||||
actions: PropTypes.shape({
|
||||
closeWebSocket: PropTypes.func.isRequired,
|
||||
initWebSocket: PropTypes.func.isRequired
|
||||
}).isRequired,
|
||||
appState: PropTypes.bool,
|
||||
isConnecting: PropTypes.bool,
|
||||
isOnline: PropTypes.bool,
|
||||
webSocketStatus: PropTypes.string
|
||||
};
|
||||
|
||||
static defaultProps: {
|
||||
appState: true,
|
||||
isOnline: true
|
||||
};
|
||||
|
||||
@@ -46,30 +53,42 @@ export default class OfflineIndicator extends Component {
|
||||
}
|
||||
|
||||
componentWillReceiveProps(nextProps) {
|
||||
const {webSocketStatus} = this.props;
|
||||
if (nextProps.isOnline) {
|
||||
if (this.state.network && webSocketStatus === RequestStatus.STARTED && nextProps.webSocketStatus === RequestStatus.SUCCESS) {
|
||||
// Show the connected animation only if we had a previous network status
|
||||
this.connected();
|
||||
} else if (webSocketStatus === RequestStatus.STARTED && nextProps.webSocketStatus === RequestStatus.FAILURE && nextProps.isConnecting) {
|
||||
// Show the connecting bar if it failed to connect at least twice
|
||||
this.connecting();
|
||||
const {appState, isConnecting, isOnline, webSocketStatus} = nextProps;
|
||||
if (appState) { // The app is in the foreground
|
||||
if (isOnline) {
|
||||
if (this.state.network && webSocketStatus === RequestStatus.SUCCESS) {
|
||||
// Show the connected animation only if we had a previous network status
|
||||
this.connected();
|
||||
} else if ((webSocketStatus === RequestStatus.STARTED || webSocketStatus === RequestStatus.FAILURE) && isConnecting) {
|
||||
// Show the connecting bar if it failed to connect at least twice
|
||||
this.connecting();
|
||||
}
|
||||
} else {
|
||||
this.offline();
|
||||
}
|
||||
} else {
|
||||
this.offline();
|
||||
}
|
||||
}
|
||||
|
||||
shouldComponentUpdate(nextProps, nextState) {
|
||||
return nextState.network !== this.state.network && nextState.network;
|
||||
}
|
||||
|
||||
offline = () => {
|
||||
this.setState({network: OFFLINE}, () => {
|
||||
this.show();
|
||||
});
|
||||
};
|
||||
|
||||
connect = () => {
|
||||
const {actions, isOnline, webSocketStatus} = this.props;
|
||||
const {closeWebSocket, initWebSocket} = actions;
|
||||
initWebSocket(Platform.OS);
|
||||
|
||||
// close the WS connection after trying for 5 seconds
|
||||
setTimeout(() => {
|
||||
if (webSocketStatus !== RequestStatus.SUCCESS) {
|
||||
closeWebSocket(true);
|
||||
this.setState({network: isOnline ? OFFLINE : CONNECTING});
|
||||
}
|
||||
}, 5000);
|
||||
};
|
||||
|
||||
connecting = () => {
|
||||
const prevState = this.state.network;
|
||||
this.setState({network: CONNECTING}, () => {
|
||||
@@ -92,7 +111,7 @@ export default class OfflineIndicator extends Component {
|
||||
this.state.top, {
|
||||
toValue: INITIAL_TOP,
|
||||
duration: 300,
|
||||
delay: 500
|
||||
delay: 1000
|
||||
}
|
||||
)
|
||||
]).start(() => {
|
||||
@@ -127,6 +146,18 @@ export default class OfflineIndicator extends Component {
|
||||
case OFFLINE:
|
||||
i18nId = 'mobile.offlineIndicator.offline';
|
||||
defaultMessage = 'No internet connection';
|
||||
action = (
|
||||
<TouchableOpacity
|
||||
onPress={this.connect}
|
||||
style={[styles.actionContainer, styles.actionButton]}
|
||||
>
|
||||
<IonIcon
|
||||
color='#FFFFFF'
|
||||
name='ios-refresh'
|
||||
size={20}
|
||||
/>
|
||||
</TouchableOpacity>
|
||||
);
|
||||
break;
|
||||
case CONNECTING:
|
||||
i18nId = 'mobile.offlineIndicator.connecting';
|
||||
|
||||
@@ -8,52 +8,30 @@ import {addReaction, createPost, deletePost, removePost} from 'mattermost-redux/
|
||||
import {getPost} from 'mattermost-redux/selectors/entities/posts';
|
||||
import {getCurrentUserId, getCurrentUserRoles} from 'mattermost-redux/selectors/entities/users';
|
||||
|
||||
import {insertToDraft, setPostTooltipVisible} from 'app/actions/views/channel';
|
||||
import {getTheme} from 'mattermost-redux/selectors/entities/preferences';
|
||||
import {setPostTooltipVisible} from 'app/actions/views/channel';
|
||||
import {getTheme} from 'app/selectors/preferences';
|
||||
|
||||
import Post from './post';
|
||||
|
||||
function mapStateToProps(state, ownProps) {
|
||||
const post = getPost(state, ownProps.postId);
|
||||
function makeMapStateToProps() {
|
||||
return function mapStateToProps(state, ownProps) {
|
||||
const post = getPost(state, ownProps.post.id);
|
||||
|
||||
const {config, license} = state.entities.general;
|
||||
const roles = getCurrentUserId(state) ? getCurrentUserRoles(state) : '';
|
||||
const {config, license} = state.entities.general;
|
||||
const roles = getCurrentUserId(state) ? getCurrentUserRoles(state) : '';
|
||||
const {tooltipVisible} = state.views.channel;
|
||||
|
||||
let isFirstReply = true;
|
||||
let isLastReply = true;
|
||||
let commentedOnPost = null;
|
||||
if (ownProps.renderReplies && post && post.root_id) {
|
||||
if (ownProps.previousPostId) {
|
||||
const previousPost = getPost(state, ownProps.previousPostId);
|
||||
|
||||
if (previousPost && (previousPost.id === post.root_id || previousPost.root_id === post.root_id)) {
|
||||
// Previous post is root post or previous post is in same thread
|
||||
isFirstReply = false;
|
||||
} else {
|
||||
// Last post is not a comment on the same message
|
||||
commentedOnPost = getPost(state, post.root_id);
|
||||
}
|
||||
}
|
||||
|
||||
if (ownProps.nextPostId) {
|
||||
const nextPost = getPost(state, ownProps.nextPostId);
|
||||
|
||||
if (nextPost && nextPost.root_id === post.root_id) {
|
||||
isLastReply = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
config,
|
||||
currentUserId: getCurrentUserId(state),
|
||||
post,
|
||||
isFirstReply,
|
||||
isLastReply,
|
||||
commentedOnPost,
|
||||
license,
|
||||
roles,
|
||||
theme: getTheme(state)
|
||||
return {
|
||||
...ownProps,
|
||||
post,
|
||||
config,
|
||||
currentUserId: getCurrentUserId(state),
|
||||
highlight: ownProps.post.highlight,
|
||||
license,
|
||||
roles,
|
||||
theme: getTheme(state),
|
||||
tooltipVisible
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
@@ -64,10 +42,9 @@ function mapDispatchToProps(dispatch) {
|
||||
createPost,
|
||||
deletePost,
|
||||
removePost,
|
||||
setPostTooltipVisible,
|
||||
insertToDraft
|
||||
setPostTooltipVisible
|
||||
}, dispatch)
|
||||
};
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(Post);
|
||||
export default connect(makeMapStateToProps, mapDispatchToProps)(Post);
|
||||
|
||||
@@ -10,7 +10,6 @@ import {
|
||||
} from 'react-native';
|
||||
import {injectIntl, intlShape} from 'react-intl';
|
||||
import MaterialIcon from 'react-native-vector-icons/MaterialIcons';
|
||||
import {isToolTipShowing} from 'react-native-tooltip';
|
||||
|
||||
import PostBody from 'app/components/post_body';
|
||||
import PostHeader from 'app/components/post_header';
|
||||
@@ -26,24 +25,21 @@ import EventEmitter from 'mattermost-redux/utils/event_emitter';
|
||||
import {canDeletePost, canEditPost, isPostEphemeral, isPostPendingOrFailed, isSystemMessage} from 'mattermost-redux/utils/post_utils';
|
||||
import {isAdmin, isSystemAdmin} from 'mattermost-redux/utils/user_utils';
|
||||
|
||||
import Config from 'assets/config';
|
||||
|
||||
class Post extends PureComponent {
|
||||
static propTypes = {
|
||||
actions: PropTypes.shape({
|
||||
addReaction: PropTypes.func.isRequired,
|
||||
createPost: PropTypes.func.isRequired,
|
||||
deletePost: PropTypes.func.isRequired,
|
||||
insertToDraft: PropTypes.func.isRequired,
|
||||
removePost: PropTypes.func.isRequired
|
||||
removePost: PropTypes.func.isRequired,
|
||||
setPostTooltipVisible: PropTypes.func.isRequired
|
||||
}).isRequired,
|
||||
config: PropTypes.object.isRequired,
|
||||
currentUserId: PropTypes.string.isRequired,
|
||||
highlight: PropTypes.bool,
|
||||
intl: intlShape.isRequired,
|
||||
style: ViewPropTypes.style,
|
||||
post: PropTypes.object,
|
||||
postId: PropTypes.string.isRequired, // Used by container // eslint-disable-line no-unused-prop-types
|
||||
post: PropTypes.object.isRequired,
|
||||
renderReplies: PropTypes.bool,
|
||||
isFirstReply: PropTypes.bool,
|
||||
isLastReply: PropTypes.bool,
|
||||
@@ -54,6 +50,7 @@ class Post extends PureComponent {
|
||||
roles: PropTypes.string,
|
||||
shouldRenderReplyButton: PropTypes.bool,
|
||||
showFullDate: PropTypes.bool,
|
||||
tooltipVisible: PropTypes.bool,
|
||||
theme: PropTypes.object.isRequired,
|
||||
onPress: PropTypes.func,
|
||||
onReply: PropTypes.func
|
||||
@@ -68,32 +65,18 @@ class Post extends PureComponent {
|
||||
|
||||
const {config, license, currentUserId, roles, post} = props;
|
||||
this.editDisableAction = new DelayedAction(this.handleEditDisable);
|
||||
if (post) {
|
||||
this.state = {
|
||||
canEdit: canEditPost(config, license, currentUserId, post, this.editDisableAction),
|
||||
canDelete: canDeletePost(config, license, currentUserId, post, isAdmin(roles), isSystemAdmin(roles))
|
||||
};
|
||||
} else {
|
||||
this.state = {
|
||||
canEdit: false,
|
||||
canDelete: false
|
||||
};
|
||||
}
|
||||
this.state = {
|
||||
canEdit: canEditPost(config, license, currentUserId, post, this.editDisableAction),
|
||||
canDelete: canDeletePost(config, license, currentUserId, post, isAdmin(roles), isSystemAdmin(roles))
|
||||
};
|
||||
}
|
||||
|
||||
componentWillReceiveProps(nextProps) {
|
||||
if (nextProps.config !== this.props.config ||
|
||||
nextProps.license !== this.props.license ||
|
||||
nextProps.currentUserId !== this.props.currentUserId ||
|
||||
nextProps.post !== this.props.post ||
|
||||
nextProps.roles !== this.props.roles) {
|
||||
const {config, license, currentUserId, roles, post} = nextProps;
|
||||
|
||||
this.setState({
|
||||
canEdit: canEditPost(config, license, currentUserId, post, this.editDisableAction),
|
||||
canDelete: canDeletePost(config, license, currentUserId, post, isAdmin(roles), isSystemAdmin(roles))
|
||||
});
|
||||
}
|
||||
const {config, license, currentUserId, roles, post} = nextProps;
|
||||
this.setState({
|
||||
canEdit: canEditPost(config, license, currentUserId, post, this.editDisableAction),
|
||||
canDelete: canDeletePost(config, license, currentUserId, post, isAdmin(roles), isSystemAdmin(roles))
|
||||
});
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
@@ -119,12 +102,6 @@ class Post extends PureComponent {
|
||||
});
|
||||
};
|
||||
|
||||
autofillUserMention = (username) => {
|
||||
// create a general action that checks for currentThreadId in the state and decides
|
||||
// whether to insert to root or thread
|
||||
this.props.actions.insertToDraft(`@${username} `);
|
||||
}
|
||||
|
||||
handleEditDisable = () => {
|
||||
this.setState({canEdit: false});
|
||||
};
|
||||
@@ -251,8 +228,8 @@ class Post extends PureComponent {
|
||||
};
|
||||
|
||||
handlePress = () => {
|
||||
const {post, onPress} = this.props;
|
||||
if (!isToolTipShowing) {
|
||||
const {post, onPress, tooltipVisible} = this.props;
|
||||
if (!tooltipVisible) {
|
||||
if (onPress && post.state !== Posts.POST_DELETED && !isSystemMessage(post) && !isPostPendingOrFailed(post)) {
|
||||
preventDoubleTap(onPress, null, post);
|
||||
} else if (isPostEphemeral(post)) {
|
||||
@@ -262,8 +239,8 @@ class Post extends PureComponent {
|
||||
};
|
||||
|
||||
handleReply = () => {
|
||||
const {post, onReply} = this.props;
|
||||
if (!isToolTipShowing && onReply) {
|
||||
const {post, onReply, tooltipVisible} = this.props;
|
||||
if (!tooltipVisible && onReply) {
|
||||
return preventDoubleTap(onReply, null, post);
|
||||
}
|
||||
|
||||
@@ -304,17 +281,18 @@ class Post extends PureComponent {
|
||||
};
|
||||
|
||||
viewUserProfile = () => {
|
||||
const {isSearchResult} = this.props;
|
||||
const {isSearchResult, tooltipVisible} = this.props;
|
||||
|
||||
if (!isSearchResult && !isToolTipShowing) {
|
||||
if (!isSearchResult && !tooltipVisible) {
|
||||
preventDoubleTap(this.goToUserProfile, this);
|
||||
}
|
||||
};
|
||||
|
||||
toggleSelected = (selected) => {
|
||||
if (!isToolTipShowing) {
|
||||
this.setState({selected});
|
||||
toggleSelected = (selected, tooltip) => {
|
||||
if (tooltip) {
|
||||
this.props.actions.setPostTooltipVisible(selected);
|
||||
}
|
||||
this.setState({selected});
|
||||
};
|
||||
|
||||
render() {
|
||||
@@ -329,17 +307,10 @@ class Post extends PureComponent {
|
||||
showFullDate,
|
||||
theme
|
||||
} = this.props;
|
||||
|
||||
if (!post) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const style = getStyleSheet(theme);
|
||||
const selected = this.state && this.state.selected ? style.selected : null;
|
||||
const highlighted = highlight ? style.highlight : null;
|
||||
|
||||
const onUsernamePress = Config.ExperimentalUsernamePressIsMention ? this.autofillUserMention : this.viewUserProfile;
|
||||
|
||||
return (
|
||||
<View style={[style.container, this.props.style, highlighted, selected]}>
|
||||
<View style={[style.profilePictureContainer, (isPostPendingOrFailed(post) && style.pendingPost)]}>
|
||||
@@ -359,7 +330,7 @@ class Post extends PureComponent {
|
||||
shouldRenderReplyButton={shouldRenderReplyButton}
|
||||
showFullDate={showFullDate}
|
||||
onPress={this.handleReply}
|
||||
onUsernamePress={onUsernamePress}
|
||||
onViewUserProfile={this.viewUserProfile}
|
||||
renderReplies={renderReplies}
|
||||
theme={theme}
|
||||
/>
|
||||
|
||||
@@ -6,8 +6,17 @@ import {bindActionCreators} from 'redux';
|
||||
|
||||
import {getOpenGraphMetadata} from 'mattermost-redux/actions/posts';
|
||||
|
||||
import {getTheme} from 'app/selectors/preferences';
|
||||
|
||||
import PostAttachmentOpenGraph from './post_attachment_opengraph';
|
||||
|
||||
function mapStateToProps(state, ownProps) {
|
||||
return {
|
||||
...ownProps,
|
||||
theme: getTheme(state)
|
||||
};
|
||||
}
|
||||
|
||||
function mapDispatchToProps(dispatch) {
|
||||
return {
|
||||
actions: bindActionCreators({
|
||||
@@ -16,4 +25,4 @@ function mapDispatchToProps(dispatch) {
|
||||
};
|
||||
}
|
||||
|
||||
export default connect(null, mapDispatchToProps)(PostAttachmentOpenGraph);
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(PostAttachmentOpenGraph);
|
||||
|
||||
@@ -40,11 +40,6 @@ export default class PostAttachmentOpenGraph extends PureComponent {
|
||||
};
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.mounted = true;
|
||||
this.getBestImageUrl(this.props.openGraphData);
|
||||
}
|
||||
|
||||
componentWillMount() {
|
||||
this.fetchData(this.props.link, this.props.openGraphData);
|
||||
}
|
||||
@@ -54,14 +49,6 @@ export default class PostAttachmentOpenGraph extends PureComponent {
|
||||
this.setState({imageLoaded: false});
|
||||
this.fetchData(nextProps.link, nextProps.openGraphData);
|
||||
}
|
||||
|
||||
if (this.props.openGraphData !== nextProps.openGraphData) {
|
||||
this.getBestImageUrl(nextProps.openGraphData);
|
||||
}
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this.mounted = false;
|
||||
}
|
||||
|
||||
calculateLargeImageDimensions = (width, height) => {
|
||||
@@ -113,8 +100,8 @@ export default class PostAttachmentOpenGraph extends PureComponent {
|
||||
}
|
||||
|
||||
getBestImageUrl(data) {
|
||||
if (!data || !data.images) {
|
||||
return;
|
||||
if (!data.images) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const bestDimensions = {
|
||||
@@ -122,11 +109,7 @@ export default class PostAttachmentOpenGraph extends PureComponent {
|
||||
height: MAX_IMAGE_HEIGHT
|
||||
};
|
||||
const bestImage = getNearestPoint(bestDimensions, data.images, 'width', 'height');
|
||||
const imageUrl = bestImage.secure_url || bestImage.url;
|
||||
|
||||
if (imageUrl) {
|
||||
this.getImageSize(imageUrl);
|
||||
}
|
||||
return bestImage.secure_url || bestImage.url;
|
||||
}
|
||||
|
||||
getImageSize = (imageUrl) => {
|
||||
@@ -143,14 +126,11 @@ export default class PostAttachmentOpenGraph extends PureComponent {
|
||||
} else {
|
||||
dimensions = this.calculateSmallImageDimensions(width, height);
|
||||
}
|
||||
if (this.mounted) {
|
||||
this.setState({
|
||||
...dimensions,
|
||||
hasLargeImage: isLarge,
|
||||
imageLoaded: true,
|
||||
imageUrl
|
||||
});
|
||||
}
|
||||
this.setState({
|
||||
...dimensions,
|
||||
hasLargeImage: isLarge,
|
||||
imageLoaded: true
|
||||
});
|
||||
}, () => null);
|
||||
}
|
||||
};
|
||||
@@ -161,14 +141,18 @@ export default class PostAttachmentOpenGraph extends PureComponent {
|
||||
|
||||
render() {
|
||||
const {openGraphData, theme} = this.props;
|
||||
const {hasLargeImage, height, imageLoaded, imageUrl, offset, width} = this.state;
|
||||
const {hasLargeImage, height, imageLoaded, offset, width} = this.state;
|
||||
|
||||
if (!openGraphData || !openGraphData.description) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const style = getStyleSheet(theme);
|
||||
const imageUrl = this.getBestImageUrl(openGraphData);
|
||||
const isThumbnail = !hasLargeImage && imageLoaded;
|
||||
if (imageUrl) {
|
||||
this.getImageSize(imageUrl);
|
||||
}
|
||||
|
||||
return (
|
||||
<View style={style.container}>
|
||||
|
||||
@@ -7,9 +7,11 @@ import {bindActionCreators} from 'redux';
|
||||
import {flagPost, unflagPost} from 'mattermost-redux/actions/posts';
|
||||
import {Posts} from 'mattermost-redux/constants';
|
||||
import {getPost} from 'mattermost-redux/selectors/entities/posts';
|
||||
import {getMyPreferences, getTheme} from 'mattermost-redux/selectors/entities/preferences';
|
||||
import {getMyPreferences} from 'mattermost-redux/selectors/entities/preferences';
|
||||
import {isPostFlagged, isPostEphemeral, isSystemMessage} from 'mattermost-redux/utils/post_utils';
|
||||
|
||||
import {getTheme} from 'app/selectors/preferences';
|
||||
|
||||
import PostBody from './post_body';
|
||||
|
||||
function mapStateToProps(state, ownProps) {
|
||||
|
||||
@@ -190,20 +190,11 @@ class PostBody extends PureComponent {
|
||||
let messageComponent;
|
||||
if (hasBeenDeleted) {
|
||||
messageComponent = (
|
||||
<TouchableHighlight
|
||||
onHideUnderlay={this.handleHideUnderlay}
|
||||
onPress={onPress}
|
||||
onShowUnderlay={this.handleShowUnderlay}
|
||||
underlayColor='transparent'
|
||||
>
|
||||
<View style={{flexDirection: 'row'}}>
|
||||
<FormattedText
|
||||
style={messageStyle}
|
||||
id='post_body.deleted'
|
||||
defaultMessage='(message deleted)'
|
||||
/>
|
||||
</View>
|
||||
</TouchableHighlight>
|
||||
<FormattedText
|
||||
style={messageStyle}
|
||||
id='post_body.deleted'
|
||||
defaultMessage='(message deleted)'
|
||||
/>
|
||||
);
|
||||
body = (<View>{messageComponent}</View>);
|
||||
} else if (message.length) {
|
||||
|
||||
@@ -9,10 +9,11 @@ import {connect} from 'react-redux';
|
||||
import {Preferences} from 'mattermost-redux/constants';
|
||||
import {getConfig} from 'mattermost-redux/selectors/entities/general';
|
||||
import {getOpenGraphMetadataForUrl} from 'mattermost-redux/selectors/entities/posts';
|
||||
import {getBool, getTheme} from 'mattermost-redux/selectors/entities/preferences';
|
||||
import {getBool} from 'mattermost-redux/selectors/entities/preferences';
|
||||
|
||||
import {ViewTypes} from 'app/constants';
|
||||
import {getDimensions} from 'app/selectors/device';
|
||||
import {getTheme} from 'app/selectors/preferences';
|
||||
import {extractFirstLink} from 'app/utils/url';
|
||||
|
||||
import PostBodyAdditionalContent from './post_body_additional_content';
|
||||
@@ -40,6 +41,7 @@ function makeMapStateToProps() {
|
||||
const link = getFirstLink(ownProps.message);
|
||||
|
||||
return {
|
||||
...ownProps,
|
||||
...getDimensions(state),
|
||||
config,
|
||||
link,
|
||||
|
||||
@@ -5,7 +5,6 @@ import React, {PureComponent} from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import {
|
||||
Image,
|
||||
ImageBackground,
|
||||
Linking,
|
||||
Platform,
|
||||
StyleSheet,
|
||||
@@ -99,7 +98,7 @@ export default class PostBodyAdditionalContent extends PureComponent {
|
||||
return null;
|
||||
}
|
||||
|
||||
const {link, openGraphData, showLinkPreviews, theme} = this.props;
|
||||
const {link, openGraphData, showLinkPreviews} = this.props;
|
||||
const attachments = this.getSlackAttachment();
|
||||
if (attachments) {
|
||||
return attachments;
|
||||
@@ -110,7 +109,6 @@ export default class PostBodyAdditionalContent extends PureComponent {
|
||||
<PostAttachmentOpenGraph
|
||||
link={link}
|
||||
openGraphData={openGraphData}
|
||||
theme={theme}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -186,7 +184,7 @@ export default class PostBodyAdditionalContent extends PureComponent {
|
||||
{...this.responder}
|
||||
onPress={this.playYouTubeVideo}
|
||||
>
|
||||
<ImageBackground
|
||||
<Image
|
||||
style={[styles.image, {width, height}]}
|
||||
source={{uri: imgUrl}}
|
||||
resizeMode={'cover'}
|
||||
@@ -198,7 +196,7 @@ export default class PostBodyAdditionalContent extends PureComponent {
|
||||
onPress={this.playYouTubeVideo}
|
||||
/>
|
||||
</TouchableWithoutFeedback>
|
||||
</ImageBackground>
|
||||
</Image>
|
||||
</TouchableWithoutFeedback>
|
||||
);
|
||||
}
|
||||
@@ -246,22 +244,22 @@ export default class PostBodyAdditionalContent extends PureComponent {
|
||||
};
|
||||
|
||||
render() {
|
||||
const {link, openGraphData, postProps} = this.props;
|
||||
const {link, openGraphData} = this.props;
|
||||
const {linkLoaded, linkLoadError} = this.state;
|
||||
const {attachments} = postProps;
|
||||
let isYouTube = false;
|
||||
let isImage = false;
|
||||
let isOpenGraph = false;
|
||||
|
||||
if (!link && !attachments) {
|
||||
return null;
|
||||
}
|
||||
if (link) {
|
||||
isYouTube = isYoutubeLink(link);
|
||||
isImage = isImageLink(link);
|
||||
isOpenGraph = Boolean(openGraphData && openGraphData.description);
|
||||
|
||||
const isYouTube = isYoutubeLink(link);
|
||||
const isImage = isImageLink(link);
|
||||
const isOpenGraph = Boolean(openGraphData && openGraphData.description);
|
||||
|
||||
if (((isImage && !isOpenGraph) || isYouTube) && !linkLoadError) {
|
||||
const embed = this.generateToggleableEmbed(isImage, isYouTube);
|
||||
if (embed && (linkLoaded || isYouTube)) {
|
||||
return embed;
|
||||
if (((isImage && !isOpenGraph) || isYouTube) && !linkLoadError) {
|
||||
const embed = this.generateToggleableEmbed(isImage, isYouTube);
|
||||
if (embed && (linkLoaded || isYouTube)) {
|
||||
return embed;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -5,11 +5,13 @@ import {connect} from 'react-redux';
|
||||
|
||||
import {Preferences} from 'mattermost-redux/constants';
|
||||
import {getPost, makeGetCommentCountForPost} from 'mattermost-redux/selectors/entities/posts';
|
||||
import {getBool, getTeammateNameDisplaySetting, getTheme} from 'mattermost-redux/selectors/entities/preferences';
|
||||
import {getBool, getTeammateNameDisplaySetting} from 'mattermost-redux/selectors/entities/preferences';
|
||||
import {getUser} from 'mattermost-redux/selectors/entities/users';
|
||||
import {isPostPendingOrFailed, isSystemMessage} from 'mattermost-redux/utils/post_utils';
|
||||
import {displayUsername} from 'mattermost-redux/utils/user_utils';
|
||||
|
||||
import {getTheme} from 'app/selectors/preferences';
|
||||
|
||||
import PostHeader from './post_header';
|
||||
|
||||
function makeMapStateToProps() {
|
||||
@@ -18,7 +20,7 @@ function makeMapStateToProps() {
|
||||
const {config} = state.entities.general;
|
||||
const post = getPost(state, ownProps.postId);
|
||||
const commentedOnUser = getUser(state, ownProps.commentedOnUserId);
|
||||
const user = getUser(state, post.user_id) || {};
|
||||
const user = getUser(state, post.user_id);
|
||||
const teammateNameDisplay = getTeammateNameDisplaySetting(state);
|
||||
const militaryTime = getBool(state, Preferences.CATEGORY_DISPLAY_SETTINGS, 'use_military_time');
|
||||
|
||||
@@ -34,8 +36,7 @@ function makeMapStateToProps() {
|
||||
isPendingOrFailedPost: isPostPendingOrFailed(post),
|
||||
isSystemMessage: isSystemMessage(post),
|
||||
overrideUsername: post.props && post.props.override_username,
|
||||
theme: getTheme(state),
|
||||
username: user.username
|
||||
theme: getTheme(state)
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
@@ -31,30 +31,26 @@ export default class PostHeader extends PureComponent {
|
||||
isSystemMessage: PropTypes.bool,
|
||||
militaryTime: PropTypes.bool,
|
||||
onPress: PropTypes.func,
|
||||
onUsernamePress: PropTypes.func,
|
||||
onViewUserProfile: PropTypes.func,
|
||||
overrideUsername: PropTypes.string,
|
||||
renderReplies: PropTypes.bool,
|
||||
shouldRenderReplyButton: PropTypes.bool,
|
||||
showFullDate: PropTypes.bool,
|
||||
theme: PropTypes.object.isRequired,
|
||||
username: PropTypes.string.isRequired
|
||||
theme: PropTypes.object.isRequired
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
commentCount: 0,
|
||||
onPress: emptyFunction,
|
||||
onUsernamePress: emptyFunction
|
||||
onViewUserProfile: emptyFunction
|
||||
};
|
||||
|
||||
handleUsernamePress = () => {
|
||||
this.props.onUsernamePress(this.props.username);
|
||||
}
|
||||
|
||||
getDisplayName = (style) => {
|
||||
const {
|
||||
enablePostUsernameOverride,
|
||||
fromWebHook,
|
||||
isSystemMessage,
|
||||
onViewUserProfile,
|
||||
overrideUsername
|
||||
} = this.props;
|
||||
|
||||
@@ -84,7 +80,7 @@ export default class PostHeader extends PureComponent {
|
||||
);
|
||||
} else if (this.props.displayName) {
|
||||
return (
|
||||
<TouchableOpacity onPress={this.handleUsernamePress}>
|
||||
<TouchableOpacity onPress={onViewUserProfile}>
|
||||
<Text style={style.displayName}>
|
||||
{this.props.displayName}
|
||||
</Text>
|
||||
|
||||
@@ -5,20 +5,14 @@ import {bindActionCreators} from 'redux';
|
||||
import {connect} from 'react-redux';
|
||||
|
||||
import {refreshChannelWithRetry} from 'app/actions/views/channel';
|
||||
import {makePreparePostIdsForPostList} from 'app/selectors/post_list';
|
||||
|
||||
import {getTheme} from 'mattermost-redux/selectors/entities/preferences';
|
||||
import {getTheme} from 'app/selectors/preferences';
|
||||
|
||||
import PostList from './post_list';
|
||||
|
||||
function makeMapStateToProps() {
|
||||
const preparePostIds = makePreparePostIdsForPostList();
|
||||
|
||||
return (state, ownProps) => {
|
||||
return {
|
||||
postIds: preparePostIds(state, ownProps),
|
||||
theme: getTheme(state)
|
||||
};
|
||||
function mapStateToProps(state, ownProps) {
|
||||
return {
|
||||
...ownProps,
|
||||
theme: getTheme(state)
|
||||
};
|
||||
}
|
||||
|
||||
@@ -30,4 +24,4 @@ function mapDispatchToProps(dispatch) {
|
||||
};
|
||||
}
|
||||
|
||||
export default connect(makeMapStateToProps, mapDispatchToProps)(PostList);
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(PostList);
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
// See License.txt for license information.
|
||||
|
||||
import React, {PureComponent} from 'react';
|
||||
import {connect} from 'react-redux';
|
||||
import PropTypes from 'prop-types';
|
||||
import {
|
||||
TouchableOpacity,
|
||||
@@ -14,7 +13,7 @@ import {makeStyleSheetFromTheme} from 'app/utils/theme';
|
||||
|
||||
import FormattedText from 'app/components/formatted_text';
|
||||
|
||||
class LoadMorePosts extends PureComponent {
|
||||
export default class LoadMorePosts extends PureComponent {
|
||||
static propTypes = {
|
||||
loading: PropTypes.bool.isRequired,
|
||||
loadMore: PropTypes.func,
|
||||
@@ -73,11 +72,3 @@ const getStyleSheet = makeStyleSheetFromTheme((theme) => {
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
function mapStateToProps(state, ownProps) {
|
||||
return {
|
||||
loading: state.views.channel.loadingPosts[ownProps.channelId] || false
|
||||
};
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps)(LoadMorePosts);
|
||||
|
||||
@@ -7,16 +7,19 @@ import {
|
||||
StyleSheet,
|
||||
View
|
||||
} from 'react-native';
|
||||
import FlatList from 'app/components/inverted_flat_list';
|
||||
|
||||
import {General} from 'mattermost-redux/constants';
|
||||
import {addDatesToPostList} from 'mattermost-redux/utils/post_utils';
|
||||
|
||||
import ChannelIntro from 'app/components/channel_intro';
|
||||
import FlatList from 'app/components/inverted_flat_list';
|
||||
import Post from 'app/components/post';
|
||||
import {DATE_LINE, START_OF_NEW_MESSAGES} from 'app/selectors/post_list';
|
||||
|
||||
import DateHeader from './date_header';
|
||||
import LoadMorePosts from './load_more_posts';
|
||||
import NewMessagesDivider from './new_messages_divider';
|
||||
|
||||
const LOAD_MORE_POSTS = 'load-more-posts';
|
||||
|
||||
export default class PostList extends PureComponent {
|
||||
static propTypes = {
|
||||
actions: PropTypes.shape({
|
||||
@@ -24,39 +27,49 @@ export default class PostList extends PureComponent {
|
||||
}).isRequired,
|
||||
channelId: PropTypes.string,
|
||||
currentUserId: PropTypes.string,
|
||||
highlightPostId: PropTypes.string,
|
||||
indicateNewMessages: PropTypes.bool,
|
||||
isLoadingMore: PropTypes.bool,
|
||||
isSearchResult: PropTypes.bool,
|
||||
lastViewedAt: PropTypes.number, // Used by container // eslint-disable-line no-unused-prop-types
|
||||
lastViewedAt: PropTypes.number,
|
||||
loadMore: PropTypes.func,
|
||||
navigator: PropTypes.object,
|
||||
onPostPress: PropTypes.func,
|
||||
onRefresh: PropTypes.func,
|
||||
postIds: PropTypes.array.isRequired,
|
||||
posts: PropTypes.array.isRequired,
|
||||
refreshing: PropTypes.bool,
|
||||
renderReplies: PropTypes.bool,
|
||||
showLoadMore: PropTypes.bool,
|
||||
shouldRenderReplyButton: PropTypes.bool,
|
||||
theme: PropTypes.object.isRequired
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
loadMore: () => true
|
||||
getPostsWithDates = () => {
|
||||
const {posts, indicateNewMessages, currentUserId, lastViewedAt, showLoadMore} = this.props;
|
||||
const list = addDatesToPostList(posts, {indicateNewMessages, currentUserId, lastViewedAt});
|
||||
|
||||
if (showLoadMore) {
|
||||
return [...list, LOAD_MORE_POSTS];
|
||||
}
|
||||
|
||||
return list;
|
||||
};
|
||||
|
||||
componentDidUpdate(prevProps) {
|
||||
if (prevProps.channelId !== this.props.channelId && this.refs.list) {
|
||||
// When switching channels make sure we start from the bottom
|
||||
this.refs.list.scrollToOffset({y: 0, animated: false});
|
||||
}
|
||||
}
|
||||
|
||||
getItem = (data, index) => data[index];
|
||||
|
||||
getItemCount = (data) => data.length;
|
||||
|
||||
keyExtractor = (item) => {
|
||||
// All keys are strings (either post IDs or special keys)
|
||||
return item;
|
||||
if (item instanceof Date) {
|
||||
return item.getTime();
|
||||
}
|
||||
if (item === General.START_OF_NEW_MESSAGES || item === LOAD_MORE_POSTS) {
|
||||
return item;
|
||||
}
|
||||
|
||||
return item.id;
|
||||
};
|
||||
|
||||
loadMorePosts = () => {
|
||||
const {loadMore, isLoadingMore} = this.props;
|
||||
if (typeof loadMore === 'function' && !isLoadingMore) {
|
||||
loadMore();
|
||||
}
|
||||
};
|
||||
|
||||
onRefresh = () => {
|
||||
@@ -75,26 +88,18 @@ export default class PostList extends PureComponent {
|
||||
}
|
||||
};
|
||||
|
||||
renderItem = ({item, index}) => {
|
||||
if (item === START_OF_NEW_MESSAGES) {
|
||||
renderChannelIntro = () => {
|
||||
const {channelId, navigator, refreshing, showLoadMore} = this.props;
|
||||
|
||||
if (channelId && !showLoadMore && !refreshing) {
|
||||
return (
|
||||
<NewMessagesDivider
|
||||
theme={this.props.theme}
|
||||
/>
|
||||
<View>
|
||||
<ChannelIntro navigator={navigator}/>
|
||||
</View>
|
||||
);
|
||||
} else if (item.indexOf(DATE_LINE) === 0) {
|
||||
const date = item.substring(DATE_LINE.length);
|
||||
return this.renderDateHeader(new Date(date));
|
||||
}
|
||||
|
||||
const postId = item;
|
||||
|
||||
// Remember that the list is rendered with item 0 at the bottom so the "previous" post
|
||||
// comes after this one in the list
|
||||
const previousPostId = index < this.props.postIds.length - 1 ? this.props.postIds[index + 1] : null;
|
||||
const nextPostId = index > 0 ? this.props.postIds[index - 1] : null;
|
||||
|
||||
return this.renderPost(postId, previousPostId, nextPostId);
|
||||
return null;
|
||||
};
|
||||
|
||||
renderDateHeader = (date) => {
|
||||
@@ -106,9 +111,35 @@ export default class PostList extends PureComponent {
|
||||
);
|
||||
};
|
||||
|
||||
renderPost = (postId, previousPostId, nextPostId) => {
|
||||
renderItem = ({item}) => {
|
||||
if (item instanceof Date) {
|
||||
return this.renderDateHeader(item);
|
||||
}
|
||||
if (item === General.START_OF_NEW_MESSAGES) {
|
||||
return (
|
||||
<NewMessagesDivider
|
||||
theme={this.props.theme}
|
||||
/>
|
||||
);
|
||||
}
|
||||
if (item === LOAD_MORE_POSTS && this.props.showLoadMore) {
|
||||
return (
|
||||
<LoadMorePosts
|
||||
loading={this.props.isLoadingMore}
|
||||
theme={this.props.theme}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return this.renderPost(item);
|
||||
};
|
||||
|
||||
getItem = (data, index) => data[index];
|
||||
|
||||
getItemCount = (data) => data.length;
|
||||
|
||||
renderPost = (post) => {
|
||||
const {
|
||||
highlightPostId,
|
||||
isSearchResult,
|
||||
navigator,
|
||||
onPostPress,
|
||||
@@ -118,45 +149,24 @@ export default class PostList extends PureComponent {
|
||||
|
||||
return (
|
||||
<Post
|
||||
postId={postId}
|
||||
previousPostId={previousPostId}
|
||||
nextPostId={nextPostId}
|
||||
highlight={highlightPostId && highlightPostId === postId}
|
||||
post={post}
|
||||
renderReplies={renderReplies}
|
||||
isFirstReply={post.isFirstReply}
|
||||
isLastReply={post.isLastReply}
|
||||
isSearchResult={isSearchResult}
|
||||
shouldRenderReplyButton={shouldRenderReplyButton}
|
||||
commentedOnPost={post.commentedOnPost}
|
||||
onPress={onPostPress}
|
||||
navigator={navigator}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
renderFooter = () => {
|
||||
if (this.props.showLoadMore) {
|
||||
return <LoadMorePosts theme={this.props.theme}/>;
|
||||
} else if (this.props.channelId) {
|
||||
// FIXME: Only show the channel intro when we are at the very start of the channel
|
||||
return (
|
||||
<View>
|
||||
<ChannelIntro navigator={this.props.navigator}/>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
render() {
|
||||
const {
|
||||
channelId,
|
||||
highlightPostId,
|
||||
loadMore,
|
||||
postIds,
|
||||
theme
|
||||
} = this.props;
|
||||
const {channelId, refreshing, theme} = this.props;
|
||||
|
||||
const refreshControl = {
|
||||
refreshing: false
|
||||
refreshing
|
||||
};
|
||||
|
||||
if (channelId) {
|
||||
@@ -165,14 +175,12 @@ export default class PostList extends PureComponent {
|
||||
|
||||
return (
|
||||
<FlatList
|
||||
ref='list'
|
||||
data={postIds}
|
||||
extraData={highlightPostId}
|
||||
initialNumToRender={15}
|
||||
data={this.getPostsWithDates()}
|
||||
initialNumToRender={20}
|
||||
inverted={true}
|
||||
keyExtractor={this.keyExtractor}
|
||||
ListFooterComponent={this.renderFooter}
|
||||
onEndReached={loadMore}
|
||||
ListFooterComponent={this.renderChannelIntro}
|
||||
onEndReached={this.loadMorePosts}
|
||||
onEndReachedThreshold={0}
|
||||
{...refreshControl}
|
||||
renderItem={this.renderItem}
|
||||
|
||||
@@ -6,7 +6,7 @@ import {connect} from 'react-redux';
|
||||
import {getPost} from 'mattermost-redux/selectors/entities/posts';
|
||||
import {isSystemMessage} from 'mattermost-redux/utils/post_utils';
|
||||
|
||||
import {getTheme} from 'mattermost-redux/selectors/entities/preferences';
|
||||
import {getTheme} from 'app/selectors/preferences';
|
||||
|
||||
import PostProfilePicture from './post_profile_picture';
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import {Image, TouchableOpacity, View} from 'react-native';
|
||||
|
||||
import AppIcon from 'app/components/app_icon';
|
||||
import MattermostIcon from 'app/components/mattermost_icon';
|
||||
import ProfilePicture from 'app/components/profile_picture';
|
||||
import {emptyFunction} from 'app/utils/general';
|
||||
import webhookIcon from 'assets/images/icons/webhook.jpg';
|
||||
@@ -25,7 +25,7 @@ function PostProfilePicture(props) {
|
||||
if (isSystemMessage) {
|
||||
return (
|
||||
<View>
|
||||
<AppIcon
|
||||
<MattermostIcon
|
||||
color={theme.centerChannelColor}
|
||||
height={PROFILE_PICTURE_SIZE}
|
||||
width={PROFILE_PICTURE_SIZE}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import React, {PureComponent} from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import {injectIntl, intlShape} from 'react-intl';
|
||||
import {
|
||||
Platform,
|
||||
StyleSheet,
|
||||
@@ -11,38 +10,21 @@ import ImagePicker from 'react-native-image-picker';
|
||||
|
||||
import {changeOpacity} from 'app/utils/theme';
|
||||
|
||||
class AttachmentButton extends PureComponent {
|
||||
export default class AttachmentButton extends PureComponent {
|
||||
static propTypes = {
|
||||
blurTextBox: PropTypes.func.isRequired,
|
||||
intl: intlShape.isRequired,
|
||||
navigator: PropTypes.object.isRequired,
|
||||
theme: PropTypes.object.isRequired,
|
||||
uploadFiles: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
attachFileFromCamera = () => {
|
||||
const {formatMessage} = this.props.intl;
|
||||
const options = {
|
||||
quality: 0.7,
|
||||
noData: true,
|
||||
storageOptions: {
|
||||
cameraRoll: true,
|
||||
waitUntilSaved: true
|
||||
},
|
||||
permissionDenied: {
|
||||
title: formatMessage({
|
||||
id: 'mobile.android.camera_permission_denied_title',
|
||||
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.'
|
||||
}),
|
||||
reTryTitle: formatMessage({
|
||||
id: 'mobile.android.permission_denied_retry',
|
||||
defaultMessage: 'Set Permission'
|
||||
}),
|
||||
okTitle: formatMessage({id: 'mobile.android.permission_denied_dismiss', defaultMessage: 'Dismiss'})
|
||||
}
|
||||
};
|
||||
|
||||
@@ -56,25 +38,9 @@ class AttachmentButton extends PureComponent {
|
||||
};
|
||||
|
||||
attachFileFromLibrary = () => {
|
||||
const {formatMessage} = this.props.intl;
|
||||
const options = {
|
||||
quality: 0.7,
|
||||
noData: true,
|
||||
permissionDenied: {
|
||||
title: formatMessage({
|
||||
id: 'mobile.android.photos_permission_denied_title',
|
||||
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.'
|
||||
}),
|
||||
reTryTitle: formatMessage({
|
||||
id: 'mobile.android.permission_denied_retry',
|
||||
defaultMessage: 'Set Permission'
|
||||
}),
|
||||
okTitle: formatMessage({id: 'mobile.android.permission_denied_dismiss', defaultMessage: 'Dismiss'})
|
||||
}
|
||||
noData: true
|
||||
};
|
||||
|
||||
if (Platform.OS === 'ios') {
|
||||
@@ -91,26 +57,10 @@ class AttachmentButton extends PureComponent {
|
||||
};
|
||||
|
||||
attachVideoFromLibraryAndroid = () => {
|
||||
const {formatMessage} = this.props.intl;
|
||||
const options = {
|
||||
quality: 0.7,
|
||||
mediaType: 'video',
|
||||
noData: true,
|
||||
permissionDenied: {
|
||||
title: formatMessage({
|
||||
id: 'mobile.android.videos_permission_denied_title',
|
||||
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.'
|
||||
}),
|
||||
reTryTitle: formatMessage({
|
||||
id: 'mobile.android.permission_denied_retry',
|
||||
defaultMessage: 'Set Permission'
|
||||
}),
|
||||
okTitle: formatMessage({id: 'mobile.android.permission_denied_dismiss', defaultMessage: 'Dismiss'})
|
||||
}
|
||||
noData: true
|
||||
};
|
||||
|
||||
ImagePicker.launchImageLibrary(options, (response) => {
|
||||
@@ -120,7 +70,7 @@ class AttachmentButton extends PureComponent {
|
||||
|
||||
this.uploadFiles([response]);
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
uploadFiles = (images) => {
|
||||
this.props.uploadFiles(images);
|
||||
@@ -225,5 +175,3 @@ const style = StyleSheet.create({
|
||||
justifyContent: 'center'
|
||||
}
|
||||
});
|
||||
|
||||
export default injectIntl(AttachmentButton);
|
||||
|
||||
@@ -5,7 +5,7 @@ import {connect} from 'react-redux';
|
||||
|
||||
import {getUsersTyping} from 'mattermost-redux/selectors/entities/typing';
|
||||
|
||||
import {getTheme} from 'mattermost-redux/selectors/entities/preferences';
|
||||
import {getTheme} from 'app/selectors/preferences';
|
||||
|
||||
import Typing from './typing';
|
||||
|
||||
|
||||
@@ -6,15 +6,14 @@ import {connect} from 'react-redux';
|
||||
|
||||
import {createPost} from 'mattermost-redux/actions/posts';
|
||||
import {userTyping} from 'mattermost-redux/actions/websocket';
|
||||
import {getCurrentChannelId} from 'mattermost-redux/selectors/entities/channels';
|
||||
import {canUploadFilesOnMobile} from 'mattermost-redux/selectors/entities/general';
|
||||
import {getCurrentUserId} from 'mattermost-redux/selectors/entities/users';
|
||||
|
||||
import {addReactionToLatestPost} from 'app/actions/views/emoji';
|
||||
import {handlePostDraftChanged, handlePostDraftSelectionChanged} from 'app/actions/views/channel';
|
||||
import {handlePostDraftChanged} from 'app/actions/views/channel';
|
||||
import {handleClearFiles, handleRemoveLastFile, handleUploadFiles} from 'app/actions/views/file_upload';
|
||||
import {handleCommentDraftChanged, handleCommentDraftSelectionChanged} from 'app/actions/views/thread';
|
||||
import {getTheme} from 'mattermost-redux/selectors/entities/preferences';
|
||||
import {handleCommentDraftChanged} from 'app/actions/views/thread';
|
||||
import {getTheme} from 'app/selectors/preferences';
|
||||
import {getCurrentChannelDraft, getThreadDraft} from 'app/selectors/views';
|
||||
|
||||
import PostTextbox from './post_textbox';
|
||||
@@ -23,7 +22,6 @@ function mapStateToProps(state, ownProps) {
|
||||
const currentDraft = ownProps.rootId ? getThreadDraft(state, ownProps.rootId) : getCurrentChannelDraft(state);
|
||||
|
||||
return {
|
||||
channelId: getCurrentChannelId(state),
|
||||
canUploadFiles: canUploadFilesOnMobile(state),
|
||||
channelIsLoading: state.views.channel.loading,
|
||||
currentUserId: getCurrentUserId(state),
|
||||
@@ -44,9 +42,7 @@ function mapDispatchToProps(dispatch) {
|
||||
handlePostDraftChanged,
|
||||
handleRemoveLastFile,
|
||||
handleUploadFiles,
|
||||
userTyping,
|
||||
handlePostDraftSelectionChanged,
|
||||
handleCommentDraftSelectionChanged
|
||||
userTyping
|
||||
}, dispatch)
|
||||
};
|
||||
}
|
||||
|
||||
@@ -39,9 +39,7 @@ class PostTextbox extends PureComponent {
|
||||
handleClearFiles: PropTypes.func.isRequired,
|
||||
handleRemoveLastFile: PropTypes.func.isRequired,
|
||||
handleUploadFiles: PropTypes.func.isRequired,
|
||||
userTyping: PropTypes.func.isRequired,
|
||||
handlePostDraftSelectionChanged: PropTypes.func.isRequired,
|
||||
handleCommentDraftSelectionChanged: PropTypes.func.isRequired
|
||||
userTyping: PropTypes.func.isRequired
|
||||
}).isRequired,
|
||||
canUploadFiles: PropTypes.bool.isRequired,
|
||||
channelId: PropTypes.string.isRequired,
|
||||
@@ -58,14 +56,14 @@ class PostTextbox extends PureComponent {
|
||||
|
||||
static defaultProps = {
|
||||
files: [],
|
||||
onSelectionChange: () => true,
|
||||
rootId: '',
|
||||
value: ''
|
||||
};
|
||||
|
||||
state = {
|
||||
contentHeight: INITIAL_HEIGHT,
|
||||
inputWidth: null,
|
||||
keyboardType: 'default'
|
||||
inputWidth: null
|
||||
};
|
||||
|
||||
componentDidMount() {
|
||||
@@ -190,19 +188,9 @@ class PostTextbox extends PureComponent {
|
||||
}
|
||||
|
||||
// Shrink the input textbox since the layout events lag slightly
|
||||
const nextState = {
|
||||
this.setState({
|
||||
contentHeight: INITIAL_HEIGHT
|
||||
};
|
||||
|
||||
// Fixes the issue where Android predictive text would prepend suggestions to the post draft when messages
|
||||
// are typed successively without blurring the input
|
||||
let callback;
|
||||
if (Platform.OS === 'android') {
|
||||
nextState.keyboardType = 'email-address';
|
||||
callback = () => this.setState({keyboardType: 'default'});
|
||||
}
|
||||
|
||||
this.setState(nextState, callback);
|
||||
});
|
||||
};
|
||||
|
||||
handleUploadFiles = (images) => {
|
||||
@@ -240,6 +228,12 @@ class PostTextbox extends PureComponent {
|
||||
actions.userTyping(channelId, rootId);
|
||||
};
|
||||
|
||||
handleSelectionChange = (event) => {
|
||||
if (this.autocomplete) {
|
||||
this.autocomplete.handleSelectionChange(event);
|
||||
}
|
||||
};
|
||||
|
||||
handleContentSizeChange = (event) => {
|
||||
let contentHeight = event.nativeEvent.layout.height;
|
||||
if (contentHeight < INITIAL_HEIGHT) {
|
||||
@@ -314,17 +308,6 @@ class PostTextbox extends PureComponent {
|
||||
return null;
|
||||
}
|
||||
|
||||
handlePostDraftSelectionChanged = (event) => {
|
||||
const cursorPosition = event.nativeEvent.selection.end;
|
||||
if (this.props.rootId) {
|
||||
this.props.actions.handleCommentDraftSelectionChanged(this.props.rootId, cursorPosition);
|
||||
} else {
|
||||
this.props.actions.handlePostDraftSelectionChanged(this.props.channelId, cursorPosition);
|
||||
}
|
||||
|
||||
this.autocomplete.handleSelectionChange(event);
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
canUploadFiles,
|
||||
@@ -388,7 +371,7 @@ class PostTextbox extends PureComponent {
|
||||
ref='input'
|
||||
value={textValue}
|
||||
onChangeText={this.handleTextChange}
|
||||
onSelectionChange={this.handlePostDraftSelectionChanged}
|
||||
onSelectionChange={this.handleSelectionChange}
|
||||
placeholder={intl.formatMessage(placeholder)}
|
||||
placeholderTextColor={changeOpacity('#000', 0.5)}
|
||||
multiline={true}
|
||||
@@ -398,7 +381,6 @@ class PostTextbox extends PureComponent {
|
||||
style={[style.input, {height: textInputHeight}]}
|
||||
onSubmitEditing={this.handleSubmit}
|
||||
onLayout={this.handleInputSizeChange}
|
||||
keyboardType={this.state.keyboardType}
|
||||
/>
|
||||
{this.renderSendButton()}
|
||||
</View>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
import {bindActionCreators} from 'redux';
|
||||
import {connect} from 'react-redux';
|
||||
|
||||
import {getTheme} from 'mattermost-redux/selectors/entities/preferences';
|
||||
import {getTheme} from 'app/selectors/preferences';
|
||||
import {getStatusesByIdsBatchedDebounced} from 'mattermost-redux/actions/users';
|
||||
import {getStatusForUserId, getUser} from 'mattermost-redux/selectors/entities/users';
|
||||
|
||||
@@ -20,7 +20,8 @@ function mapStateToProps(state, ownProps) {
|
||||
return {
|
||||
theme: ownProps.theme || getTheme(state),
|
||||
status,
|
||||
user
|
||||
user,
|
||||
...ownProps
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ import PropTypes from 'prop-types';
|
||||
import {Animated, Text, View} from 'react-native';
|
||||
import Icon from 'react-native-vector-icons/MaterialIcons';
|
||||
|
||||
import {getTheme} from 'mattermost-redux/selectors/entities/preferences';
|
||||
import {getTheme} from 'app/selectors/preferences';
|
||||
import {makeStyleSheetFromTheme} from 'app/utils/theme';
|
||||
|
||||
const DISABLED_OPACITY = 0.26;
|
||||
|
||||
@@ -8,7 +8,7 @@ import {addReaction, getReactionsForPost, removeReaction} from 'mattermost-redux
|
||||
import {makeGetReactionsForPost} from 'mattermost-redux/selectors/entities/posts';
|
||||
import {getCurrentUserId} from 'mattermost-redux/selectors/entities/users';
|
||||
|
||||
import {getTheme} from 'mattermost-redux/selectors/entities/preferences';
|
||||
import {getTheme} from 'app/selectors/preferences';
|
||||
|
||||
import Reactions from './reactions';
|
||||
|
||||
|
||||
@@ -1,23 +0,0 @@
|
||||
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
import {connect} from 'react-redux';
|
||||
|
||||
import {getConnection} from 'app/selectors/device';
|
||||
|
||||
import RefreshList from './refresh_list';
|
||||
|
||||
function mapStateToProps(state) {
|
||||
const networkOnline = getConnection(state);
|
||||
let {refreshing} = state.views.channel;
|
||||
|
||||
if (!networkOnline) {
|
||||
refreshing = false;
|
||||
}
|
||||
|
||||
return {
|
||||
refreshing
|
||||
};
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps)(RefreshList);
|
||||
@@ -1,19 +0,0 @@
|
||||
// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
import React, {PureComponent} from 'react';
|
||||
import {RefreshControl} from 'react-native';
|
||||
|
||||
export default class RefreshList extends PureComponent {
|
||||
static propTypes = {
|
||||
...RefreshControl.propTypes
|
||||
};
|
||||
|
||||
render() {
|
||||
return (
|
||||
<RefreshControl
|
||||
{...this.props}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
import {connect} from 'react-redux';
|
||||
|
||||
import {RequestStatus} from 'mattermost-redux/constants';
|
||||
import {getConnection} from 'app/selectors/device';
|
||||
|
||||
import RetryBarIndicator from './retry_bar_indicator';
|
||||
|
||||
function mapStateToProps(state) {
|
||||
const {websocket: websocketRequest} = state.requests.general;
|
||||
const networkOnline = getConnection(state);
|
||||
const webSocketOnline = websocketRequest.status === RequestStatus.SUCCESS;
|
||||
|
||||
let failed = state.views.channel.retryFailed && webSocketOnline;
|
||||
if (!networkOnline) {
|
||||
failed = false;
|
||||
}
|
||||
|
||||
return {
|
||||
failed
|
||||
};
|
||||
}
|
||||
export default connect(mapStateToProps)(RetryBarIndicator);
|
||||
@@ -1,65 +0,0 @@
|
||||
// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
import React, {PureComponent} from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import {
|
||||
Animated,
|
||||
StyleSheet
|
||||
} from 'react-native';
|
||||
import FormattedText from 'app/components/formatted_text';
|
||||
|
||||
const {View: AnimatedView} = Animated;
|
||||
|
||||
export default class RetryBarIndicator extends PureComponent {
|
||||
static propTypes = {
|
||||
failed: PropTypes.bool
|
||||
};
|
||||
|
||||
state = {
|
||||
retryMessageHeight: new Animated.Value(0)
|
||||
};
|
||||
|
||||
componentWillReceiveProps(nextProps) {
|
||||
if (this.props.failed !== nextProps.failed) {
|
||||
this.toggleRetryMessage(nextProps.failed);
|
||||
}
|
||||
}
|
||||
|
||||
toggleRetryMessage = (show = true) => {
|
||||
const value = show ? 38 : 0;
|
||||
Animated.timing(this.state.retryMessageHeight, {
|
||||
toValue: value,
|
||||
duration: 350
|
||||
}).start();
|
||||
};
|
||||
|
||||
render() {
|
||||
const {retryMessageHeight} = this.state;
|
||||
const refreshIndicatorDimensions = {
|
||||
height: retryMessageHeight
|
||||
};
|
||||
|
||||
return (
|
||||
<AnimatedView style={[style.refreshIndicator, refreshIndicatorDimensions]}>
|
||||
<FormattedText
|
||||
id='mobile.retry_message'
|
||||
defaultMessage='Refreshing messages failed. Pull up to try again.'
|
||||
style={{color: 'white', flex: 1, fontSize: 12}}
|
||||
/>
|
||||
</AnimatedView>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const style = StyleSheet.create({
|
||||
refreshIndicator: {
|
||||
alignItems: 'center',
|
||||
backgroundColor: '#fb8000',
|
||||
flexDirection: 'row',
|
||||
paddingHorizontal: 10,
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
overflow: 'hidden',
|
||||
width: '100%'
|
||||
}
|
||||
});
|
||||
@@ -8,7 +8,7 @@ import {getCurrentChannelId} from 'mattermost-redux/selectors/entities/channels'
|
||||
import {getCurrentUrl} from 'mattermost-redux/selectors/entities/general';
|
||||
|
||||
import {getCurrentLocale} from 'app/selectors/i18n';
|
||||
import {getTheme} from 'mattermost-redux/selectors/entities/preferences';
|
||||
import {getTheme} from 'app/selectors/preferences';
|
||||
import {removeProtocol} from 'app/utils/url';
|
||||
|
||||
import Root from './root';
|
||||
|
||||
@@ -49,8 +49,8 @@ export default class SearchBarAndroid extends PureComponent {
|
||||
|
||||
static defaultProps = {
|
||||
backArrowSize: 24,
|
||||
deleteIconSize: 20,
|
||||
searchIconSize: 24,
|
||||
deleteIconSize: 16,
|
||||
searchIconSize: 16,
|
||||
blurOnSubmit: true,
|
||||
placeholder: 'Search',
|
||||
showCancelButton: true,
|
||||
@@ -99,10 +99,6 @@ export default class SearchBarAndroid extends PureComponent {
|
||||
});
|
||||
};
|
||||
|
||||
onClearPress = () => {
|
||||
this.onChangeText('');
|
||||
};
|
||||
|
||||
onChangeText = (value) => {
|
||||
this.props.onChangeText(value);
|
||||
};
|
||||
@@ -174,7 +170,7 @@ export default class SearchBarAndroid extends PureComponent {
|
||||
{
|
||||
backgroundColor: inputColor,
|
||||
height: inputHeight,
|
||||
paddingLeft: 7
|
||||
paddingLeft: isFocused ? 0 : inputHeight * 0.25
|
||||
}
|
||||
]}
|
||||
>
|
||||
@@ -216,14 +212,13 @@ export default class SearchBarAndroid extends PureComponent {
|
||||
style={[
|
||||
styles.searchBarInput,
|
||||
inputNoBackground,
|
||||
{height: this.props.inputHeight},
|
||||
isFocused ? {} : styles.searchBarBlurredInput
|
||||
{height: this.props.inputHeight}
|
||||
]}
|
||||
/>
|
||||
{isFocused && value ?
|
||||
<TouchableWithoutFeedback onPress={this.onClearPress}>
|
||||
<TouchableWithoutFeedback onPress={() => this.onChangeText('')}>
|
||||
<Icon
|
||||
style={[{paddingRight: 7}]}
|
||||
style={[{paddingRight: (inputHeight * 0.2)}]}
|
||||
name='close'
|
||||
size={deleteIconSize}
|
||||
color={tintColorDelete || placeholderTextColor}
|
||||
@@ -241,7 +236,8 @@ const styles = StyleSheet.create({
|
||||
backgroundColor: 'grey',
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'flex-start',
|
||||
alignItems: 'center'
|
||||
alignItems: 'center',
|
||||
padding: 5
|
||||
},
|
||||
searchBar: {
|
||||
flex: 1,
|
||||
@@ -252,10 +248,7 @@ const styles = StyleSheet.create({
|
||||
flex: 1,
|
||||
fontWeight: 'normal',
|
||||
textAlignVertical: 'center',
|
||||
fontSize: 15,
|
||||
padding: 0,
|
||||
includeFontPadding: false
|
||||
},
|
||||
searchBarBlurredInput: {
|
||||
padding: 0
|
||||
}
|
||||
});
|
||||
|
||||
@@ -101,10 +101,10 @@ export default class SearchBarIos extends PureComponent {
|
||||
<Search
|
||||
{...this.props}
|
||||
ref='search'
|
||||
placeholderCollapsedMargin={33}
|
||||
placeholderExpandedMargin={33}
|
||||
searchIconCollapsedMargin={10}
|
||||
searchIconExpandedMargin={10}
|
||||
placeholderCollapsedMargin={25}
|
||||
placeholderExpandedMargin={25}
|
||||
searchIconCollapsedMargin={15}
|
||||
searchIconExpandedMargin={15}
|
||||
shadowVisible={false}
|
||||
onCancel={this.onCancel}
|
||||
onChangeText={this.onChangeText}
|
||||
|
||||
@@ -15,12 +15,10 @@ import {
|
||||
StyleSheet,
|
||||
View
|
||||
} from 'react-native';
|
||||
import EvilIcon from 'react-native-vector-icons/EvilIcons';
|
||||
import IonIcon from 'react-native-vector-icons/Ionicons';
|
||||
|
||||
const AnimatedTextInput = Animated.createAnimatedComponent(TextInput);
|
||||
const AnimatedIonIcon = Animated.createAnimatedComponent(IonIcon);
|
||||
const AnimatedEvilcon = Animated.createAnimatedComponent(EvilIcon);
|
||||
const AnimatedIcon = Animated.createAnimatedComponent(IonIcon);
|
||||
const containerHeight = 40;
|
||||
const middleHeight = 20;
|
||||
|
||||
@@ -330,6 +328,11 @@ export default class Search extends Component {
|
||||
};
|
||||
|
||||
render() {
|
||||
let iconSize = 16;
|
||||
if (this.props.inputStyle && this.props.inputStyle.fontSize) {
|
||||
iconSize = this.props.inputStyle.fontSize + 2;
|
||||
}
|
||||
|
||||
return (
|
||||
<Animated.View
|
||||
ref='searchContainer'
|
||||
@@ -388,16 +391,16 @@ export default class Search extends Component {
|
||||
>
|
||||
{this.props.iconSearch}
|
||||
</Animated.View> :
|
||||
<AnimatedEvilcon
|
||||
name='search'
|
||||
size={24}
|
||||
<AnimatedIcon
|
||||
name='ios-search-outline'
|
||||
size={iconSize}
|
||||
style={[
|
||||
styles.iconSearch,
|
||||
styles.iconSearchDefault,
|
||||
this.props.tintColorSearch && {color: this.props.tintColorSearch},
|
||||
{
|
||||
left: this.iconSearchAnimated,
|
||||
top: middleHeight - 10
|
||||
top: middleHeight - (iconSize / 2)
|
||||
}
|
||||
]}
|
||||
/>
|
||||
@@ -414,10 +417,10 @@ export default class Search extends Component {
|
||||
>
|
||||
{this.props.iconDelete}
|
||||
</Animated.View> :
|
||||
<View style={[styles.iconDelete, this.props.inputHeight && {height: this.props.inputHeight}]}>
|
||||
<AnimatedIonIcon
|
||||
<View style={[styles.iconDelete, this.props.inputHeight && {height: this.props.inputHeight, width: iconSize + 5}]}>
|
||||
<AnimatedIcon
|
||||
name='ios-close-circle'
|
||||
size={17}
|
||||
size={iconSize}
|
||||
style={[
|
||||
styles.iconDeleteDefault,
|
||||
this.props.tintColorDelete && {color: this.props.tintColorDelete},
|
||||
@@ -465,13 +468,13 @@ const styles = StyleSheet.create({
|
||||
},
|
||||
input: {
|
||||
height: containerHeight - 10,
|
||||
paddingTop: 7,
|
||||
paddingTop: 5,
|
||||
paddingBottom: 5,
|
||||
paddingRight: 32,
|
||||
paddingRight: 20,
|
||||
borderColor: '#444',
|
||||
backgroundColor: '#f7f7f7',
|
||||
borderRadius: 5,
|
||||
fontSize: 15
|
||||
fontSize: 13
|
||||
},
|
||||
iconSearch: {
|
||||
flex: 1,
|
||||
@@ -481,13 +484,10 @@ const styles = StyleSheet.create({
|
||||
color: 'grey'
|
||||
},
|
||||
iconDelete: {
|
||||
alignItems: 'flex-start',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
position: 'absolute',
|
||||
paddingLeft: 1,
|
||||
paddingTop: 3,
|
||||
right: 65,
|
||||
width: 25
|
||||
right: 70
|
||||
},
|
||||
iconDeleteDefault: {
|
||||
color: 'grey'
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user