RN-97 Changed navigation from NavigationExperimental to ReactNative Navigation (#540)
* Add react-navigator dependency * Add react-native-navigation dependency * react-native-navigation, ios and android config * update react-native-navigation * New navigation and code cleanup * Set ScreenBackgroundColor * Fix channel drawer event issue * Applied RNPN non-working popInitial notification * Android navbar and back button * packages and notices * MM-redux update * Fix draft on team switch * Remove custom NavBar
This commit is contained in:
parent
74e93cae68
commit
248dafbe0e
135 changed files with 2824 additions and 4656 deletions
215
NOTICE.txt
215
NOTICE.txt
|
|
@ -8,49 +8,6 @@ This document includes a list of open source components used in Mattermost Mobil
|
|||
|
||||
--------
|
||||
|
||||
## fbjs
|
||||
|
||||
This product contains a modified portion of 'fbjs', a collection of JavaScript utilities by Facebook, Inc.
|
||||
|
||||
* HOMEPAGE:
|
||||
* https://github.com/facebook/fbjs
|
||||
|
||||
* LICENSE:
|
||||
|
||||
BSD License
|
||||
|
||||
For fbjs software
|
||||
|
||||
Copyright (c) 2013-present, Facebook, Inc.
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification,
|
||||
are permitted provided that the following conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
* Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
|
||||
* Neither the name Facebook nor the names of its contributors may be used to
|
||||
endorse or promote products derived from this software without specific
|
||||
prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
|
||||
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
||||
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
---
|
||||
|
||||
## react-native-search-bar
|
||||
|
||||
This product contains a modified portion of 'react-native-search-bar', a high-quality iOS native search bar for react native by Zhao Han.
|
||||
|
|
@ -117,49 +74,6 @@ SOFTWARE.
|
|||
|
||||
---
|
||||
|
||||
## harmony-reflect
|
||||
|
||||
[Note: the software referenced below is made available under two licenses. Mattermost, Inc. has elected to license the software pursuant to the Apache License, Version 2.0.]
|
||||
|
||||
This product contains 'harmony-reflect', a shim for ECMAScript 6 Reflect and Proxy objects.
|
||||
|
||||
* HOMEPAGE:
|
||||
* https://github.com/tvcutsem/harmony-reflect
|
||||
|
||||
* LICENSE:
|
||||
|
||||
Copyright (C) 2011-2014 Software Languages Lab, Vrije Universiteit Brussel
|
||||
Copyright (C) 2015 Tom Van Cutsem
|
||||
|
||||
This code is dual-licensed under both the Apache License and the MPL
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
Version: MPL 1.1
|
||||
|
||||
The contents of this file are subject to the Mozilla Public License Version
|
||||
1.1 (the "License"); you may not use this file except in compliance with
|
||||
the License. You may obtain a copy of the License at
|
||||
|
||||
http://www.mozilla.org/MPL/
|
||||
|
||||
Software distributed under the License is distributed on an "AS IS" basis,
|
||||
WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
|
||||
for the specific language governing rights and limitations under the
|
||||
License.
|
||||
|
||||
---
|
||||
|
||||
## intl
|
||||
|
||||
This product contains 'Intl.js', to bring long overdue localization methods to ECMAScript implementations by Andy Earnshaw.
|
||||
|
|
@ -237,39 +151,6 @@ the property of their respective owners.
|
|||
|
||||
---
|
||||
|
||||
## isomorphic-fetch
|
||||
|
||||
This product contains 'isomorphic-fetch', to provide the fetch function for node by Matt Andrews.
|
||||
|
||||
* HOMEPAGE:
|
||||
* https://github.com/matthew-andrews/isomorphic-fetch
|
||||
|
||||
* LICENSE:
|
||||
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2015 Matt Andrews
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
---
|
||||
|
||||
## react
|
||||
|
||||
This product contains 'react', a declarative, efficient, and
|
||||
|
|
@ -769,39 +650,6 @@ SOFTWARE.
|
|||
|
||||
---
|
||||
|
||||
## react-native-message-bar
|
||||
|
||||
This product contains 'react-native-message-bar', a library for displaying a message bar notification component at the top of the screen for React Native (Android and iOS) projects.
|
||||
|
||||
* HOMEPAGE
|
||||
* https://github.com/KBLNY/react-native-message-bar
|
||||
|
||||
* LICENSE:
|
||||
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2016 KBLNY
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
---
|
||||
|
||||
## react-native-device-info
|
||||
|
||||
This product contains 'react-native-device-info', library to provide a device information to react native on both iOS and Android.
|
||||
|
|
@ -889,37 +737,6 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI
|
|||
|
||||
---
|
||||
|
||||
## redux-action-buffer
|
||||
|
||||
A middleware for redux that buffers all actions into a queue until a breaker condition is met, at which point the queue is released (i.e. actions are triggered).
|
||||
|
||||
* HOMEPAGE
|
||||
* https://github.com/rt2zz/redux-action-buffer
|
||||
|
||||
* LICENSE
|
||||
|
||||
The MIT License (MIT)
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
---
|
||||
|
||||
## redux-persist
|
||||
|
||||
Persist and rehydrate a redux store.
|
||||
|
|
@ -1024,3 +841,35 @@ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
---
|
||||
|
||||
## react-native-navigation
|
||||
|
||||
This product contains 'react-native-navigation', App-wide support for 100% native navigation with an easy cross-platform interface.
|
||||
|
||||
* HOMEPAGE:
|
||||
* https://github.com/wix/react-native-navigation
|
||||
|
||||
* LICENSE:
|
||||
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) Wix.com
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal in
|
||||
the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
|
|
|||
|
|
@ -84,8 +84,8 @@ def enableSeparateBuildPerCPUArchitecture = false
|
|||
def enableProguardInReleaseBuilds = false
|
||||
|
||||
android {
|
||||
compileSdkVersion 23
|
||||
buildToolsVersion "23.0.1"
|
||||
compileSdkVersion 25
|
||||
buildToolsVersion "25.0.1"
|
||||
|
||||
defaultConfig {
|
||||
applicationId "com.mattermost.react.native"
|
||||
|
|
@ -144,8 +144,11 @@ android {
|
|||
}
|
||||
|
||||
dependencies {
|
||||
compile fileTree(dir: "libs", include: ["*.jar"])
|
||||
compile "com.android.support:appcompat-v7:25.0.1"
|
||||
compile "com.facebook.react:react-native:+" // From node_modules
|
||||
compile project(':react-native-navigation')
|
||||
compile project(':react-native-image-picker')
|
||||
compile project(':react-native-smart-splashscreen')
|
||||
compile project(':react-native-orientation')
|
||||
compile project(':react-native-bottom-sheet')
|
||||
compile project(':react-native-push-notification')
|
||||
|
|
@ -157,9 +160,6 @@ dependencies {
|
|||
compile project(':react-native-linear-gradient')
|
||||
compile project(':react-native-vector-icons')
|
||||
compile project(':react-native-svg')
|
||||
compile fileTree(dir: "libs", include: ["*.jar"])
|
||||
compile "com.android.support:appcompat-v7:23.0.1"
|
||||
compile "com.facebook.react:react-native:+" // From node_modules
|
||||
}
|
||||
|
||||
// Run this once to be able to run the application with BUCK
|
||||
|
|
|
|||
|
|
@ -34,8 +34,6 @@
|
|||
android:label="@string/app_name"
|
||||
android:windowSoftInputMode="adjustResize"
|
||||
android:configChanges="keyboard|keyboardHidden|orientation|screenSize"
|
||||
android:launchMode="singleInstance"
|
||||
android:taskAffinity=""
|
||||
>
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
|
|
|
|||
|
|
@ -1,49 +1,67 @@
|
|||
package com.mattermost;
|
||||
|
||||
import com.facebook.react.ReactActivity;
|
||||
import com.github.yamill.orientation.OrientationPackage;
|
||||
import com.psykar.cookiemanager.CookieManagerPackage;
|
||||
import com.BV.LinearGradient.LinearGradientPackage;
|
||||
import android.content.Intent;
|
||||
import android.content.res.Configuration;
|
||||
import android.os.Bundle;
|
||||
import com.reactnativecomponent.splashscreen.RCTSplashScreen;
|
||||
|
||||
public class MainActivity extends ReactActivity {
|
||||
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 {
|
||||
|
||||
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
|
||||
// protected String getMainComponentName() {
|
||||
// return "Mattermost";
|
||||
// }
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
RCTSplashScreen.openSplashScreen(this);
|
||||
super.onCreate(savedInstanceState);
|
||||
public LinearLayout createSplashLayout() {
|
||||
wr_activity = new WeakReference<>(this);
|
||||
final int drawableId = getImageId();
|
||||
|
||||
Context context = getActivity();
|
||||
imageView = new ImageView(context);
|
||||
imageView.setImageResource(drawableId);
|
||||
|
||||
LayoutParams layoutParams = new LinearLayout.LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
|
||||
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;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onConfigurationChanged(Configuration newConfig) {
|
||||
super.onConfigurationChanged(newConfig);
|
||||
Intent intent = new Intent("onConfigurationChanged");
|
||||
intent.putExtra("newConfig", newConfig);
|
||||
this.sendBroadcast(intent);
|
||||
}
|
||||
|
||||
/**
|
||||
* When the back button is pressed and the app is closed it will
|
||||
* be sent to the background instead of exiting fixing the newIntent
|
||||
* problem where the splash screen wasn't being removed
|
||||
*/
|
||||
@Override
|
||||
public void invokeDefaultOnBackPressed() {
|
||||
Intent setIntent = new Intent(Intent.ACTION_MAIN);
|
||||
setIntent.addCategory(Intent.CATEGORY_HOME);
|
||||
setIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
startActivity(setIntent);
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,8 +2,15 @@ package com.mattermost;
|
|||
|
||||
import android.app.Application;
|
||||
import android.util.Log;
|
||||
import android.support.annotation.NonNull;
|
||||
|
||||
import com.facebook.react.ReactApplication;
|
||||
import com.facebook.react.ReactInstanceManager;
|
||||
import com.facebook.react.ReactNativeHost;
|
||||
import com.facebook.react.ReactPackage;
|
||||
import com.facebook.react.shell.MainReactPackage;
|
||||
import com.facebook.soloader.SoLoader;
|
||||
|
||||
import com.imagepicker.ImagePickerPackage;
|
||||
import com.gnet.bottomsheet.RNBottomSheetPackage;
|
||||
import com.learnium.RNDeviceInfo.RNDeviceInfo;
|
||||
|
|
@ -11,32 +18,47 @@ import com.psykar.cookiemanager.CookieManagerPackage;
|
|||
import com.dieam.reactnativepushnotification.ReactNativePushNotificationPackage;
|
||||
import com.oblador.vectoricons.VectorIconsPackage;
|
||||
import com.horcrux.svg.SvgPackage;
|
||||
import com.facebook.react.ReactInstanceManager;
|
||||
import com.facebook.react.ReactNativeHost;
|
||||
import com.facebook.react.ReactPackage;
|
||||
import com.facebook.react.shell.MainReactPackage;
|
||||
import com.facebook.soloader.SoLoader;
|
||||
import com.BV.LinearGradient.LinearGradientPackage;
|
||||
import com.reactnativecomponent.splashscreen.RCTSplashScreenPackage;
|
||||
import com.github.yamill.orientation.OrientationPackage;
|
||||
import com.reactnativenavigation.NavigationApplication;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
public class MainApplication extends Application implements ReactApplication {
|
||||
// public class MainApplication extends Application implements ReactApplication {
|
||||
public class MainApplication extends NavigationApplication {
|
||||
|
||||
private final ReactNativeHost mReactNativeHost = new ReactNativeHost(this) {
|
||||
@Override
|
||||
public boolean getUseDeveloperSupport() {
|
||||
return BuildConfig.DEBUG;
|
||||
}
|
||||
// private final ReactNativeHost mReactNativeHost = new ReactNativeHost(this) {
|
||||
// @Override
|
||||
// protected boolean getUseDeveloperSupport() {
|
||||
// return BuildConfig.DEBUG;
|
||||
// }
|
||||
//
|
||||
// @Override
|
||||
// protected List<ReactPackage> getPackages() {
|
||||
// return Arrays.<ReactPackage>asList(
|
||||
// new MainReactPackage()
|
||||
// );
|
||||
// }
|
||||
// };
|
||||
//
|
||||
// @Override
|
||||
// public ReactNativeHost getReactNativeHost() {
|
||||
// return mReactNativeHost;
|
||||
// }
|
||||
|
||||
@Override
|
||||
protected List<ReactPackage> getPackages() {
|
||||
return Arrays.<ReactPackage>asList(
|
||||
new MainReactPackage(),
|
||||
@Override
|
||||
public boolean isDebug() {
|
||||
return BuildConfig.DEBUG;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public List<ReactPackage> createAdditionalReactPackages() {
|
||||
// Add the packages you require here.
|
||||
// No need to add RnnPackage and MainReactPackage
|
||||
return Arrays.<ReactPackage>asList(
|
||||
new ImagePickerPackage(),
|
||||
new RCTSplashScreenPackage(),
|
||||
new RNBottomSheetPackage(),
|
||||
new RNDeviceInfo(),
|
||||
new CookieManagerPackage(),
|
||||
|
|
@ -45,13 +67,7 @@ public class MainApplication extends Application implements ReactApplication {
|
|||
new SvgPackage(),
|
||||
new LinearGradientPackage(),
|
||||
new OrientationPackage()
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
@Override
|
||||
public ReactNativeHost getReactNativeHost() {
|
||||
return mReactNativeHost;
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
rootProject.name = 'Mattermost'
|
||||
include ':react-native-navigation'
|
||||
project(':react-native-navigation').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-navigation/android/app/')
|
||||
include ':react-native-image-picker'
|
||||
project(':react-native-image-picker').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-image-picker/android')
|
||||
include ':react-native-smart-splashscreen'
|
||||
project(':react-native-smart-splashscreen').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-smart-splash-screen/android')
|
||||
include ':react-native-bottom-sheet'
|
||||
project(':react-native-bottom-sheet').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-bottom-sheet/android')
|
||||
include ':react-native-device-info'
|
||||
|
|
|
|||
|
|
@ -1,298 +0,0 @@
|
|||
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
import {NavigationTypes} from 'app/constants';
|
||||
import Routes from 'app/navigation/routes';
|
||||
import {General} from 'mattermost-redux/constants';
|
||||
import {selectPost} from 'mattermost-redux/actions/posts';
|
||||
|
||||
export function closeDrawers() {
|
||||
return async (dispatch, getState) => {
|
||||
dispatch({
|
||||
type: NavigationTypes.NAVIGATION_CLOSE_DRAWERS
|
||||
}, getState);
|
||||
};
|
||||
}
|
||||
|
||||
export function closeModal() {
|
||||
return async (dispatch, getState) => {
|
||||
dispatch({
|
||||
type: NavigationTypes.NAVIGATION_CLOSE_MODAL
|
||||
}, getState);
|
||||
};
|
||||
}
|
||||
|
||||
export function goBack() {
|
||||
return async (dispatch, getState) => {
|
||||
dispatch({
|
||||
type: NavigationTypes.NAVIGATION_POP
|
||||
}, getState);
|
||||
};
|
||||
}
|
||||
|
||||
export function goToAbout() {
|
||||
return async (dispatch, getState) => {
|
||||
dispatch({
|
||||
type: NavigationTypes.NAVIGATION_PUSH,
|
||||
route: Routes.About
|
||||
}, getState);
|
||||
};
|
||||
}
|
||||
|
||||
export function goToAccountNotifications() {
|
||||
return async (dispatch, getState) => {
|
||||
dispatch({
|
||||
type: NavigationTypes.NAVIGATION_PUSH,
|
||||
route: Routes.AccountNotifications
|
||||
}, getState);
|
||||
};
|
||||
}
|
||||
|
||||
export function goToAccountSettings() {
|
||||
return async (dispatch, getState) => {
|
||||
dispatch({
|
||||
type: NavigationTypes.NAVIGATION_PUSH,
|
||||
route: Routes.AccountSettings
|
||||
}, getState);
|
||||
};
|
||||
}
|
||||
|
||||
export function goToChannelAddMembers() {
|
||||
return async (dispatch, getState) => {
|
||||
dispatch({
|
||||
type: NavigationTypes.NAVIGATION_PUSH,
|
||||
route: Routes.ChannelAddMembers
|
||||
}, getState);
|
||||
};
|
||||
}
|
||||
|
||||
export function goToChannelInfo() {
|
||||
return async (dispatch, getState) => {
|
||||
dispatch({
|
||||
type: NavigationTypes.NAVIGATION_PUSH,
|
||||
route: Routes.ChannelInfo
|
||||
}, getState);
|
||||
};
|
||||
}
|
||||
|
||||
export function goToChannelMembers() {
|
||||
return async (dispatch, getState) => {
|
||||
dispatch({
|
||||
type: NavigationTypes.NAVIGATION_PUSH,
|
||||
route: Routes.ChannelMembers
|
||||
}, getState);
|
||||
};
|
||||
}
|
||||
|
||||
export function goToCreateChannel(channelType) {
|
||||
return async (dispatch, getState) => {
|
||||
closeDrawers()(dispatch, getState);
|
||||
let type;
|
||||
let route;
|
||||
switch (channelType) {
|
||||
case General.OPEN_CHANNEL:
|
||||
type = NavigationTypes.NAVIGATION_PUSH;
|
||||
route = Routes.CreatePublicChannel;
|
||||
break;
|
||||
case General.PRIVATE_CHANNEL:
|
||||
type = NavigationTypes.NAVIGATION_MODAL;
|
||||
route = Routes.CreatePrivateChannel;
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
|
||||
dispatch({
|
||||
type,
|
||||
route: {
|
||||
...route,
|
||||
props: {
|
||||
channelType
|
||||
}
|
||||
}
|
||||
}, getState);
|
||||
};
|
||||
}
|
||||
|
||||
export function goToImagePreviewModal(post, fileId) {
|
||||
return async (dispatch, getState) => {
|
||||
dispatch({
|
||||
type: NavigationTypes.NAVIGATION_MODAL,
|
||||
route: {
|
||||
...Routes.ImagePreview,
|
||||
props: {
|
||||
post,
|
||||
fileId
|
||||
}
|
||||
}
|
||||
}, getState);
|
||||
};
|
||||
}
|
||||
|
||||
export function goToLoadTeam() {
|
||||
return async (dispatch, getState) => {
|
||||
dispatch({
|
||||
type: NavigationTypes.NAVIGATION_PUSH,
|
||||
route: Routes.LoadTeam
|
||||
}, getState);
|
||||
};
|
||||
}
|
||||
|
||||
export function goToLogin() {
|
||||
return async (dispatch, getState) => {
|
||||
dispatch({
|
||||
type: NavigationTypes.NAVIGATION_PUSH,
|
||||
route: Routes.Login
|
||||
}, getState);
|
||||
};
|
||||
}
|
||||
|
||||
export function goToLoginOptions() {
|
||||
return async (dispatch, getState) => {
|
||||
dispatch({
|
||||
type: NavigationTypes.NAVIGATION_PUSH,
|
||||
route: Routes.LoginOptions
|
||||
}, getState);
|
||||
};
|
||||
}
|
||||
|
||||
export function goToMfa() {
|
||||
return async (dispatch, getState) => {
|
||||
dispatch({
|
||||
type: NavigationTypes.NAVIGATION_PUSH,
|
||||
route: Routes.Mfa
|
||||
}, getState);
|
||||
};
|
||||
}
|
||||
|
||||
export function goToSaml() {
|
||||
return async (dispatch, getState) => {
|
||||
dispatch({
|
||||
type: NavigationTypes.NAVIGATION_PUSH,
|
||||
route: Routes.Saml
|
||||
}, getState);
|
||||
};
|
||||
}
|
||||
|
||||
export function goToSelectTeam() {
|
||||
return async (dispatch, getState) => {
|
||||
dispatch({
|
||||
type: NavigationTypes.NAVIGATION_PUSH,
|
||||
route: Routes.SelectTeam
|
||||
}, getState);
|
||||
};
|
||||
}
|
||||
|
||||
export function goToThread(channelId, rootId) {
|
||||
return async (dispatch, getState) => {
|
||||
selectPost(rootId)(dispatch, getState);
|
||||
|
||||
dispatch({
|
||||
type: NavigationTypes.NAVIGATION_PUSH,
|
||||
route: {
|
||||
...Routes.Thread,
|
||||
key: Routes.Thread.key + rootId,
|
||||
props: {
|
||||
channelId,
|
||||
rootId
|
||||
}
|
||||
}
|
||||
}, getState);
|
||||
};
|
||||
}
|
||||
|
||||
export function goToUserProfile(userId) {
|
||||
return async (dispatch, getState) => {
|
||||
dispatch({
|
||||
type: NavigationTypes.NAVIGATION_PUSH,
|
||||
route: {
|
||||
...Routes.UserProfile,
|
||||
props: {
|
||||
userId
|
||||
}
|
||||
}
|
||||
}, getState);
|
||||
};
|
||||
}
|
||||
|
||||
export function openChannelDrawer() {
|
||||
return async (dispatch, getState) => {
|
||||
dispatch({
|
||||
type: NavigationTypes.NAVIGATION_OPEN_LEFT_DRAWER,
|
||||
route: Routes.ChannelDrawer
|
||||
}, getState);
|
||||
};
|
||||
}
|
||||
|
||||
export function openEditPostModal(post) {
|
||||
return async (dispatch, getState) => {
|
||||
dispatch({
|
||||
type: NavigationTypes.NAVIGATION_MODAL,
|
||||
route: {
|
||||
...Routes.EditPost,
|
||||
props: {
|
||||
post
|
||||
}
|
||||
}
|
||||
}, getState);
|
||||
};
|
||||
}
|
||||
|
||||
export function openSettingsModal() {
|
||||
return async (dispatch, getState) => {
|
||||
dispatch({
|
||||
type: NavigationTypes.NAVIGATION_MODAL,
|
||||
route: Routes.Settings
|
||||
}, getState);
|
||||
};
|
||||
}
|
||||
|
||||
export function requestCloseModal() {
|
||||
return async (dispatch, getState) => {
|
||||
dispatch({
|
||||
type: NavigationTypes.NAVIGATION_REQUEST_CLOSE_MODAL
|
||||
}, getState);
|
||||
};
|
||||
}
|
||||
|
||||
export function renderDrawer() {
|
||||
return async (dispatch, getState) => {
|
||||
dispatch({type: NavigationTypes.NAVIGATION_RENDER_LEFT_DRAWER, data: true}, getState);
|
||||
};
|
||||
}
|
||||
|
||||
export function unrenderDrawer() {
|
||||
return async (dispatch, getState) => {
|
||||
dispatch({type: NavigationTypes.NAVIGATION_RENDER_LEFT_DRAWER, data: false}, getState);
|
||||
};
|
||||
}
|
||||
|
||||
export function showOptionsModal(options) {
|
||||
return async (dispatch, getState) => {
|
||||
dispatch({
|
||||
type: NavigationTypes.NAVIGATION_MODAL,
|
||||
route: {
|
||||
...Routes.OptionsModal,
|
||||
props: options
|
||||
}
|
||||
}, getState);
|
||||
};
|
||||
}
|
||||
|
||||
export function showMoreChannelsModal() {
|
||||
return async (dispatch, getState) => {
|
||||
dispatch({
|
||||
type: NavigationTypes.NAVIGATION_MODAL,
|
||||
route: Routes.MoreChannels
|
||||
}, getState);
|
||||
};
|
||||
}
|
||||
|
||||
export function showDirectMessagesModal() {
|
||||
return async (dispatch, getState) => {
|
||||
dispatch({
|
||||
type: NavigationTypes.NAVIGATION_MODAL,
|
||||
route: Routes.MoreDirectMessages
|
||||
}, getState);
|
||||
};
|
||||
}
|
||||
|
|
@ -26,6 +26,10 @@ export function handleUpdateUserNotifyProps(notifyProps) {
|
|||
}
|
||||
|
||||
const props = {...otherProps, email};
|
||||
updateMe({notify_props: props})(dispatch, getState);
|
||||
try {
|
||||
await updateMe({notify_props: props})(dispatch, getState);
|
||||
} catch (error) {
|
||||
// do nothing
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -288,19 +288,6 @@ export function closeGMChannel(channel) {
|
|||
};
|
||||
}
|
||||
|
||||
export function closeDirectChannel(channel) {
|
||||
return async (dispatch, getState) => {
|
||||
switch (channel.type) {
|
||||
case General.DM_CHANNEL:
|
||||
return closeDMChannel(channel)(dispatch, getState);
|
||||
case General.GM_CHANNEL:
|
||||
return closeGMChannel(channel)(dispatch, getState);
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
}
|
||||
|
||||
export function markFavorite(channelId) {
|
||||
return async (dispatch, getState) => {
|
||||
const {currentUserId} = getState().entities.users;
|
||||
|
|
|
|||
|
|
@ -1,38 +1,14 @@
|
|||
// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
import {NavigationTypes, ViewTypes} from 'app/constants';
|
||||
import Routes from 'app/navigation/routes';
|
||||
import {ViewTypes} from 'app/constants';
|
||||
|
||||
export function goToChannelView() {
|
||||
export function initialize() {
|
||||
return async (dispatch, getState) => {
|
||||
const state = getState();
|
||||
if (state.views.login.loginId) {
|
||||
setTimeout(() => {
|
||||
dispatch({
|
||||
type: NavigationTypes.NAVIGATION_REPLACE_AT_ROOT,
|
||||
route: Routes.ChannelView
|
||||
type: ViewTypes.APPLICATION_INITIALIZED
|
||||
}, getState);
|
||||
|
||||
dispatch({
|
||||
type: NavigationTypes.NAVIGATION_CLOSE_MODAL
|
||||
}, getState);
|
||||
|
||||
setTimeout(() => {
|
||||
dispatch({
|
||||
type: ViewTypes.APPLICATION_INITIALIZED
|
||||
}, getState);
|
||||
}, 400);
|
||||
} else {
|
||||
dispatch({
|
||||
type: NavigationTypes.NAVIGATION_RESET,
|
||||
routes: [Routes.ChannelView]
|
||||
}, getState);
|
||||
|
||||
setTimeout(() => {
|
||||
dispatch({
|
||||
type: ViewTypes.APPLICATION_INITIALIZED
|
||||
}, getState);
|
||||
}, 400);
|
||||
}
|
||||
}, 400);
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,30 +0,0 @@
|
|||
// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
import {ViewTypes} from 'app/constants';
|
||||
|
||||
export function openModal(title, options) {
|
||||
return async (dispatch, getState) => {
|
||||
dispatch({
|
||||
type: ViewTypes.OPTIONS_MODAL_CHANGED,
|
||||
data: {
|
||||
title,
|
||||
options,
|
||||
visible: true
|
||||
}
|
||||
}, getState);
|
||||
};
|
||||
}
|
||||
|
||||
export function closeModal() {
|
||||
return async (dispatch, getState) => {
|
||||
dispatch({
|
||||
type: ViewTypes.OPTIONS_MODAL_CHANGED,
|
||||
data: {
|
||||
title: '',
|
||||
options: [],
|
||||
visible: false
|
||||
}
|
||||
}, getState);
|
||||
};
|
||||
}
|
||||
|
|
@ -1,39 +0,0 @@
|
|||
// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
import {batchActions} from 'redux-batched-actions';
|
||||
|
||||
import {NavigationTypes} from 'app/constants';
|
||||
import Routes from 'app/navigation/routes';
|
||||
|
||||
export function goToRecentMentions() {
|
||||
return async (dispatch, getState) => {
|
||||
dispatch(batchActions([{
|
||||
type: NavigationTypes.NAVIGATION_CLOSE_DRAWERS
|
||||
}, {
|
||||
type: NavigationTypes.NAVIGATION_PUSH,
|
||||
route: {
|
||||
...Routes.Search,
|
||||
props: {
|
||||
searchType: 'recent_mentions'
|
||||
}
|
||||
}
|
||||
}]), getState);
|
||||
};
|
||||
}
|
||||
|
||||
export function goToFlaggedPosts() {
|
||||
return async (dispatch, getState) => {
|
||||
dispatch(batchActions([{
|
||||
type: NavigationTypes.NAVIGATION_CLOSE_DRAWERS
|
||||
}, {
|
||||
type: NavigationTypes.NAVIGATION_PUSH,
|
||||
route: {
|
||||
...Routes.Search,
|
||||
props: {
|
||||
searchType: 'flagged_posts'
|
||||
}
|
||||
}
|
||||
}]), getState);
|
||||
};
|
||||
}
|
||||
|
|
@ -1,28 +1,17 @@
|
|||
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
import {NavigationTypes, ViewTypes} from 'app/constants';
|
||||
import Routes from 'app/navigation/routes';
|
||||
import {ViewTypes} from 'app/constants';
|
||||
import {
|
||||
handleSelectChannel,
|
||||
loadChannelsIfNecessary,
|
||||
loadProfilesAndTeamMembersForDMSidebar
|
||||
} from 'app/actions/views/channel';
|
||||
import {goToChannelView} from 'app/actions/views/load_team';
|
||||
import {handleTeamChange, selectFirstAvailableTeam} from 'app/actions/views/select_team';
|
||||
|
||||
import {getClientConfig, getLicenseConfig, setServerVersion} from 'mattermost-redux/actions/general';
|
||||
import {markChannelAsRead, viewChannel} from 'mattermost-redux/actions/channels';
|
||||
|
||||
export function goToSelectServer() {
|
||||
return async (dispatch, getState) => {
|
||||
dispatch({
|
||||
type: NavigationTypes.NAVIGATION_MODAL,
|
||||
route: Routes.SelectServer
|
||||
}, getState);
|
||||
};
|
||||
}
|
||||
|
||||
export function loadConfigAndLicense(serverVersion) {
|
||||
return async (dispatch, getState) => {
|
||||
getClientConfig()(dispatch, getState);
|
||||
|
|
@ -54,26 +43,24 @@ export function goToNotification(notification) {
|
|||
let teamId = data.team_id || currentTeamId;
|
||||
|
||||
if (teamId) {
|
||||
await handleTeamChange(teams[teamId])(dispatch, getState);
|
||||
handleTeamChange(teams[teamId])(dispatch, getState);
|
||||
await loadChannelsIfNecessary(teamId)(dispatch, getState);
|
||||
} else {
|
||||
await selectFirstAvailableTeam()(dispatch, getState);
|
||||
teamId = currentTeamId;
|
||||
}
|
||||
|
||||
viewChannel(teamId, channelId)(dispatch, getState);
|
||||
await loadProfilesAndTeamMembersForDMSidebar(teamId)(dispatch, getState);
|
||||
viewChannel(channelId)(dispatch, getState);
|
||||
loadProfilesAndTeamMembersForDMSidebar(teamId)(dispatch, getState);
|
||||
|
||||
// when the notification is tapped go to the channel view before selecting the channel to prevent
|
||||
// weird behavior
|
||||
goToChannelView()(dispatch, getState);
|
||||
await handleSelectChannel(channelId)(dispatch, getState);
|
||||
markChannelAsRead(teamId, channelId)(dispatch, getState);
|
||||
handleSelectChannel(channelId)(dispatch, getState);
|
||||
markChannelAsRead(teamId, channelId)(dispatch, getState).then(() => true).catch(() => true);
|
||||
};
|
||||
}
|
||||
|
||||
export default {
|
||||
goToSelectServer,
|
||||
loadConfigAndLicense,
|
||||
queueNotification,
|
||||
clearNotification,
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@
|
|||
// See License.txt for license information.
|
||||
|
||||
import {ViewTypes} from 'app/constants';
|
||||
import {goToLogin, goToLoginOptions} from 'app/actions/navigation';
|
||||
|
||||
export function handleServerUrlChanged(serverUrl) {
|
||||
return async (dispatch, getState) => {
|
||||
|
|
@ -13,26 +12,6 @@ export function handleServerUrlChanged(serverUrl) {
|
|||
};
|
||||
}
|
||||
|
||||
export function handleLoginOptions() {
|
||||
return async (dispatch, getState) => {
|
||||
const {config, license} = getState().entities.general;
|
||||
|
||||
const samlEnabled = config.EnableSaml === 'true' && license.IsLicensed === 'true' && license.SAML === 'true';
|
||||
|
||||
let options = 0;
|
||||
if (samlEnabled) {
|
||||
options += 1;
|
||||
}
|
||||
|
||||
if (options) {
|
||||
await goToLoginOptions()(dispatch, getState);
|
||||
} else {
|
||||
await goToLogin()(dispatch, getState);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export default {
|
||||
handleServerUrlChanged,
|
||||
handleLoginOptions
|
||||
handleServerUrlChanged
|
||||
};
|
||||
|
|
|
|||
|
|
@ -2,11 +2,9 @@
|
|||
// See License.txt for license information.
|
||||
|
||||
import {makeDirectChannel} from 'app/actions/views/more_dms';
|
||||
import {NavigationTypes} from 'app/constants';
|
||||
|
||||
export function handleSendMessage(otherUserId) {
|
||||
return async (dispatch, getState) => {
|
||||
await makeDirectChannel(otherUserId)(dispatch, getState);
|
||||
dispatch({type: NavigationTypes.NAVIGATION_POP_TO_INDEX, index: 0}, getState);
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,125 +0,0 @@
|
|||
// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
import React, {PropTypes, PureComponent} from 'react';
|
||||
import {connect} from 'react-redux';
|
||||
import {
|
||||
TouchableOpacity,
|
||||
View
|
||||
} from 'react-native';
|
||||
import EventEmitter from 'mattermost-redux/utils/event_emitter';
|
||||
|
||||
import FormattedText from 'app/components/formatted_text';
|
||||
import Loading from 'app/components/loading';
|
||||
import {getTheme} from 'app/selectors/preferences';
|
||||
import {changeOpacity} from 'app/utils/theme';
|
||||
|
||||
class ActionButton extends PureComponent {
|
||||
static propTypes = {
|
||||
actionEventName: PropTypes.string.isRequired,
|
||||
emitter: PropTypes.func.isRequired,
|
||||
enabled: PropTypes.bool,
|
||||
enableEventName: PropTypes.string,
|
||||
labelDefaultMessage: PropTypes.string.isRequired,
|
||||
labelId: PropTypes.string.isRequired,
|
||||
loadingEventName: PropTypes.string.isRequired,
|
||||
theme: PropTypes.object
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
theme: {},
|
||||
enabled: true
|
||||
};
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
enabled: props.enabled,
|
||||
loading: false
|
||||
};
|
||||
}
|
||||
|
||||
componentWillMount() {
|
||||
EventEmitter.on(this.props.enableEventName, this.handleEnableEvent);
|
||||
EventEmitter.on(this.props.loadingEventName, this.handleLoadingEvent);
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
EventEmitter.off(this.props.enableEventName, this.handleEnableEvent);
|
||||
EventEmitter.off(this.props.loadingEventName, this.handleLoadingEvent);
|
||||
}
|
||||
|
||||
handleEnableEvent = (enabled) => {
|
||||
this.setState({enabled});
|
||||
};
|
||||
|
||||
handleLoadingEvent = (loading) => {
|
||||
this.setState({loading});
|
||||
};
|
||||
|
||||
onPress = () => {
|
||||
if (this.state.enabled) {
|
||||
this.props.emitter(this.props.actionEventName);
|
||||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
const {labelDefaultMessage, labelId, theme} = this.props;
|
||||
const {enabled, loading} = this.state;
|
||||
let color = changeOpacity(theme.sidebarHeaderTextColor, 0.4);
|
||||
let contents;
|
||||
if (enabled) {
|
||||
color = theme.sidebarHeaderTextColor;
|
||||
contents = (
|
||||
<View style={{flex: 1, flexDirection: 'column', justifyContent: 'center', alignItems: 'center'}}>
|
||||
<TouchableOpacity
|
||||
onPress={this.onPress}
|
||||
style={{paddingHorizontal: 15}}
|
||||
>
|
||||
<FormattedText
|
||||
id={labelId}
|
||||
defaultMessage={labelDefaultMessage}
|
||||
style={{color}}
|
||||
/>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
);
|
||||
} else {
|
||||
contents = (
|
||||
<View style={{flex: 1, flexDirection: 'column', justifyContent: 'center', alignItems: 'center', paddingHorizontal: 15}}>
|
||||
<FormattedText
|
||||
id={labelId}
|
||||
defaultMessage={labelDefaultMessage}
|
||||
style={{color}}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<Loading
|
||||
color={color}
|
||||
size='small'
|
||||
style={{
|
||||
alignItems: 'center',
|
||||
flex: 1,
|
||||
flexDirection: 'column',
|
||||
justifyContent: 'center',
|
||||
paddingHorizontal: 20}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return contents;
|
||||
}
|
||||
}
|
||||
|
||||
function mapStateToProps(state) {
|
||||
return {
|
||||
theme: getTheme(state)
|
||||
};
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps)(ActionButton);
|
||||
|
|
@ -38,6 +38,7 @@ export default class AtMention extends Component {
|
|||
|
||||
static defaultProps = {
|
||||
autocompleteUsersInCurrentChannel: {},
|
||||
defaultChannel: {},
|
||||
postDraft: ''
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ function mapStateToProps(state, ownProps) {
|
|||
let postDraft;
|
||||
if (ownProps.rootId.length) {
|
||||
postDraft = state.views.thread.drafts[ownProps.rootId].draft;
|
||||
} else {
|
||||
} else if (currentChannelId) {
|
||||
postDraft = state.views.channel.drafts[currentChannelId].draft;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ function mapStateToProps(state, ownProps) {
|
|||
let postDraft;
|
||||
if (ownProps.rootId.length) {
|
||||
postDraft = state.views.thread.drafts[ownProps.rootId].draft;
|
||||
} else {
|
||||
} else if (currentChannelId) {
|
||||
postDraft = state.views.channel.drafts[currentChannelId].draft;
|
||||
}
|
||||
|
||||
|
|
|
|||
190
app/components/channel_drawer/channel_drawer.js
Normal file
190
app/components/channel_drawer/channel_drawer.js
Normal file
|
|
@ -0,0 +1,190 @@
|
|||
// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
import React, {PropTypes, PureComponent} from 'react';
|
||||
import {BackAndroid, InteractionManager, Keyboard} from 'react-native';
|
||||
|
||||
import Drawer from 'app/components/drawer';
|
||||
import ChannelDrawerList from 'app/components/channel_drawer_list';
|
||||
|
||||
import EventEmitter from 'mattermost-redux/utils/event_emitter';
|
||||
|
||||
export default class ChannelDrawer extends PureComponent {
|
||||
static propTypes = {
|
||||
actions: PropTypes.shape({
|
||||
handleSelectChannel: PropTypes.func.isRequired,
|
||||
viewChannel: PropTypes.func.isRequired,
|
||||
markChannelAsRead: PropTypes.func.isRequired,
|
||||
setChannelLoading: PropTypes.func.isRequired
|
||||
}).isRequired,
|
||||
blurPostTextBox: PropTypes.func.isRequired,
|
||||
navigator: PropTypes.object,
|
||||
children: PropTypes.node,
|
||||
currentTeam: PropTypes.object,
|
||||
currentChannel: PropTypes.object,
|
||||
channels: PropTypes.object,
|
||||
channelMembers: PropTypes.object,
|
||||
theme: PropTypes.object.isRequired
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
currentTeam: {},
|
||||
currentChannel: {}
|
||||
};
|
||||
|
||||
state = {
|
||||
openDrawer: false
|
||||
};
|
||||
|
||||
componentDidMount() {
|
||||
EventEmitter.on('open_channel_drawer', this.openChannelDrawer);
|
||||
EventEmitter.on('close_channel_drawer', this.closeChannelDrawer);
|
||||
BackAndroid.addEventListener('hardwareBackPress', this.handleAndroidBack);
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
EventEmitter.off('open_channel_drawer', this.openChannelDrawer);
|
||||
EventEmitter.off('close_channel_drawer', this.closeChannelDrawer);
|
||||
BackAndroid.removeEventListener('hardwareBackPress', this.handleAndroidBack);
|
||||
}
|
||||
|
||||
handleAndroidBack = () => {
|
||||
if (this.state.openDrawer) {
|
||||
this.setState({openDrawer: false});
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
closeChannelDrawer = () => {
|
||||
this.setState({openDrawer: false});
|
||||
};
|
||||
|
||||
handleDrawerClose = () => {
|
||||
setTimeout(() => {
|
||||
InteractionManager.clearInteractionHandle(this.closeLeftHandle);
|
||||
});
|
||||
};
|
||||
|
||||
handleDrawerCloseStart = () => {
|
||||
this.closeLeftHandle = InteractionManager.createInteractionHandle();
|
||||
};
|
||||
|
||||
handleDrawerOpen = () => {
|
||||
this.setState({openDrawer: true});
|
||||
Keyboard.dismiss();
|
||||
setTimeout(() => {
|
||||
InteractionManager.clearInteractionHandle(this.openLeftHandle);
|
||||
});
|
||||
};
|
||||
|
||||
handleDrawerOpenStart = () => {
|
||||
this.openLeftHandle = InteractionManager.createInteractionHandle();
|
||||
};
|
||||
|
||||
handleDrawerTween = (ratio) => {
|
||||
const opacity = (ratio / 2);
|
||||
|
||||
EventEmitter.emit('drawer_opacity', opacity);
|
||||
|
||||
return {
|
||||
mainOverlay: {
|
||||
backgroundColor: this.props.theme.centerChannelBg,
|
||||
opacity
|
||||
},
|
||||
drawerOverlay: {
|
||||
backgroundColor: ratio ? '#000' : '#FFF',
|
||||
opacity: ratio ? (1 - ratio) / 2 : 1
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
openChannelDrawer = () => {
|
||||
this.props.blurPostTextBox();
|
||||
this.setState({openDrawer: true});
|
||||
};
|
||||
|
||||
selectChannel = (id) => {
|
||||
const {
|
||||
actions,
|
||||
currentChannel
|
||||
} = this.props;
|
||||
|
||||
const {
|
||||
handleSelectChannel,
|
||||
markChannelAsRead,
|
||||
setChannelLoading,
|
||||
viewChannel
|
||||
} = actions;
|
||||
|
||||
markChannelAsRead(id, currentChannel.id);
|
||||
setChannelLoading();
|
||||
viewChannel(id, currentChannel.id);
|
||||
this.closeChannelDrawer();
|
||||
InteractionManager.runAfterInteractions(() => {
|
||||
handleSelectChannel(id);
|
||||
});
|
||||
};
|
||||
|
||||
render() {
|
||||
const {
|
||||
children,
|
||||
currentChannel,
|
||||
currentTeam,
|
||||
channels,
|
||||
channelMembers,
|
||||
navigator,
|
||||
theme
|
||||
} = this.props;
|
||||
|
||||
return (
|
||||
<Drawer
|
||||
open={this.state.openDrawer}
|
||||
onOpenStart={this.handleDrawerOpenStart}
|
||||
onOpen={this.handleDrawerOpen}
|
||||
onCloseStart={this.handleDrawerCloseStart}
|
||||
onClose={this.handleDrawerClose}
|
||||
captureGestures='open'
|
||||
type='static'
|
||||
acceptTap={true}
|
||||
disabled={false}
|
||||
content={
|
||||
<ChannelDrawerList
|
||||
currentTeam={currentTeam}
|
||||
currentChannel={currentChannel}
|
||||
channels={channels}
|
||||
channelMembers={channelMembers}
|
||||
theme={theme}
|
||||
onSelectChannel={this.selectChannel}
|
||||
navigator={navigator}
|
||||
/>
|
||||
}
|
||||
tapToClose={true}
|
||||
openDrawerOffset={40}
|
||||
onRequestClose={this.closeChannelDrawer}
|
||||
panOpenMask={0.2}
|
||||
panCloseMask={40}
|
||||
panThreshold={0.25}
|
||||
acceptPan={true}
|
||||
negotiatePan={true}
|
||||
useInteractionManager={false}
|
||||
tweenHandler={this.handleDrawerTween}
|
||||
elevation={-5}
|
||||
styles={{
|
||||
main: {
|
||||
shadowColor: '#000000',
|
||||
shadowOpacity: 0.4,
|
||||
shadowRadius: 12,
|
||||
shadowOffset: {
|
||||
width: -4,
|
||||
height: 0
|
||||
}
|
||||
}
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</Drawer>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -4,7 +4,6 @@
|
|||
import {bindActionCreators} from 'redux';
|
||||
import {connect} from 'react-redux';
|
||||
|
||||
import {closeDrawers} from 'app/actions/navigation';
|
||||
import {handleSelectChannel, setChannelLoading} from 'app/actions/views/channel';
|
||||
|
||||
import {getChannelsByCategory, getCurrentChannel} from 'mattermost-redux/selectors/entities/channels';
|
||||
|
|
@ -15,8 +14,9 @@ import {viewChannel, markChannelAsRead} from 'mattermost-redux/actions/channels'
|
|||
|
||||
import ChannelDrawer from './channel_drawer.js';
|
||||
|
||||
function mapStateToProps(state) {
|
||||
function mapStateToProps(state, ownProps) {
|
||||
return {
|
||||
...ownProps,
|
||||
currentTeam: getCurrentTeam(state),
|
||||
currentChannel: getCurrentChannel(state),
|
||||
channels: getChannelsByCategory(state),
|
||||
|
|
@ -28,7 +28,6 @@ function mapStateToProps(state) {
|
|||
function mapDispatchToProps(dispatch) {
|
||||
return {
|
||||
actions: bindActionCreators({
|
||||
closeDrawers,
|
||||
handleSelectChannel,
|
||||
viewChannel,
|
||||
markChannelAsRead,
|
||||
|
|
@ -26,24 +26,13 @@ import UnreadIndicator from './unread_indicator';
|
|||
|
||||
class ChannelDrawerList extends Component {
|
||||
static propTypes = {
|
||||
actions: PropTypes.shape({
|
||||
closeDirectChannel: PropTypes.func.isRequired,
|
||||
closeOptionsModal: PropTypes.func.isRequired,
|
||||
goToCreateChannel: PropTypes.func.isRequired,
|
||||
leaveChannel: PropTypes.func.isRequired,
|
||||
markFavorite: PropTypes.func.isRequired,
|
||||
openSettingsModal: React.PropTypes.func.isRequired,
|
||||
unmarkFavorite: PropTypes.func.isRequired,
|
||||
showOptionsModal: PropTypes.func.isRequired,
|
||||
showDirectMessagesModal: PropTypes.func.isRequired,
|
||||
showMoreChannelsModal: PropTypes.func.isRequired
|
||||
}).isRequired,
|
||||
canCreatePrivateChannels: PropTypes.bool.isRequired,
|
||||
channels: PropTypes.object.isRequired,
|
||||
channelMembers: PropTypes.object,
|
||||
currentTeam: PropTypes.object.isRequired,
|
||||
currentChannel: PropTypes.object,
|
||||
intl: intlShape.isRequired,
|
||||
navigator: PropTypes.object,
|
||||
onSelectChannel: PropTypes.func.isRequired,
|
||||
theme: PropTypes.object.isRequired
|
||||
};
|
||||
|
|
@ -64,6 +53,10 @@ class ChannelDrawerList extends Component {
|
|||
rowHasChanged: (a, b) => a !== b
|
||||
}).cloneWithRows(this.buildData(props))
|
||||
};
|
||||
MaterialIcon.getImageSource('close', 20, this.props.theme.sidebarHeaderTextColor).
|
||||
then((source) => {
|
||||
this.closeButton = source;
|
||||
});
|
||||
}
|
||||
|
||||
shouldComponentUpdate(nextProps, nextState) {
|
||||
|
|
@ -178,7 +171,25 @@ class ChannelDrawerList extends Component {
|
|||
};
|
||||
|
||||
createPrivateChannel = () => {
|
||||
this.props.actions.goToCreateChannel(General.PRIVATE_CHANNEL);
|
||||
const {intl, navigator, theme} = this.props;
|
||||
|
||||
navigator.showModal({
|
||||
screen: 'CreateChannel',
|
||||
animationType: 'slide-up',
|
||||
title: intl.formatMessage({id: 'mobile.create_channel.private', defaultMessage: 'New Private Channel'}),
|
||||
backButtonTitle: '',
|
||||
animated: true,
|
||||
navigatorStyle: {
|
||||
navBarTextColor: theme.sidebarHeaderTextColor,
|
||||
navBarBackgroundColor: theme.sidebarHeaderBg,
|
||||
navBarButtonColor: theme.sidebarHeaderTextColor,
|
||||
screenBackgroundColor: theme.centerChannelBg
|
||||
},
|
||||
passProps: {
|
||||
channelType: General.PRIVATE_CHANNEL,
|
||||
closeButton: this.closeButton
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
buildData = (props) => {
|
||||
|
|
@ -206,7 +217,7 @@ class ChannelDrawerList extends Component {
|
|||
}
|
||||
|
||||
data.push(
|
||||
this.renderTitle(styles, 'sidebar.channels', 'CHANNELS', this.props.actions.showMoreChannelsModal, publicChannels.length > 0),
|
||||
this.renderTitle(styles, 'sidebar.channels', 'CHANNELS', this.showMoreChannelsModal, publicChannels.length > 0),
|
||||
...publicChannels
|
||||
);
|
||||
|
||||
|
|
@ -220,7 +231,7 @@ class ChannelDrawerList extends Component {
|
|||
);
|
||||
|
||||
data.push(
|
||||
this.renderTitle(styles, 'sidebar.direct', 'DIRECT MESSAGES', this.props.actions.showDirectMessagesModal, directAndGroupChannels.length > 0),
|
||||
this.renderTitle(styles, 'sidebar.direct', 'DIRECT MESSAGES', this.showDirectMessagesModal, directAndGroupChannels.length > 0),
|
||||
...directAndGroupChannels
|
||||
);
|
||||
|
||||
|
|
@ -231,6 +242,76 @@ class ChannelDrawerList extends Component {
|
|||
return data;
|
||||
};
|
||||
|
||||
openSettingsModal = () => {
|
||||
const {intl, navigator, theme} = this.props;
|
||||
|
||||
navigator.showModal({
|
||||
screen: 'Settings',
|
||||
title: intl.formatMessage({id: 'mobile.routes.settings', defaultMessage: 'Settings'}),
|
||||
animationType: 'slide-up',
|
||||
animated: true,
|
||||
backButtonTitle: '',
|
||||
navigatorStyle: {
|
||||
navBarTextColor: theme.sidebarHeaderTextColor,
|
||||
navBarBackgroundColor: theme.sidebarHeaderBg,
|
||||
navBarButtonColor: theme.sidebarHeaderTextColor,
|
||||
screenBackgroundColor: theme.centerChannelBg
|
||||
},
|
||||
navigatorButtons: {
|
||||
leftButtons: [{
|
||||
id: 'close-settings',
|
||||
icon: this.closeButton
|
||||
}]
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
showDirectMessagesModal = () => {
|
||||
const {intl, navigator, theme} = this.props;
|
||||
|
||||
navigator.showModal({
|
||||
screen: 'MoreDirectMessages',
|
||||
title: intl.formatMessage({id: 'more_direct_channels.title', defaultMessage: 'Direct Messages'}),
|
||||
animationType: 'slide-up',
|
||||
animated: true,
|
||||
backButtonTitle: '',
|
||||
navigatorStyle: {
|
||||
navBarTextColor: theme.sidebarHeaderTextColor,
|
||||
navBarBackgroundColor: theme.sidebarHeaderBg,
|
||||
navBarButtonColor: theme.sidebarHeaderTextColor,
|
||||
screenBackgroundColor: theme.centerChannelBg
|
||||
},
|
||||
navigatorButtons: {
|
||||
leftButtons: [{
|
||||
id: 'close-dms',
|
||||
icon: this.closeButton
|
||||
}]
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
showMoreChannelsModal = () => {
|
||||
const {intl, navigator, theme} = this.props;
|
||||
|
||||
navigator.showModal({
|
||||
screen: 'MoreChannels',
|
||||
animationType: 'slide-up',
|
||||
title: intl.formatMessage({id: 'more_channels.title', defaultMessage: 'More Channels'}),
|
||||
backButtonTitle: '',
|
||||
animated: true,
|
||||
navigatorStyle: {
|
||||
navBarTextColor: theme.sidebarHeaderTextColor,
|
||||
navBarBackgroundColor: theme.sidebarHeaderBg,
|
||||
navBarButtonColor: theme.sidebarHeaderTextColor,
|
||||
screenBackgroundColor: theme.centerChannelBg
|
||||
},
|
||||
passProps: {
|
||||
channelType: General.PRIVATE_CHANNEL,
|
||||
closeButton: this.closeButton
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
renderSectionAction = (styles, action) => {
|
||||
const {theme} = this.props;
|
||||
return (
|
||||
|
|
@ -293,7 +374,7 @@ class ChannelDrawerList extends Component {
|
|||
const settings = (
|
||||
<TouchableHighlight
|
||||
style={styles.settingsContainer}
|
||||
onPress={() => preventDoubleTap(this.props.actions.openSettingsModal)}
|
||||
onPress={() => preventDoubleTap(this.openSettingsModal)}
|
||||
underlayColor={changeOpacity(theme.sidebarHeaderBg, 0.5)}
|
||||
>
|
||||
<AwesomeIcon
|
||||
|
|
|
|||
|
|
@ -1,25 +1,8 @@
|
|||
// 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 {
|
||||
goToCreateChannel,
|
||||
openSettingsModal,
|
||||
requestCloseModal,
|
||||
showMoreChannelsModal,
|
||||
showDirectMessagesModal,
|
||||
showOptionsModal
|
||||
} from 'app/actions/navigation';
|
||||
|
||||
import {
|
||||
closeDirectChannel,
|
||||
leaveChannel,
|
||||
markFavorite,
|
||||
unmarkFavorite
|
||||
} from 'app/actions/views/channel';
|
||||
|
||||
import {General} from 'mattermost-redux/constants';
|
||||
import {getCurrentUserId, getCurrentUserRoles} from 'mattermost-redux/selectors/entities/users';
|
||||
import {showCreateOption} from 'mattermost-redux/utils/channel_utils';
|
||||
|
|
@ -37,21 +20,4 @@ function mapStateToProps(state, ownProps) {
|
|||
};
|
||||
}
|
||||
|
||||
function mapDispatchToProps(dispatch) {
|
||||
return {
|
||||
actions: bindActionCreators({
|
||||
closeDirectChannel,
|
||||
goToCreateChannel,
|
||||
leaveChannel,
|
||||
markFavorite,
|
||||
openSettingsModal,
|
||||
unmarkFavorite,
|
||||
showOptionsModal,
|
||||
showDirectMessagesModal,
|
||||
showMoreChannelsModal,
|
||||
closeOptionsModal: requestCloseModal
|
||||
}, dispatch)
|
||||
};
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(ChannelDrawerList);
|
||||
export default connect(mapStateToProps)(ChannelDrawerList);
|
||||
|
|
|
|||
|
|
@ -24,6 +24,21 @@ function ChannelListRow(props) {
|
|||
const {id, displayName, purpose, onPress, theme} = props;
|
||||
const style = getStyleFromTheme(theme);
|
||||
|
||||
let purposeComponent;
|
||||
if (purpose) {
|
||||
purposeComponent = (
|
||||
<View style={{flexShrink: 1, flexDirection: 'column', flexWrap: 'wrap'}}>
|
||||
<Text
|
||||
style={style.purpose}
|
||||
ellipsizeMode='tail'
|
||||
numberOfLines={1}
|
||||
>
|
||||
{purpose}
|
||||
</Text>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
const RowComponent = (
|
||||
<View style={style.container}>
|
||||
<View style={style.titleContainer}>
|
||||
|
|
@ -54,15 +69,7 @@ function ChannelListRow(props) {
|
|||
</View>
|
||||
</View>
|
||||
</View>
|
||||
<View style={{flexShrink: 1, flexDirection: 'column', flexWrap: 'wrap'}}>
|
||||
<Text
|
||||
style={style.purpose}
|
||||
ellipsizeMode='tail'
|
||||
numberOfLines={1}
|
||||
>
|
||||
{purpose}
|
||||
</Text>
|
||||
</View>
|
||||
{purposeComponent}
|
||||
</View>
|
||||
);
|
||||
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ export default class FileAttachmentList extends Component {
|
|||
fetchCache: PropTypes.object.isRequired,
|
||||
files: PropTypes.array.isRequired,
|
||||
hideOptionsContext: PropTypes.func.isRequired,
|
||||
navigator: PropTypes.object,
|
||||
onLongPress: PropTypes.func,
|
||||
onPress: PropTypes.func,
|
||||
post: PropTypes.object.isRequired,
|
||||
|
|
@ -43,14 +44,33 @@ export default class FileAttachmentList extends Component {
|
|||
}
|
||||
}
|
||||
|
||||
goToImagePreview = (post, fileId) => {
|
||||
this.props.navigator.showModal({
|
||||
screen: 'ImagePreview',
|
||||
title: '',
|
||||
animationType: 'none',
|
||||
passProps: {
|
||||
fileId,
|
||||
post
|
||||
},
|
||||
navigatorStyle: {
|
||||
navBarHidden: true,
|
||||
statusBarHidden: false,
|
||||
statusBarHideWithNavBar: false,
|
||||
screenBackgroundColor: 'transparent',
|
||||
modalPresentationStyle: 'overCurrentContext'
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
handleInfoPress = () => {
|
||||
this.props.hideOptionsContext();
|
||||
this.props.onPress();
|
||||
}
|
||||
};
|
||||
|
||||
handlePreviewPress = (file) => {
|
||||
this.props.hideOptionsContext();
|
||||
preventDoubleTap(this.props.actions.goToImagePreviewModal, this, this.props.post, file.id);
|
||||
preventDoubleTap(this.goToImagePreview, this, this.props.post, file.id);
|
||||
};
|
||||
|
||||
render() {
|
||||
|
|
|
|||
|
|
@ -8,7 +8,6 @@ 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 'app/selectors/preferences';
|
||||
import {goToImagePreviewModal} from 'app/actions/navigation';
|
||||
|
||||
import FileAttachmentList from './file_attachment_list';
|
||||
|
||||
|
|
@ -29,7 +28,6 @@ function mapDispatchToProps(dispatch) {
|
|||
return {
|
||||
actions: bindActionCreators({
|
||||
addFileToFetchCache,
|
||||
goToImagePreviewModal,
|
||||
loadFilesForPostIfNecessary
|
||||
}, dispatch)
|
||||
};
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ import FormattedText from 'app/components/formatted_text';
|
|||
|
||||
import {RequestStatus} from 'mattermost-redux/constants';
|
||||
|
||||
const INITIAL_TOP = Platform.OS === 'ios' ? -80 : -60;
|
||||
const INITIAL_TOP = -46;
|
||||
const OFFLINE = 'offline';
|
||||
const CONNECTING = 'connecting';
|
||||
const CONNECTED = 'connected';
|
||||
|
|
@ -197,18 +197,18 @@ export default class OfflineIndicator extends PureComponent {
|
|||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
flex: 1,
|
||||
zIndex: 10,
|
||||
position: 'absolute',
|
||||
elevation: 2
|
||||
height: 38,
|
||||
width: Dimensions.get('window').width,
|
||||
zIndex: 9,
|
||||
position: 'relative',
|
||||
elevation: 1
|
||||
},
|
||||
wrapper: {
|
||||
alignItems: 'center',
|
||||
flex: 1,
|
||||
height: 39,
|
||||
flexDirection: 'row',
|
||||
height: 38,
|
||||
paddingHorizontal: 12,
|
||||
width: Dimensions.get('window').width,
|
||||
backgroundColor: 'red'
|
||||
},
|
||||
message: {
|
||||
|
|
|
|||
|
|
@ -10,7 +10,6 @@ import {getCurrentUserId, getCurrentUserRoles, getUser} from 'mattermost-redux/s
|
|||
import {isPostFlagged} from 'mattermost-redux/utils/post_utils';
|
||||
import {displayUsername} from 'mattermost-redux/utils/user_utils';
|
||||
|
||||
import {goToUserProfile, openEditPostModal, requestCloseModal, showOptionsModal} from 'app/actions/navigation';
|
||||
import {getTheme} from 'app/selectors/preferences';
|
||||
|
||||
import Post from './post';
|
||||
|
|
@ -46,11 +45,7 @@ function mapDispatchToProps(dispatch) {
|
|||
createPost,
|
||||
deletePost,
|
||||
flagPost,
|
||||
goToUserProfile,
|
||||
openEditPostModal,
|
||||
removePost,
|
||||
requestCloseModal,
|
||||
showOptionsModal,
|
||||
unflagPost
|
||||
}, dispatch)
|
||||
};
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ import {
|
|||
} from 'react-native';
|
||||
import {injectIntl, intlShape} from 'react-intl';
|
||||
import Icon from 'react-native-vector-icons/Ionicons';
|
||||
import MaterialIcon from 'react-native-vector-icons/MaterialIcons';
|
||||
|
||||
import FileAttachmentList from 'app/components/file_attachment_list';
|
||||
import FormattedText from 'app/components/formatted_text';
|
||||
|
|
@ -23,6 +24,7 @@ import Markdown from 'app/components/markdown';
|
|||
import OptionsContext from 'app/components/options_context';
|
||||
import ProfilePicture from 'app/components/profile_picture';
|
||||
import ReplyIcon from 'app/components/reply_icon';
|
||||
import {NavigationTypes} from 'app/constants';
|
||||
import {preventDoubleTap} from 'app/utils/tap';
|
||||
import {changeOpacity, makeStyleSheetFromTheme} from 'app/utils/theme';
|
||||
|
||||
|
|
@ -30,6 +32,7 @@ import webhookIcon from 'assets/images/icons/webhook.jpg';
|
|||
|
||||
import {Posts} from 'mattermost-redux/constants';
|
||||
import DelayedAction from 'mattermost-redux/utils/delayed_action';
|
||||
import EventEmitter from 'mattermost-redux/utils/event_emitter';
|
||||
import {canDeletePost, canEditPost, isSystemMessage} from 'mattermost-redux/utils/post_utils';
|
||||
import {isAdmin, isSystemAdmin} from 'mattermost-redux/utils/user_utils';
|
||||
|
||||
|
|
@ -52,6 +55,7 @@ class Post extends PureComponent {
|
|||
commentedOnDisplayName: PropTypes.string,
|
||||
commentedOnPost: PropTypes.object,
|
||||
license: PropTypes.object.isRequired,
|
||||
navigator: PropTypes.object,
|
||||
roles: PropTypes.string,
|
||||
theme: PropTypes.object.isRequired,
|
||||
onPress: PropTypes.func,
|
||||
|
|
@ -59,12 +63,8 @@ class Post extends PureComponent {
|
|||
createPost: PropTypes.func.isRequired,
|
||||
deletePost: PropTypes.func.isRequired,
|
||||
flagPost: PropTypes.func.isRequired,
|
||||
goToUserProfile: PropTypes.func.isRequired,
|
||||
openEditPostModal: PropTypes.func.isRequired,
|
||||
removePost: PropTypes.func.isRequired,
|
||||
unflagPost: PropTypes.func.isRequired,
|
||||
requestCloseModal: PropTypes.func.isRequired,
|
||||
showOptionsModal: PropTypes.func.isRequired
|
||||
unflagPost: PropTypes.func.isRequired
|
||||
}).isRequired
|
||||
};
|
||||
|
||||
|
|
@ -91,6 +91,25 @@ class Post extends PureComponent {
|
|||
this.editDisableAction.cancel();
|
||||
}
|
||||
|
||||
goToUserProfile = (userId) => {
|
||||
const {intl, navigator, theme} = this.props;
|
||||
navigator.push({
|
||||
screen: 'UserProfile',
|
||||
title: intl.formatMessage({id: 'mobile.routes.user_profile', defaultMessage: 'Profile'}),
|
||||
animated: true,
|
||||
backButtonTitle: '',
|
||||
passProps: {
|
||||
userId
|
||||
},
|
||||
navigatorStyle: {
|
||||
navBarTextColor: theme.sidebarHeaderTextColor,
|
||||
navBarBackgroundColor: theme.sidebarHeaderBg,
|
||||
navBarButtonColor: theme.sidebarHeaderTextColor,
|
||||
screenBackgroundColor: theme.centerChannelBg
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
handleEditDisable = () => {
|
||||
this.setState({canEdit: false});
|
||||
};
|
||||
|
|
@ -117,8 +136,25 @@ class Post extends PureComponent {
|
|||
};
|
||||
|
||||
handlePostEdit = () => {
|
||||
const {actions, post} = this.props;
|
||||
actions.openEditPostModal(post);
|
||||
const {intl, navigator, post, theme} = this.props;
|
||||
MaterialIcon.getImageSource('close', 20, theme.sidebarHeaderTextColor).
|
||||
then((source) => {
|
||||
navigator.showModal({
|
||||
screen: 'EditPost',
|
||||
title: intl.formatMessage({id: 'mobile.edit_post.title', defaultMessage: 'Editing Message'}),
|
||||
animated: true,
|
||||
navigatorStyle: {
|
||||
navBarTextColor: theme.sidebarHeaderTextColor,
|
||||
navBarBackgroundColor: theme.sidebarHeaderBg,
|
||||
navBarButtonColor: theme.sidebarHeaderTextColor,
|
||||
screenBackgroundColor: theme.centerChannelBg
|
||||
},
|
||||
passProps: {
|
||||
post,
|
||||
closeButton: source
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
handleFailedPostPress = () => {
|
||||
|
|
@ -131,7 +167,7 @@ class Post extends PureComponent {
|
|||
action: () => {
|
||||
const {failed, id, ...post} = this.props.post; // eslint-disable-line
|
||||
|
||||
this.props.actions.requestCloseModal();
|
||||
EventEmitter.emit(NavigationTypes.NAVIGATION_CLOSE_MODAL);
|
||||
this.props.actions.createPost(post);
|
||||
},
|
||||
text: {
|
||||
|
|
@ -140,7 +176,7 @@ class Post extends PureComponent {
|
|||
}
|
||||
}, {
|
||||
action: () => {
|
||||
this.props.actions.requestCloseModal();
|
||||
EventEmitter.emit(NavigationTypes.NAVIGATION_CLOSE_MODAL);
|
||||
this.onRemovePost(this.props.post);
|
||||
},
|
||||
text: {
|
||||
|
|
@ -153,7 +189,22 @@ class Post extends PureComponent {
|
|||
}]
|
||||
};
|
||||
|
||||
this.props.actions.showOptionsModal(options);
|
||||
this.props.navigator.showModal({
|
||||
screen: 'OptionsModal',
|
||||
title: '',
|
||||
animationType: 'none',
|
||||
passProps: {
|
||||
items: options.items,
|
||||
title: options.title
|
||||
},
|
||||
navigatorStyle: {
|
||||
navBarHidden: true,
|
||||
statusBarHidden: false,
|
||||
statusBarHideWithNavBar: false,
|
||||
screenBackgroundColor: 'transparent',
|
||||
modalPresentationStyle: 'overCurrentContext'
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
handlePress = () => {
|
||||
|
|
@ -242,7 +293,7 @@ class Post extends PureComponent {
|
|||
};
|
||||
|
||||
renderFileAttachments() {
|
||||
const {post} = this.props;
|
||||
const {navigator, post} = this.props;
|
||||
const fileIds = post.file_ids || [];
|
||||
|
||||
let attachments;
|
||||
|
|
@ -254,6 +305,7 @@ class Post extends PureComponent {
|
|||
onPress={this.handlePress}
|
||||
post={post}
|
||||
toggleSelected={this.toggleSelected}
|
||||
navigator={navigator}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
|
@ -389,7 +441,7 @@ class Post extends PureComponent {
|
|||
};
|
||||
|
||||
viewUserProfile = () => {
|
||||
preventDoubleTap(this.props.actions.goToUserProfile, null, this.props.user.id);
|
||||
preventDoubleTap(this.goToUserProfile, null, this.props.user.id);
|
||||
};
|
||||
|
||||
toggleSelected = (selected) => {
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ export default class PostList extends Component {
|
|||
posts: PropTypes.array.isRequired,
|
||||
theme: PropTypes.object.isRequired,
|
||||
loadMore: PropTypes.func,
|
||||
navigator: PropTypes.object,
|
||||
isLoadingMore: PropTypes.bool,
|
||||
showLoadMore: PropTypes.bool,
|
||||
onPostPress: PropTypes.func,
|
||||
|
|
@ -142,6 +143,7 @@ export default class PostList extends Component {
|
|||
isLastReply={post.isLastReply}
|
||||
commentedOnPost={post.commentedOnPost}
|
||||
onPress={this.props.onPostPress}
|
||||
navigator={this.props.navigator}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -6,7 +6,6 @@ import {connect} from 'react-redux';
|
|||
import {createPost} from 'mattermost-redux/actions/posts';
|
||||
import {userTyping} from 'mattermost-redux/actions/websocket';
|
||||
|
||||
import {showOptionsModal, requestCloseModal} from 'app/actions/navigation';
|
||||
import {handleClearFiles, handleRemoveLastFile, handleUploadFiles} from 'app/actions/views/file_upload';
|
||||
import {getTheme} from 'app/selectors/preferences';
|
||||
import {getCurrentUserId} from 'mattermost-redux/selectors/entities/users';
|
||||
|
|
@ -29,11 +28,9 @@ function mapDispatchToProps(dispatch) {
|
|||
return {
|
||||
actions: bindActionCreators({
|
||||
createPost,
|
||||
closeModal: requestCloseModal,
|
||||
handleClearFiles,
|
||||
handleRemoveLastFile,
|
||||
handleUploadFiles,
|
||||
showOptionsModal,
|
||||
userTyping
|
||||
}, dispatch)
|
||||
};
|
||||
|
|
|
|||
|
|
@ -30,12 +30,10 @@ const MAX_MESSAGE_LENGTH = 4000;
|
|||
class PostTextbox extends PureComponent {
|
||||
static propTypes = {
|
||||
actions: PropTypes.shape({
|
||||
closeModal: PropTypes.func.isRequired,
|
||||
createPost: PropTypes.func.isRequired,
|
||||
handleClearFiles: PropTypes.func.isRequired,
|
||||
handleRemoveLastFile: PropTypes.func.isRequired,
|
||||
handleUploadFiles: PropTypes.func.isRequired,
|
||||
showOptionsModal: PropTypes.func.isRequired,
|
||||
userTyping: PropTypes.func.isRequired
|
||||
}).isRequired,
|
||||
channelId: PropTypes.string.isRequired,
|
||||
|
|
@ -43,6 +41,7 @@ class PostTextbox extends PureComponent {
|
|||
currentUserId: PropTypes.string.isRequired,
|
||||
files: PropTypes.array,
|
||||
intl: intlShape.isRequired,
|
||||
navigator: PropTypes.object,
|
||||
onChangeText: PropTypes.func.isRequired,
|
||||
rootId: PropTypes.string,
|
||||
theme: PropTypes.object.isRequired,
|
||||
|
|
@ -207,7 +206,9 @@ class PostTextbox extends PureComponent {
|
|||
};
|
||||
|
||||
attachFileFromCamera = () => {
|
||||
this.props.actions.closeModal();
|
||||
this.props.navigator.dismissModal({
|
||||
animationType: 'none'
|
||||
});
|
||||
|
||||
const options = {
|
||||
quality: 0.7,
|
||||
|
|
@ -228,7 +229,9 @@ class PostTextbox extends PureComponent {
|
|||
};
|
||||
|
||||
attachFileFromLibrary = () => {
|
||||
this.props.actions.closeModal();
|
||||
this.props.navigator.dismissModal({
|
||||
animationType: 'none'
|
||||
});
|
||||
|
||||
const options = {
|
||||
quality: 0.7,
|
||||
|
|
@ -268,7 +271,21 @@ class PostTextbox extends PureComponent {
|
|||
}]
|
||||
};
|
||||
|
||||
this.props.actions.showOptionsModal(options);
|
||||
this.props.navigator.showModal({
|
||||
screen: 'OptionsModal',
|
||||
title: '',
|
||||
animationType: 'none',
|
||||
passProps: {
|
||||
items: options.items
|
||||
},
|
||||
navigatorStyle: {
|
||||
navBarHidden: true,
|
||||
statusBarHidden: false,
|
||||
statusBarHideWithNavBar: false,
|
||||
screenBackgroundColor: 'transparent',
|
||||
modalPresentationStyle: 'overCurrentContext'
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
renderTyping = () => {
|
||||
|
|
|
|||
|
|
@ -1,23 +1,17 @@
|
|||
// 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 Config from 'assets/config.json';
|
||||
import {getCurrentChannelId} from 'mattermost-redux/selectors/entities/channels';
|
||||
|
||||
import {closeDrawers, closeModal, goBack, unrenderDrawer} from 'app/actions/navigation';
|
||||
import {goToNotification, loadConfigAndLicense, queueNotification} from 'app/actions/views/root';
|
||||
import {setAppState, setDeviceToken} from 'mattermost-redux/actions/general';
|
||||
import {logout} from 'mattermost-redux/actions/users';
|
||||
import Config from 'assets/config.json';
|
||||
|
||||
import Root from './root';
|
||||
|
||||
function mapStateToProps(state, ownProps) {
|
||||
const users = state.entities.users;
|
||||
const {currentUserId} = users;
|
||||
const {currentTeamId} = state.entities.teams;
|
||||
const {currentChannelId} = state.entities.channels;
|
||||
|
||||
let locale = Config.DefaultLocale;
|
||||
if (currentUserId && users.profiles[currentUserId]) {
|
||||
|
|
@ -26,28 +20,9 @@ function mapStateToProps(state, ownProps) {
|
|||
|
||||
return {
|
||||
...ownProps,
|
||||
currentTeamId,
|
||||
currentChannelId,
|
||||
locale,
|
||||
navigation: state.navigation
|
||||
currentChannelId: getCurrentChannelId(state),
|
||||
locale
|
||||
};
|
||||
}
|
||||
|
||||
function mapDispatchToProps(dispatch) {
|
||||
return {
|
||||
actions: bindActionCreators({
|
||||
closeDrawers,
|
||||
closeModal,
|
||||
goBack,
|
||||
loadConfigAndLicense,
|
||||
logout,
|
||||
goToNotification,
|
||||
queueNotification,
|
||||
setAppState,
|
||||
setDeviceToken,
|
||||
unrenderDrawer
|
||||
}, dispatch)
|
||||
};
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(Root);
|
||||
export default connect(mapStateToProps)(Root);
|
||||
|
|
|
|||
|
|
@ -1,128 +1,47 @@
|
|||
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
import React, {Component, PropTypes} from 'react';
|
||||
import {
|
||||
Alert,
|
||||
AppState,
|
||||
BackAndroid,
|
||||
InteractionManager,
|
||||
Platform,
|
||||
StatusBar,
|
||||
View
|
||||
} from 'react-native';
|
||||
import React, {PropTypes, PureComponent} from 'react';
|
||||
import {IntlProvider} from 'react-intl';
|
||||
import DeviceInfo from 'react-native-device-info';
|
||||
import semver from 'semver';
|
||||
|
||||
import PushNotification from 'app/components/push_notification';
|
||||
import {getTranslations} from 'app/i18n';
|
||||
|
||||
import Config from 'assets/config';
|
||||
|
||||
import {Client4} from 'mattermost-redux/client';
|
||||
import {General} from 'mattermost-redux/constants';
|
||||
import EventEmitter from 'mattermost-redux/utils/event_emitter';
|
||||
|
||||
export default class Root extends Component {
|
||||
import {ViewTypes} from 'app/constants';
|
||||
import {getTranslations} from 'app/i18n';
|
||||
|
||||
export default class Root extends PureComponent {
|
||||
static propTypes = {
|
||||
children: PropTypes.node,
|
||||
currentTeamId: PropTypes.string,
|
||||
navigator: PropTypes.object,
|
||||
currentChannelId: PropTypes.string,
|
||||
locale: PropTypes.string.isRequired,
|
||||
navigation: PropTypes.object.isRequired,
|
||||
actions: PropTypes.shape({
|
||||
closeDrawers: PropTypes.func.isRequired,
|
||||
closeModal: PropTypes.func.isRequired,
|
||||
goBack: PropTypes.func.isRequired,
|
||||
loadConfigAndLicense: PropTypes.func.isRequired,
|
||||
logout: PropTypes.func.isRequired,
|
||||
setAppState: PropTypes.func.isRequired,
|
||||
unrenderDrawer: PropTypes.func.isRequired
|
||||
}).isRequired
|
||||
locale: PropTypes.string.isRequired
|
||||
};
|
||||
|
||||
componentDidMount() {
|
||||
this.props.actions.setAppState(AppState.currentState === 'active');
|
||||
AppState.addEventListener('change', this.handleAppStateChange);
|
||||
EventEmitter.on(General.CONFIG_CHANGED, this.handleConfigChanged);
|
||||
Client4.setUserAgent(DeviceInfo.getUserAgent());
|
||||
|
||||
if (Platform.OS === 'android') {
|
||||
BackAndroid.addEventListener('hardwareBackPress', this.handleAndroidBack);
|
||||
}
|
||||
componentWillMount() {
|
||||
EventEmitter.on(ViewTypes.NOTIFICATION_IN_APP, this.handleInAppNotification);
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
AppState.removeEventListener('change', this.handleAppStateChange);
|
||||
EventEmitter.off(General.CONFIG_CHANGED, this.handleConfigChanged);
|
||||
|
||||
if (Platform.OS === 'android') {
|
||||
BackAndroid.removeEventListener('hardwareBackPress', this.handleAndroidBack);
|
||||
}
|
||||
EventEmitter.off(ViewTypes.NOTIFICATION_IN_APP, this.handleInAppNotification);
|
||||
}
|
||||
|
||||
handleAppStateChange = (appState) => {
|
||||
this.props.actions.setAppState(appState === 'active');
|
||||
handleInAppNotification = (notification) => {
|
||||
const {data} = notification;
|
||||
const {currentChannelId, navigator} = this.props;
|
||||
|
||||
if (appState === 'inactive') {
|
||||
// TODO: See if we still need this
|
||||
if (data.channel_id !== currentChannelId) {
|
||||
navigator.showInAppNotification({
|
||||
screen: 'Notification',
|
||||
position: 'top',
|
||||
autoDismissTimerSec: 15,
|
||||
dismissWithSwipe: true,
|
||||
passProps: {
|
||||
notification
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
handleAndroidBack = () => {
|
||||
const {index, isModal, leftDrawerOpen, modal} = this.props.navigation;
|
||||
const {closeDrawers, closeModal, goBack} = this.props.actions;
|
||||
|
||||
if (isModal) {
|
||||
if (modal.index > 0) {
|
||||
goBack();
|
||||
return true;
|
||||
}
|
||||
closeModal();
|
||||
return true;
|
||||
}
|
||||
|
||||
if (leftDrawerOpen) {
|
||||
closeDrawers();
|
||||
return true;
|
||||
} else if (index > 0) {
|
||||
goBack();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
handleConfigChanged = (serverVersion) => {
|
||||
const {loadConfigAndLicense} = this.props.actions;
|
||||
const version = serverVersion.match(/^[0-9]*.[0-9]*.[0-9]*(-[a-zA-Z0-9.-]*)?/g)[0];
|
||||
if (serverVersion) {
|
||||
if (semver.valid(version) && semver.lt(version, Config.MinServerVersion)) {
|
||||
Alert.alert(
|
||||
'Server upgrade required',
|
||||
'A server upgrade is required to use the Mattermost app. Please ask your System Administrator for details.',
|
||||
[{
|
||||
text: 'OK',
|
||||
onPress: this.handleVersionUpgrade
|
||||
}]
|
||||
);
|
||||
} else {
|
||||
loadConfigAndLicense(serverVersion);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
handleVersionUpgrade = async () => {
|
||||
const {closeDrawers, logout, unrenderDrawer} = this.props.actions;
|
||||
|
||||
Client4.serverVersion = '';
|
||||
|
||||
closeDrawers();
|
||||
unrenderDrawer();
|
||||
InteractionManager.runAfterInteractions(logout);
|
||||
};
|
||||
|
||||
render() {
|
||||
const locale = this.props.locale;
|
||||
|
||||
|
|
@ -131,11 +50,7 @@ export default class Root extends Component {
|
|||
locale={locale}
|
||||
messages={getTranslations(locale)}
|
||||
>
|
||||
<View style={{flex: 1}}>
|
||||
<StatusBar barStyle='light-content'/>
|
||||
{this.props.children}
|
||||
<PushNotification/>
|
||||
</View>
|
||||
{this.props.children}
|
||||
</IntlProvider>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,11 +2,9 @@
|
|||
// See License.txt for license information.
|
||||
|
||||
import NavigationTypes from './navigation';
|
||||
import StorageTypes from './storage';
|
||||
import ViewTypes from './view';
|
||||
|
||||
export {
|
||||
NavigationTypes,
|
||||
StorageTypes,
|
||||
ViewTypes
|
||||
};
|
||||
|
|
|
|||
|
|
@ -4,21 +4,8 @@
|
|||
import keyMirror from 'mattermost-redux/utils/key_mirror';
|
||||
|
||||
const NavigationTypes = keyMirror({
|
||||
NAVIGATION_PUSH: null,
|
||||
NAVIGATION_POP: null,
|
||||
NAVIGATION_POP_TO_INDEX: null,
|
||||
NAVIGATION_OPEN_LEFT_DRAWER: null,
|
||||
NAVIGATION_RENDER_LEFT_DRAWER: null,
|
||||
NAVIGATION_OPEN_RIGHT_DRAWER: null,
|
||||
NAVIGATION_CLOSE_DRAWERS: null,
|
||||
NAVIGATION_JUMP: null,
|
||||
NAVIGATION_JUMP_TO_INDEX: null,
|
||||
NAVIGATION_REPLACE: null,
|
||||
NAVIGATION_REPLACE_AT_ROOT: null,
|
||||
NAVIGATION_RESET: null,
|
||||
NAVIGATION_MODAL: null,
|
||||
NAVIGATION_CLOSE_MODAL: null,
|
||||
NAVIGATION_REQUEST_CLOSE_MODAL: null
|
||||
NAVIGATION_CLOSE_MODAL: null
|
||||
});
|
||||
|
||||
export default NavigationTypes;
|
||||
|
|
|
|||
|
|
@ -1,15 +0,0 @@
|
|||
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
import keyMirror from 'mattermost-redux/utils/key_mirror';
|
||||
|
||||
const StorageTypes = keyMirror({
|
||||
SAVE_TO_STORAGE: null,
|
||||
SAVE_TO_STORAGE_ERROR: null,
|
||||
LOAD_FROM_STORAGE: null,
|
||||
LOAD_FROM_STORAGE_ERROR: null,
|
||||
REMOVE_FROM_STORAGE: null,
|
||||
REMOVE_FROM_STORAGE_ERROR: null
|
||||
});
|
||||
|
||||
export default StorageTypes;
|
||||
|
|
@ -14,9 +14,8 @@ const ViewTypes = keyMirror({
|
|||
POST_DRAFT_CHANGED: null,
|
||||
COMMENT_DRAFT_CHANGED: null,
|
||||
|
||||
OPTIONS_MODAL_CHANGED: null,
|
||||
|
||||
NOTIFICATION_CHANGED: null,
|
||||
NOTIFICATION_IN_APP: null,
|
||||
|
||||
CONNECTION_CHANGED: null,
|
||||
|
||||
|
|
|
|||
|
|
@ -3,8 +3,6 @@
|
|||
|
||||
import Config from 'assets/config.json';
|
||||
|
||||
import Routes from 'app/navigation/routes';
|
||||
|
||||
const state = {
|
||||
entities: {
|
||||
general: {
|
||||
|
|
@ -252,23 +250,7 @@ const state = {
|
|||
}
|
||||
}
|
||||
},
|
||||
navigation: {
|
||||
index: 0,
|
||||
routes: [
|
||||
Routes.Root
|
||||
],
|
||||
modal: {
|
||||
index: 0,
|
||||
requestClose: false,
|
||||
routes: []
|
||||
},
|
||||
isModal: false,
|
||||
leftDrawerOpen: false,
|
||||
leftDrawerRoute: Routes.ChannelDrawer,
|
||||
rightDrawerOpen: false,
|
||||
rightDrawerRoute: null,
|
||||
shouldRenderDrawer: false
|
||||
},
|
||||
navigation: '',
|
||||
views: {
|
||||
channel: {
|
||||
drafts: {},
|
||||
|
|
@ -284,12 +266,8 @@ const state = {
|
|||
password: ''
|
||||
},
|
||||
notification: null,
|
||||
optionsModal: {
|
||||
title: '',
|
||||
options: [],
|
||||
visible: false
|
||||
},
|
||||
root: {
|
||||
appInitializing: false,
|
||||
hydrationComplete: false
|
||||
},
|
||||
selectServer: {
|
||||
|
|
|
|||
|
|
@ -1,49 +1,169 @@
|
|||
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
import 'harmony-reflect';
|
||||
import React from 'react';
|
||||
import 'babel-polyfill';
|
||||
import Orientation from 'react-native-orientation';
|
||||
import {Provider} from 'react-redux';
|
||||
import {Navigation} from 'react-native-navigation';
|
||||
import DeviceNotification from 'react-native-push-notification';
|
||||
import {
|
||||
Alert,
|
||||
AppState,
|
||||
InteractionManager,
|
||||
Platform
|
||||
} from 'react-native';
|
||||
import DeviceInfo from 'react-native-device-info';
|
||||
import semver from 'semver';
|
||||
|
||||
import Root from 'app/components/root';
|
||||
import Router from 'app/navigation/router';
|
||||
import {setAppState, setDeviceToken} from 'mattermost-redux/actions/general';
|
||||
import {logout} from 'mattermost-redux/actions/users';
|
||||
import {Client4} from 'mattermost-redux/client';
|
||||
import {General} from 'mattermost-redux/constants';
|
||||
import EventEmitter from 'mattermost-redux/utils/event_emitter';
|
||||
|
||||
import {goToNotification, loadConfigAndLicense, queueNotification} from 'app/actions/views/root';
|
||||
import {NavigationTypes, ViewTypes} from 'app/constants';
|
||||
import initialState from 'app/initial_state';
|
||||
import {registerScreens} from 'app/screens';
|
||||
import configureStore from 'app/store';
|
||||
|
||||
import initialState from './initial_state';
|
||||
import Config from 'assets/config';
|
||||
|
||||
const store = configureStore(initialState);
|
||||
registerScreens(store, Provider);
|
||||
|
||||
export default class Mattermost extends React.Component {
|
||||
state = {
|
||||
hydrated: false
|
||||
export default class Mattermost {
|
||||
constructor() {
|
||||
this.isConfigured = false;
|
||||
Orientation.lockToPortrait();
|
||||
this.unsubscribeFromStore = store.subscribe(this.listenForHydration);
|
||||
AppState.addEventListener('change', this.handleAppStateChange);
|
||||
EventEmitter.on(General.CONFIG_CHANGED, this.handleConfigChanged);
|
||||
EventEmitter.on(NavigationTypes.NAVIGATION_RESET, this.handleReset);
|
||||
|
||||
this.handleAppStateChange(AppState.currentState);
|
||||
Client4.setUserAgent(DeviceInfo.getUserAgent());
|
||||
}
|
||||
|
||||
handleAppStateChange = (appState) => {
|
||||
const {dispatch, getState} = store;
|
||||
setAppState(appState === 'active')(dispatch, getState);
|
||||
};
|
||||
|
||||
componentWillMount() {
|
||||
this.unsubscribeFromStore = store.subscribe(this.listenForHydration);
|
||||
}
|
||||
handleConfigChanged = (serverVersion) => {
|
||||
const {dispatch, getState} = store;
|
||||
const version = serverVersion.match(/^[0-9]*.[0-9]*.[0-9]*(-[a-zA-Z0-9.-]*)?/g)[0];
|
||||
if (serverVersion) {
|
||||
if (semver.valid(version) && semver.lt(version, Config.MinServerVersion)) {
|
||||
Alert.alert(
|
||||
'Server upgrade required',
|
||||
'A server upgrade is required to use the Mattermost app. Please ask your System Administrator for details.',
|
||||
[{
|
||||
text: 'OK',
|
||||
onPress: this.handleVersionUpgrade
|
||||
}]
|
||||
);
|
||||
} else {
|
||||
loadConfigAndLicense(serverVersion)(dispatch, getState);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
handleReset = () => {
|
||||
Client4.serverVersion = '';
|
||||
this.startApp('fade');
|
||||
};
|
||||
|
||||
handleVersionUpgrade = async () => {
|
||||
const {dispatch, getState} = store;
|
||||
|
||||
// const {closeDrawers, logout, unrenderDrawer} = this.props.actions;
|
||||
|
||||
Client4.serverVersion = '';
|
||||
|
||||
// closeDrawers();
|
||||
// unrenderDrawer();
|
||||
if (getState().entities.general.credentials.token) {
|
||||
InteractionManager.runAfterInteractions(() => {
|
||||
logout()(dispatch, getState);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// We need to wait for hydration to occur before load the router.
|
||||
listenForHydration = () => {
|
||||
const state = store.getState();
|
||||
if (state.views.root.hydrationComplete) {
|
||||
this.unsubscribeFromStore();
|
||||
this.setState({
|
||||
hydrated: true
|
||||
});
|
||||
this.startApp();
|
||||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
if (!this.state.hydrated) {
|
||||
return null;
|
||||
configurePushNotifications = () => {
|
||||
DeviceNotification.configure({
|
||||
onRegister: this.onRegisterDevice,
|
||||
onNotification: this.onPushNotification,
|
||||
senderID: Config.GooglePlaySenderId,
|
||||
popInitialNotification: true,
|
||||
requestPermissions: true
|
||||
});
|
||||
};
|
||||
|
||||
onRegisterDevice = (data) => {
|
||||
const prefix = Platform.OS === 'ios' ? General.PUSH_NOTIFY_APPLE_REACT_NATIVE : General.PUSH_NOTIFY_ANDROID_REACT_NATIVE;
|
||||
const {dispatch, getState} = store;
|
||||
setDeviceToken(`${prefix}:${data.token}`)(dispatch, getState);
|
||||
this.isConfigured = true;
|
||||
};
|
||||
|
||||
onPushNotification = (deviceNotification) => {
|
||||
const {foreground, userInteraction, data, message} = deviceNotification;
|
||||
let notification;
|
||||
|
||||
if (Platform.OS === 'android') {
|
||||
notification = {
|
||||
data: {
|
||||
channel_id: deviceNotification.channel_id,
|
||||
team_id: deviceNotification.team_id
|
||||
},
|
||||
message
|
||||
};
|
||||
} else {
|
||||
notification = {
|
||||
data,
|
||||
message
|
||||
};
|
||||
}
|
||||
|
||||
return (
|
||||
<Provider store={store}>
|
||||
<Root>
|
||||
<Router/>
|
||||
</Root>
|
||||
</Provider>
|
||||
);
|
||||
}
|
||||
if (foreground) {
|
||||
EventEmitter.emit(ViewTypes.NOTIFICATION_IN_APP, notification);
|
||||
} else if (userInteraction) {
|
||||
const {dispatch, getState} = store;
|
||||
const state = getState();
|
||||
|
||||
if (!state.views.root.appInitializing) {
|
||||
// go to notification if the app is initialized
|
||||
goToNotification(notification)(dispatch, getState);
|
||||
} else if (state.entities.general.credentials.token) {
|
||||
// queue notification if app is not initialized but we are logged in
|
||||
queueNotification(notification)(dispatch, getState);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
startApp = (animationType = 'none') => {
|
||||
this.configurePushNotifications();
|
||||
|
||||
Navigation.startSingleScreenApp({
|
||||
screen: {
|
||||
screen: 'Root',
|
||||
navigatorStyle: {
|
||||
navBarHidden: true,
|
||||
statusBarHidden: false,
|
||||
statusBarHideWithNavBar: false
|
||||
}
|
||||
},
|
||||
animationType
|
||||
});
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,172 +0,0 @@
|
|||
// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
import React, {PropTypes, PureComponent} from 'react';
|
||||
import {
|
||||
Animated,
|
||||
InteractionManager
|
||||
} from 'react-native';
|
||||
|
||||
const {View: AnimatedView} = Animated;
|
||||
|
||||
const ANIMATION_TYPES = {
|
||||
SlideFromBottom: 'slideFromBottom',
|
||||
Fade: 'fade'
|
||||
};
|
||||
|
||||
export default class NavigationModal extends PureComponent {
|
||||
static propTypes = {
|
||||
animationType: PropTypes.oneOf([
|
||||
ANIMATION_TYPES.SlideFromBottom,
|
||||
ANIMATION_TYPES.Fade
|
||||
]),
|
||||
children: PropTypes.node,
|
||||
deviceHeight: PropTypes.number.isRequired,
|
||||
deviceWidth: PropTypes.number.isRequired,
|
||||
duration: PropTypes.number,
|
||||
show: PropTypes.bool
|
||||
}
|
||||
|
||||
static defaultProps = {
|
||||
animationType: ANIMATION_TYPES.SlideFromBottom,
|
||||
duration: 400,
|
||||
show: false
|
||||
}
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
const top = props.show ? 0 : props.deviceHeight;
|
||||
this.state = {
|
||||
top: new Animated.Value(top),
|
||||
opacity: new Animated.Value(100)
|
||||
};
|
||||
}
|
||||
|
||||
componentWillReceiveProps(nextProps) {
|
||||
if (nextProps.show) {
|
||||
let currentRouteKeys;
|
||||
const nextRouteKeys = nextProps.children.props.navigationState.routes.map((r) => r.key).join('');
|
||||
if (this.state.children) {
|
||||
currentRouteKeys = this.state.children.props.navigationState.routes.map((r) => r.key).join('');
|
||||
}
|
||||
|
||||
const routesDidChange = currentRouteKeys !== nextRouteKeys;
|
||||
|
||||
// In order for the scene to be shown throughout the
|
||||
// animated slide down we have to hang on to it by
|
||||
// storing it in state
|
||||
if (routesDidChange) {
|
||||
this.setState({
|
||||
children: nextProps.children
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (nextProps.show !== this.props.show) {
|
||||
const animationType = nextProps.show ? nextProps.animationType : this.props.animationType;
|
||||
|
||||
if (animationType === ANIMATION_TYPES.SlideFromBottom) {
|
||||
InteractionManager.runAfterInteractions(() => {
|
||||
this.slideFromBottomAnimationRunner(nextProps);
|
||||
});
|
||||
} else if (animationType === ANIMATION_TYPES.Fade) {
|
||||
InteractionManager.runAfterInteractions(() => {
|
||||
this.fadeAnimationRunner(nextProps);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fadeAnimationRunner = (nextProps) => {
|
||||
if (nextProps.show) {
|
||||
const setOpacityToZero = Animated.timing(this.state.opacity, {
|
||||
toValue: 0,
|
||||
duration: 0
|
||||
});
|
||||
const setTopToZero = Animated.timing(this.state.top, {
|
||||
toValue: 0,
|
||||
duration: 0
|
||||
});
|
||||
const setOpacityToFull = Animated.timing(this.state.opacity, {
|
||||
toValue: 1,
|
||||
duration: nextProps.duration
|
||||
});
|
||||
Animated.sequence([
|
||||
setOpacityToZero,
|
||||
setTopToZero,
|
||||
setOpacityToFull
|
||||
]).start(() => {
|
||||
// Once the scene has finished sliding down we can release the child scene
|
||||
// which will unmount the scene correctly.
|
||||
if (!this.props.show && !nextProps.show) {
|
||||
this.setState({
|
||||
children: null
|
||||
});
|
||||
}
|
||||
});
|
||||
} else {
|
||||
const setOpacityToZero = Animated.timing(this.state.opacity, {
|
||||
toValue: 0,
|
||||
duration: nextProps.duration
|
||||
});
|
||||
const setTopToDeviceHeight = Animated.timing(this.state.top, {
|
||||
toValue: this.props.deviceHeight,
|
||||
duration: 0
|
||||
});
|
||||
const setOpacityToFull = Animated.timing(this.state.opacity, {
|
||||
toValue: 1,
|
||||
duration: 0
|
||||
});
|
||||
Animated.sequence([
|
||||
setOpacityToZero,
|
||||
setTopToDeviceHeight,
|
||||
setOpacityToFull
|
||||
]).start(() => {
|
||||
// Once the scene has finished sliding down we can release the child scene
|
||||
// which will unmount the scene correctly.
|
||||
if (!this.props.show && !nextProps.show) {
|
||||
this.setState({
|
||||
children: null
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
slideFromBottomAnimationRunner = (nextProps) => {
|
||||
const animateValue = nextProps.show ? 0 : this.props.deviceHeight;
|
||||
|
||||
Animated.timing(this.state.top, {
|
||||
toValue: animateValue,
|
||||
duration: nextProps.duration
|
||||
}).start(() => {
|
||||
// Once the scene has finished sliding down we can release the child scene
|
||||
// which will unmount the scene correctly.
|
||||
if (!this.props.show && !nextProps.show) {
|
||||
this.setState({
|
||||
children: null
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<AnimatedView
|
||||
onLayout={this.onLayout}
|
||||
style={{
|
||||
position: 'absolute',
|
||||
width: this.props.deviceWidth,
|
||||
height: this.props.deviceHeight,
|
||||
top: this.state.top,
|
||||
left: 0,
|
||||
backgroundColor: '#0000',
|
||||
opacity: this.state.opacity
|
||||
}}
|
||||
>
|
||||
{this.state.children}
|
||||
</AnimatedView>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,360 +0,0 @@
|
|||
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
import React, {Component, PropTypes} from 'react';
|
||||
import {
|
||||
Dimensions,
|
||||
Easing,
|
||||
InteractionManager,
|
||||
Keyboard,
|
||||
NavigationExperimental,
|
||||
View
|
||||
} from 'react-native';
|
||||
import {connect} from 'react-redux';
|
||||
import {bindActionCreators} from 'redux';
|
||||
|
||||
import {closeDrawers, goBack, openChannelDrawer} from 'app/actions/navigation';
|
||||
import Drawer from 'app/components/drawer';
|
||||
import FormattedText from 'app/components/formatted_text';
|
||||
import {RouteTransitions} from 'app/navigation/routes';
|
||||
import {getTheme} from 'app/selectors/preferences';
|
||||
|
||||
import EventEmitter from 'mattermost-redux/utils/event_emitter';
|
||||
|
||||
import NavigationModal from './navigation_modal';
|
||||
|
||||
const navigationPanResponder = NavigationExperimental.Card.CardStackPanResponder;
|
||||
|
||||
const {width: deviceWidth, height: deviceHeight} = Dimensions.get('window');
|
||||
|
||||
class Router extends Component {
|
||||
static propTypes = {
|
||||
navigation: PropTypes.object,
|
||||
theme: PropTypes.object,
|
||||
actions: PropTypes.shape({
|
||||
closeDrawers: PropTypes.func.isRequired,
|
||||
goBack: PropTypes.func.isRequired,
|
||||
openChannelDrawer: PropTypes.func.isRequired
|
||||
}).isRequired
|
||||
};
|
||||
|
||||
headerEventSubscriptions = {};
|
||||
|
||||
state = {
|
||||
deviceHeight,
|
||||
deviceWidth
|
||||
};
|
||||
|
||||
emitHeaderEvent = (key) => (event) => {
|
||||
const subscription = this.headerEventSubscriptions[`${key}-${event}`];
|
||||
if (subscription) {
|
||||
subscription();
|
||||
}
|
||||
};
|
||||
|
||||
subscribeToHeaderEvent = (key) => (event, callback) => {
|
||||
this.headerEventSubscriptions[`${key}-${event}`] = callback;
|
||||
};
|
||||
|
||||
unsubscribeFromHeaderEvent = (key) => (event) => {
|
||||
if (this.headerEventSubscriptions[event]) {
|
||||
Reflect.deleteProperty(this.headerEventSubscriptions, `${key}-${event}`);
|
||||
}
|
||||
};
|
||||
|
||||
wrapHeaderComponent = (fx, emitter) => (props) => {
|
||||
if (fx && props.scene.isActive) {
|
||||
return fx(props, emitter, this.props.theme);
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
extractNavigationProps = (route) => {
|
||||
if (!route) {
|
||||
return {};
|
||||
}
|
||||
|
||||
let component = route.component;
|
||||
if (!component) {
|
||||
component = {};
|
||||
}
|
||||
|
||||
return Object.assign({}, route.navigationProps, component.navigationProps);
|
||||
};
|
||||
|
||||
renderTransition = (transitionProps) => {
|
||||
const navigationProps = this.extractNavigationProps(transitionProps.scene.route);
|
||||
let navBar = null;
|
||||
if (!navigationProps.hideNavBar) {
|
||||
const emitter = this.emitHeaderEvent(transitionProps.scene.route.key);
|
||||
const renderLeftComponent = transitionProps.navigationState.index > 0 ? this.wrapHeaderComponent(navigationProps.renderBackButton, emitter) : this.wrapHeaderComponent(navigationProps.renderLeftComponent, emitter);
|
||||
const renderTitleComponent = navigationProps.renderTitleComponent ? this.wrapHeaderComponent(navigationProps.renderTitleComponent, emitter) : this.renderTitle;
|
||||
const renderRightComponent = this.wrapHeaderComponent(navigationProps.renderRightComponent, emitter);
|
||||
|
||||
navBar = (
|
||||
<NavigationExperimental.Header
|
||||
{...transitionProps}
|
||||
onNavigateBack={this.props.actions.goBack}
|
||||
renderLeftComponent={renderLeftComponent}
|
||||
renderTitleComponent={renderTitleComponent}
|
||||
renderRightComponent={renderRightComponent}
|
||||
style={[{backgroundColor: this.props.theme.sidebarHeaderBg}, navigationProps.headerStyle]}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
const renderedScenes = transitionProps.scenes.map((scene) => {
|
||||
const cardProps = {
|
||||
...transitionProps,
|
||||
scene
|
||||
};
|
||||
|
||||
let style;
|
||||
if (scene.route.transition === RouteTransitions.Horizontal) {
|
||||
style = NavigationExperimental.Card.PagerStyleInterpolator.forHorizontal({
|
||||
...cardProps
|
||||
});
|
||||
} else {
|
||||
// have to override shadow props for transparent modals
|
||||
style = {
|
||||
backgroundColor: 'transparent',
|
||||
shadowColor: null,
|
||||
shadowOffset: null,
|
||||
shadowOpacity: null,
|
||||
shadowRadius: null
|
||||
};
|
||||
}
|
||||
|
||||
let panHandlers = null;
|
||||
if (navigationProps.allowSceneSwipe) {
|
||||
panHandlers = navigationPanResponder.forHorizontal({
|
||||
...cardProps,
|
||||
gestureResponseDistance: (this.state.deviceWidth / 4), // sets the distance from the edge for swiping
|
||||
onNavigateBack: this.props.actions.goBack
|
||||
});
|
||||
}
|
||||
return (
|
||||
<NavigationExperimental.Card
|
||||
{...cardProps}
|
||||
onNavigateBack={this.props.actions.goBack}
|
||||
panHandlers={panHandlers}
|
||||
style={style}
|
||||
renderScene={this.renderScene}
|
||||
key={scene.key}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
return (
|
||||
<View style={{flex: 1, flexDirection: 'column-reverse'}}>
|
||||
<View style={{flex: 1}}>
|
||||
{renderedScenes}
|
||||
</View>
|
||||
{navBar}
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
renderTitle = ({scene}) => {
|
||||
const navigationProps = this.extractNavigationProps(scene.route);
|
||||
const title = navigationProps.title;
|
||||
|
||||
if (!title) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<NavigationExperimental.Header.Title>
|
||||
<FormattedText
|
||||
id={title.id}
|
||||
defaultMessage={title.defaultMessage}
|
||||
/>
|
||||
</NavigationExperimental.Header.Title>
|
||||
);
|
||||
};
|
||||
|
||||
renderScene = ({scene}) => {
|
||||
return this.renderRoute(scene.route);
|
||||
};
|
||||
|
||||
renderRoute = (route) => {
|
||||
const SceneComponent = route.component;
|
||||
|
||||
const emitterSubscriber = this.subscribeToHeaderEvent(route.key);
|
||||
const emitterUnsubscriber = this.unsubscribeFromHeaderEvent(route.key);
|
||||
|
||||
return (
|
||||
<SceneComponent
|
||||
subscribeToHeaderEvent={emitterSubscriber}
|
||||
unsubscribeFromHeaderEvent={emitterUnsubscriber}
|
||||
{...route.props}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
configureTransition = () => {
|
||||
return {
|
||||
duration: 300,
|
||||
easing: Easing.inOut(Easing.ease)
|
||||
};
|
||||
};
|
||||
|
||||
onLayout = (event) => {
|
||||
if (event.nativeEvent.layout.width !== this.state.deviceWidth) {
|
||||
this.setState({
|
||||
deviceHeight: event.nativeEvent.layout.height,
|
||||
deviceWidth: event.nativeEvent.layout.width
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
handleDrawerTween = (ratio) => {
|
||||
const opacity = (ratio / 2);
|
||||
|
||||
EventEmitter.emit('drawer_opacity', opacity);
|
||||
|
||||
return {
|
||||
mainOverlay: {
|
||||
backgroundColor: this.props.theme.centerChannelBg,
|
||||
opacity
|
||||
},
|
||||
drawerOverlay: {
|
||||
backgroundColor: ratio ? '#000' : '#FFF',
|
||||
opacity: ratio ? (1 - ratio) / 2 : 1
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
handleLeftDrawerClose = () => {
|
||||
setTimeout(() => {
|
||||
InteractionManager.clearInteractionHandle(this.closeLeftHandle);
|
||||
});
|
||||
};
|
||||
|
||||
handleLeftDrawerCloseStart = () => {
|
||||
this.closeLeftHandle = InteractionManager.createInteractionHandle();
|
||||
};
|
||||
|
||||
handleLeftDrawerOpen = () => {
|
||||
Keyboard.dismiss();
|
||||
setTimeout(() => {
|
||||
InteractionManager.clearInteractionHandle(this.openLeftHandle);
|
||||
});
|
||||
};
|
||||
|
||||
handleLeftDrawerOpenStart = () => {
|
||||
this.props.actions.openChannelDrawer();
|
||||
this.openLeftHandle = InteractionManager.createInteractionHandle();
|
||||
};
|
||||
|
||||
render = () => {
|
||||
const {
|
||||
index,
|
||||
isModal: modalVisible,
|
||||
leftDrawerOpen,
|
||||
leftDrawerRoute,
|
||||
modal,
|
||||
shouldRenderDrawer,
|
||||
routes
|
||||
} = this.props.navigation;
|
||||
|
||||
const navigationProps = this.extractNavigationProps(routes[index]);
|
||||
const modalNavigationProps = this.extractNavigationProps(modal.routes[0]);
|
||||
|
||||
let leftDrawerContent;
|
||||
if (leftDrawerRoute && shouldRenderDrawer) {
|
||||
leftDrawerContent = this.renderRoute(leftDrawerRoute);
|
||||
}
|
||||
|
||||
return (
|
||||
<View
|
||||
style={{flex: 1}}
|
||||
onLayout={this.onLayout}
|
||||
>
|
||||
<Drawer
|
||||
open={leftDrawerOpen}
|
||||
onOpenStart={this.handleLeftDrawerOpenStart}
|
||||
onOpen={this.handleLeftDrawerOpen}
|
||||
onCloseStart={this.handleLeftDrawerCloseStart}
|
||||
onClose={this.handleLeftDrawerClose}
|
||||
type='static'
|
||||
disabled={modalVisible}
|
||||
content={leftDrawerContent}
|
||||
tapToClose={true}
|
||||
openDrawerOffset={40}
|
||||
onRequestClose={this.props.actions.closeDrawers}
|
||||
panOpenMask={0.2}
|
||||
panCloseMask={40}
|
||||
panThreshold={0.25}
|
||||
acceptPan={navigationProps.allowMenuSwipe}
|
||||
negotiatePan={true}
|
||||
useInteractionManager={false}
|
||||
tweenHandler={this.handleDrawerTween}
|
||||
elevation={-5}
|
||||
styles={{
|
||||
main: {shadowColor: '#000000', shadowOpacity: 0.4, shadowRadius: 12, shadowOffset: {width: -4, height: 0}}
|
||||
}}
|
||||
>
|
||||
<Drawer
|
||||
open={false}
|
||||
type='displace'
|
||||
side='right'
|
||||
disabled={true}
|
||||
content={null}
|
||||
tapToClose={true}
|
||||
openDrawerOffset={50}
|
||||
onRequestClose={this.props.actions.closeDrawers}
|
||||
panOpenMask={0}
|
||||
panCloseMask={50}
|
||||
panThreshold={0}
|
||||
acceptPan={false}
|
||||
negotiatePan={false}
|
||||
useInteractionManager={false}
|
||||
tweenHandler={this.handleDrawerTween}
|
||||
>
|
||||
<NavigationExperimental.Transitioner
|
||||
style={{flex: 1}}
|
||||
navigationState={this.props.navigation}
|
||||
render={this.renderTransition}
|
||||
configureTransition={this.configureTransition}
|
||||
/>
|
||||
</Drawer>
|
||||
</Drawer>
|
||||
<NavigationModal
|
||||
animationType={modalNavigationProps.modalAnimationType}
|
||||
deviceHeight={this.state.deviceHeight}
|
||||
deviceWidth={this.state.deviceWidth}
|
||||
duration={modalNavigationProps.duration}
|
||||
show={modalVisible}
|
||||
>
|
||||
<NavigationExperimental.Transitioner
|
||||
style={{flex: 1}}
|
||||
navigationState={modal}
|
||||
render={this.renderTransition}
|
||||
configureTransition={this.configureTransition}
|
||||
/>
|
||||
</NavigationModal>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
function mapStateToProps(state) {
|
||||
return {
|
||||
navigation: state.navigation,
|
||||
theme: getTheme(state)
|
||||
};
|
||||
}
|
||||
|
||||
function mapDispatchToProps(dispatch) {
|
||||
return {
|
||||
actions: bindActionCreators({
|
||||
closeDrawers,
|
||||
goBack,
|
||||
openChannelDrawer
|
||||
}, dispatch)
|
||||
};
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(Router);
|
||||
|
|
@ -1,225 +0,0 @@
|
|||
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
import {
|
||||
About,
|
||||
AccountNotifications,
|
||||
AccountSettings,
|
||||
ChannelView,
|
||||
ChannelDrawer,
|
||||
ChannelInfo,
|
||||
ChannelMembers,
|
||||
ChannelAddMembers,
|
||||
CreateChannel,
|
||||
EditPost,
|
||||
ImagePreview,
|
||||
LoadTeam,
|
||||
Login,
|
||||
LoginOptions,
|
||||
Mfa,
|
||||
MoreChannels,
|
||||
MoreDirectMessages,
|
||||
OptionsModal,
|
||||
Settings,
|
||||
Root,
|
||||
Saml,
|
||||
Search,
|
||||
SelectServer,
|
||||
SelectTeam,
|
||||
Thread,
|
||||
UserProfile
|
||||
} from 'app/scenes';
|
||||
|
||||
import keyMirror from 'mattermost-redux/utils/key_mirror';
|
||||
|
||||
export const RouteTransitions = keyMirror({
|
||||
Horizontal: null
|
||||
});
|
||||
|
||||
export const Routes = {
|
||||
About: {
|
||||
key: 'About',
|
||||
transition: RouteTransitions.Horizontal,
|
||||
component: About,
|
||||
navigationProps: {
|
||||
title: {id: 'about.titles', defaultMessage: 'About Mattermost'}
|
||||
}
|
||||
},
|
||||
AccountNotifications: {
|
||||
key: 'About',
|
||||
transition: RouteTransitions.Horizontal,
|
||||
component: AccountNotifications,
|
||||
navigationProps: {
|
||||
title: {id: 'user.settings.modal.notifications', defaultMessage: 'Notifications'}
|
||||
}
|
||||
},
|
||||
AccountSettings: {
|
||||
key: 'AccountSettings',
|
||||
transition: RouteTransitions.Horizontal,
|
||||
component: AccountSettings,
|
||||
navigationProps: {
|
||||
title: {id: 'user.settings.modal.title', defaultMessage: 'Account Settings'}
|
||||
}
|
||||
},
|
||||
ChannelInfo: {
|
||||
key: 'ChannelInfo',
|
||||
transition: RouteTransitions.Horizontal,
|
||||
component: ChannelInfo,
|
||||
navigationProps: {
|
||||
title: {id: 'mobile.routes.channelInfo', defaultMessage: 'Info'}
|
||||
}
|
||||
},
|
||||
ChannelDrawer: {
|
||||
key: 'ChannelDrawer',
|
||||
component: ChannelDrawer
|
||||
},
|
||||
ChannelMembers: {
|
||||
key: 'ChannelMembers',
|
||||
transition: RouteTransitions.Horizontal,
|
||||
component: ChannelMembers,
|
||||
navigationProps: {
|
||||
title: {id: 'channel_header.manageMembers', defaultMessage: 'Manage Members'}
|
||||
}
|
||||
},
|
||||
ChannelAddMembers: {
|
||||
key: 'ChannelAddMembers',
|
||||
transition: RouteTransitions.Horizontal,
|
||||
component: ChannelAddMembers,
|
||||
navigationProps: {
|
||||
title: {id: 'channel_header.addMembers', defaultMessage: 'Add Members'}
|
||||
}
|
||||
},
|
||||
ChannelView: {
|
||||
key: 'ChannelView',
|
||||
transition: RouteTransitions.Horizontal,
|
||||
component: ChannelView
|
||||
},
|
||||
CreatePublicChannel: {
|
||||
key: 'CreatePublicChannel',
|
||||
component: CreateChannel,
|
||||
transition: RouteTransitions.Horizontal,
|
||||
navigationProps: {
|
||||
title: {id: 'mobile.create_channel.public', defaultMessage: 'New Public Channel'}
|
||||
}
|
||||
},
|
||||
CreatePrivateChannel: {
|
||||
key: 'CreatePrivateChannel',
|
||||
component: CreateChannel,
|
||||
navigationProps: {
|
||||
title: {id: 'mobile.create_channel.private', defaultMessage: 'New Private Channel'}
|
||||
}
|
||||
},
|
||||
EditPost: {
|
||||
key: 'EditPost',
|
||||
component: EditPost,
|
||||
navigationProps: {
|
||||
title: {id: 'mobile.edit_post.title', defaultMessage: 'Editing Message'}
|
||||
}
|
||||
},
|
||||
ImagePreview: {
|
||||
key: 'ImagePreview',
|
||||
component: ImagePreview
|
||||
},
|
||||
LoadTeam: {
|
||||
key: 'LoadTeam',
|
||||
component: LoadTeam
|
||||
},
|
||||
Login: {
|
||||
key: 'Login',
|
||||
transition: RouteTransitions.Horizontal,
|
||||
component: Login,
|
||||
navigationProps: {
|
||||
title: {id: 'mobile.routes.login', defaultMessage: 'Login'}
|
||||
}
|
||||
},
|
||||
LoginOptions: {
|
||||
key: 'LoginOptions',
|
||||
transition: RouteTransitions.Horizontal,
|
||||
component: LoginOptions,
|
||||
navigationProps: {
|
||||
title: {id: 'mobile.routes.loginOptions', defaultMessage: 'Login Chooser'}
|
||||
}
|
||||
},
|
||||
Mfa: {
|
||||
key: 'Mfa',
|
||||
transition: RouteTransitions.Horizontal,
|
||||
component: Mfa,
|
||||
navigationProps: {
|
||||
title: {id: 'mobile.routes.mfa', defaultMessage: 'Multi-factor Authentication'}
|
||||
}
|
||||
},
|
||||
MoreChannels: {
|
||||
key: 'MoreChannels',
|
||||
component: MoreChannels,
|
||||
navigationProps: {
|
||||
title: {id: 'more_channels.title', defaultMessage: 'More Channels'}
|
||||
}
|
||||
},
|
||||
MoreDirectMessages: {
|
||||
key: 'MoreDirectMessages',
|
||||
component: MoreDirectMessages,
|
||||
navigationProps: {
|
||||
title: {id: 'more_direct_channels.title', defaultMessage: 'Direct Messages'}
|
||||
}
|
||||
},
|
||||
OptionsModal: {
|
||||
key: 'OptionsModal',
|
||||
component: OptionsModal
|
||||
},
|
||||
Root: {
|
||||
key: 'Root',
|
||||
component: Root
|
||||
},
|
||||
Saml: {
|
||||
key: 'Saml',
|
||||
transition: RouteTransitions.Horizontal,
|
||||
component: Saml,
|
||||
navigationProps: {
|
||||
title: {id: 'mobile.routes.saml', defaultMessage: 'Single SignOn'}
|
||||
}
|
||||
},
|
||||
Search: {
|
||||
key: 'Search',
|
||||
transition: RouteTransitions.Horizontal,
|
||||
component: Search
|
||||
},
|
||||
SelectServer: {
|
||||
key: 'SelectServer',
|
||||
component: SelectServer,
|
||||
navigationProps: {
|
||||
title: {id: 'mobile.routes.enterServerUrl', defaultMessage: 'Enter Server URL'},
|
||||
duration: 0,
|
||||
modalAnimationType: 'fade'
|
||||
}
|
||||
},
|
||||
SelectTeam: {
|
||||
key: 'SelectTeam',
|
||||
component: SelectTeam,
|
||||
transition: RouteTransitions.Horizontal,
|
||||
navigationProps: {
|
||||
title: {id: 'mobile.routes.selectTeam', defaultMessage: 'Select Team'}
|
||||
}
|
||||
},
|
||||
Settings: {
|
||||
key: 'Settings',
|
||||
component: Settings,
|
||||
navigationProps: {
|
||||
title: {id: 'mobile.routes.settings', defaultMessage: 'Settings'}
|
||||
}
|
||||
},
|
||||
Thread: {
|
||||
key: 'Thread',
|
||||
transition: RouteTransitions.Horizontal,
|
||||
component: Thread
|
||||
},
|
||||
UserProfile: {
|
||||
key: 'UserProfile',
|
||||
transition: RouteTransitions.Horizontal,
|
||||
component: UserProfile,
|
||||
navigationProps: {
|
||||
title: {id: 'mobile.routes.user_profile', defaultMessage: 'Profile'}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export default Routes;
|
||||
|
|
@ -1,229 +1,16 @@
|
|||
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
import {NavigationExperimental} from 'react-native';
|
||||
|
||||
import {UserTypes} from 'mattermost-redux/action_types';
|
||||
import {NavigationTypes} from 'app/constants';
|
||||
import {UserTypes} from 'mattermost-redux/action_types';
|
||||
import EventEmitter from 'mattermost-redux/utils/event_emitter';
|
||||
|
||||
import Routes from 'app/navigation/routes';
|
||||
|
||||
const initialState = {
|
||||
index: 0,
|
||||
routes: [
|
||||
Routes.Root
|
||||
],
|
||||
modal: {
|
||||
index: 0,
|
||||
requestClose: false,
|
||||
routes: []
|
||||
},
|
||||
isModal: false,
|
||||
leftDrawerOpen: false,
|
||||
leftDrawerRoute: null,
|
||||
rightDrawerOpen: false,
|
||||
rightDrawerRoute: null,
|
||||
shouldRenderDrawer: false
|
||||
};
|
||||
|
||||
export default function(state = initialState, action) {
|
||||
export default function(state = '', action) {
|
||||
switch (action.type) {
|
||||
case NavigationTypes.NAVIGATION_PUSH: {
|
||||
if (state.isModal) {
|
||||
const modalState = NavigationExperimental.StateUtils.push(state.modal, {props: action.props, ...action.route});
|
||||
return {
|
||||
...state,
|
||||
modal: modalState
|
||||
};
|
||||
}
|
||||
return NavigationExperimental.StateUtils.push(state, action.route);
|
||||
}
|
||||
|
||||
case NavigationTypes.NAVIGATION_POP:
|
||||
if (!state.isModal && (state.leftDrawerOpen || state.rightDrawerOpen)) {
|
||||
return {
|
||||
...state,
|
||||
leftDrawerOpen: false,
|
||||
rightDrawerOpen: false
|
||||
};
|
||||
}
|
||||
|
||||
if (state.isModal) {
|
||||
const nextModalState = state.modal.routes.length > 1 ?
|
||||
NavigationExperimental.StateUtils.pop(state.modal) :
|
||||
{
|
||||
index: 0,
|
||||
routes: []
|
||||
};
|
||||
|
||||
nextModalState.requestClose = false;
|
||||
|
||||
const isModal = nextModalState.routes.length > 0;
|
||||
return {
|
||||
...state,
|
||||
modal: nextModalState,
|
||||
isModal
|
||||
};
|
||||
}
|
||||
|
||||
return NavigationExperimental.StateUtils.pop(state);
|
||||
|
||||
case NavigationTypes.NAVIGATION_POP_TO_INDEX: {
|
||||
let newState = {...state};
|
||||
|
||||
if (!newState.isModal && (newState.leftDrawerOpen || newState.rightDrawerOpen)) {
|
||||
newState = {
|
||||
...newState,
|
||||
leftDrawerOpen: false,
|
||||
rightDrawerOpen: false
|
||||
};
|
||||
}
|
||||
|
||||
if (newState.isModal) {
|
||||
newState = {
|
||||
...newState,
|
||||
modal: {
|
||||
index: 0,
|
||||
routes: []
|
||||
},
|
||||
isModal: false
|
||||
};
|
||||
}
|
||||
|
||||
if (action.index === newState.index || action.index > newState.routes.length - 1) {
|
||||
return newState;
|
||||
}
|
||||
|
||||
const nextRoutes = newState.routes.slice(0, action.index + 1);
|
||||
|
||||
return {
|
||||
...newState,
|
||||
index: action.index,
|
||||
routes: nextRoutes
|
||||
};
|
||||
}
|
||||
|
||||
case NavigationTypes.NAVIGATION_OPEN_LEFT_DRAWER:
|
||||
return {
|
||||
...state,
|
||||
leftDrawerOpen: true,
|
||||
leftDrawerRoute: action.route
|
||||
};
|
||||
|
||||
case NavigationTypes.NAVIGATION_OPEN_RIGHT_DRAWER:
|
||||
return {
|
||||
...state,
|
||||
rightDrawerOpen: true,
|
||||
rightDrawerRoute: action.route
|
||||
};
|
||||
|
||||
case NavigationTypes.NAVIGATION_CLOSE_DRAWERS:
|
||||
return {
|
||||
...state,
|
||||
leftDrawerOpen: false,
|
||||
rightDrawerOpen: false
|
||||
};
|
||||
|
||||
case NavigationTypes.NAVIGATION_JUMP: {
|
||||
if (state.isModal) {
|
||||
const modalState = NavigationExperimental.StateUtils.jumpTo(state.modal, action.key);
|
||||
return {
|
||||
...state,
|
||||
modal: modalState
|
||||
};
|
||||
}
|
||||
|
||||
return NavigationExperimental.StateUtils.jumpTo(state, action.key);
|
||||
}
|
||||
|
||||
case NavigationTypes.NAVIGATION_JUMP_TO_INDEX: {
|
||||
if (state.isModal) {
|
||||
const modalState = NavigationExperimental.StateUtils.jumpToIndex(state.modal, action.index);
|
||||
return {
|
||||
...state,
|
||||
modal: modalState
|
||||
};
|
||||
}
|
||||
|
||||
return NavigationExperimental.StateUtils.jumpToIndex(state, action.index);
|
||||
}
|
||||
|
||||
case NavigationTypes.NAVIGATION_RESET:
|
||||
return {
|
||||
...state,
|
||||
...NavigationExperimental.StateUtils.reset(state, action.routes),
|
||||
modal: {
|
||||
index: 0,
|
||||
requestClose: false,
|
||||
routes: []
|
||||
},
|
||||
isModal: false,
|
||||
leftDrawerOpen: false,
|
||||
rightDrawerOpen: false
|
||||
};
|
||||
|
||||
case NavigationTypes.NAVIGATION_REPLACE: {
|
||||
if (state.isModal) {
|
||||
const modalState = NavigationExperimental.StateUtils.replaceAtIndex(state.modal, state.modal.index, action.index);
|
||||
return {
|
||||
...state,
|
||||
modal: modalState
|
||||
};
|
||||
}
|
||||
|
||||
return NavigationExperimental.StateUtils.replaceAtIndex(state, state.index, action.route);
|
||||
}
|
||||
|
||||
case NavigationTypes.NAVIGATION_REPLACE_AT_ROOT:
|
||||
return NavigationExperimental.StateUtils.replaceAtIndex(state, state.index, action.route);
|
||||
|
||||
case NavigationTypes.NAVIGATION_MODAL: {
|
||||
const modal = {
|
||||
index: 0,
|
||||
routes: [
|
||||
{
|
||||
...action.route
|
||||
}
|
||||
]
|
||||
};
|
||||
return {
|
||||
...state,
|
||||
modal,
|
||||
isModal: true
|
||||
};
|
||||
}
|
||||
|
||||
case NavigationTypes.NAVIGATION_CLOSE_MODAL:
|
||||
return {
|
||||
...state,
|
||||
modal: {
|
||||
index: 0,
|
||||
requestClose: false,
|
||||
routes: []
|
||||
},
|
||||
isModal: false
|
||||
};
|
||||
|
||||
case NavigationTypes.NAVIGATION_REQUEST_CLOSE_MODAL: {
|
||||
const modalState = Object.assign({}, state.modal, {requestClose: true});
|
||||
|
||||
return {
|
||||
...state,
|
||||
modal: modalState
|
||||
};
|
||||
}
|
||||
|
||||
case NavigationTypes.NAVIGATION_RENDER_LEFT_DRAWER: {
|
||||
return {
|
||||
...state,
|
||||
shouldRenderDrawer: action.data
|
||||
};
|
||||
}
|
||||
|
||||
case UserTypes.LOGOUT_SUCCESS:
|
||||
return NavigationExperimental.StateUtils.reset(state, initialState.routes, initialState.index);
|
||||
|
||||
default:
|
||||
return state;
|
||||
EventEmitter.emit(NavigationTypes.NAVIGATION_RESET);
|
||||
break;
|
||||
}
|
||||
|
||||
return state;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
// See License.txt for license information.
|
||||
|
||||
import {combineReducers} from 'redux';
|
||||
import {ChannelTypes, FileTypes, UserTypes} from 'mattermost-redux/action_types';
|
||||
import {ChannelTypes, FileTypes} from 'mattermost-redux/action_types';
|
||||
|
||||
import {ViewTypes} from 'app/constants';
|
||||
|
||||
|
|
@ -172,19 +172,7 @@ function loading(state = false, action) {
|
|||
}
|
||||
}
|
||||
|
||||
function appInitializing(state = true, action) {
|
||||
switch (action.type) {
|
||||
case ViewTypes.APPLICATION_INITIALIZED:
|
||||
return false;
|
||||
case UserTypes.RESET_LOGOUT_STATE:
|
||||
return true;
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
}
|
||||
|
||||
export default combineReducers({
|
||||
appInitializing,
|
||||
drafts,
|
||||
loading
|
||||
});
|
||||
|
|
|
|||
|
|
@ -9,7 +9,6 @@ import fetchCache from './fetch_cache';
|
|||
import i18n from './i18n';
|
||||
import login from './login';
|
||||
import notification from './notification';
|
||||
import optionsModal from './options_modal';
|
||||
import root from './root';
|
||||
import selectServer from './select_server';
|
||||
import team from './team';
|
||||
|
|
@ -22,7 +21,6 @@ export default combineReducers({
|
|||
i18n,
|
||||
login,
|
||||
notification,
|
||||
optionsModal,
|
||||
root,
|
||||
selectServer,
|
||||
team,
|
||||
|
|
|
|||
|
|
@ -1,46 +0,0 @@
|
|||
// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
import {combineReducers} from 'redux';
|
||||
|
||||
import {UserTypes} from 'mattermost-redux/action_types';
|
||||
import {ViewTypes} from 'app/constants';
|
||||
|
||||
function title(state = '', action) {
|
||||
switch (action.type) {
|
||||
case ViewTypes.OPTIONS_MODAL_CHANGED:
|
||||
return action.data.title;
|
||||
case UserTypes.LOGOUT_SUCCESS:
|
||||
return '';
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
}
|
||||
|
||||
function options(state = [], action) {
|
||||
switch (action.type) {
|
||||
case ViewTypes.OPTIONS_MODAL_CHANGED:
|
||||
return action.data.options;
|
||||
case UserTypes.LOGOUT_SUCCESS:
|
||||
return [];
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
}
|
||||
|
||||
function visible(state = false, action) {
|
||||
switch (action.type) {
|
||||
case ViewTypes.OPTIONS_MODAL_CHANGED:
|
||||
return action.data.visible;
|
||||
case UserTypes.LOGOUT_SUCCESS:
|
||||
return false;
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
}
|
||||
|
||||
export default combineReducers({
|
||||
title,
|
||||
options,
|
||||
visible
|
||||
});
|
||||
|
|
@ -2,7 +2,22 @@
|
|||
// See License.txt for license information.
|
||||
|
||||
import {combineReducers} from 'redux';
|
||||
|
||||
import {General} from 'mattermost-redux/constants';
|
||||
import {UserTypes} from 'mattermost-redux/action_types';
|
||||
|
||||
import {ViewTypes} from 'app/constants';
|
||||
|
||||
function appInitializing(state = true, action) {
|
||||
switch (action.type) {
|
||||
case ViewTypes.APPLICATION_INITIALIZED:
|
||||
return false;
|
||||
case UserTypes.RESET_LOGOUT_STATE:
|
||||
return true;
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
}
|
||||
|
||||
function hydrationComplete(state = false, action) {
|
||||
switch (action.type) {
|
||||
|
|
@ -14,5 +29,6 @@ function hydrationComplete(state = false, action) {
|
|||
}
|
||||
|
||||
export default combineReducers({
|
||||
appInitializing,
|
||||
hydrationComplete
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,95 +0,0 @@
|
|||
// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
import React, {PropTypes, PureComponent} from 'react';
|
||||
import {connect} from 'react-redux';
|
||||
import {
|
||||
TouchableOpacity,
|
||||
View
|
||||
} from 'react-native';
|
||||
|
||||
import FormattedText from 'app/components/formatted_text';
|
||||
import Loading from 'app/components/loading';
|
||||
|
||||
import {getTheme} from 'app/selectors/preferences';
|
||||
import EventEmitter from 'mattermost-redux/utils/event_emitter';
|
||||
|
||||
class AccountNotifcationsButton extends PureComponent {
|
||||
static propTypes = {
|
||||
emitter: PropTypes.func.isRequired,
|
||||
theme: PropTypes.object
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
theme: {}
|
||||
};
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
loading: false
|
||||
};
|
||||
}
|
||||
|
||||
componentWillMount() {
|
||||
EventEmitter.on('saving_notify_props', this.onLoading);
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
EventEmitter.off('saving_notify_props', this.onLoading);
|
||||
}
|
||||
|
||||
onLoading = (loading) => {
|
||||
this.setState({loading});
|
||||
};
|
||||
|
||||
onPress = () => {
|
||||
this.props.emitter('save_notify_props');
|
||||
};
|
||||
|
||||
render() {
|
||||
const {theme} = this.props;
|
||||
const {loading} = this.state;
|
||||
const color = theme.sidebarHeaderTextColor;
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<Loading
|
||||
color={color}
|
||||
size='small'
|
||||
style={{
|
||||
alignItems: 'center',
|
||||
flex: 1,
|
||||
flexDirection: 'column',
|
||||
justifyContent: 'center',
|
||||
paddingHorizontal: 20
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<View style={{flexDirection: 'column', justifyContent: 'center', alignItems: 'center', flex: 1}}>
|
||||
<TouchableOpacity
|
||||
onPress={this.onPress}
|
||||
style={{paddingHorizontal: 15}}
|
||||
>
|
||||
<FormattedText
|
||||
id='add_command.save'
|
||||
defaultMessage='Save'
|
||||
style={{color}}
|
||||
/>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function mapStateToProps(state) {
|
||||
return {
|
||||
theme: getTheme(state)
|
||||
};
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps)(AccountNotifcationsButton);
|
||||
|
|
@ -1,73 +0,0 @@
|
|||
// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
import React, {PropTypes, PureComponent} from 'react';
|
||||
import {InteractionManager} from 'react-native';
|
||||
|
||||
import ChannelDrawerList from 'app/components/channel_drawer_list';
|
||||
|
||||
export default class ChannelDrawer extends PureComponent {
|
||||
static propTypes = {
|
||||
actions: PropTypes.shape({
|
||||
closeDrawers: PropTypes.func.isRequired,
|
||||
handleSelectChannel: PropTypes.func.isRequired,
|
||||
viewChannel: PropTypes.func.isRequired,
|
||||
markChannelAsRead: PropTypes.func.isRequired,
|
||||
setChannelLoading: PropTypes.func.isRequired
|
||||
}).isRequired,
|
||||
currentTeam: PropTypes.object,
|
||||
currentChannel: PropTypes.object,
|
||||
channels: PropTypes.object,
|
||||
channelMembers: PropTypes.object,
|
||||
theme: PropTypes.object.isRequired
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
currentTeam: {},
|
||||
currentChannel: {}
|
||||
};
|
||||
|
||||
selectChannel = (id) => {
|
||||
const {
|
||||
actions,
|
||||
currentChannel
|
||||
} = this.props;
|
||||
|
||||
const {
|
||||
closeDrawers,
|
||||
handleSelectChannel,
|
||||
markChannelAsRead,
|
||||
setChannelLoading,
|
||||
viewChannel
|
||||
} = actions;
|
||||
|
||||
markChannelAsRead(id, currentChannel.id);
|
||||
setChannelLoading();
|
||||
viewChannel(id);
|
||||
closeDrawers();
|
||||
InteractionManager.runAfterInteractions(() => {
|
||||
handleSelectChannel(id);
|
||||
});
|
||||
};
|
||||
|
||||
render() {
|
||||
const {
|
||||
currentChannel,
|
||||
currentTeam,
|
||||
channels,
|
||||
channelMembers,
|
||||
theme
|
||||
} = this.props;
|
||||
|
||||
return (
|
||||
<ChannelDrawerList
|
||||
currentTeam={currentTeam}
|
||||
currentChannel={currentChannel}
|
||||
channels={channels}
|
||||
channelMembers={channelMembers}
|
||||
theme={theme}
|
||||
onSelectChannel={this.selectChannel}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,44 +0,0 @@
|
|||
// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
import React, {PropTypes} from 'react';
|
||||
import {connect} from 'react-redux';
|
||||
import {View} from 'react-native';
|
||||
|
||||
import {getCurrentChannel, canManageChannelMembers} from 'mattermost-redux/selectors/entities/channels';
|
||||
import {getTheme} from 'app/selectors/preferences';
|
||||
|
||||
import FormattedText from 'app/components/formatted_text';
|
||||
|
||||
function ChannelMembersTitle(props) {
|
||||
const {canManageUsers} = props;
|
||||
return (
|
||||
<View style={{alignItems: 'center', justifyContent: 'center', flex: 1, marginHorizontal: 50}}>
|
||||
<FormattedText
|
||||
id={canManageUsers ? 'channel_header.manageMembers' : 'channel_header.viewMembers'}
|
||||
defaultMessage={canManageUsers ? 'Manage Members' : 'View Members'}
|
||||
style={{color: props.theme.sidebarHeaderTextColor, fontSize: 15, fontWeight: 'bold', textAlign: 'center'}}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
ChannelMembersTitle.propTypes = {
|
||||
canManageUsers: PropTypes.bool.isRequired,
|
||||
currentChannel: PropTypes.object.isRequired,
|
||||
theme: PropTypes.object.isRequired
|
||||
};
|
||||
|
||||
ChannelMembersTitle.defaultProps = {
|
||||
theme: {}
|
||||
};
|
||||
|
||||
function mapStateToProps(state) {
|
||||
return {
|
||||
canManageUsers: canManageChannelMembers(state),
|
||||
currentChannel: getCurrentChannel(state),
|
||||
theme: getTheme(state)
|
||||
};
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps)(ChannelMembersTitle);
|
||||
|
|
@ -1,41 +0,0 @@
|
|||
// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
import React, {PropTypes} from 'react';
|
||||
import {connect} from 'react-redux';
|
||||
|
||||
import ActionButton from 'app/components/action_button';
|
||||
|
||||
import {canManageChannelMembers} from 'mattermost-redux/selectors/entities/channels';
|
||||
|
||||
function RemoveMemberButton(props) {
|
||||
const {canManageUsers} = props;
|
||||
if (!canManageUsers) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<ActionButton
|
||||
actionEventName='remove_members'
|
||||
emitter={props.emitter}
|
||||
enabled={false}
|
||||
enableEventName='can_remove_members'
|
||||
labelDefaultMessage='Remove'
|
||||
labelId='channel_members_modal.remove'
|
||||
loadingEventName='removing_members'
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
RemoveMemberButton.propTypes = {
|
||||
emitter: PropTypes.func.isRequired,
|
||||
canManageUsers: PropTypes.bool.isRequired
|
||||
};
|
||||
|
||||
function mapStateToProps(state) {
|
||||
return {
|
||||
canManageUsers: canManageChannelMembers(state)
|
||||
};
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps)(RemoveMemberButton);
|
||||
|
|
@ -1,371 +0,0 @@
|
|||
// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
import React, {PropTypes, PureComponent} from 'react';
|
||||
import {injectIntl, intlShape} from 'react-intl';
|
||||
import {
|
||||
Dimensions,
|
||||
Keyboard,
|
||||
Platform,
|
||||
StyleSheet,
|
||||
TouchableOpacity,
|
||||
TouchableWithoutFeedback,
|
||||
View,
|
||||
findNodeHandle
|
||||
} from 'react-native';
|
||||
import {KeyboardAwareScrollView} from 'react-native-keyboard-aware-scroll-view';
|
||||
|
||||
import ErrorText from 'app/components/error_text';
|
||||
import FormattedText from 'app/components/formatted_text';
|
||||
import TextInputWithLocalizedPlaceholder from 'app/components/text_input_with_localized_placeholder';
|
||||
|
||||
import {changeOpacity, makeStyleSheetFromTheme} from 'app/utils/theme';
|
||||
|
||||
import {General, RequestStatus} from 'mattermost-redux/constants';
|
||||
import EventEmitter from 'mattermost-redux/utils/event_emitter';
|
||||
|
||||
import ActionButton from 'app/components/action_button';
|
||||
|
||||
class CreateChannel extends PureComponent {
|
||||
static propTypes = {
|
||||
intl: intlShape.isRequired,
|
||||
createChannelRequest: PropTypes.object.isRequired,
|
||||
theme: PropTypes.object.isRequired,
|
||||
channelType: PropTypes.string,
|
||||
subscribeToHeaderEvent: React.PropTypes.func.isRequired,
|
||||
unsubscribeFromHeaderEvent: React.PropTypes.func.isRequired,
|
||||
actions: PropTypes.shape({
|
||||
goBack: PropTypes.func.isRequired,
|
||||
closeModal: PropTypes.func.isRequired,
|
||||
handleCreateChannel: PropTypes.func.isRequired
|
||||
})
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
channelType: General.OPEN_CHANNEL
|
||||
};
|
||||
|
||||
static navigationProps = {
|
||||
renderLeftComponent: (props, emitter, theme) => {
|
||||
return (
|
||||
<TouchableOpacity
|
||||
style={{flex: 1, paddingHorizontal: 15, justifyContent: 'center'}}
|
||||
onPress={() => emitter('close')}
|
||||
>
|
||||
<FormattedText
|
||||
id='more_direct_channels.close'
|
||||
defaultMessage='Close'
|
||||
style={{color: theme.sidebarHeaderTextColor}}
|
||||
/>
|
||||
</TouchableOpacity>
|
||||
);
|
||||
},
|
||||
renderRightComponent: (props, emitter) => {
|
||||
return (
|
||||
<ActionButton
|
||||
actionEventName='create_channel'
|
||||
emitter={emitter}
|
||||
enabled={false}
|
||||
enableEventName='can_create_channel'
|
||||
labelDefaultMessage='Create'
|
||||
labelId='mobile.create_channel'
|
||||
loadingEventName='creating_channel'
|
||||
/>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
displayName: '',
|
||||
header: '',
|
||||
purpose: ''
|
||||
};
|
||||
}
|
||||
|
||||
onCreateChannel = async () => {
|
||||
Keyboard.dismiss();
|
||||
const {displayName, purpose, header} = this.state;
|
||||
await this.props.actions.handleCreateChannel(displayName, purpose, header, this.props.channelType);
|
||||
};
|
||||
|
||||
onDisplayNameChangeText = (displayName) => {
|
||||
this.setState({displayName});
|
||||
if (displayName && displayName.length >= 2) {
|
||||
this.emitCanCreateChannel(true);
|
||||
} else {
|
||||
this.emitCanCreateChannel(false);
|
||||
}
|
||||
};
|
||||
|
||||
onPurposeChangeText = (purpose) => {
|
||||
this.setState({purpose});
|
||||
};
|
||||
|
||||
onHeaderChangeText = (header) => {
|
||||
this.setState({header});
|
||||
};
|
||||
|
||||
emitCanCreateChannel = (enabled) => {
|
||||
EventEmitter.emit('can_create_channel', enabled);
|
||||
};
|
||||
|
||||
emitCreating = (loading) => {
|
||||
EventEmitter.emit('creating_channel', loading);
|
||||
};
|
||||
|
||||
blur = () => {
|
||||
this.nameInput.refs.wrappedInstance.blur();
|
||||
this.purposeInput.refs.wrappedInstance.blur();
|
||||
this.headerInput.refs.wrappedInstance.blur();
|
||||
this.scroll.scrollToPosition(0, 0, true);
|
||||
};
|
||||
|
||||
channelNameRef = (ref) => {
|
||||
this.nameInput = ref;
|
||||
};
|
||||
|
||||
channelPurposeRef = (ref) => {
|
||||
this.purposeInput = ref;
|
||||
};
|
||||
|
||||
channelHeaderRef = (ref) => {
|
||||
this.headerInput = ref;
|
||||
};
|
||||
|
||||
scrollRef = (ref) => {
|
||||
this.scroll = ref;
|
||||
};
|
||||
|
||||
lastTextRef = (ref) => {
|
||||
this.lastText = ref;
|
||||
};
|
||||
|
||||
scrollToEnd = () => {
|
||||
this.scroll.scrollToFocusedInput(findNodeHandle(this.lastText));
|
||||
};
|
||||
|
||||
componentWillMount() {
|
||||
this.props.subscribeToHeaderEvent('close', this.props.actions.goBack);
|
||||
this.props.subscribeToHeaderEvent('create_channel', this.onCreateChannel);
|
||||
}
|
||||
|
||||
componentWillReceiveProps(nextProps) {
|
||||
const {createChannelRequest} = nextProps;
|
||||
|
||||
if (this.props.createChannelRequest !== createChannelRequest) {
|
||||
switch (createChannelRequest.status) {
|
||||
case RequestStatus.STARTED:
|
||||
this.emitCreating(true);
|
||||
this.setState({error: null});
|
||||
break;
|
||||
case RequestStatus.SUCCESS:
|
||||
this.emitCreating(false);
|
||||
this.setState({error: null});
|
||||
this.props.actions.closeModal();
|
||||
break;
|
||||
case RequestStatus.FAILURE:
|
||||
this.emitCreating(false);
|
||||
this.setState({error: createChannelRequest.error});
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.emitCanCreateChannel(false);
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this.props.unsubscribeFromHeaderEvent('close');
|
||||
this.props.unsubscribeFromHeaderEvent('create_channel');
|
||||
}
|
||||
|
||||
render() {
|
||||
const {channelType, theme} = this.props;
|
||||
const {displayName, header, purpose, error} = this.state;
|
||||
const {formatMessage} = this.props.intl;
|
||||
const {height, width} = Dimensions.get('window');
|
||||
|
||||
const style = getStyleSheet(theme);
|
||||
|
||||
let term;
|
||||
if (channelType === General.OPEN_CHANNEL) {
|
||||
term = formatMessage({id: 'channel_modal.channel', defaultMessage: 'Channel'});
|
||||
} else if (channelType === General.PRIVATE_CHANNEL) {
|
||||
term = formatMessage({id: 'channel_modal.group', defaultMessage: 'Group'});
|
||||
}
|
||||
|
||||
let displayError;
|
||||
if (error) {
|
||||
displayError = (
|
||||
<View style={[style.errorContainer, {width}]}>
|
||||
<View style={style.errorWrapper}>
|
||||
<ErrorText error={error}/>
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<KeyboardAwareScrollView
|
||||
ref={this.scrollRef}
|
||||
style={style.container}
|
||||
>
|
||||
<TouchableWithoutFeedback onPress={this.blur}>
|
||||
<View style={[style.scrollView, {height: height + (Platform.OS === 'android' ? 200 : 0)}]}>
|
||||
{displayError}
|
||||
<View>
|
||||
<FormattedText
|
||||
style={[style.title, {marginTop: (error ? 10 : 0)}]}
|
||||
id='channel_modal.name'
|
||||
defaultMessage='Name'
|
||||
/>
|
||||
</View>
|
||||
<View style={style.inputContainer}>
|
||||
<TextInputWithLocalizedPlaceholder
|
||||
ref={this.channelNameRef}
|
||||
value={displayName}
|
||||
onChangeText={this.onDisplayNameChangeText}
|
||||
style={style.input}
|
||||
autoCapitalize='none'
|
||||
autoCorrect={false}
|
||||
placeholder={{id: 'channel_modal.nameEx', defaultMessage: 'E.g.: "Bugs", "Marketing", "客户支持"'}}
|
||||
placeholderTextColor={changeOpacity('#000', 0.5)}
|
||||
underlineColorAndroid='transparent'
|
||||
/>
|
||||
</View>
|
||||
<View style={style.titleContainer30}>
|
||||
<FormattedText
|
||||
style={style.title}
|
||||
id='channel_modal.purpose'
|
||||
defaultMessage='Purpose'
|
||||
/>
|
||||
<FormattedText
|
||||
style={style.optional}
|
||||
id='channel_modal.optional'
|
||||
defaultMessage='(optional)'
|
||||
/>
|
||||
</View>
|
||||
<View style={style.inputContainer}>
|
||||
<TextInputWithLocalizedPlaceholder
|
||||
ref={this.channelPurposeRef}
|
||||
value={purpose}
|
||||
onChangeText={this.onPurposeChangeText}
|
||||
style={[style.input, {height: 110}]}
|
||||
placeholder={{id: 'channel_modal.purposeEx', defaultMessage: 'E.g.: "A channel to file bugs and improvements"'}}
|
||||
placeholderTextColor={changeOpacity('#000', 0.5)}
|
||||
multiline={true}
|
||||
underlineColorAndroid='transparent'
|
||||
/>
|
||||
</View>
|
||||
<View>
|
||||
<FormattedText
|
||||
style={style.helpText}
|
||||
id='channel_modal.descriptionHelp'
|
||||
defaultMessage='Describe how this {term} should be used.'
|
||||
values={{
|
||||
term
|
||||
}}
|
||||
/>
|
||||
</View>
|
||||
<View style={style.titleContainer15}>
|
||||
<FormattedText
|
||||
style={style.title}
|
||||
id='channel_modal.header'
|
||||
defaultMessage='Header'
|
||||
/>
|
||||
<FormattedText
|
||||
style={style.optional}
|
||||
id='channel_modal.optional'
|
||||
defaultMessage='(optional)'
|
||||
/>
|
||||
</View>
|
||||
<View style={style.inputContainer}>
|
||||
<TextInputWithLocalizedPlaceholder
|
||||
ref={this.channelHeaderRef}
|
||||
value={header}
|
||||
onChangeText={this.onHeaderChangeText}
|
||||
style={[style.input, {height: 110}]}
|
||||
placeholder={{id: 'channel_modal.headerEx', defaultMessage: 'E.g.: "[Link Title](http://example.com)"'}}
|
||||
placeholderTextColor={changeOpacity('#000', 0.5)}
|
||||
multiline={true}
|
||||
onFocus={this.scrollToEnd}
|
||||
underlineColorAndroid='transparent'
|
||||
/>
|
||||
</View>
|
||||
<View ref={this.lastTextRef}>
|
||||
<FormattedText
|
||||
style={style.helpText}
|
||||
id='channel_modal.headerHelp'
|
||||
defaultMessage={'Set text that will appear in the header of the {term} beside the {term} name. For example, include frequently used links by typing [Link Title](http://example.com).'}
|
||||
values={{
|
||||
term
|
||||
}}
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
</TouchableWithoutFeedback>
|
||||
</KeyboardAwareScrollView>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const getStyleSheet = makeStyleSheetFromTheme((theme) => {
|
||||
return StyleSheet.create({
|
||||
container: {
|
||||
flex: 1,
|
||||
backgroundColor: theme.centerChannelBg
|
||||
},
|
||||
scrollView: {
|
||||
flex: 1,
|
||||
backgroundColor: changeOpacity(theme.centerChannelColor, 0.03),
|
||||
paddingTop: 30
|
||||
},
|
||||
errorContainer: {
|
||||
position: 'absolute'
|
||||
},
|
||||
errorWrapper: {
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
marginBottom: 10
|
||||
},
|
||||
inputContainer: {
|
||||
marginTop: 10,
|
||||
backgroundColor: '#fff'
|
||||
},
|
||||
input: {
|
||||
color: '#333',
|
||||
fontSize: 14,
|
||||
height: 40,
|
||||
paddingHorizontal: 15
|
||||
},
|
||||
titleContainer30: {
|
||||
flexDirection: 'row',
|
||||
marginTop: 30
|
||||
},
|
||||
titleContainer15: {
|
||||
flexDirection: 'row',
|
||||
marginTop: 15
|
||||
},
|
||||
title: {
|
||||
fontSize: 14,
|
||||
color: theme.centerChannelColor,
|
||||
marginLeft: 15
|
||||
},
|
||||
optional: {
|
||||
color: changeOpacity(theme.centerChannelColor, 0.5),
|
||||
fontSize: 14,
|
||||
marginLeft: 5
|
||||
},
|
||||
helpText: {
|
||||
fontSize: 14,
|
||||
color: changeOpacity(theme.centerChannelColor, 0.5),
|
||||
marginTop: 10,
|
||||
marginHorizontal: 15
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
export default injectIntl(CreateChannel);
|
||||
|
|
@ -1,58 +0,0 @@
|
|||
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
import About from './about';
|
||||
import AccountNotifications from './account_notifications';
|
||||
import AccountSettings from './account_settings';
|
||||
import Channel from './channel';
|
||||
import ChannelDrawer from './channel_drawer';
|
||||
import ChannelInfo from './channel_info';
|
||||
import ChannelMembers from './channel_members';
|
||||
import ChannelAddMembers from './channel_add_members';
|
||||
import CreateChannel from './create_channel';
|
||||
import EditPost from './edit_post';
|
||||
import ImagePreview from './image_preview';
|
||||
import LoadTeam from './load_team';
|
||||
import Login from './login';
|
||||
import LoginOptions from './login_options';
|
||||
import Mfa from './mfa';
|
||||
import MoreChannels from './more_channels';
|
||||
import MoreDirectMessages from './more_dms';
|
||||
import OptionsModal from './options_modal';
|
||||
import Settings from './settings';
|
||||
import Root from './root';
|
||||
import Search from './search';
|
||||
import SelectServer from './select_server';
|
||||
import SelectTeam from './select_team';
|
||||
import Thread from './thread';
|
||||
import UserProfile from './user_profile';
|
||||
import Saml from './saml';
|
||||
|
||||
module.exports = {
|
||||
About,
|
||||
AccountNotifications,
|
||||
AccountSettings,
|
||||
ChannelView: Channel, // Special case the name for this one to avoid ambiguity
|
||||
ChannelDrawer,
|
||||
ChannelInfo,
|
||||
ChannelMembers,
|
||||
ChannelAddMembers,
|
||||
CreateChannel,
|
||||
EditPost,
|
||||
ImagePreview,
|
||||
LoadTeam,
|
||||
Login,
|
||||
LoginOptions,
|
||||
Mfa,
|
||||
MoreChannels,
|
||||
MoreDirectMessages,
|
||||
OptionsModal,
|
||||
Settings,
|
||||
Root,
|
||||
Search,
|
||||
SelectServer,
|
||||
SelectTeam,
|
||||
Thread,
|
||||
UserProfile,
|
||||
Saml
|
||||
};
|
||||
|
|
@ -1,29 +0,0 @@
|
|||
// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
import {bindActionCreators} from 'redux';
|
||||
|
||||
import navigationSceneConnect from '../navigationSceneConnect';
|
||||
|
||||
import {goToLogin, goToSaml} from 'app/actions/navigation';
|
||||
|
||||
import LoginOptions from './login_options';
|
||||
|
||||
function mapStateToProps(state) {
|
||||
const {config, license} = state.entities.general;
|
||||
return {
|
||||
config,
|
||||
license
|
||||
};
|
||||
}
|
||||
|
||||
function mapDispatchToProps(dispatch) {
|
||||
return {
|
||||
actions: bindActionCreators({
|
||||
goToLogin,
|
||||
goToSaml
|
||||
}, dispatch)
|
||||
};
|
||||
}
|
||||
|
||||
export default navigationSceneConnect(mapStateToProps, mapDispatchToProps)(LoginOptions);
|
||||
|
|
@ -1,54 +0,0 @@
|
|||
// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
import React, {PropTypes} from 'react';
|
||||
import {connect} from 'react-redux';
|
||||
|
||||
import ActionButton from 'app/components/action_button';
|
||||
|
||||
import {getTheme} from 'app/selectors/preferences';
|
||||
|
||||
import {General} from 'mattermost-redux/constants';
|
||||
import {getCurrentUserRoles} from 'mattermost-redux/selectors/entities/users';
|
||||
import {showCreateOption} from 'mattermost-redux/utils/channel_utils';
|
||||
import {isAdmin, isSystemAdmin} from 'mattermost-redux/utils/user_utils';
|
||||
|
||||
function CreateButton(props) {
|
||||
if (!props.canCreateChannels) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<ActionButton
|
||||
actionEventName='create_channel'
|
||||
emitter={props.emitter}
|
||||
enabled={true}
|
||||
enableEventName='can_create_channel'
|
||||
labelDefaultMessage='Create'
|
||||
labelId='mobile.create_channel'
|
||||
loadingEventName='creating_channel'
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
CreateButton.propTypes = {
|
||||
canCreateChannels: PropTypes.bool.isRequired,
|
||||
emitter: PropTypes.func.isRequired,
|
||||
theme: PropTypes.object
|
||||
};
|
||||
|
||||
CreateButton.defaultProps = {
|
||||
theme: {}
|
||||
};
|
||||
|
||||
function mapStateToProps(state) {
|
||||
const {config, license} = state.entities.general;
|
||||
const roles = getCurrentUserRoles(state);
|
||||
|
||||
return {
|
||||
canCreateChannels: showCreateOption(config, license, General.PRIVATE_CHANNEL, isAdmin(roles), isSystemAdmin(roles)),
|
||||
theme: getTheme(state)
|
||||
};
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps)(CreateButton);
|
||||
|
|
@ -1,69 +0,0 @@
|
|||
// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
/* eslint-disable */
|
||||
|
||||
import {connect} from 'react-redux';
|
||||
import React, {PropTypes} from 'react';
|
||||
import {
|
||||
Platform,
|
||||
TouchableOpacity,
|
||||
View
|
||||
} from 'react-native';
|
||||
import Icon from 'react-native-vector-icons/FontAwesome';
|
||||
|
||||
import FormattedText from 'app/components/formatted_text';
|
||||
|
||||
const defaults = {
|
||||
renderBackButton: (props, emitter, theme) => {
|
||||
return (
|
||||
<TouchableOpacity
|
||||
style={{flexDirection: 'row', flex: 1, paddingHorizontal: 15, alignItems: 'center'}}
|
||||
onPress={props.onNavigateBack}
|
||||
>
|
||||
<Icon
|
||||
style={{fontWeight: 'bold'}}
|
||||
name='angle-left'
|
||||
size={35}
|
||||
color={theme.sidebarHeaderTextColor}
|
||||
/>
|
||||
</TouchableOpacity>
|
||||
);
|
||||
},
|
||||
renderTitleComponent: (props, emitter, theme) => {
|
||||
const navProps = props.scene.route.navigationProps || {};
|
||||
const title = navProps.title;
|
||||
if (title) {
|
||||
return (
|
||||
<View style={{alignItems: 'center', justifyContent: 'center', flex: 1, marginHorizontal: 50}}>
|
||||
<FormattedText
|
||||
id={title.id}
|
||||
defaultMessage={title.defaultMessage}
|
||||
ellipsizeMode='tail'
|
||||
numberOfLines={1}
|
||||
style={{
|
||||
textAlign: 'center',
|
||||
color: theme.sidebarHeaderTextColor,
|
||||
fontSize: 15,
|
||||
fontWeight: 'bold'
|
||||
}}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
},
|
||||
headerStyle: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center'
|
||||
},
|
||||
allowSceneSwipe: true,
|
||||
allowMenuSwipe: false
|
||||
};
|
||||
|
||||
export default (stateProps, dispatchProps) => (WrappedComponent) => {
|
||||
const componentNavigationProps = WrappedComponent.WrappedComponent ? WrappedComponent.WrappedComponent.navigationProps : WrappedComponent.navigationProps;
|
||||
|
||||
WrappedComponent.navigationProps = Object.assign({}, defaults, componentNavigationProps);
|
||||
|
||||
return connect(stateProps, dispatchProps)(WrappedComponent);
|
||||
};
|
||||
|
|
@ -1,27 +0,0 @@
|
|||
// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
import {bindActionCreators} from 'redux';
|
||||
|
||||
import {closeModal} from 'app/actions/navigation';
|
||||
|
||||
import navigationSceneConnect from '../navigationSceneConnect';
|
||||
|
||||
import OptionsModal from './options_modal';
|
||||
|
||||
function mapStateToProps(state, ownProps) {
|
||||
return {
|
||||
...ownProps,
|
||||
requestClose: state.navigation.modal.requestClose
|
||||
};
|
||||
}
|
||||
|
||||
function mapDispatchToProps(dispatch) {
|
||||
return {
|
||||
actions: bindActionCreators({
|
||||
closeModal
|
||||
}, dispatch)
|
||||
};
|
||||
}
|
||||
|
||||
export default navigationSceneConnect(mapStateToProps, mapDispatchToProps)(OptionsModal);
|
||||
|
|
@ -1,37 +0,0 @@
|
|||
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
import {bindActionCreators} from 'redux';
|
||||
|
||||
import navigationSceneConnect from '../navigationSceneConnect';
|
||||
|
||||
import {goToSelectServer} from 'app/actions/views/root';
|
||||
import {handleServerUrlChanged} from 'app/actions/views/select_server';
|
||||
import {goToLoadTeam} from 'app/actions/navigation';
|
||||
|
||||
import {loadMe} from 'mattermost-redux/actions/users';
|
||||
|
||||
import Root from './root';
|
||||
|
||||
function mapStateToProps(state, ownProps) {
|
||||
return {
|
||||
...ownProps,
|
||||
credentials: state.entities.general.credentials,
|
||||
hydrationComplete: state.views.root.hydrationComplete,
|
||||
logoutRequest: state.requests.users.logout,
|
||||
loginRequest: state.requests.users.login
|
||||
};
|
||||
}
|
||||
|
||||
function mapDispatchToProps(dispatch) {
|
||||
return {
|
||||
actions: bindActionCreators({
|
||||
goToLoadTeam,
|
||||
goToSelectServer,
|
||||
handleServerUrlChanged,
|
||||
loadMe
|
||||
}, dispatch)
|
||||
};
|
||||
}
|
||||
|
||||
export default navigationSceneConnect(mapStateToProps, mapDispatchToProps)(Root);
|
||||
|
|
@ -1,80 +0,0 @@
|
|||
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
import React, {PropTypes, PureComponent} from 'react';
|
||||
import Orientation from 'react-native-orientation';
|
||||
import SplashScreen from 'react-native-smart-splash-screen';
|
||||
|
||||
import {Client, Client4} from 'mattermost-redux/client';
|
||||
import {RequestStatus} from 'mattermost-redux/constants';
|
||||
|
||||
import Loading from 'app/components/loading';
|
||||
import {stripTrailingSlashes} from 'app/utils/url';
|
||||
|
||||
export default class Root extends PureComponent {
|
||||
static propTypes = {
|
||||
credentials: PropTypes.object,
|
||||
logoutRequest: PropTypes.object,
|
||||
loginRequest: PropTypes.object,
|
||||
actions: PropTypes.shape({
|
||||
goToLoadTeam: PropTypes.func,
|
||||
goToSelectServer: PropTypes.func,
|
||||
handleServerUrlChanged: PropTypes.func.isRequired,
|
||||
loadMe: PropTypes.func
|
||||
}).isRequired
|
||||
};
|
||||
|
||||
static navigationProps = {
|
||||
hideNavBar: true
|
||||
};
|
||||
|
||||
componentDidMount() {
|
||||
Orientation.lockToPortrait();
|
||||
|
||||
this.loadStoreAndScene(this.props.credentials);
|
||||
setTimeout(() => {
|
||||
this.handleCloseSplashScreen();
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
componentWillReceiveProps(nextProps) {
|
||||
if (this.props.logoutRequest.status === RequestStatus.SUCCESS &&
|
||||
nextProps.logoutRequest.status === RequestStatus.NOT_STARTED) {
|
||||
this.loadStoreAndScene(nextProps.credentials);
|
||||
}
|
||||
}
|
||||
|
||||
handleCloseSplashScreen = (options = {}) => {
|
||||
const opts = {
|
||||
animationType: SplashScreen.animationType.scale,
|
||||
duration: 850,
|
||||
delay: 500
|
||||
};
|
||||
|
||||
SplashScreen.close(Object.assign({}, opts, options));
|
||||
};
|
||||
|
||||
loadStoreAndScene = (credentials = {}) => {
|
||||
const {actions, loginRequest} = this.props;
|
||||
const {goToLoadTeam, goToSelectServer, loadMe} = actions;
|
||||
if (credentials.token && credentials.url) {
|
||||
// Will probably need to make this optimistic since we
|
||||
// assume that the stored token is good.
|
||||
if (loginRequest.status === RequestStatus.NOT_STARTED) {
|
||||
Client4.setToken(credentials.token);
|
||||
Client4.setUrl(stripTrailingSlashes(credentials.url));
|
||||
Client.setUrl(stripTrailingSlashes(credentials.url));
|
||||
|
||||
loadMe().then(goToLoadTeam).catch(goToLoadTeam);
|
||||
} else {
|
||||
goToLoadTeam();
|
||||
}
|
||||
} else {
|
||||
goToSelectServer();
|
||||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
return <Loading/>;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,30 +0,0 @@
|
|||
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import Button from 'app/components/button';
|
||||
import {GlobalStyles} from 'app/styles';
|
||||
import {Text, View} from 'react-native';
|
||||
|
||||
export default class Search extends React.Component {
|
||||
static propTypes = {
|
||||
actions: React.PropTypes.object.isRequired,
|
||||
searchType: React.PropTypes.string.isRequired
|
||||
};
|
||||
|
||||
render() {
|
||||
return (
|
||||
<View style={GlobalStyles.container}>
|
||||
<Text>
|
||||
{'This is a search for ' + this.props.searchType}
|
||||
</Text>
|
||||
<Button onPress={this.props.actions.goBack}>
|
||||
<Text>
|
||||
{'<< Go back'}
|
||||
</Text>
|
||||
</Button>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,66 +0,0 @@
|
|||
// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
import React, {PropTypes} from 'react';
|
||||
import {connect} from 'react-redux';
|
||||
import {
|
||||
View
|
||||
} from 'react-native';
|
||||
|
||||
import {General} from 'mattermost-redux/constants';
|
||||
import {getCurrentChannel} from 'mattermost-redux/selectors/entities/channels';
|
||||
import {getTheme} from 'app/selectors/preferences';
|
||||
|
||||
import FormattedText from 'app/components/formatted_text';
|
||||
|
||||
function ThreadTitle(props) {
|
||||
const {currentChannel, theme} = props;
|
||||
let label;
|
||||
if (currentChannel.type === General.DM_CHANNEL) {
|
||||
label = (
|
||||
<FormattedText
|
||||
id='mobile.routes.thread_dm'
|
||||
defaultMessage='Direct Message Thread'
|
||||
ellipsizeMode='tail'
|
||||
numberOfLines={1}
|
||||
style={{color: theme.sidebarHeaderTextColor, fontSize: 15, fontWeight: 'bold'}}
|
||||
/>
|
||||
);
|
||||
} else {
|
||||
const channelName = currentChannel.display_name;
|
||||
label = (
|
||||
<FormattedText
|
||||
id='mobile.routes.thread'
|
||||
defaultMessage='{channelName} Thread'
|
||||
ellipsizeMode='tail'
|
||||
numberOfLines={1}
|
||||
values={{channelName}}
|
||||
style={{color: theme.sidebarHeaderTextColor, fontSize: 15, fontWeight: 'bold'}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<View style={{justifyContent: 'center', flex: 1, marginHorizontal: 1}}>
|
||||
{label}
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
ThreadTitle.propTypes = {
|
||||
currentChannel: PropTypes.object.isRequired,
|
||||
theme: PropTypes.object
|
||||
};
|
||||
|
||||
ThreadTitle.defaultProps = {
|
||||
theme: {}
|
||||
};
|
||||
|
||||
function mapStateToProps(state) {
|
||||
return {
|
||||
currentChannel: getCurrentChannel(state),
|
||||
theme: getTheme(state)
|
||||
};
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps)(ThreadTitle);
|
||||
|
|
@ -1,8 +1,9 @@
|
|||
// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
import {connect} from 'react-redux';
|
||||
|
||||
import {getTheme} from 'app/selectors/preferences';
|
||||
import navigationSceneConnect from 'app/scenes/navigationSceneConnect';
|
||||
|
||||
import About from './about';
|
||||
|
||||
|
|
@ -16,4 +17,4 @@ function mapStateToProps(state) {
|
|||
};
|
||||
}
|
||||
|
||||
export default navigationSceneConnect(mapStateToProps)(About);
|
||||
export default connect(mapStateToProps)(About);
|
||||
|
|
@ -2,43 +2,43 @@
|
|||
// See License.txt for license information.
|
||||
|
||||
import React, {PropTypes, PureComponent} from 'react';
|
||||
import {injectIntl, intlShape} from 'react-intl';
|
||||
import {
|
||||
Keyboard,
|
||||
Platform,
|
||||
ScrollView,
|
||||
StatusBar,
|
||||
StyleSheet,
|
||||
View
|
||||
} from 'react-native';
|
||||
|
||||
import {Preferences, RequestStatus} from 'mattermost-redux/constants';
|
||||
import {getPreferencesByCategory} from 'mattermost-redux/utils/preference_utils';
|
||||
|
||||
import Loading from 'app/components/loading';
|
||||
import TextInputWithLocalizedPlaceholder from 'app/components/text_input_with_localized_placeholder';
|
||||
import {changeOpacity, makeStyleSheetFromTheme} from 'app/utils/theme';
|
||||
import {Preferences} from 'mattermost-redux/constants';
|
||||
import {getPreferencesByCategory} from 'mattermost-redux/utils/preference_utils';
|
||||
|
||||
import Section from './section';
|
||||
import SectionItem from './section_item';
|
||||
import SaveNotificationsButton from './save_notifications_button';
|
||||
|
||||
const SAVE_NOTIFY_PROPS = 'save_notify_props';
|
||||
|
||||
export default class AccountNotifications extends PureComponent {
|
||||
class AccountNotifications extends PureComponent {
|
||||
static propTypes = {
|
||||
actions: PropTypes.shape({
|
||||
goBack: PropTypes.func.isRequired,
|
||||
handleUpdateUserNotifyProps: PropTypes.func.isRequired
|
||||
}),
|
||||
config: PropTypes.object.isRequired,
|
||||
currentUser: PropTypes.object.isRequired,
|
||||
intl: intlShape.isRequired,
|
||||
myPreferences: PropTypes.object.isRequired,
|
||||
subscribeToHeaderEvent: PropTypes.func.isRequired,
|
||||
navigator: PropTypes.object,
|
||||
theme: PropTypes.object.isRequired,
|
||||
unsubscribeFromHeaderEvent: PropTypes.func.isRequired
|
||||
updateMeRequest: PropTypes.object.isRequired
|
||||
};
|
||||
|
||||
static navigationProps = {
|
||||
renderRightComponent: (props, emitter) => {
|
||||
return <SaveNotificationsButton emitter={emitter}/>;
|
||||
}
|
||||
saveButton = {
|
||||
id: 'save-notifications',
|
||||
showAsAction: 'never'
|
||||
};
|
||||
|
||||
constructor(props) {
|
||||
|
|
@ -46,11 +46,16 @@ export default class AccountNotifications extends PureComponent {
|
|||
|
||||
const {currentUser} = props;
|
||||
const notifyProps = currentUser.notify_props || {};
|
||||
this.setStateFromNotifyProps(notifyProps);
|
||||
}
|
||||
|
||||
componentWillMount() {
|
||||
this.props.subscribeToHeaderEvent(SAVE_NOTIFY_PROPS, this.saveUserNotifyProps);
|
||||
props.navigator.setOnNavigatorEvent(this.onNavigatorEvent);
|
||||
props.navigator.setButtons({
|
||||
rightButtons: [{
|
||||
...this.saveButton,
|
||||
title: props.intl.formatMessage({id: 'edit_post.save', defaultMessage: 'Save'})
|
||||
}]
|
||||
});
|
||||
|
||||
this.setStateFromNotifyProps(notifyProps);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
|
|
@ -60,24 +65,49 @@ export default class AccountNotifications extends PureComponent {
|
|||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this.props.unsubscribeFromHeaderEvent(SAVE_NOTIFY_PROPS);
|
||||
|
||||
if (Platform.OS === 'android') {
|
||||
Keyboard.removeListener('keyboardDidHide', this.handleAndroidKeyboard);
|
||||
}
|
||||
}
|
||||
|
||||
componentWillReceiveProps(nextProps) {
|
||||
if (nextProps.currentUser !== this.props.currentUser) {
|
||||
const {notify_props: notifyProps} = nextProps.currentUser;
|
||||
const {currentUser, updateMeRequest} = nextProps;
|
||||
if (currentUser !== this.props.currentUser) {
|
||||
const {notify_props: notifyProps} = currentUser;
|
||||
this.setStateFromNotifyProps(notifyProps);
|
||||
}
|
||||
|
||||
if (this.props.updateMeRequest !== updateMeRequest) {
|
||||
switch (updateMeRequest.status) {
|
||||
case RequestStatus.STARTED:
|
||||
this.savingNotifyProps(true);
|
||||
this.setState({error: null, saving: true});
|
||||
break;
|
||||
case RequestStatus.SUCCESS:
|
||||
this.savingNotifyProps(false);
|
||||
this.setState({error: null, saving: false});
|
||||
this.close();
|
||||
break;
|
||||
case RequestStatus.FAILURE:
|
||||
this.savingNotifyProps(false);
|
||||
this.setState({error: updateMeRequest.error, saving: false});
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
handleAndroidKeyboard = () => {
|
||||
this.refs.mention_keys.getWrappedInstance().blur();
|
||||
};
|
||||
|
||||
onNavigatorEvent = (event) => {
|
||||
if (event.type === 'NavBarButtonPress') {
|
||||
if (event.id === 'save-notifications') {
|
||||
this.saveUserNotifyProps();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
setStateFromNotifyProps = (notifyProps) => {
|
||||
const mentionKeys = (notifyProps.mention_keys || '').split(',');
|
||||
const usernameMentionIndex = mentionKeys.indexOf(this.props.currentUser.username);
|
||||
|
|
@ -108,31 +138,37 @@ export default class AccountNotifications extends PureComponent {
|
|||
} else {
|
||||
this.state = {...newState};
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
toggleFirstNameMention = () => {
|
||||
this.setState({
|
||||
first_name: (!(this.state.first_name === 'true')).toString()
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
toggleUsernameMention = () => {
|
||||
this.setState({
|
||||
usernameMention: !this.state.usernameMention
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
toggleChannelMentions = () => {
|
||||
this.setState({
|
||||
channel: (!(this.state.channel === 'true')).toString()
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
updateMentionKeys = (text) => {
|
||||
this.setState({
|
||||
mention_keys: text
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
savingNotifyProps = (loading) => {
|
||||
this.props.navigator.setButtons({
|
||||
rightButtons: [{...this.saveButton, disabled: loading}]
|
||||
});
|
||||
};
|
||||
|
||||
setEmailNotifications = (value) => {
|
||||
const {config} = this.props;
|
||||
|
|
@ -149,25 +185,25 @@ export default class AccountNotifications extends PureComponent {
|
|||
email,
|
||||
interval
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
setReplyNotifications = (value) => {
|
||||
this.setState({
|
||||
comments: value
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
setMobilePush = (value) => {
|
||||
this.setState({
|
||||
push: value
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
setMobilePushStatus = (value) => {
|
||||
this.setState({
|
||||
push_status: value
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
saveUserNotifyProps = () => {
|
||||
let {mention_keys: mentionKeys, usernameMention, ...notifyProps} = this.state; //eslint-disable-line prefer-const
|
||||
|
|
@ -189,9 +225,7 @@ export default class AccountNotifications extends PureComponent {
|
|||
mention_keys: mentionKeys,
|
||||
user_id: this.props.currentUser.id
|
||||
});
|
||||
|
||||
this.props.actions.goBack();
|
||||
}
|
||||
};
|
||||
|
||||
buildMentionSection = () => {
|
||||
const {currentUser, theme} = this.props;
|
||||
|
|
@ -258,7 +292,7 @@ export default class AccountNotifications extends PureComponent {
|
|||
</SectionItem>
|
||||
</Section>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
buildEmailSection = () => {
|
||||
const {config, theme} = this.props;
|
||||
|
|
@ -348,7 +382,7 @@ export default class AccountNotifications extends PureComponent {
|
|||
}
|
||||
</Section>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
buildReplySection = () => {
|
||||
const {theme} = this.props;
|
||||
|
|
@ -393,7 +427,7 @@ export default class AccountNotifications extends PureComponent {
|
|||
/>
|
||||
</Section>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
buildMobilePushSection = () => {
|
||||
const {config, theme} = this.props;
|
||||
|
|
@ -443,7 +477,7 @@ export default class AccountNotifications extends PureComponent {
|
|||
/>
|
||||
</Section>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
buildMobilePushStatusSection = () => {
|
||||
const {config, theme} = this.props;
|
||||
|
|
@ -493,14 +527,28 @@ export default class AccountNotifications extends PureComponent {
|
|||
/>
|
||||
</Section>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
close = () => {
|
||||
this.props.navigator.pop({animated: true});
|
||||
};
|
||||
|
||||
render() {
|
||||
const {theme} = this.props;
|
||||
const style = getStyleSheet(theme);
|
||||
|
||||
if (this.state.saving) {
|
||||
return (
|
||||
<View style={style.wrapper}>
|
||||
<StatusBar barStyle='light-content'/>
|
||||
<Loading/>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<View style={style.wrapper}>
|
||||
<StatusBar barStyle='light-content'/>
|
||||
<ScrollView
|
||||
style={style.scrollView}
|
||||
contentContainerStyle={style.scrollViewContent}
|
||||
|
|
@ -518,6 +566,10 @@ export default class AccountNotifications extends PureComponent {
|
|||
|
||||
const getStyleSheet = makeStyleSheetFromTheme((theme) => {
|
||||
return StyleSheet.create({
|
||||
wrapper: {
|
||||
flex: 1,
|
||||
backgroundColor: theme.centerChannelBg
|
||||
},
|
||||
input: {
|
||||
color: theme.centerChannelColor,
|
||||
fontSize: 12,
|
||||
|
|
@ -535,10 +587,8 @@ const getStyleSheet = makeStyleSheetFromTheme((theme) => {
|
|||
},
|
||||
scrollViewContent: {
|
||||
paddingBottom: 30
|
||||
},
|
||||
wrapper: {
|
||||
flex: 1,
|
||||
backgroundColor: theme.centerChannelBg
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
export default injectIntl(AccountNotifications);
|
||||
|
|
@ -2,21 +2,22 @@
|
|||
// See License.txt for license information.
|
||||
|
||||
import {bindActionCreators} from 'redux';
|
||||
import {connect} from 'react-redux';
|
||||
|
||||
import {goBack} from 'app/actions/navigation';
|
||||
import {handleUpdateUserNotifyProps} from 'app/actions/views/account_notifications';
|
||||
import {getTheme} from 'app/selectors/preferences';
|
||||
import {getCurrentUser} from 'mattermost-redux/selectors/entities/users';
|
||||
|
||||
import navigationSceneConnect from '../navigationSceneConnect';
|
||||
import {handleUpdateUserNotifyProps} from 'app/actions/views/account_notifications';
|
||||
import {getTheme} from 'app/selectors/preferences';
|
||||
|
||||
import AccountNotifications from './account_notifications';
|
||||
|
||||
function mapStateToProps(state) {
|
||||
function mapStateToProps(state, ownProps) {
|
||||
return {
|
||||
...ownProps,
|
||||
config: state.entities.general.config,
|
||||
myPreferences: state.entities.preferences.myPreferences,
|
||||
currentUser: getCurrentUser(state),
|
||||
updateMeRequest: state.requests.users.updateMe,
|
||||
theme: getTheme(state)
|
||||
};
|
||||
}
|
||||
|
|
@ -24,10 +25,9 @@ function mapStateToProps(state) {
|
|||
function mapDispatchToProps(dispatch) {
|
||||
return {
|
||||
actions: bindActionCreators({
|
||||
goBack,
|
||||
handleUpdateUserNotifyProps
|
||||
}, dispatch)
|
||||
};
|
||||
}
|
||||
|
||||
export default navigationSceneConnect(mapStateToProps, mapDispatchToProps)(AccountNotifications);
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(AccountNotifications);
|
||||
|
|
@ -2,8 +2,10 @@
|
|||
// See License.txt for license information.
|
||||
|
||||
import React, {PropTypes, PureComponent} from 'react';
|
||||
import {injectIntl, intlShape} from 'react-intl';
|
||||
import {
|
||||
StyleSheet,
|
||||
StatusBar,
|
||||
TouchableOpacity,
|
||||
View
|
||||
} from 'react-native';
|
||||
|
|
@ -13,31 +15,13 @@ import FormattedText from 'app/components/formatted_text';
|
|||
import {preventDoubleTap} from 'app/utils/tap';
|
||||
import {changeOpacity, makeStyleSheetFromTheme} from 'app/utils/theme';
|
||||
|
||||
export default class AccountSettings extends PureComponent {
|
||||
class AccountSettings extends PureComponent {
|
||||
static propTypes = {
|
||||
actions: PropTypes.shape({
|
||||
goToAccountNotifications: PropTypes.func.isRequired
|
||||
}),
|
||||
intl: intlShape.isRequired,
|
||||
navigator: PropTypes.object,
|
||||
theme: PropTypes.object.isRequired
|
||||
};
|
||||
|
||||
static navigationProps = {
|
||||
renderLeftComponent: (props, emitter, theme) => {
|
||||
return (
|
||||
<TouchableOpacity
|
||||
style={{flex: 1, paddingHorizontal: 15, justifyContent: 'center'}}
|
||||
onPress={props.onNavigateBack}
|
||||
>
|
||||
<FormattedText
|
||||
id='admin.select_team.close'
|
||||
defaultMessage='Close'
|
||||
style={{color: theme.sidebarHeaderTextColor}}
|
||||
/>
|
||||
</TouchableOpacity>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
// The enabled arg can be removed once all the scenes have been implemented.
|
||||
buildItemRow = (icon, id, defaultMessage, action, separator = true, enabled = false) => {
|
||||
if (!enabled) {
|
||||
|
|
@ -79,11 +63,27 @@ export default class AccountSettings extends PureComponent {
|
|||
);
|
||||
};
|
||||
|
||||
goToAccountNotifications = () => {
|
||||
const {intl, navigator, theme} = this.props;
|
||||
navigator.push({
|
||||
backButtonTitle: '',
|
||||
screen: 'AccountNotifications',
|
||||
title: intl.formatMessage({id: 'user.settings.modal.notifications', defaultMessage: 'Notifications'}),
|
||||
animated: true,
|
||||
navigatorStyle: {
|
||||
navBarTextColor: theme.sidebarHeaderTextColor,
|
||||
navBarBackgroundColor: theme.sidebarHeaderBg,
|
||||
navBarButtonColor: theme.sidebarHeaderTextColor,
|
||||
screenBackgroundColor: theme.centerChannelBg
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
renderItems = () => {
|
||||
return [
|
||||
this.buildItemRow('gear', 'user.settings.modal.general', 'General', () => true, true, false),
|
||||
this.buildItemRow('lock', 'user.settings.modal.security', 'Security', () => true, true, false),
|
||||
this.buildItemRow('bell', 'user.settings.modal.notifications', 'Notifications', this.props.actions.goToAccountNotifications, false, true),
|
||||
this.buildItemRow('bell', 'user.settings.modal.notifications', 'Notifications', this.goToAccountNotifications, false, true),
|
||||
this.buildItemRow('mobile', 'user.settings.modal.display', 'Display', () => true, true, false),
|
||||
this.buildItemRow('wrench', 'user.settings.modal.advanced', 'Advanced', () => true, false, false)
|
||||
];
|
||||
|
|
@ -95,6 +95,7 @@ export default class AccountSettings extends PureComponent {
|
|||
|
||||
return (
|
||||
<View style={style.wrapper}>
|
||||
<StatusBar barStyle='light-content'/>
|
||||
<View style={style.container}>
|
||||
<View style={style.itemsContainer}>
|
||||
{this.renderItems()}
|
||||
|
|
@ -154,3 +155,5 @@ const getStyleSheet = makeStyleSheetFromTheme((theme) => {
|
|||
}
|
||||
});
|
||||
});
|
||||
|
||||
export default injectIntl(AccountSettings);
|
||||
16
app/screens/account_settings/index.js
Normal file
16
app/screens/account_settings/index.js
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
import {connect} from 'react-redux';
|
||||
|
||||
import {getTheme} from 'app/selectors/preferences';
|
||||
|
||||
import AccountSettings from './account_settings';
|
||||
|
||||
function mapStateToProps(state) {
|
||||
return {
|
||||
theme: getTheme(state)
|
||||
};
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps)(AccountSettings);
|
||||
|
|
@ -2,27 +2,32 @@
|
|||
// See License.txt for license information.
|
||||
|
||||
import React, {PropTypes, PureComponent} from 'react';
|
||||
import {injectIntl, intlShape} from 'react-intl';
|
||||
import {
|
||||
Dimensions,
|
||||
NetInfo,
|
||||
Platform,
|
||||
StatusBar,
|
||||
StyleSheet,
|
||||
View
|
||||
} from 'react-native';
|
||||
|
||||
import {RequestStatus} from 'mattermost-redux/constants';
|
||||
import EventEmitter from 'mattermost-redux/utils/event_emitter';
|
||||
|
||||
import ChannelDrawer from 'app/components/channel_drawer';
|
||||
import KeyboardLayout from 'app/components/layout/keyboard_layout';
|
||||
import Loading from 'app/components/loading';
|
||||
import OfflineIndicator from 'app/components/offline_indicator';
|
||||
import PostTextbox from 'app/components/post_textbox';
|
||||
import {preventDoubleTap} from 'app/utils/tap';
|
||||
|
||||
import {RequestStatus} from 'mattermost-redux/constants';
|
||||
import EventEmitter from 'mattermost-redux/utils/event_emitter';
|
||||
import {makeStyleSheetFromTheme} from 'app/utils/theme';
|
||||
|
||||
import ChannelDrawerButton from './channel_drawer_button';
|
||||
import ChannelTitle from './channel_title';
|
||||
import ChannelPostList from './channel_post_list';
|
||||
import ChannelPostList from './channel_post_list/index';
|
||||
|
||||
export default class Channel extends PureComponent {
|
||||
class Channel extends PureComponent {
|
||||
static propTypes = {
|
||||
actions: PropTypes.shape({
|
||||
connection: PropTypes.func.isRequired,
|
||||
|
|
@ -30,15 +35,14 @@ export default class Channel extends PureComponent {
|
|||
loadProfilesAndTeamMembersForDMSidebar: PropTypes.func.isRequired,
|
||||
selectFirstAvailableTeam: PropTypes.func.isRequired,
|
||||
selectInitialChannel: PropTypes.func.isRequired,
|
||||
openChannelDrawer: PropTypes.func.isRequired,
|
||||
handlePostDraftChanged: PropTypes.func.isRequired,
|
||||
goToChannelInfo: PropTypes.func.isRequired,
|
||||
initWebSocket: PropTypes.func.isRequired,
|
||||
closeWebSocket: PropTypes.func.isRequired,
|
||||
startPeriodicStatusUpdates: PropTypes.func.isRequired,
|
||||
stopPeriodicStatusUpdates: PropTypes.func.isRequired,
|
||||
renderDrawer: PropTypes.func.isRequired
|
||||
stopPeriodicStatusUpdates: PropTypes.func.isRequired
|
||||
}).isRequired,
|
||||
intl: intlShape.isRequired,
|
||||
navigator: PropTypes.object,
|
||||
appState: PropTypes.bool,
|
||||
currentTeam: PropTypes.object,
|
||||
currentChannel: PropTypes.object,
|
||||
|
|
@ -49,21 +53,9 @@ export default class Channel extends PureComponent {
|
|||
webSocketRequest: PropTypes.object
|
||||
};
|
||||
|
||||
static navigationProps = {
|
||||
allowMenuSwipe: true,
|
||||
renderLeftComponent: (props, emitter) => {
|
||||
return <ChannelDrawerButton emitter={emitter}/>;
|
||||
},
|
||||
renderTitleComponent: (props, emitter) => {
|
||||
return <ChannelTitle emitter={emitter}/>;
|
||||
}
|
||||
};
|
||||
|
||||
componentWillMount() {
|
||||
this.props.subscribeToHeaderEvent('open_channel_drawer', () => preventDoubleTap(this.openChannelDrawer, this));
|
||||
this.props.subscribeToHeaderEvent('show_channel_info', () => preventDoubleTap(this.props.actions.goToChannelInfo));
|
||||
NetInfo.isConnected.addEventListener('change', this.handleConnectionChange);
|
||||
EventEmitter.on('leave_team', this.handleLeaveTeam);
|
||||
NetInfo.isConnected.addEventListener('change', this.handleConnectionChange);
|
||||
NetInfo.isConnected.fetch().then(this.handleConnectionChange);
|
||||
|
||||
if (this.props.currentTeam) {
|
||||
|
|
@ -73,13 +65,13 @@ export default class Channel extends PureComponent {
|
|||
}
|
||||
|
||||
componentDidMount() {
|
||||
const {renderDrawer, startPeriodicStatusUpdates} = this.props.actions;
|
||||
const {startPeriodicStatusUpdates} = this.props.actions;
|
||||
|
||||
try {
|
||||
startPeriodicStatusUpdates();
|
||||
} catch (error) {
|
||||
// We don't care about the error
|
||||
}
|
||||
renderDrawer();
|
||||
}
|
||||
|
||||
componentWillReceiveProps(nextProps) {
|
||||
|
|
@ -90,13 +82,40 @@ export default class Channel extends PureComponent {
|
|||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this.props.actions.closeWebSocket();
|
||||
this.props.actions.stopPeriodicStatusUpdates();
|
||||
this.props.unsubscribeFromHeaderEvent('open_channel_drawer');
|
||||
NetInfo.isConnected.removeEventListener('change', this.handleConnectionChange);
|
||||
const {closeWebSocket, stopPeriodicStatusUpdates} = this.props.actions;
|
||||
|
||||
EventEmitter.off('leave_team', this.handleLeaveTeam);
|
||||
NetInfo.isConnected.removeEventListener('change', this.handleConnectionChange);
|
||||
|
||||
closeWebSocket();
|
||||
stopPeriodicStatusUpdates();
|
||||
}
|
||||
|
||||
attachPostTextbox = (ref) => {
|
||||
this.postTextbox = ref;
|
||||
};
|
||||
|
||||
blurPostTextBox = () => {
|
||||
this.postTextbox.getWrappedInstance().getWrappedInstance().blur();
|
||||
};
|
||||
|
||||
goToChannelInfo = () => {
|
||||
const {intl, navigator, theme} = this.props;
|
||||
|
||||
navigator.push({
|
||||
screen: 'ChannelInfo',
|
||||
title: intl.formatMessage({id: 'mobile.routes.channelInfo', defaultMessage: 'Info'}),
|
||||
animated: true,
|
||||
backButtonTitle: '',
|
||||
navigatorStyle: {
|
||||
navBarTextColor: theme.sidebarHeaderTextColor,
|
||||
navBarBackgroundColor: theme.sidebarHeaderBg,
|
||||
navBarButtonColor: theme.sidebarHeaderTextColor,
|
||||
screenBackgroundColor: theme.centerChannelBg
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
handleConnectionChange = (isConnected) => {
|
||||
const {actions, webSocketRequest} = this.props;
|
||||
const {closeWebSocket, connection, initWebSocket} = actions;
|
||||
|
|
@ -115,27 +134,6 @@ export default class Channel extends PureComponent {
|
|||
connection(isConnected);
|
||||
};
|
||||
|
||||
loadChannels = (teamId) => {
|
||||
const {
|
||||
loadChannelsIfNecessary,
|
||||
loadProfilesAndTeamMembersForDMSidebar,
|
||||
selectInitialChannel
|
||||
} = this.props.actions;
|
||||
loadChannelsIfNecessary(teamId).then(() => {
|
||||
loadProfilesAndTeamMembersForDMSidebar(teamId);
|
||||
return selectInitialChannel(teamId);
|
||||
});
|
||||
};
|
||||
|
||||
openChannelDrawer = () => {
|
||||
this.postTextbox.getWrappedInstance().getWrappedInstance().blur();
|
||||
this.props.actions.openChannelDrawer();
|
||||
};
|
||||
|
||||
attachPostTextbox = (ref) => {
|
||||
this.postTextbox = ref;
|
||||
};
|
||||
|
||||
handleDraftChanged = (value) => {
|
||||
this.props.actions.handlePostDraftChanged(this.props.currentChannel.id, value);
|
||||
};
|
||||
|
|
@ -144,10 +142,26 @@ export default class Channel extends PureComponent {
|
|||
this.props.actions.selectFirstAvailableTeam();
|
||||
};
|
||||
|
||||
loadChannels = (teamId) => {
|
||||
const {
|
||||
loadChannelsIfNecessary,
|
||||
loadProfilesAndTeamMembersForDMSidebar,
|
||||
selectInitialChannel
|
||||
} = this.props.actions;
|
||||
|
||||
loadChannelsIfNecessary(teamId).then(() => {
|
||||
loadProfilesAndTeamMembersForDMSidebar(teamId);
|
||||
return selectInitialChannel(teamId);
|
||||
}).catch(() => {
|
||||
return selectInitialChannel(teamId);
|
||||
});
|
||||
};
|
||||
|
||||
render() {
|
||||
const {
|
||||
currentTeam,
|
||||
currentChannel,
|
||||
navigator,
|
||||
theme
|
||||
} = this.props;
|
||||
|
||||
|
|
@ -159,25 +173,67 @@ export default class Channel extends PureComponent {
|
|||
);
|
||||
}
|
||||
|
||||
const channelDraft = this.props.drafts[this.props.currentChannel.id];
|
||||
const channelDraft = this.props.drafts[currentChannel.id];
|
||||
const style = getStyleFromTheme(theme);
|
||||
|
||||
return (
|
||||
<KeyboardLayout
|
||||
behavior='padding'
|
||||
style={{flex: 1, backgroundColor: theme.centerChannelBg}}
|
||||
keyboardVerticalOffset={65}
|
||||
<ChannelDrawer
|
||||
blurPostTextBox={this.blurPostTextBox}
|
||||
navigator={navigator}
|
||||
>
|
||||
<StatusBar barStyle='light-content'/>
|
||||
<OfflineIndicator/>
|
||||
<ChannelPostList channel={currentChannel}/>
|
||||
<PostTextbox
|
||||
ref={this.attachPostTextbox}
|
||||
files={channelDraft.files}
|
||||
value={channelDraft.draft}
|
||||
channelId={currentChannel.id}
|
||||
onChangeText={this.handleDraftChanged}
|
||||
/>
|
||||
</KeyboardLayout>
|
||||
<KeyboardLayout
|
||||
behavior='padding'
|
||||
style={{flex: 1, backgroundColor: theme.centerChannelBg}}
|
||||
keyboardVerticalOffset={0}
|
||||
>
|
||||
<ChannelPostList
|
||||
channel={currentChannel}
|
||||
navigator={navigator}
|
||||
/>
|
||||
<PostTextbox
|
||||
ref={this.attachPostTextbox}
|
||||
files={channelDraft.files}
|
||||
value={channelDraft.draft}
|
||||
channelId={currentChannel.id}
|
||||
onChangeText={this.handleDraftChanged}
|
||||
navigator={navigator}
|
||||
/>
|
||||
</KeyboardLayout>
|
||||
<View style={{flex: 1, position: 'absolute'}}>
|
||||
<View style={style.header}>
|
||||
<ChannelDrawerButton/>
|
||||
<ChannelTitle
|
||||
onPress={() => preventDoubleTap(this.goToChannelInfo, this)}
|
||||
/>
|
||||
</View>
|
||||
<OfflineIndicator/>
|
||||
</View>
|
||||
</ChannelDrawer>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const getStyleFromTheme = makeStyleSheetFromTheme((theme) => {
|
||||
return StyleSheet.create({
|
||||
header: {
|
||||
backgroundColor: theme.sidebarHeaderBg,
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'flex-start',
|
||||
width: Dimensions.get('window').width,
|
||||
zIndex: 10,
|
||||
elevation: 2,
|
||||
...Platform.select({
|
||||
android: {
|
||||
height: 46
|
||||
},
|
||||
ios: {
|
||||
height: 64,
|
||||
paddingTop: 20
|
||||
}
|
||||
})
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
export default injectIntl(Channel);
|
||||
|
|
@ -10,10 +10,12 @@ import {
|
|||
TouchableOpacity,
|
||||
View
|
||||
} from 'react-native';
|
||||
import DeviceNotification from 'react-native-push-notification';
|
||||
import Icon from 'react-native-vector-icons/Ionicons';
|
||||
|
||||
import Badge from 'app/components/badge';
|
||||
import {getTheme} from 'app/selectors/preferences';
|
||||
import {preventDoubleTap} from 'app/utils/tap';
|
||||
|
||||
import {getUnreads} from 'mattermost-redux/selectors/entities/channels';
|
||||
import EventEmitter from 'mattermost-redux/utils/event_emitter';
|
||||
|
|
@ -21,7 +23,6 @@ import EventEmitter from 'mattermost-redux/utils/event_emitter';
|
|||
class ChannelDrawerButton extends PureComponent {
|
||||
static propTypes = {
|
||||
applicationInitializing: PropTypes.bool.isRequired,
|
||||
emitter: PropTypes.func.isRequired,
|
||||
theme: PropTypes.object,
|
||||
messageCount: PropTypes.number,
|
||||
mentionCount: PropTypes.number
|
||||
|
|
@ -56,6 +57,12 @@ class ChannelDrawerButton extends PureComponent {
|
|||
EventEmitter.on('drawer_opacity', this.setOpacity);
|
||||
}
|
||||
|
||||
componentWillReceiveProps(nextProps) {
|
||||
if (nextProps.mentionCount !== this.props.mentionCount) {
|
||||
DeviceNotification.setApplicationIconBadgeNumber(nextProps.mentionCount);
|
||||
}
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
EventEmitter.off('drawer_opacity', this.setOpacity);
|
||||
}
|
||||
|
|
@ -65,7 +72,7 @@ class ChannelDrawerButton extends PureComponent {
|
|||
};
|
||||
|
||||
handlePress = () => {
|
||||
this.props.emitter('open_channel_drawer');
|
||||
EventEmitter.emit('open_channel_drawer');
|
||||
};
|
||||
|
||||
render() {
|
||||
|
|
@ -88,7 +95,7 @@ class ChannelDrawerButton extends PureComponent {
|
|||
count={badgeCount}
|
||||
minHeight={5}
|
||||
minWidth={5}
|
||||
onPress={this.handlePress}
|
||||
onPress={() => preventDoubleTap(this.handlePress, this)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
|
@ -114,7 +121,7 @@ class ChannelDrawerButton extends PureComponent {
|
|||
|
||||
const style = StyleSheet.create({
|
||||
container: {
|
||||
flex: 1
|
||||
width: 40
|
||||
},
|
||||
wrapper: {
|
||||
alignItems: 'center',
|
||||
|
|
@ -151,7 +158,7 @@ const style = StyleSheet.create({
|
|||
|
||||
function mapStateToProps(state) {
|
||||
return {
|
||||
applicationInitializing: state.views.channel.appInitializing,
|
||||
applicationInitializing: state.views.root.appInitializing,
|
||||
theme: getTheme(state),
|
||||
...getUnreads(state)
|
||||
};
|
||||
|
|
@ -2,6 +2,7 @@
|
|||
// See License.txt for license information.
|
||||
|
||||
import React, {PropTypes, PureComponent} from 'react';
|
||||
import {injectIntl, intlShape} from 'react-intl';
|
||||
import {
|
||||
Animated,
|
||||
Dimensions,
|
||||
|
|
@ -17,17 +18,19 @@ import PostListRetry from 'app/components/post_list_retry';
|
|||
const {View: AnimatedView} = Animated;
|
||||
const {height: deviceHeight, width: deviceWidth} = Dimensions.get('window');
|
||||
|
||||
export default class ChannelPostList extends PureComponent {
|
||||
class ChannelPostList extends PureComponent {
|
||||
static propTypes = {
|
||||
actions: PropTypes.shape({
|
||||
loadPostsIfNecessary: PropTypes.func.isRequired,
|
||||
getPostsBefore: PropTypes.func.isRequired,
|
||||
goToThread: PropTypes.func.isRequired
|
||||
selectPost: PropTypes.func.isRequired
|
||||
}).isRequired,
|
||||
applicationInitializing: PropTypes.bool.isRequired,
|
||||
channel: PropTypes.object.isRequired,
|
||||
channelIsLoading: PropTypes.bool,
|
||||
intl: intlShape.isRequired,
|
||||
myMember: PropTypes.object.isRequired,
|
||||
navigator: PropTypes.object,
|
||||
postsRequests: PropTypes.shape({
|
||||
getPosts: PropTypes.object.isRequired,
|
||||
getPostsBefore: PropTypes.object.isRequired,
|
||||
|
|
@ -105,7 +108,36 @@ export default class ChannelPostList extends PureComponent {
|
|||
};
|
||||
|
||||
goToThread = (post) => {
|
||||
this.props.actions.goToThread(post.channel_id, post.root_id || post.id);
|
||||
const {actions, channel, intl, navigator, theme} = this.props;
|
||||
const channelId = post.channel_id;
|
||||
const rootId = (post.root_id || post.id);
|
||||
|
||||
actions.selectPost(rootId);
|
||||
|
||||
let title;
|
||||
if (channel.type === General.DM_CHANNEL) {
|
||||
title = intl.formatMessage({id: 'mobile.routes.thread_dm', defaultMessage: 'Direct Message Thread'});
|
||||
} else {
|
||||
const channelName = channel.display_name;
|
||||
title = intl.formatMessage({id: 'mobile.routes.thread', defaultMessage: '{channelName} Thread'}, {channelName});
|
||||
}
|
||||
|
||||
navigator.push({
|
||||
screen: 'Thread',
|
||||
title,
|
||||
animated: true,
|
||||
backButtonTitle: '',
|
||||
navigatorStyle: {
|
||||
navBarTextColor: theme.sidebarHeaderTextColor,
|
||||
navBarBackgroundColor: theme.sidebarHeaderBg,
|
||||
navBarButtonColor: theme.sidebarHeaderTextColor,
|
||||
screenBackgroundColor: theme.centerChannelBg
|
||||
},
|
||||
passProps: {
|
||||
channelId,
|
||||
rootId
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
render() {
|
||||
|
|
@ -114,6 +146,7 @@ export default class ChannelPostList extends PureComponent {
|
|||
applicationInitializing,
|
||||
channel,
|
||||
channelIsLoading,
|
||||
navigator,
|
||||
posts,
|
||||
postsRequests,
|
||||
theme,
|
||||
|
|
@ -141,6 +174,7 @@ export default class ChannelPostList extends PureComponent {
|
|||
currentUserId={this.props.myMember.user_id}
|
||||
lastViewedAt={this.state.lastViewedAt}
|
||||
channel={channel}
|
||||
navigator={navigator}
|
||||
/>
|
||||
);
|
||||
} else {
|
||||
|
|
@ -167,3 +201,5 @@ export default class ChannelPostList extends PureComponent {
|
|||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default injectIntl(ChannelPostList);
|
||||
|
|
@ -4,11 +4,10 @@
|
|||
import {bindActionCreators} from 'redux';
|
||||
import {connect} from 'react-redux';
|
||||
import {createSelector} from 'reselect';
|
||||
import {getPostsBefore} from 'mattermost-redux/actions/posts';
|
||||
import {selectPost, getPostsBefore} from 'mattermost-redux/actions/posts';
|
||||
import {getAllPosts, getPostsInCurrentChannel} from 'mattermost-redux/selectors/entities/posts';
|
||||
import {getMyCurrentChannelMembership} from 'mattermost-redux/selectors/entities/channels';
|
||||
|
||||
import {goToThread} from 'app/actions/navigation';
|
||||
import {loadPostsIfNecessary} from 'app/actions/views/channel';
|
||||
import {getTheme} from 'app/selectors/preferences';
|
||||
|
||||
|
|
@ -67,7 +66,7 @@ const getPostsInCurrentChannelWithReplyProps = createSelector(
|
|||
function mapStateToProps(state, ownProps) {
|
||||
return {
|
||||
...ownProps,
|
||||
applicationInitializing: state.views.channel.appInitializing,
|
||||
applicationInitializing: state.views.root.appInitializing,
|
||||
channelIsLoading: state.views.channel.loading,
|
||||
myMember: getMyCurrentChannelMembership(state),
|
||||
postsRequests: state.requests.posts,
|
||||
|
|
@ -82,7 +81,7 @@ function mapDispatchToProps(dispatch) {
|
|||
actions: bindActionCreators({
|
||||
loadPostsIfNecessary,
|
||||
getPostsBefore,
|
||||
goToThread
|
||||
selectPost
|
||||
}, dispatch)
|
||||
};
|
||||
}
|
||||
|
|
@ -11,6 +11,7 @@ import {
|
|||
import Icon from 'react-native-vector-icons/FontAwesome';
|
||||
|
||||
import {getCurrentChannel} from 'mattermost-redux/selectors/entities/channels';
|
||||
|
||||
import {getTheme} from 'app/selectors/preferences';
|
||||
|
||||
function ChannelTitle(props) {
|
||||
|
|
@ -33,8 +34,8 @@ function ChannelTitle(props) {
|
|||
|
||||
return (
|
||||
<TouchableOpacity
|
||||
style={{flexDirection: 'row', flex: 1}}
|
||||
onPress={() => props.emitter('show_channel_info')}
|
||||
style={{flexDirection: 'row', flex: 1, marginRight: 40}}
|
||||
onPress={props.onPress}
|
||||
>
|
||||
<View style={{flex: 1, flexDirection: 'row', alignItems: 'center', justifyContent: 'center', marginHorizontal: 15}}>
|
||||
<Text
|
||||
|
|
@ -56,7 +57,7 @@ function ChannelTitle(props) {
|
|||
ChannelTitle.propTypes = {
|
||||
applicationInitializing: PropTypes.bool.isRequired,
|
||||
currentChannel: PropTypes.object,
|
||||
emitter: PropTypes.func.isRequired,
|
||||
onPress: PropTypes.func,
|
||||
theme: PropTypes.object
|
||||
};
|
||||
|
||||
|
|
@ -67,7 +68,7 @@ ChannelTitle.defaultProps = {
|
|||
|
||||
function mapStateToProps(state) {
|
||||
return {
|
||||
applicationInitializing: state.views.channel.appInitializing,
|
||||
applicationInitializing: state.views.root.appInitializing,
|
||||
currentChannel: getCurrentChannel(state),
|
||||
theme: getTheme(state)
|
||||
};
|
||||
|
|
@ -2,14 +2,8 @@
|
|||
// See License.txt for license information.
|
||||
|
||||
import {bindActionCreators} from 'redux';
|
||||
import {connect} from 'react-redux';
|
||||
|
||||
import navigationSceneConnect from '../navigationSceneConnect';
|
||||
|
||||
import {
|
||||
goToChannelInfo,
|
||||
openChannelDrawer,
|
||||
renderDrawer
|
||||
} from 'app/actions/navigation';
|
||||
import {
|
||||
loadChannelsIfNecessary,
|
||||
loadProfilesAndTeamMembersForDMSidebar,
|
||||
|
|
@ -52,16 +46,13 @@ function mapDispatchToProps(dispatch) {
|
|||
loadProfilesAndTeamMembersForDMSidebar,
|
||||
selectFirstAvailableTeam,
|
||||
selectInitialChannel,
|
||||
openChannelDrawer,
|
||||
handlePostDraftChanged,
|
||||
goToChannelInfo,
|
||||
initWebSocket,
|
||||
closeWebSocket,
|
||||
startPeriodicStatusUpdates,
|
||||
stopPeriodicStatusUpdates,
|
||||
renderDrawer
|
||||
stopPeriodicStatusUpdates
|
||||
}, dispatch)
|
||||
};
|
||||
}
|
||||
|
||||
export default navigationSceneConnect(mapStateToProps, mapDispatchToProps)(Channel);
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(Channel);
|
||||
|
|
@ -8,18 +8,18 @@ import {
|
|||
Keyboard,
|
||||
Platform,
|
||||
InteractionManager,
|
||||
StatusBar,
|
||||
StyleSheet,
|
||||
View
|
||||
} from 'react-native';
|
||||
|
||||
import ActionButton from 'app/components/action_button';
|
||||
import Loading from 'app/components/loading';
|
||||
import MemberList from 'app/components/custom_list';
|
||||
import SearchBar from 'app/components/search_bar';
|
||||
import {createMembersSections, loadingText, markSelectedProfiles, renderMemberRow} from 'app/utils/member_list';
|
||||
import {changeOpacity, makeStyleSheetFromTheme} from 'app/utils/theme';
|
||||
|
||||
import {General, RequestStatus} from 'mattermost-redux/constants';
|
||||
import EventEmitter from 'mattermost-redux/utils/event_emitter';
|
||||
import {filterProfilesMatchingTerm} from 'mattermost-redux/utils/user_utils';
|
||||
|
||||
class ChannelAddMembers extends PureComponent {
|
||||
|
|
@ -31,35 +31,23 @@ class ChannelAddMembers extends PureComponent {
|
|||
membersNotInChannel: PropTypes.array.isRequired,
|
||||
currentTeam: PropTypes.object,
|
||||
currentTeamMemberCount: PropTypes.number,
|
||||
navigator: PropTypes.object,
|
||||
preferences: PropTypes.object,
|
||||
loadMoreRequestStatus: PropTypes.string,
|
||||
searchRequestStatus: PropTypes.string,
|
||||
addChannelMemberStatus: PropTypes.string,
|
||||
subscribeToHeaderEvent: React.PropTypes.func,
|
||||
unsubscribeFromHeaderEvent: React.PropTypes.func,
|
||||
actions: PropTypes.shape({
|
||||
getTeamStats: PropTypes.func.isRequired,
|
||||
getProfilesNotInChannel: PropTypes.func.isRequired,
|
||||
goBack: PropTypes.func.isRequired,
|
||||
handleAddChannelMembers: PropTypes.func.isRequired,
|
||||
searchProfiles: PropTypes.func.isRequired
|
||||
})
|
||||
};
|
||||
|
||||
static navigationProps = {
|
||||
renderRightComponent: (props, emitter) => {
|
||||
return (
|
||||
<ActionButton
|
||||
actionEventName='add_members'
|
||||
emitter={emitter}
|
||||
enabled={false}
|
||||
enableEventName='can_add_members'
|
||||
labelDefaultMessage='Add'
|
||||
labelId='integrations.add'
|
||||
loadingEventName='adding_members'
|
||||
/>
|
||||
);
|
||||
}
|
||||
addButton = {
|
||||
disabled: true,
|
||||
id: 'add-members',
|
||||
showAsAction: 'never'
|
||||
};
|
||||
|
||||
constructor(props) {
|
||||
|
|
@ -76,10 +64,12 @@ class ChannelAddMembers extends PureComponent {
|
|||
selectedMembers: {},
|
||||
showNoResults: false
|
||||
};
|
||||
}
|
||||
this.addButton.title = props.intl.formatMessage({id: 'integrations.add', defaultMessage: 'Add'});
|
||||
|
||||
componentWillMount() {
|
||||
this.props.subscribeToHeaderEvent('add_members', this.handleAddMembersPress);
|
||||
props.navigator.setOnNavigatorEvent(this.onNavigatorEvent);
|
||||
props.navigator.setButtons({
|
||||
rightButtons: [this.addButton]
|
||||
});
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
|
|
@ -96,8 +86,6 @@ class ChannelAddMembers extends PureComponent {
|
|||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this.props.unsubscribeFromHeaderEvent('add_members');
|
||||
|
||||
if (Platform.OS === 'android') {
|
||||
Keyboard.removeListener('keyboardDidHide', this.handleAndroidKeyboard);
|
||||
}
|
||||
|
|
@ -128,20 +116,46 @@ class ChannelAddMembers extends PureComponent {
|
|||
switch (addChannelMemberStatus) {
|
||||
case RequestStatus.STARTED:
|
||||
this.emitAdding(true);
|
||||
this.setState({error: null});
|
||||
this.setState({error: null, adding: true, canSelect: false});
|
||||
break;
|
||||
case RequestStatus.SUCCESS:
|
||||
this.emitAdding(false);
|
||||
this.setState({error: null});
|
||||
this.props.actions.goBack();
|
||||
this.setState({error: null, adding: false, canSelect: false});
|
||||
this.close();
|
||||
break;
|
||||
case RequestStatus.FAILURE:
|
||||
this.emitAdding(false);
|
||||
this.setState({adding: false, canSelect: true});
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
cancelSearch = () => {
|
||||
this.setState({
|
||||
searching: false,
|
||||
term: null,
|
||||
page: 0,
|
||||
profiles: markSelectedProfiles(this.props.membersNotInChannel, this.state.selectedMembers)
|
||||
});
|
||||
};
|
||||
|
||||
close = () => {
|
||||
this.props.navigator.pop({animated: true});
|
||||
};
|
||||
|
||||
emitAdding = (loading) => {
|
||||
this.props.navigator.setButtons({
|
||||
rightButtons: [{...this.addButton, disabled: loading}]
|
||||
});
|
||||
};
|
||||
|
||||
emitCanAddMembers = (enabled) => {
|
||||
this.props.navigator.setButtons({
|
||||
rightButtons: [{...this.addButton, disabled: !enabled}]
|
||||
});
|
||||
};
|
||||
|
||||
handleAddMembersPress = () => {
|
||||
const {selectedMembers} = this.state;
|
||||
const {actions, currentChannel} = this.props;
|
||||
|
|
@ -159,6 +173,20 @@ class ChannelAddMembers extends PureComponent {
|
|||
this.onSearchButtonPress();
|
||||
};
|
||||
|
||||
handleRowSelect = (id) => {
|
||||
const selectedMembers = Object.assign({}, this.state.selectedMembers, {[id]: !this.state.selectedMembers[id]});
|
||||
|
||||
if (Object.values(selectedMembers).filter((selected) => selected).length) {
|
||||
this.emitCanAddMembers(true);
|
||||
} else {
|
||||
this.emitCanAddMembers(false);
|
||||
}
|
||||
this.setState({
|
||||
profiles: markSelectedProfiles(this.state.profiles, selectedMembers),
|
||||
selectedMembers
|
||||
});
|
||||
};
|
||||
|
||||
loadMoreMembers = () => {
|
||||
const {actions, loadMoreRequestStatus, currentChannel, currentTeam} = this.props;
|
||||
const {next, searching} = this.state;
|
||||
|
|
@ -178,37 +206,22 @@ class ChannelAddMembers extends PureComponent {
|
|||
}
|
||||
};
|
||||
|
||||
handleRowSelect = (id) => {
|
||||
const selectedMembers = Object.assign({}, this.state.selectedMembers, {[id]: !this.state.selectedMembers[id]});
|
||||
|
||||
if (Object.values(selectedMembers).filter((selected) => selected).length) {
|
||||
this.emitCanAddMembers(true);
|
||||
} else {
|
||||
this.emitCanAddMembers(false);
|
||||
onNavigatorEvent = (event) => {
|
||||
if (event.type === 'NavBarButtonPress') {
|
||||
if (event.id === 'add-members') {
|
||||
this.handleAddMembersPress();
|
||||
}
|
||||
}
|
||||
this.setState({
|
||||
profiles: markSelectedProfiles(this.state.profiles, selectedMembers),
|
||||
selectedMembers
|
||||
});
|
||||
};
|
||||
|
||||
emitCanAddMembers = (enabled) => {
|
||||
EventEmitter.emit('can_add_members', enabled);
|
||||
};
|
||||
|
||||
emitAdding = (loading) => {
|
||||
this.setState({canSelect: false});
|
||||
EventEmitter.emit('adding_members', loading);
|
||||
};
|
||||
|
||||
searchBarRef = (ref) => {
|
||||
this.searchBar = ref;
|
||||
};
|
||||
|
||||
onSearchButtonPress = () => {
|
||||
this.searchBar.blur();
|
||||
};
|
||||
|
||||
searchBarRef = (ref) => {
|
||||
this.searchBar = ref;
|
||||
};
|
||||
|
||||
searchProfiles = (event) => {
|
||||
const term = event.nativeEvent.text.toLowerCase();
|
||||
const {actions, currentChannel, currentTeam} = this.props;
|
||||
|
|
@ -225,26 +238,27 @@ class ChannelAddMembers extends PureComponent {
|
|||
}
|
||||
};
|
||||
|
||||
cancelSearch = () => {
|
||||
this.setState({
|
||||
searching: false,
|
||||
term: null,
|
||||
page: 0,
|
||||
profiles: markSelectedProfiles(this.props.membersNotInChannel, this.state.selectedMembers)
|
||||
});
|
||||
};
|
||||
|
||||
render() {
|
||||
const {intl, loadMoreRequestStatus, searchRequestStatus, preferences, theme} = this.props;
|
||||
const {profiles, searching} = this.state;
|
||||
const {adding, profiles, searching} = this.state;
|
||||
const {formatMessage} = intl;
|
||||
const isLoading = (loadMoreRequestStatus === RequestStatus.STARTED) ||
|
||||
(searchRequestStatus === RequestStatus.STARTED);
|
||||
const style = getStyleFromTheme(theme);
|
||||
const more = searching ? () => true : this.loadMoreMembers;
|
||||
|
||||
if (adding) {
|
||||
return (
|
||||
<View style={style.container}>
|
||||
<StatusBar barStyle='light-content'/>
|
||||
<Loading/>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<View style={style.container}>
|
||||
<StatusBar barStyle='light-content'/>
|
||||
<View
|
||||
style={{marginVertical: 5}}
|
||||
>
|
||||
|
|
@ -2,11 +2,9 @@
|
|||
// See License.txt for license information.
|
||||
|
||||
import {bindActionCreators} from 'redux';
|
||||
|
||||
import navigationSceneConnect from '../navigationSceneConnect';
|
||||
import {connect} from 'react-redux';
|
||||
|
||||
import {handleAddChannelMembers} from 'app/actions/views/channel_add_members';
|
||||
import {goBack} from 'app/actions/navigation';
|
||||
import {getTheme} from 'app/selectors/preferences';
|
||||
import {getCurrentChannel, getCurrentChannelStats} from 'mattermost-redux/selectors/entities/channels';
|
||||
import {getMyPreferences} from 'mattermost-redux/selectors/entities/preferences';
|
||||
|
|
@ -41,11 +39,10 @@ function mapDispatchToProps(dispatch) {
|
|||
actions: bindActionCreators({
|
||||
getTeamStats,
|
||||
getProfilesNotInChannel,
|
||||
goBack,
|
||||
handleAddChannelMembers,
|
||||
searchProfiles
|
||||
}, dispatch)
|
||||
};
|
||||
}
|
||||
|
||||
export default navigationSceneConnect(mapStateToProps, mapDispatchToProps)(ChannelAddMembers);
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(ChannelAddMembers);
|
||||
|
|
@ -6,6 +6,7 @@ import {injectIntl, intlShape} from 'react-intl';
|
|||
import {
|
||||
Alert,
|
||||
ScrollView,
|
||||
StatusBar,
|
||||
StyleSheet,
|
||||
View
|
||||
} from 'react-native';
|
||||
|
|
@ -25,6 +26,7 @@ class ChannelInfo extends PureComponent {
|
|||
currentChannel: PropTypes.object.isRequired,
|
||||
currentChannelCreatorName: PropTypes.string,
|
||||
currentChannelMemberCount: PropTypes.number,
|
||||
navigator: PropTypes.object,
|
||||
status: PropTypes.string,
|
||||
theme: PropTypes.object.isRequired,
|
||||
isCurrent: PropTypes.bool.isRequired,
|
||||
|
|
@ -35,9 +37,6 @@ class ChannelInfo extends PureComponent {
|
|||
closeGMChannel: PropTypes.func.isRequired,
|
||||
deleteChannel: PropTypes.func.isRequired,
|
||||
getChannelStats: PropTypes.func.isRequired,
|
||||
goBack: PropTypes.func.isRequired,
|
||||
goToChannelAddMembers: PropTypes.func.isRequired,
|
||||
goToChannelMembers: PropTypes.func.isRequired,
|
||||
leaveChannel: PropTypes.func.isRequired,
|
||||
markFavorite: PropTypes.func.isRequired,
|
||||
unmarkFavorite: PropTypes.func.isRequired
|
||||
|
|
@ -63,6 +62,41 @@ class ChannelInfo extends PureComponent {
|
|||
}
|
||||
}
|
||||
|
||||
goToChannelAddMembers = () => {
|
||||
const {intl, navigator, theme} = this.props;
|
||||
navigator.push({
|
||||
backButtonTitle: '',
|
||||
screen: 'ChannelAddMembers',
|
||||
title: intl.formatMessage({id: 'channel_header.addMembers', defaultMessage: 'Add Members'}),
|
||||
animated: true,
|
||||
navigatorStyle: {
|
||||
navBarTextColor: theme.sidebarHeaderTextColor,
|
||||
navBarBackgroundColor: theme.sidebarHeaderBg,
|
||||
navBarButtonColor: theme.sidebarHeaderTextColor,
|
||||
screenBackgroundColor: theme.centerChannelBg
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
goToChannelMembers = () => {
|
||||
const {canManageUsers, intl, navigator, theme} = this.props;
|
||||
const id = canManageUsers ? 'channel_header.manageMembers' : 'channel_header.viewMembers';
|
||||
const defaultMessage = canManageUsers ? 'Manage Members' : 'View Members';
|
||||
|
||||
navigator.push({
|
||||
backButtonTitle: '',
|
||||
screen: 'ChannelMembers',
|
||||
title: intl.formatMessage({id, defaultMessage}),
|
||||
animated: true,
|
||||
navigatorStyle: {
|
||||
navBarTextColor: theme.sidebarHeaderTextColor,
|
||||
navBarBackgroundColor: theme.sidebarHeaderBg,
|
||||
navBarButtonColor: theme.sidebarHeaderTextColor,
|
||||
screenBackgroundColor: theme.centerChannelBg
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
handleDeleteOrLeave(eventType) {
|
||||
const {formatMessage} = this.props.intl;
|
||||
const channel = this.props.currentChannel;
|
||||
|
|
@ -79,7 +113,9 @@ class ChannelInfo extends PureComponent {
|
|||
defaultMessage: 'Are you sure you want to leave the {term} {name}?'
|
||||
};
|
||||
onPressAction = () => {
|
||||
this.props.actions.leaveChannel(channel, true).then(this.props.actions.goBack);
|
||||
this.props.actions.leaveChannel(channel, true).then(() => {
|
||||
this.props.navigator.pop({animated: true});
|
||||
});
|
||||
};
|
||||
} else if (eventType === 'delete') {
|
||||
title = {id: 'mobile.channel_info.alertTitleDeleteChannel', defaultMessage: 'Delete {term}'};
|
||||
|
|
@ -88,7 +124,9 @@ class ChannelInfo extends PureComponent {
|
|||
defaultMessage: 'Are you sure you want to delete the {term} {name}?'
|
||||
};
|
||||
onPressAction = () => {
|
||||
this.props.actions.deleteChannel(channel.id).then(this.props.actions.goBack);
|
||||
this.props.actions.deleteChannel(channel.id).then(() => {
|
||||
this.props.navigator.pop({animated: true});
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -113,14 +151,18 @@ class ChannelInfo extends PureComponent {
|
|||
handleClose = () => {
|
||||
const {currentChannel, isCurrent, isFavorite} = this.props;
|
||||
const channel = Object.assign({}, currentChannel, {isCurrent}, {isFavorite});
|
||||
const {closeDMChannel, closeGMChannel, goBack} = this.props.actions;
|
||||
const {closeDMChannel, closeGMChannel} = this.props.actions;
|
||||
|
||||
switch (channel.type) {
|
||||
case General.DM_CHANNEL:
|
||||
closeDMChannel(channel).then(goBack);
|
||||
closeDMChannel(channel).then(() => {
|
||||
this.props.navigator.pop({animated: true});
|
||||
});
|
||||
break;
|
||||
case General.GM_CHANNEL:
|
||||
closeGMChannel(channel).then(goBack);
|
||||
closeGMChannel(channel).then(() => {
|
||||
this.props.navigator.pop({animated: true});
|
||||
});
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
|
@ -178,6 +220,7 @@ class ChannelInfo extends PureComponent {
|
|||
|
||||
return (
|
||||
<View style={style.container}>
|
||||
<StatusBar barStyle='light-content'/>
|
||||
<ScrollView
|
||||
style={style.scrollView}
|
||||
>
|
||||
|
|
@ -217,7 +260,7 @@ class ChannelInfo extends PureComponent {
|
|||
**/
|
||||
}
|
||||
<ChannelInfoRow
|
||||
action={() => preventDoubleTap(this.props.actions.goToChannelMembers)}
|
||||
action={() => preventDoubleTap(this.goToChannelMembers)}
|
||||
defaultMessage={canManageUsers ? 'Manage Members' : 'View Members'}
|
||||
detail={currentChannelMemberCount}
|
||||
icon='users'
|
||||
|
|
@ -228,7 +271,7 @@ class ChannelInfo extends PureComponent {
|
|||
<View>
|
||||
<View style={style.separator}/>
|
||||
<ChannelInfoRow
|
||||
action={() => preventDoubleTap(this.props.actions.goToChannelAddMembers)}
|
||||
action={() => preventDoubleTap(this.goToChannelAddMembers)}
|
||||
defaultMessage='Add Members'
|
||||
icon='user-plus'
|
||||
textId='channel_header.addMembers'
|
||||
|
|
@ -2,8 +2,8 @@
|
|||
// See License.txt for license information.
|
||||
|
||||
import {bindActionCreators} from 'redux';
|
||||
import {connect} from 'react-redux';
|
||||
|
||||
import {goToChannelMembers, goToChannelAddMembers, goBack} from 'app/actions/navigation';
|
||||
import {
|
||||
closeDMChannel,
|
||||
closeGMChannel,
|
||||
|
|
@ -11,7 +11,6 @@ import {
|
|||
markFavorite,
|
||||
unmarkFavorite
|
||||
} from 'app/actions/views/channel';
|
||||
import navigationSceneConnect from 'app/scenes/navigationSceneConnect';
|
||||
import {getTheme} from 'app/selectors/preferences';
|
||||
|
||||
import {getChannelStats, deleteChannel} from 'mattermost-redux/actions/channels';
|
||||
|
|
@ -67,9 +66,6 @@ function mapDispatchToProps(dispatch) {
|
|||
closeGMChannel,
|
||||
deleteChannel,
|
||||
getChannelStats,
|
||||
goBack,
|
||||
goToChannelAddMembers,
|
||||
goToChannelMembers,
|
||||
leaveChannel,
|
||||
markFavorite,
|
||||
unmarkFavorite
|
||||
|
|
@ -77,4 +73,4 @@ function mapDispatchToProps(dispatch) {
|
|||
};
|
||||
}
|
||||
|
||||
export default navigationSceneConnect(mapStateToProps, mapDispatchToProps)(ChannelInfo);
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(ChannelInfo);
|
||||
|
|
@ -7,11 +7,13 @@ import {
|
|||
Keyboard,
|
||||
Platform,
|
||||
InteractionManager,
|
||||
StatusBar,
|
||||
StyleSheet,
|
||||
View
|
||||
} from 'react-native';
|
||||
import {injectIntl, intlShape} from 'react-intl';
|
||||
|
||||
import Loading from 'app/components/loading';
|
||||
import MemberList from 'app/components/custom_list';
|
||||
import SearchBar from 'app/components/search_bar';
|
||||
import {createMembersSections, loadingText, markSelectedProfiles} from 'app/utils/member_list';
|
||||
|
|
@ -19,12 +21,8 @@ import MemberListRow from 'app/components/custom_list/member_list_row';
|
|||
import {changeOpacity, makeStyleSheetFromTheme} from 'app/utils/theme';
|
||||
|
||||
import {General, RequestStatus} from 'mattermost-redux/constants';
|
||||
import EventEmitter from 'mattermost-redux/utils/event_emitter';
|
||||
import {displayUsername, filterProfilesMatchingTerm} from 'mattermost-redux/utils/user_utils';
|
||||
|
||||
import ChannelMembersTitle from './channel_members_title';
|
||||
import RemoveMemberButton from './remove_member_button';
|
||||
|
||||
class ChannelMembers extends PureComponent {
|
||||
static propTypes = {
|
||||
intl: intlShape.isRequired,
|
||||
|
|
@ -33,28 +31,23 @@ class ChannelMembers extends PureComponent {
|
|||
currentChannelMembers: PropTypes.array.isRequired,
|
||||
currentChannelMemberCount: PropTypes.number.isRequired,
|
||||
currentUserId: PropTypes.string.isRequired,
|
||||
navigator: PropTypes.object,
|
||||
preferences: PropTypes.object,
|
||||
requestStatus: PropTypes.string,
|
||||
searchRequestStatus: PropTypes.string,
|
||||
removeMembersStatus: PropTypes.string,
|
||||
canManageUsers: PropTypes.bool.isRequired,
|
||||
subscribeToHeaderEvent: React.PropTypes.func,
|
||||
unsubscribeFromHeaderEvent: React.PropTypes.func,
|
||||
actions: PropTypes.shape({
|
||||
getProfilesInChannel: PropTypes.func.isRequired,
|
||||
goBack: PropTypes.func.isRequired,
|
||||
handleRemoveChannelMembers: PropTypes.func.isRequired,
|
||||
searchProfiles: PropTypes.func.isRequired
|
||||
})
|
||||
};
|
||||
|
||||
static navigationProps = {
|
||||
renderTitleComponent: () => {
|
||||
return <ChannelMembersTitle/>;
|
||||
},
|
||||
renderRightComponent: (props, emitter) => {
|
||||
return <RemoveMemberButton emitter={emitter}/>;
|
||||
}
|
||||
removeButton = {
|
||||
disabled: true,
|
||||
id: 'remove-members',
|
||||
showAsAction: 'never'
|
||||
};
|
||||
|
||||
constructor(props) {
|
||||
|
|
@ -71,10 +64,14 @@ class ChannelMembers extends PureComponent {
|
|||
selectedMembers: {},
|
||||
showNoResults: false
|
||||
};
|
||||
}
|
||||
this.removeButton.title = props.intl.formatMessage({id: 'channel_members_modal.remove', defaultMessage: 'Remove'});
|
||||
|
||||
componentWillMount() {
|
||||
this.props.subscribeToHeaderEvent('remove_members', this.handleRemoveMembersPress);
|
||||
props.navigator.setOnNavigatorEvent(this.onNavigatorEvent);
|
||||
if (props.canManageUsers) {
|
||||
props.navigator.setButtons({
|
||||
rightButtons: [this.removeButton]
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
|
|
@ -90,8 +87,6 @@ class ChannelMembers extends PureComponent {
|
|||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this.props.unsubscribeFromHeaderEvent('remove_members');
|
||||
|
||||
if (Platform.OS === 'android') {
|
||||
Keyboard.removeListener('keyboardDidHide', this.handleAndroidKeyboard);
|
||||
}
|
||||
|
|
@ -122,20 +117,51 @@ class ChannelMembers extends PureComponent {
|
|||
switch (removeMembersStatus) {
|
||||
case RequestStatus.STARTED:
|
||||
this.emitRemoving(true);
|
||||
this.setState({error: null});
|
||||
this.setState({error: null, canSelect: false, removing: true});
|
||||
break;
|
||||
case RequestStatus.SUCCESS:
|
||||
this.emitRemoving(false);
|
||||
this.setState({error: null});
|
||||
this.props.actions.goBack();
|
||||
this.setState({error: null, canSelect: false, removing: false});
|
||||
this.close();
|
||||
break;
|
||||
case RequestStatus.FAILURE:
|
||||
this.emitRemoving(false);
|
||||
this.setState({canSelect: true, removing: false});
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
cancelSearch = () => {
|
||||
this.setState({
|
||||
searching: false,
|
||||
term: null,
|
||||
page: 0,
|
||||
profiles: markSelectedProfiles(this.props.currentChannelMembers, this.state.selectedMembers)
|
||||
});
|
||||
};
|
||||
|
||||
close = () => {
|
||||
this.props.navigator.pop({animated: true});
|
||||
};
|
||||
|
||||
emitCanRemoveMembers = (enabled) => {
|
||||
this.props.navigator.setButtons({
|
||||
rightButtons: [{...this.removeButton, disabled: !enabled}]
|
||||
});
|
||||
};
|
||||
|
||||
emitRemoving = (loading) => {
|
||||
this.setState({canSelect: false, removing: loading});
|
||||
this.props.navigator.setButtons({
|
||||
rightButtons: [{...this.removeButton, disabled: loading}]
|
||||
});
|
||||
};
|
||||
|
||||
handleAndroidKeyboard = () => {
|
||||
this.onSearchButtonPress();
|
||||
};
|
||||
|
||||
handleRemoveMembersPress = () => {
|
||||
const {selectedMembers} = this.state;
|
||||
const membersToRemove = Object.keys(selectedMembers).filter((m) => selectedMembers[m]);
|
||||
|
|
@ -173,13 +199,17 @@ class ChannelMembers extends PureComponent {
|
|||
);
|
||||
};
|
||||
|
||||
handleAndroidKeyboard = () => {
|
||||
this.onSearchButtonPress();
|
||||
};
|
||||
|
||||
removeMembers = (membersToRemove) => {
|
||||
const {actions, currentChannel} = this.props;
|
||||
actions.handleRemoveChannelMembers(currentChannel.id, membersToRemove);
|
||||
handleRowSelect = (id) => {
|
||||
const selectedMembers = Object.assign({}, this.state.selectedMembers, {[id]: !this.state.selectedMembers[id]});
|
||||
if (Object.values(selectedMembers).filter((selected) => selected).length) {
|
||||
this.emitCanRemoveMembers(true);
|
||||
} else {
|
||||
this.emitCanRemoveMembers(false);
|
||||
}
|
||||
this.setState({
|
||||
profiles: markSelectedProfiles(this.state.profiles, selectedMembers),
|
||||
selectedMembers
|
||||
});
|
||||
};
|
||||
|
||||
loadMoreMembers = () => {
|
||||
|
|
@ -201,58 +231,21 @@ class ChannelMembers extends PureComponent {
|
|||
}
|
||||
};
|
||||
|
||||
handleRowSelect = (id) => {
|
||||
const selectedMembers = Object.assign({}, this.state.selectedMembers, {[id]: !this.state.selectedMembers[id]});
|
||||
if (Object.values(selectedMembers).filter((selected) => selected).length) {
|
||||
this.emitCanRemoveMembers(true);
|
||||
} else {
|
||||
this.emitCanRemoveMembers(false);
|
||||
onNavigatorEvent = (event) => {
|
||||
if (event.type === 'NavBarButtonPress') {
|
||||
if (event.id === 'remove-members') {
|
||||
this.handleRemoveMembersPress();
|
||||
}
|
||||
}
|
||||
this.setState({
|
||||
profiles: markSelectedProfiles(this.state.profiles, selectedMembers),
|
||||
selectedMembers
|
||||
});
|
||||
};
|
||||
|
||||
emitCanRemoveMembers = (enabled) => {
|
||||
EventEmitter.emit('can_remove_members', enabled);
|
||||
};
|
||||
|
||||
emitRemoving = (loading) => {
|
||||
this.setState({canSelect: false});
|
||||
EventEmitter.emit('removing_members', loading);
|
||||
};
|
||||
|
||||
searchBarRef = (ref) => {
|
||||
this.searchBar = ref;
|
||||
};
|
||||
|
||||
onSearchButtonPress = () => {
|
||||
this.searchBar.blur();
|
||||
};
|
||||
|
||||
searchProfiles = (event) => {
|
||||
const term = event.nativeEvent.text.toLowerCase();
|
||||
|
||||
if (term) {
|
||||
this.setState({searching: true, term});
|
||||
clearTimeout(this.searchTimeoutId);
|
||||
|
||||
this.searchTimeoutId = setTimeout(() => {
|
||||
this.props.actions.searchProfiles(term, {in_channel_id: this.props.currentChannel.id});
|
||||
}, General.SEARCH_TIMEOUT_MILLISECONDS);
|
||||
} else {
|
||||
this.cancelSearch();
|
||||
}
|
||||
};
|
||||
|
||||
cancelSearch = () => {
|
||||
this.setState({
|
||||
searching: false,
|
||||
term: null,
|
||||
page: 0,
|
||||
profiles: markSelectedProfiles(this.props.currentChannelMembers, this.state.selectedMembers)
|
||||
});
|
||||
removeMembers = (membersToRemove) => {
|
||||
const {actions, currentChannel} = this.props;
|
||||
actions.handleRemoveChannelMembers(currentChannel.id, membersToRemove);
|
||||
};
|
||||
|
||||
renderMemberRow = (user, sectionId, rowId, preferences, theme, selectable, onPress, onSelect) => {
|
||||
|
|
@ -281,17 +274,46 @@ class ChannelMembers extends PureComponent {
|
|||
);
|
||||
};
|
||||
|
||||
searchBarRef = (ref) => {
|
||||
this.searchBar = ref;
|
||||
};
|
||||
|
||||
searchProfiles = (event) => {
|
||||
const term = event.nativeEvent.text.toLowerCase();
|
||||
|
||||
if (term) {
|
||||
this.setState({searching: true, term});
|
||||
clearTimeout(this.searchTimeoutId);
|
||||
|
||||
this.searchTimeoutId = setTimeout(() => {
|
||||
this.props.actions.searchProfiles(term, {in_channel_id: this.props.currentChannel.id});
|
||||
}, General.SEARCH_TIMEOUT_MILLISECONDS);
|
||||
} else {
|
||||
this.cancelSearch();
|
||||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
const {canManageUsers, intl, preferences, requestStatus, searchRequestStatus, theme} = this.props;
|
||||
const {formatMessage} = intl;
|
||||
const {profiles, searching, showNoResults} = this.state;
|
||||
const {profiles, removing, searching, showNoResults} = this.state;
|
||||
const isLoading = (requestStatus === RequestStatus.STARTED) || (requestStatus.status === RequestStatus.NOT_STARTED) ||
|
||||
(searchRequestStatus === RequestStatus.STARTED);
|
||||
const more = searching ? () => true : this.loadMoreMembers;
|
||||
const style = getStyleFromTheme(theme);
|
||||
|
||||
if (removing) {
|
||||
return (
|
||||
<View style={style.container}>
|
||||
<StatusBar barStyle='light-content'/>
|
||||
<Loading/>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<View style={style.container}>
|
||||
<StatusBar barStyle='light-content'/>
|
||||
<View
|
||||
style={{marginVertical: 5}}
|
||||
>
|
||||
|
|
@ -2,10 +2,8 @@
|
|||
// See License.txt for license information.
|
||||
|
||||
import {bindActionCreators} from 'redux';
|
||||
import {connect} from 'react-redux';
|
||||
|
||||
import navigationSceneConnect from '../navigationSceneConnect';
|
||||
|
||||
import {goBack} from 'app/actions/navigation';
|
||||
import {handleRemoveChannelMembers} from 'app/actions/views/channel_members';
|
||||
import {getTheme} from 'app/selectors/preferences';
|
||||
import {getCurrentChannel, getCurrentChannelStats, canManageChannelMembers} from 'mattermost-redux/selectors/entities/channels';
|
||||
|
|
@ -36,11 +34,10 @@ function mapDispatchToProps(dispatch) {
|
|||
return {
|
||||
actions: bindActionCreators({
|
||||
getProfilesInChannel,
|
||||
goBack,
|
||||
handleRemoveChannelMembers,
|
||||
searchProfiles
|
||||
}, dispatch)
|
||||
};
|
||||
}
|
||||
|
||||
export default navigationSceneConnect(mapStateToProps, mapDispatchToProps)(ChannelMembers);
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(ChannelMembers);
|
||||
410
app/screens/create_channel/create_channel.js
Normal file
410
app/screens/create_channel/create_channel.js
Normal file
|
|
@ -0,0 +1,410 @@
|
|||
// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
import React, {PropTypes, PureComponent} from 'react';
|
||||
import {injectIntl, intlShape} from 'react-intl';
|
||||
import {
|
||||
Dimensions,
|
||||
Keyboard,
|
||||
InteractionManager,
|
||||
Platform,
|
||||
StatusBar,
|
||||
StyleSheet,
|
||||
TouchableWithoutFeedback,
|
||||
View,
|
||||
findNodeHandle
|
||||
} from 'react-native';
|
||||
import {KeyboardAwareScrollView} from 'react-native-keyboard-aware-scroll-view';
|
||||
|
||||
import {General, RequestStatus} from 'mattermost-redux/constants';
|
||||
import EventEmitter from 'mattermost-redux/utils/event_emitter';
|
||||
|
||||
import ErrorText from 'app/components/error_text';
|
||||
import FormattedText from 'app/components/formatted_text';
|
||||
import Loading from 'app/components/loading';
|
||||
import TextInputWithLocalizedPlaceholder from 'app/components/text_input_with_localized_placeholder';
|
||||
|
||||
import {changeOpacity, makeStyleSheetFromTheme} from 'app/utils/theme';
|
||||
|
||||
class CreateChannel extends PureComponent {
|
||||
static propTypes = {
|
||||
intl: intlShape.isRequired,
|
||||
createChannelRequest: PropTypes.object.isRequired,
|
||||
navigator: PropTypes.object,
|
||||
theme: PropTypes.object.isRequired,
|
||||
channelType: PropTypes.string,
|
||||
closeButton: PropTypes.object,
|
||||
actions: PropTypes.shape({
|
||||
handleCreateChannel: PropTypes.func.isRequired
|
||||
})
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
channelType: General.OPEN_CHANNEL
|
||||
};
|
||||
|
||||
leftButton = {
|
||||
id: 'close-new-channel'
|
||||
};
|
||||
|
||||
rightButton = {
|
||||
id: 'create-channel',
|
||||
disabled: true,
|
||||
showAsAction: 'never'
|
||||
};
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
displayName: '',
|
||||
header: '',
|
||||
purpose: ''
|
||||
};
|
||||
this.rightButton.title = props.intl.formatMessage({id: 'mobile.create_channel', defaultMessage: 'Create'});
|
||||
|
||||
if (props.channelType === General.PRIVATE_CHANNEL) {
|
||||
this.left = {...this.leftButton, icon: props.closeButton};
|
||||
}
|
||||
|
||||
const buttons = {
|
||||
rightButtons: [this.rightButton]
|
||||
};
|
||||
|
||||
if (this.left) {
|
||||
buttons.leftButtons = [this.left];
|
||||
}
|
||||
|
||||
props.navigator.setOnNavigatorEvent(this.onNavigatorEvent);
|
||||
props.navigator.setButtons(buttons);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.emitCanCreateChannel(false);
|
||||
}
|
||||
|
||||
componentWillReceiveProps(nextProps) {
|
||||
const {createChannelRequest} = nextProps;
|
||||
|
||||
if (this.props.createChannelRequest !== createChannelRequest) {
|
||||
switch (createChannelRequest.status) {
|
||||
case RequestStatus.STARTED:
|
||||
this.emitCreating(true);
|
||||
this.setState({error: null, creating: true});
|
||||
break;
|
||||
case RequestStatus.SUCCESS:
|
||||
EventEmitter.emit('close_channel_drawer');
|
||||
InteractionManager.runAfterInteractions(() => {
|
||||
this.emitCreating(false);
|
||||
this.setState({error: null, creating: false});
|
||||
this.close(false);
|
||||
});
|
||||
break;
|
||||
case RequestStatus.FAILURE:
|
||||
this.emitCreating(false);
|
||||
this.setState({error: createChannelRequest.error, creating: false});
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
blur = () => {
|
||||
this.nameInput.refs.wrappedInstance.blur();
|
||||
this.purposeInput.refs.wrappedInstance.blur();
|
||||
this.headerInput.refs.wrappedInstance.blur();
|
||||
this.scroll.scrollToPosition(0, 0, true);
|
||||
};
|
||||
|
||||
channelNameRef = (ref) => {
|
||||
this.nameInput = ref;
|
||||
};
|
||||
|
||||
channelPurposeRef = (ref) => {
|
||||
this.purposeInput = ref;
|
||||
};
|
||||
|
||||
channelHeaderRef = (ref) => {
|
||||
this.headerInput = ref;
|
||||
};
|
||||
|
||||
close = (goBack = false) => {
|
||||
if (goBack) {
|
||||
this.props.navigator.pop({animated: true});
|
||||
} else {
|
||||
this.props.navigator.dismissAllModals({
|
||||
animationType: 'slide-down'
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
emitCanCreateChannel = (enabled) => {
|
||||
const buttons = {
|
||||
rightButtons: [{...this.rightButton, disabled: !enabled}]
|
||||
};
|
||||
|
||||
if (this.left) {
|
||||
buttons.leftButtons = [this.left];
|
||||
}
|
||||
|
||||
this.props.navigator.setButtons(buttons);
|
||||
};
|
||||
|
||||
emitCreating = (loading) => {
|
||||
const buttons = {
|
||||
rightButtons: [{...this.rightButton, disabled: loading}]
|
||||
};
|
||||
|
||||
if (this.left) {
|
||||
buttons.leftButtons = [this.left];
|
||||
}
|
||||
|
||||
this.props.navigator.setButtons(buttons);
|
||||
};
|
||||
|
||||
lastTextRef = (ref) => {
|
||||
this.lastText = ref;
|
||||
};
|
||||
|
||||
onCreateChannel = () => {
|
||||
Keyboard.dismiss();
|
||||
const {displayName, purpose, header} = this.state;
|
||||
this.props.actions.handleCreateChannel(displayName, purpose, header, this.props.channelType);
|
||||
};
|
||||
|
||||
onDisplayNameChangeText = (displayName) => {
|
||||
this.setState({displayName});
|
||||
if (displayName && displayName.length >= 2) {
|
||||
this.emitCanCreateChannel(true);
|
||||
} else {
|
||||
this.emitCanCreateChannel(false);
|
||||
}
|
||||
};
|
||||
|
||||
onNavigatorEvent = (event) => {
|
||||
if (event.type === 'NavBarButtonPress') {
|
||||
switch (event.id) {
|
||||
case 'close-new-channel':
|
||||
this.close(this.props.channelType === General.OPEN_CHANNEL);
|
||||
break;
|
||||
case 'create-channel':
|
||||
this.onCreateChannel();
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
onPurposeChangeText = (purpose) => {
|
||||
this.setState({purpose});
|
||||
};
|
||||
|
||||
onHeaderChangeText = (header) => {
|
||||
this.setState({header});
|
||||
};
|
||||
|
||||
scrollRef = (ref) => {
|
||||
this.scroll = ref;
|
||||
};
|
||||
|
||||
scrollToEnd = () => {
|
||||
this.scroll.scrollToFocusedInput(findNodeHandle(this.lastText));
|
||||
};
|
||||
|
||||
render() {
|
||||
const {theme} = this.props;
|
||||
const {creating, displayName, header, purpose, error} = this.state;
|
||||
const {height, width} = Dimensions.get('window');
|
||||
|
||||
const style = getStyleSheet(theme);
|
||||
|
||||
if (creating) {
|
||||
return (
|
||||
<View style={{flex: 1}}>
|
||||
<StatusBar barStyle='light-content'/>
|
||||
<Loading/>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
let displayError;
|
||||
if (error) {
|
||||
displayError = (
|
||||
<View style={[style.errorContainer, {width}]}>
|
||||
<View style={style.errorWrapper}>
|
||||
<ErrorText error={error}/>
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<View style={{flex: 1}}>
|
||||
<StatusBar barStyle='light-content'/>
|
||||
<KeyboardAwareScrollView
|
||||
ref={this.scrollRef}
|
||||
style={style.container}
|
||||
>
|
||||
<TouchableWithoutFeedback onPress={this.blur}>
|
||||
<View style={[style.scrollView, {height: height + (Platform.OS === 'android' ? 200 : 0)}]}>
|
||||
{displayError}
|
||||
<View>
|
||||
<FormattedText
|
||||
style={[style.title, {marginTop: (error ? 10 : 0)}]}
|
||||
id='channel_modal.name'
|
||||
defaultMessage='Name'
|
||||
/>
|
||||
</View>
|
||||
<View style={style.inputContainer}>
|
||||
<TextInputWithLocalizedPlaceholder
|
||||
ref={this.channelNameRef}
|
||||
value={displayName}
|
||||
onChangeText={this.onDisplayNameChangeText}
|
||||
style={style.input}
|
||||
autoCapitalize='none'
|
||||
autoCorrect={false}
|
||||
placeholder={{id: 'channel_modal.nameEx', defaultMessage: 'E.g.: "Bugs", "Marketing", "客户支持"'}}
|
||||
placeholderTextColor={changeOpacity('#000', 0.5)}
|
||||
underlineColorAndroid='transparent'
|
||||
/>
|
||||
</View>
|
||||
<View style={style.titleContainer30}>
|
||||
<FormattedText
|
||||
style={style.title}
|
||||
id='channel_modal.purpose'
|
||||
defaultMessage='Purpose'
|
||||
/>
|
||||
<FormattedText
|
||||
style={style.optional}
|
||||
id='channel_modal.optional'
|
||||
defaultMessage='(optional)'
|
||||
/>
|
||||
</View>
|
||||
<View style={style.inputContainer}>
|
||||
<TextInputWithLocalizedPlaceholder
|
||||
ref={this.channelPurposeRef}
|
||||
value={purpose}
|
||||
onChangeText={this.onPurposeChangeText}
|
||||
style={[style.input, {height: 110}]}
|
||||
autoCapitalize='none'
|
||||
autoCorrect={false}
|
||||
placeholder={{id: 'channel_modal.purposeEx', defaultMessage: 'E.g.: "A channel to file bugs and improvements"'}}
|
||||
placeholderTextColor={changeOpacity('#000', 0.5)}
|
||||
multiline={true}
|
||||
underlineColorAndroid='transparent'
|
||||
/>
|
||||
</View>
|
||||
<View>
|
||||
<FormattedText
|
||||
style={style.helpText}
|
||||
id='channel_modal.descriptionHelp'
|
||||
defaultMessage='Describe how this channel should be used.'
|
||||
/>
|
||||
</View>
|
||||
<View style={style.titleContainer15}>
|
||||
<FormattedText
|
||||
style={style.title}
|
||||
id='channel_modal.header'
|
||||
defaultMessage='Header'
|
||||
/>
|
||||
<FormattedText
|
||||
style={style.optional}
|
||||
id='channel_modal.optional'
|
||||
defaultMessage='(optional)'
|
||||
/>
|
||||
</View>
|
||||
<View style={style.inputContainer}>
|
||||
<TextInputWithLocalizedPlaceholder
|
||||
ref={this.channelHeaderRef}
|
||||
value={header}
|
||||
onChangeText={this.onHeaderChangeText}
|
||||
style={[style.input, {height: 110}]}
|
||||
autoCapitalize='none'
|
||||
autoCorrect={false}
|
||||
placeholder={{id: 'channel_modal.headerEx', defaultMessage: 'E.g.: "[Link Title](http://example.com)"'}}
|
||||
placeholderTextColor={changeOpacity('#000', 0.5)}
|
||||
multiline={true}
|
||||
onFocus={this.scrollToEnd}
|
||||
underlineColorAndroid='transparent'
|
||||
/>
|
||||
</View>
|
||||
<View ref={this.lastTextRef}>
|
||||
<FormattedText
|
||||
style={style.helpText}
|
||||
id='channel_modal.headerHelp'
|
||||
defaultMessage={'Set text that will appear in the header of the channel beside the channel name. For example, include frequently used links by typing [Link Title](http://example.com).'}
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
</TouchableWithoutFeedback>
|
||||
</KeyboardAwareScrollView>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const getStyleSheet = makeStyleSheetFromTheme((theme) => {
|
||||
return StyleSheet.create({
|
||||
container: {
|
||||
flex: 1,
|
||||
backgroundColor: theme.centerChannelBg
|
||||
},
|
||||
scrollView: {
|
||||
flex: 1,
|
||||
backgroundColor: changeOpacity(theme.centerChannelColor, 0.03),
|
||||
paddingTop: 30
|
||||
},
|
||||
errorContainer: {
|
||||
position: 'absolute'
|
||||
},
|
||||
errorWrapper: {
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
marginBottom: 10
|
||||
},
|
||||
inputContainer: {
|
||||
marginTop: 10,
|
||||
backgroundColor: '#fff'
|
||||
},
|
||||
input: {
|
||||
color: '#333',
|
||||
fontSize: 14,
|
||||
height: 40,
|
||||
paddingHorizontal: 15
|
||||
},
|
||||
titleContainer30: {
|
||||
flexDirection: 'row',
|
||||
marginTop: 30
|
||||
},
|
||||
titleContainer15: {
|
||||
flexDirection: 'row',
|
||||
marginTop: 15
|
||||
},
|
||||
title: {
|
||||
fontSize: 14,
|
||||
color: theme.centerChannelColor,
|
||||
marginLeft: 15
|
||||
},
|
||||
optional: {
|
||||
color: changeOpacity(theme.centerChannelColor, 0.5),
|
||||
fontSize: 14,
|
||||
marginLeft: 5
|
||||
},
|
||||
helpText: {
|
||||
fontSize: 14,
|
||||
color: changeOpacity(theme.centerChannelColor, 0.5),
|
||||
marginTop: 10,
|
||||
marginHorizontal: 15
|
||||
},
|
||||
navTitle: {
|
||||
...Platform.select({
|
||||
android: {
|
||||
fontSize: 18
|
||||
},
|
||||
ios: {
|
||||
fontSize: 15,
|
||||
fontWeight: 'bold'
|
||||
}
|
||||
})
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
export default injectIntl(CreateChannel);
|
||||
|
|
@ -2,10 +2,8 @@
|
|||
// See License.txt for license information.
|
||||
|
||||
import {bindActionCreators} from 'redux';
|
||||
import {connect} from 'react-redux';
|
||||
|
||||
import navigationSceneConnect from '../navigationSceneConnect';
|
||||
|
||||
import {goBack, closeModal} from 'app/actions/navigation';
|
||||
import {handleCreateChannel} from 'app/actions/views/create_channel';
|
||||
|
||||
import {getTheme} from 'app/selectors/preferences';
|
||||
|
|
@ -26,11 +24,9 @@ function mapStateToProps(state, ownProps) {
|
|||
function mapDispatchToProps(dispatch) {
|
||||
return {
|
||||
actions: bindActionCreators({
|
||||
goBack,
|
||||
closeModal,
|
||||
handleCreateChannel
|
||||
}, dispatch)
|
||||
};
|
||||
}
|
||||
|
||||
export default navigationSceneConnect(mapStateToProps, mapDispatchToProps)(CreateChannel);
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(CreateChannel);
|
||||
|
|
@ -1,106 +1,60 @@
|
|||
// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
import React, {PropTypes, PureComponent} from 'react';
|
||||
import {injectIntl, intlShape} from 'react-intl';
|
||||
import {
|
||||
Dimensions,
|
||||
Platform,
|
||||
StatusBar,
|
||||
StyleSheet,
|
||||
TouchableOpacity,
|
||||
View
|
||||
} from 'react-native';
|
||||
|
||||
import ActionButton from 'app/components/action_button';
|
||||
import ErrorText from 'app/components/error_text';
|
||||
import FormattedText from 'app/components/formatted_text';
|
||||
import KeyboardLayout from 'app/components/layout/keyboard_layout';
|
||||
import Loading from 'app/components/loading';
|
||||
import TextInputWithLocalizedPlaceholder from 'app/components/text_input_with_localized_placeholder';
|
||||
import {changeOpacity, makeStyleSheetFromTheme} from 'app/utils/theme';
|
||||
|
||||
import {RequestStatus} from 'mattermost-redux/constants';
|
||||
import EventEmitter from 'mattermost-redux/utils/event_emitter';
|
||||
|
||||
export default class EditPost extends PureComponent {
|
||||
class EditPost extends PureComponent {
|
||||
static propTypes = {
|
||||
editPostRequest: PropTypes.object.isRequired,
|
||||
post: PropTypes.object.isRequired,
|
||||
theme: PropTypes.object.isRequired,
|
||||
subscribeToHeaderEvent: React.PropTypes.func.isRequired,
|
||||
unsubscribeFromHeaderEvent: React.PropTypes.func.isRequired,
|
||||
actions: PropTypes.shape({
|
||||
closeModal: PropTypes.func.isRequired,
|
||||
editPost: PropTypes.func.isRequired
|
||||
})
|
||||
}),
|
||||
closeButton: PropTypes.object,
|
||||
editPostRequest: PropTypes.object.isRequired,
|
||||
intl: intlShape.isRequired,
|
||||
navigator: PropTypes.object,
|
||||
post: PropTypes.object.isRequired,
|
||||
theme: PropTypes.object.isRequired
|
||||
};
|
||||
|
||||
static navigationProps = {
|
||||
renderLeftComponent: (props, emitter, theme) => {
|
||||
return (
|
||||
<TouchableOpacity
|
||||
style={{flex: 1, paddingHorizontal: 15, justifyContent: 'center'}}
|
||||
onPress={() => emitter('close')}
|
||||
>
|
||||
<FormattedText
|
||||
id='edit_post.cancel'
|
||||
defaultMessage='Cancel'
|
||||
style={{color: theme.sidebarHeaderTextColor}}
|
||||
/>
|
||||
</TouchableOpacity>
|
||||
);
|
||||
},
|
||||
renderRightComponent: (props, emitter) => {
|
||||
return (
|
||||
<ActionButton
|
||||
actionEventName='edit_post'
|
||||
emitter={emitter}
|
||||
enabled={true}
|
||||
enableEventName='can_edit_post'
|
||||
labelDefaultMessage='Save'
|
||||
labelId='edit_post.save'
|
||||
loadingEventName='editing_post'
|
||||
/>
|
||||
);
|
||||
}
|
||||
leftButton = {
|
||||
id: 'close-edit-post'
|
||||
};
|
||||
|
||||
rightButton = {
|
||||
id: 'edit-post',
|
||||
showAsAction: 'always'
|
||||
};
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {message: props.post.message};
|
||||
this.rightButton.title = props.intl.formatMessage({id: 'edit_post.save', defaultMessage: 'Save'});
|
||||
|
||||
props.navigator.setOnNavigatorEvent(this.onNavigatorEvent);
|
||||
props.navigator.setButtons({
|
||||
leftButtons: [{...this.leftButton, icon: props.closeButton}],
|
||||
rightButtons: [this.rightButton]
|
||||
});
|
||||
}
|
||||
|
||||
onEditPost = async () => {
|
||||
const {message} = this.state;
|
||||
const post = Object.assign({}, this.props.post, {message});
|
||||
await this.props.actions.editPost(post);
|
||||
};
|
||||
|
||||
onPostChangeText = (message) => {
|
||||
this.setState({message});
|
||||
if (message) {
|
||||
this.emitCanEditPost(true);
|
||||
} else {
|
||||
this.emitCanEditPost(false);
|
||||
}
|
||||
};
|
||||
|
||||
emitCanEditPost = (enabled) => {
|
||||
EventEmitter.emit('can_edit_post', enabled);
|
||||
};
|
||||
|
||||
emitEditing = (loading) => {
|
||||
EventEmitter.emit('editing_post', loading);
|
||||
};
|
||||
|
||||
focus = () => {
|
||||
this.messageInput.refs.wrappedInstance.focus();
|
||||
};
|
||||
|
||||
messageRef = (ref) => {
|
||||
this.messageInput = ref;
|
||||
};
|
||||
|
||||
componentWillMount() {
|
||||
this.props.subscribeToHeaderEvent('close', this.props.actions.closeModal);
|
||||
this.props.subscribeToHeaderEvent('edit_post', this.onEditPost);
|
||||
componentDidMount() {
|
||||
this.focus();
|
||||
}
|
||||
|
||||
componentWillReceiveProps(nextProps) {
|
||||
|
|
@ -110,29 +64,44 @@ export default class EditPost extends PureComponent {
|
|||
switch (editPostRequest.status) {
|
||||
case RequestStatus.STARTED:
|
||||
this.emitEditing(true);
|
||||
this.setState({error: null});
|
||||
this.setState({error: null, editing: true});
|
||||
break;
|
||||
case RequestStatus.SUCCESS:
|
||||
this.emitEditing(false);
|
||||
this.setState({error: null});
|
||||
this.props.actions.closeModal();
|
||||
this.setState({error: null, editing: false});
|
||||
this.close();
|
||||
break;
|
||||
case RequestStatus.FAILURE:
|
||||
this.emitEditing(false);
|
||||
this.setState({error: editPostRequest.error});
|
||||
this.setState({error: editPostRequest.error, editing: false});
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.focus();
|
||||
}
|
||||
close = () => {
|
||||
this.props.navigator.dismissModal({
|
||||
animationType: 'slide-down'
|
||||
});
|
||||
};
|
||||
|
||||
componentWillUnmount() {
|
||||
this.props.unsubscribeFromHeaderEvent('close');
|
||||
this.props.unsubscribeFromHeaderEvent('edit_post');
|
||||
}
|
||||
emitCanEditPost = (enabled) => {
|
||||
this.props.navigator.setButtons({
|
||||
leftButtons: [this.leftButton],
|
||||
rightButtons: [{...this.rightButton, disabled: !enabled}]
|
||||
});
|
||||
};
|
||||
|
||||
emitEditing = (loading) => {
|
||||
this.props.navigator.setButtons({
|
||||
leftButtons: [this.leftButton],
|
||||
rightButtons: [{...this.rightButton, disabled: loading}]
|
||||
});
|
||||
};
|
||||
|
||||
focus = () => {
|
||||
this.messageInput.refs.wrappedInstance.focus();
|
||||
};
|
||||
|
||||
handleSubmit = () => {
|
||||
// Workaround for android as the multiline is not working
|
||||
|
|
@ -148,13 +117,54 @@ export default class EditPost extends PureComponent {
|
|||
}
|
||||
};
|
||||
|
||||
messageRef = (ref) => {
|
||||
this.messageInput = ref;
|
||||
};
|
||||
|
||||
onEditPost = () => {
|
||||
const {message} = this.state;
|
||||
const post = Object.assign({}, this.props.post, {message});
|
||||
this.props.actions.editPost(post);
|
||||
};
|
||||
|
||||
onNavigatorEvent = (event) => {
|
||||
if (event.type === 'NavBarButtonPress') {
|
||||
switch (event.id) {
|
||||
case 'close-edit-post':
|
||||
this.close();
|
||||
break;
|
||||
case 'edit-post':
|
||||
this.onEditPost();
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
onPostChangeText = (message) => {
|
||||
this.setState({message});
|
||||
if (message) {
|
||||
this.emitCanEditPost(true);
|
||||
} else {
|
||||
this.emitCanEditPost(false);
|
||||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
const {theme} = this.props;
|
||||
const {message, error} = this.state;
|
||||
const {editing, message, error} = this.state;
|
||||
const {height, width} = Dimensions.get('window');
|
||||
|
||||
const style = getStyleSheet(theme);
|
||||
|
||||
if (editing) {
|
||||
return (
|
||||
<View style={style.container}>
|
||||
<StatusBar barStyle='light-content'/>
|
||||
<Loading/>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
let displayError;
|
||||
if (error) {
|
||||
displayError = (
|
||||
|
|
@ -172,6 +182,7 @@ export default class EditPost extends PureComponent {
|
|||
style={style.container}
|
||||
keyboardVerticalOffset={0}
|
||||
>
|
||||
<StatusBar barStyle='light-content'/>
|
||||
<View style={style.scrollView}>
|
||||
{displayError}
|
||||
<View style={[style.inputContainer, {height: Platform.OS === 'android' ? (height / 2) - 20 : (height / 2)}]}>
|
||||
|
|
@ -231,3 +242,5 @@ const getStyleSheet = makeStyleSheetFromTheme((theme) => {
|
|||
}
|
||||
});
|
||||
});
|
||||
|
||||
export default injectIntl(EditPost);
|
||||
|
|
@ -2,10 +2,8 @@
|
|||
// See License.txt for license information.
|
||||
|
||||
import {bindActionCreators} from 'redux';
|
||||
import {connect} from 'react-redux';
|
||||
|
||||
import navigationSceneConnect from '../navigationSceneConnect';
|
||||
|
||||
import {closeModal} from 'app/actions/navigation';
|
||||
import {getTheme} from 'app/selectors/preferences';
|
||||
|
||||
import {editPost} from 'mattermost-redux/actions/posts';
|
||||
|
|
@ -26,10 +24,9 @@ function mapStateToProps(state, ownProps) {
|
|||
function mapDispatchToProps(dispatch) {
|
||||
return {
|
||||
actions: bindActionCreators({
|
||||
closeModal,
|
||||
editPost
|
||||
}, dispatch)
|
||||
};
|
||||
}
|
||||
|
||||
export default navigationSceneConnect(mapStateToProps, mapDispatchToProps)(EditPost);
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(EditPost);
|
||||
|
|
@ -37,21 +37,15 @@ const STATUSBAR_HEIGHT = Platform.select({
|
|||
export default class ImagePreview extends PureComponent {
|
||||
static propTypes = {
|
||||
actions: PropTypes.shape({
|
||||
addFileToFetchCache: PropTypes.func.isRequired,
|
||||
goBack: PropTypes.func.isRequired
|
||||
addFileToFetchCache: PropTypes.func.isRequired
|
||||
}),
|
||||
fetchCache: PropTypes.object.isRequired,
|
||||
fileId: PropTypes.string.isRequired,
|
||||
files: PropTypes.array.isRequired,
|
||||
navigator: PropTypes.object,
|
||||
theme: PropTypes.object.isRequired
|
||||
};
|
||||
|
||||
static navigationProps = {
|
||||
allowSceneSwipe: false,
|
||||
hideNavBar: true,
|
||||
modalAnimationType: 'fade'
|
||||
};
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
|
|
@ -111,6 +105,10 @@ export default class ImagePreview extends PureComponent {
|
|||
}
|
||||
}
|
||||
|
||||
close = () => {
|
||||
this.props.navigator.dismissModal({animationType: 'none'});
|
||||
};
|
||||
|
||||
mainViewMoveShouldSetPanResponderCapture = (evt, gestureState) => {
|
||||
if (gestureState.numberActiveTouches === 2 || this.state.isZooming) {
|
||||
return false;
|
||||
|
|
@ -124,24 +122,24 @@ export default class ImagePreview extends PureComponent {
|
|||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
mainViewPanResponderRelease = (evt, gestureState) => {
|
||||
if (Math.abs(gestureState.dy) > DRAG_VERTICAL_THRESHOLD_END) {
|
||||
this.props.actions.goBack();
|
||||
this.close();
|
||||
} else {
|
||||
this.setHeaderAndFileInfoVisible(true);
|
||||
Animated.spring(this.state.drag, {
|
||||
toValue: {x: 0, y: 0}
|
||||
}).start();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
handleClose = () => {
|
||||
if (this.state.showFileInfo) {
|
||||
this.props.actions.goBack();
|
||||
this.close();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
handleImageTap = () => {
|
||||
/*if (!this.lastPress) {
|
||||
|
|
@ -157,7 +155,7 @@ export default class ImagePreview extends PureComponent {
|
|||
}*/
|
||||
|
||||
this.setHeaderAndFileInfoVisible(!this.state.showFileInfo);
|
||||
}
|
||||
};
|
||||
|
||||
setHeaderAndFileInfoVisible = (show) => {
|
||||
this.setState({
|
||||
|
|
@ -174,7 +172,7 @@ export default class ImagePreview extends PureComponent {
|
|||
toValue: opacity,
|
||||
duration: 300
|
||||
}).start();
|
||||
}
|
||||
};
|
||||
|
||||
handleScroll = (event) => {
|
||||
if (event.nativeEvent.contentOffset.x % this.state.deviceWidth === 0) {
|
||||
|
|
@ -183,11 +181,11 @@ export default class ImagePreview extends PureComponent {
|
|||
pagingEnabled: true
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
attachScrollView = (c) => {
|
||||
this.scrollView = c;
|
||||
}
|
||||
};
|
||||
|
||||
onLayout = (event) => {
|
||||
if (event.nativeEvent.layout.width !== this.state.deviceWidth) {
|
||||
|
|
@ -196,7 +194,7 @@ export default class ImagePreview extends PureComponent {
|
|||
deviceWidth: event.nativeEvent.layout.width
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
imageIsZooming = (zooming) => {
|
||||
if (zooming !== this.state.isZooming) {
|
||||
|
|
@ -205,7 +203,7 @@ export default class ImagePreview extends PureComponent {
|
|||
isZooming: zooming
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
const maxImageHeight = this.state.deviceHeight - STATUSBAR_HEIGHT;
|
||||
|
|
@ -2,16 +2,14 @@
|
|||
// See License.txt for license information.
|
||||
|
||||
import {bindActionCreators} from 'redux';
|
||||
import {connect} from 'react-redux';
|
||||
|
||||
import {goBack} from 'app/actions/navigation';
|
||||
import {addFileToFetchCache} from 'app/actions/views/file_preview';
|
||||
import {getTheme} from 'app/selectors/preferences';
|
||||
import {makeGetFilesForPost} from 'mattermost-redux/selectors/entities/files';
|
||||
|
||||
import ImagePreview from './image_preview';
|
||||
|
||||
import navigationSceneConnect from '../navigationSceneConnect';
|
||||
|
||||
function makeMapStateToProps() {
|
||||
const getFilesForPost = makeGetFilesForPost();
|
||||
return function mapStateToProps(state, ownProps) {
|
||||
|
|
@ -27,10 +25,9 @@ function makeMapStateToProps() {
|
|||
function mapDispatchToProps(dispatch) {
|
||||
return {
|
||||
actions: bindActionCreators({
|
||||
addFileToFetchCache,
|
||||
goBack
|
||||
addFileToFetchCache
|
||||
}, dispatch)
|
||||
};
|
||||
}
|
||||
|
||||
export default navigationSceneConnect(makeMapStateToProps, mapDispatchToProps)(ImagePreview);
|
||||
export default connect(makeMapStateToProps, mapDispatchToProps)(ImagePreview);
|
||||
72
app/screens/index.js
Normal file
72
app/screens/index.js
Normal file
|
|
@ -0,0 +1,72 @@
|
|||
// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
import React from 'react';
|
||||
import {Navigation} from 'react-native-navigation';
|
||||
|
||||
import About from 'app/screens/about';
|
||||
import AccountSettings from 'app/screens/account_settings';
|
||||
import AccountNotifications from 'app/screens/account_notifications';
|
||||
import Channel from 'app/screens/channel';
|
||||
import ChannelAddMembers from 'app/screens/channel_add_members';
|
||||
import ChannelInfo from 'app/screens/channel_info';
|
||||
import ChannelMembers from 'app/screens/channel_members';
|
||||
import CreateChannel from 'app/screens/create_channel';
|
||||
import EditPost from 'app/screens/edit_post';
|
||||
import ImagePreview from 'app/screens/image_preview';
|
||||
import LoadTeam from 'app/screens/load_team';
|
||||
import Login from 'app/screens/login';
|
||||
import LoginOptions from 'app/screens/login_options';
|
||||
import Mfa from 'app/screens/mfa';
|
||||
import MoreChannels from 'app/screens/more_channels';
|
||||
import MoreDirectMessages from 'app/screens/more_dms';
|
||||
import Notification from 'app/screens/notification';
|
||||
import OptionsModal from 'app/screens/options_modal';
|
||||
import Root from 'app/screens/root';
|
||||
import Saml from 'app/screens/saml';
|
||||
import SelectServer from 'app/screens/select_server';
|
||||
import SelectTeam from 'app/screens/select_team';
|
||||
import Settings from 'app/screens/settings';
|
||||
import Thread from 'app/screens/thread';
|
||||
import UserProfile from 'app/screens/user_profile';
|
||||
|
||||
import IntlWrapper from 'app/components/root';
|
||||
|
||||
function wrapWithContextProvider(Comp) {
|
||||
return (props) => { //eslint-disable-line react/display-name
|
||||
const {navigator} = props; //eslint-disable-line react/prop-types
|
||||
return (
|
||||
<IntlWrapper navigator={navigator}>
|
||||
<Comp {...props}/>
|
||||
</IntlWrapper>
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
export function registerScreens(store, Provider) {
|
||||
Navigation.registerComponent('About', () => wrapWithContextProvider(About), store, Provider);
|
||||
Navigation.registerComponent('AccountSettings', () => wrapWithContextProvider(AccountSettings), store, Provider);
|
||||
Navigation.registerComponent('AccountNotifications', () => wrapWithContextProvider(AccountNotifications), store, Provider);
|
||||
Navigation.registerComponent('Channel', () => wrapWithContextProvider(Channel), store, Provider);
|
||||
Navigation.registerComponent('ChannelAddMembers', () => wrapWithContextProvider(ChannelAddMembers), store, Provider);
|
||||
Navigation.registerComponent('ChannelInfo', () => wrapWithContextProvider(ChannelInfo), store, Provider);
|
||||
Navigation.registerComponent('ChannelMembers', () => wrapWithContextProvider(ChannelMembers), store, Provider);
|
||||
Navigation.registerComponent('CreateChannel', () => wrapWithContextProvider(CreateChannel), store, Provider);
|
||||
Navigation.registerComponent('EditPost', () => wrapWithContextProvider(EditPost), store, Provider);
|
||||
Navigation.registerComponent('ImagePreview', () => wrapWithContextProvider(ImagePreview), store, Provider);
|
||||
Navigation.registerComponent('LoadTeam', () => LoadTeam, store, Provider);
|
||||
Navigation.registerComponent('Login', () => wrapWithContextProvider(Login), store, Provider);
|
||||
Navigation.registerComponent('LoginOptions', () => wrapWithContextProvider(LoginOptions), store, Provider);
|
||||
Navigation.registerComponent('MFA', () => wrapWithContextProvider(Mfa), store, Provider);
|
||||
Navigation.registerComponent('MoreChannels', () => wrapWithContextProvider(MoreChannels), store, Provider);
|
||||
Navigation.registerComponent('MoreDirectMessages', () => wrapWithContextProvider(MoreDirectMessages), store, Provider);
|
||||
Navigation.registerComponent('OptionsModal', () => wrapWithContextProvider(OptionsModal), store, Provider);
|
||||
Navigation.registerComponent('Notification', () => Notification, store, Provider);
|
||||
Navigation.registerComponent('Root', () => Root, store, Provider);
|
||||
Navigation.registerComponent('SAML', () => wrapWithContextProvider(Saml), store, Provider);
|
||||
Navigation.registerComponent('SelectServer', () => wrapWithContextProvider(SelectServer), store, Provider);
|
||||
Navigation.registerComponent('SelectTeam', () => wrapWithContextProvider(SelectTeam), store, Provider);
|
||||
Navigation.registerComponent('Settings', () => wrapWithContextProvider(Settings), store, Provider);
|
||||
Navigation.registerComponent('Thread', () => wrapWithContextProvider(Thread), store, Provider);
|
||||
Navigation.registerComponent('UserProfile', () => wrapWithContextProvider(UserProfile), store, Provider);
|
||||
}
|
||||
|
|
@ -2,20 +2,20 @@
|
|||
// See License.txt for license information.
|
||||
|
||||
import {bindActionCreators} from 'redux';
|
||||
import {connect} from 'react-redux';
|
||||
|
||||
import {getCurrentTeam} from 'mattermost-redux/selectors/entities/teams';
|
||||
|
||||
import {goToChannelView} from 'app/actions/views/load_team';
|
||||
import {initialize} from 'app/actions/views/load_team';
|
||||
import {clearNotification, goToNotification} from 'app/actions/views/root';
|
||||
import {handleTeamChange} from 'app/actions/views/select_team';
|
||||
import {getTheme} from 'app/selectors/preferences';
|
||||
|
||||
import navigationSceneConnect from '../navigationSceneConnect';
|
||||
|
||||
import LoadTeam from './load_team';
|
||||
|
||||
function mapStateToProps(state) {
|
||||
function mapStateToProps(state, ownProps) {
|
||||
return {
|
||||
...ownProps,
|
||||
config: state.entities.general.config,
|
||||
theme: getTheme(state),
|
||||
teamsRequest: state.requests.teams.getMyTeams,
|
||||
|
|
@ -30,11 +30,11 @@ function mapDispatchToProps(dispatch) {
|
|||
return {
|
||||
actions: bindActionCreators({
|
||||
clearNotification,
|
||||
goToChannelView,
|
||||
goToNotification,
|
||||
handleTeamChange
|
||||
handleTeamChange,
|
||||
initialize
|
||||
}, dispatch)
|
||||
};
|
||||
}
|
||||
|
||||
export default navigationSceneConnect(mapStateToProps, mapDispatchToProps)(LoadTeam);
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(LoadTeam);
|
||||
|
|
@ -2,7 +2,7 @@
|
|||
// See License.txt for license information.
|
||||
|
||||
import React, {PropTypes, PureComponent} from 'react';
|
||||
import {View} from 'react-native';
|
||||
import {StatusBar, View} from 'react-native';
|
||||
|
||||
import {RequestStatus} from 'mattermost-redux/constants';
|
||||
|
||||
|
|
@ -10,6 +10,7 @@ import ChannelLoader from 'app/components/channel_loader';
|
|||
|
||||
export default class LoadTeam extends PureComponent {
|
||||
static propTypes = {
|
||||
navigator: PropTypes.object,
|
||||
notification: PropTypes.object,
|
||||
teams: PropTypes.object.isRequired,
|
||||
myMembers: PropTypes.object.isRequired,
|
||||
|
|
@ -17,26 +18,25 @@ export default class LoadTeam extends PureComponent {
|
|||
currentTeam: PropTypes.object,
|
||||
actions: PropTypes.shape({
|
||||
clearNotification: PropTypes.func.isRequired,
|
||||
goToChannelView: PropTypes.func.isRequired,
|
||||
goToNotification: PropTypes.func.isRequired,
|
||||
handleTeamChange: PropTypes.func.isRequired
|
||||
handleTeamChange: PropTypes.func.isRequired,
|
||||
initialize: PropTypes.func.isRequired
|
||||
}).isRequired,
|
||||
theme: PropTypes.object.isRequired
|
||||
};
|
||||
|
||||
static navigationProps = {
|
||||
renderBackButton: () => null,
|
||||
renderTitleComponent: () => null
|
||||
};
|
||||
|
||||
componentDidMount() {
|
||||
const {notification, currentTeam, myMembers, teams} = this.props;
|
||||
const {clearNotification, goToNotification} = this.props.actions;
|
||||
const {currentTeam, myMembers, teams} = this.props;
|
||||
|
||||
if (notification) {
|
||||
clearNotification();
|
||||
goToNotification(notification);
|
||||
} else if (currentTeam) {
|
||||
// TODO: GO TO CHANNEL WHEN NOTIFICATION IS TAPPED AND THE APP IS CLOSED
|
||||
// const {notification, currentTeam, myMembers, teams} = this.props;
|
||||
// const {clearNotification, goToNotification} = this.props.actions;
|
||||
|
||||
// if (notification) {
|
||||
// clearNotification();
|
||||
// goToNotification(notification);
|
||||
// this.goToChannelView();
|
||||
if (currentTeam) {
|
||||
this.onSelectTeam(currentTeam);
|
||||
} else if (!currentTeam) {
|
||||
this.selectFirstTeam(teams, myMembers);
|
||||
|
|
@ -60,13 +60,30 @@ export default class LoadTeam extends PureComponent {
|
|||
}
|
||||
|
||||
onSelectTeam(team) {
|
||||
const {handleTeamChange, goToChannelView} = this.props.actions;
|
||||
handleTeamChange(team).then(goToChannelView);
|
||||
const {handleTeamChange} = this.props.actions;
|
||||
handleTeamChange(team).then(this.goToChannelView);
|
||||
}
|
||||
|
||||
goToChannelView = () => {
|
||||
const {actions, navigator, theme} = this.props;
|
||||
|
||||
actions.initialize();
|
||||
navigator.resetTo({
|
||||
screen: 'Channel',
|
||||
animated: false,
|
||||
navigatorStyle: {
|
||||
navBarHidden: true,
|
||||
statusBarHidden: false,
|
||||
statusBarHideWithNavBar: false,
|
||||
screenBackgroundColor: theme.centerChannelBg
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
render() {
|
||||
return (
|
||||
<View style={{flex: 1}}>
|
||||
<StatusBar barStyle='light-content'/>
|
||||
<ChannelLoader theme={this.props.theme}/>
|
||||
</View>
|
||||
);
|
||||
|
|
@ -2,24 +2,26 @@
|
|||
// See License.txt for license information.
|
||||
|
||||
import {bindActionCreators} from 'redux';
|
||||
|
||||
import navigationSceneConnect from '../navigationSceneConnect';
|
||||
import {connect} from 'react-redux';
|
||||
|
||||
import LoginActions from 'app/actions/views/login';
|
||||
import {goToMfa, goToLoadTeam} from 'app/actions/navigation';
|
||||
import {getTheme} from 'app/selectors/preferences';
|
||||
|
||||
import {checkMfa, login} from 'mattermost-redux/actions/users';
|
||||
|
||||
import Login from './login.js';
|
||||
|
||||
function mapStateToProps(state) {
|
||||
function mapStateToProps(state, ownProps) {
|
||||
const {checkMfa: checkMfaRequest, login: loginRequest} = state.requests.users;
|
||||
const {config, license} = state.entities.general;
|
||||
return {
|
||||
...ownProps,
|
||||
...state.views.login,
|
||||
checkMfaRequest,
|
||||
loginRequest,
|
||||
config,
|
||||
license
|
||||
license,
|
||||
theme: getTheme(state)
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -28,11 +30,9 @@ function mapDispatchToProps(dispatch) {
|
|||
actions: bindActionCreators({
|
||||
...LoginActions,
|
||||
checkMfa,
|
||||
login,
|
||||
goToMfa,
|
||||
goToLoadTeam
|
||||
login
|
||||
}, dispatch)
|
||||
};
|
||||
}
|
||||
|
||||
export default navigationSceneConnect(mapStateToProps, mapDispatchToProps)(Login);
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(Login);
|
||||
|
|
@ -1,14 +1,16 @@
|
|||
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
import React, {Component, PropTypes} from 'react';
|
||||
import React, {PureComponent, PropTypes} from 'react';
|
||||
import {injectIntl, intlShape} from 'react-intl';
|
||||
import {
|
||||
ActivityIndicator,
|
||||
Image,
|
||||
InteractionManager,
|
||||
Keyboard,
|
||||
KeyboardAvoidingView,
|
||||
Platform,
|
||||
StatusBar,
|
||||
Text,
|
||||
TextInput,
|
||||
TouchableWithoutFeedback,
|
||||
|
|
@ -24,17 +26,17 @@ import logo from 'assets/images/logo.png';
|
|||
|
||||
import {RequestStatus} from 'mattermost-redux/constants';
|
||||
|
||||
class Login extends Component {
|
||||
class Login extends PureComponent {
|
||||
static propTypes = {
|
||||
intl: intlShape.isRequired,
|
||||
navigator: PropTypes.object,
|
||||
theme: PropTypes.object,
|
||||
actions: React.PropTypes.shape({
|
||||
handleLoginIdChanged: React.PropTypes.func.isRequired,
|
||||
handlePasswordChanged: React.PropTypes.func.isRequired,
|
||||
handleSuccessfulLogin: React.PropTypes.func.isRequired,
|
||||
checkMfa: React.PropTypes.func.isRequired,
|
||||
login: React.PropTypes.func.isRequired,
|
||||
goToMfa: React.PropTypes.func.isRequired,
|
||||
goToLoadTeam: React.PropTypes.func.isRequired
|
||||
login: React.PropTypes.func.isRequired
|
||||
}).isRequired,
|
||||
config: PropTypes.object.isRequired,
|
||||
license: PropTypes.object.isRequired,
|
||||
|
|
@ -60,7 +62,7 @@ class Login extends Component {
|
|||
|
||||
componentWillReceiveProps(nextProps) {
|
||||
if (this.props.loginRequest.status === RequestStatus.STARTED && nextProps.loginRequest.status === RequestStatus.SUCCESS) {
|
||||
this.props.actions.handleSuccessfulLogin().then(this.props.actions.goToLoadTeam);
|
||||
this.props.actions.handleSuccessfulLogin().then(this.goToLoadTeam);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -70,6 +72,40 @@ class Login extends Component {
|
|||
}
|
||||
}
|
||||
|
||||
goToLoadTeam = () => {
|
||||
const {navigator, theme} = this.props;
|
||||
navigator.resetTo({
|
||||
screen: 'LoadTeam',
|
||||
title: '',
|
||||
animated: false,
|
||||
backButtonTitle: '',
|
||||
navigatorStyle: {
|
||||
statusBarHidden: false,
|
||||
statusBarHideWithNavBar: false,
|
||||
navBarTextColor: theme.sidebarHeaderTextColor,
|
||||
navBarBackgroundColor: theme.sidebarHeaderBg,
|
||||
navBarButtonColor: theme.sidebarHeaderTextColor,
|
||||
screenBackgroundColor: theme.centerChannelBg
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
goToMfa = () => {
|
||||
const {intl, navigator, theme} = this.props;
|
||||
navigator.push({
|
||||
screen: 'MFA',
|
||||
title: intl.formatMessage({id: 'mobile.routes.mfa', defaultMessage: 'Multi-factor Authentication'}),
|
||||
animated: true,
|
||||
backButtonTitle: '',
|
||||
navigatorStyle: {
|
||||
navBarTextColor: theme.sidebarHeaderTextColor,
|
||||
navBarBackgroundColor: theme.sidebarHeaderBg,
|
||||
navBarButtonColor: theme.sidebarHeaderTextColor,
|
||||
screenBackgroundColor: theme.centerChannelBg
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
handleAndroidKeyboard = () => {
|
||||
this.blur();
|
||||
};
|
||||
|
|
@ -82,56 +118,62 @@ class Login extends Component {
|
|||
preSignIn = () => {
|
||||
this.setState({error: null});
|
||||
Keyboard.dismiss();
|
||||
if (!this.props.loginId) {
|
||||
// it's slightly weird to be constructing the message ID, but it's a bit nicer than triply nested if statements
|
||||
let msgId = 'login.no';
|
||||
if (this.props.config.EnableSignInWithEmail === 'true') {
|
||||
msgId += 'Email';
|
||||
}
|
||||
if (this.props.config.EnableSignInWithUsername === 'true') {
|
||||
msgId += 'Username';
|
||||
}
|
||||
if (this.props.license.IsLicensed === 'true' && this.props.config.EnableLdap === 'true') {
|
||||
msgId += 'LdapUsername';
|
||||
}
|
||||
InteractionManager.runAfterInteractions(() => {
|
||||
if (!this.props.loginId) {
|
||||
// it's slightly weird to be constructing the message ID, but it's a bit nicer than triply nested if statements
|
||||
let msgId = 'login.no';
|
||||
if (this.props.config.EnableSignInWithEmail === 'true') {
|
||||
msgId += 'Email';
|
||||
}
|
||||
if (this.props.config.EnableSignInWithUsername === 'true') {
|
||||
msgId += 'Username';
|
||||
}
|
||||
if (this.props.license.IsLicensed === 'true' && this.props.config.EnableLdap === 'true') {
|
||||
msgId += 'LdapUsername';
|
||||
}
|
||||
|
||||
this.setState({
|
||||
error: {
|
||||
intl: {
|
||||
id: msgId,
|
||||
defaultMessage: '',
|
||||
values: {
|
||||
ldapUsername: this.props.config.LdapLoginFieldName ||
|
||||
this.props.intl.formatMessage({id: 'login.ldapUsernameLower', defaultMessage: 'AD/LDAP username'})
|
||||
this.setState({
|
||||
error: {
|
||||
intl: {
|
||||
id: msgId,
|
||||
defaultMessage: '',
|
||||
values: {
|
||||
ldapUsername: this.props.config.LdapLoginFieldName ||
|
||||
this.props.intl.formatMessage({
|
||||
id: 'login.ldapUsernameLower',
|
||||
defaultMessage: 'AD/LDAP username'
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
return;
|
||||
}
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.props.password) {
|
||||
this.setState({
|
||||
error: {
|
||||
intl: {
|
||||
id: 'login.noPassword',
|
||||
defaultMessage: 'Please enter your password'
|
||||
if (!this.props.password) {
|
||||
this.setState({
|
||||
error: {
|
||||
intl: {
|
||||
id: 'login.noPassword',
|
||||
defaultMessage: 'Please enter your password'
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (this.props.config.EnableMultifactorAuthentication === 'true') {
|
||||
this.props.actions.checkMfa(this.props.loginId).then((result) => {
|
||||
if (result) {
|
||||
this.props.actions.goToMfa();
|
||||
} else {
|
||||
this.signIn();
|
||||
}
|
||||
});
|
||||
} else {
|
||||
this.signIn();
|
||||
}
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.props.config.EnableMultifactorAuthentication === 'true') {
|
||||
this.props.actions.checkMfa(this.props.loginId).then((result) => {
|
||||
if (result) {
|
||||
this.goToMfa();
|
||||
} else {
|
||||
this.signIn();
|
||||
}
|
||||
});
|
||||
} else {
|
||||
this.signIn();
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
signIn = () => {
|
||||
|
|
@ -256,8 +298,9 @@ class Login extends Component {
|
|||
<KeyboardAvoidingView
|
||||
behavior='padding'
|
||||
style={{flex: 1}}
|
||||
keyboardVerticalOffset={65}
|
||||
keyboardVerticalOffset={Platform.OS === 'ios' ? 65 : 0}
|
||||
>
|
||||
<StatusBar barStyle='light-content'/>
|
||||
<TouchableWithoutFeedback onPress={this.blur}>
|
||||
<View style={[GlobalStyles.container, GlobalStyles.signupContainer]}>
|
||||
<Image
|
||||
20
app/screens/login_options/index.js
Normal file
20
app/screens/login_options/index.js
Normal file
|
|
@ -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 'app/selectors/preferences';
|
||||
|
||||
import LoginOptions from './login_options';
|
||||
|
||||
function mapStateToProps(state, ownProps) {
|
||||
const {config, license} = state.entities.general;
|
||||
return {
|
||||
...ownProps,
|
||||
config,
|
||||
license,
|
||||
theme: getTheme(state)
|
||||
};
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps)(LoginOptions);
|
||||
|
|
@ -1,9 +1,11 @@
|
|||
// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
import React, {PureComponent, PropTypes} from 'react';
|
||||
import React, {PropTypes, PureComponent} from 'react';
|
||||
import {injectIntl, intlShape} from 'react-intl';
|
||||
import {
|
||||
Image,
|
||||
StatusBar,
|
||||
Text,
|
||||
View
|
||||
} from 'react-native';
|
||||
|
|
@ -15,23 +17,54 @@ import {preventDoubleTap} from 'app/utils/tap';
|
|||
|
||||
import logo from 'assets/images/logo.png';
|
||||
|
||||
export default class LoginOptions extends PureComponent {
|
||||
class LoginOptions extends PureComponent {
|
||||
static propTypes = {
|
||||
actions: React.PropTypes.shape({
|
||||
goToLogin: PropTypes.func.isRequired,
|
||||
goToSaml: PropTypes.func.isRequired
|
||||
}).isRequired,
|
||||
intl: intlShape.isRequired,
|
||||
navigator: PropTypes.object,
|
||||
config: PropTypes.object.isRequired,
|
||||
license: PropTypes.object.isRequired
|
||||
license: PropTypes.object.isRequired,
|
||||
theme: PropTypes.object
|
||||
};
|
||||
|
||||
goToLogin = () => {
|
||||
const {intl, navigator, theme} = this.props;
|
||||
navigator.push({
|
||||
screen: 'Login',
|
||||
title: intl.formatMessage({id: 'mobile.routes.login', defaultMessage: 'Login'}),
|
||||
animated: true,
|
||||
backButtonTitle: '',
|
||||
navigatorStyle: {
|
||||
navBarTextColor: theme.sidebarHeaderTextColor,
|
||||
navBarBackgroundColor: theme.sidebarHeaderBg,
|
||||
navBarButtonColor: theme.sidebarHeaderTextColor,
|
||||
screenBackgroundColor: theme.centerChannelBg
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
goToSaml = () => {
|
||||
const {intl, navigator, theme} = this.props;
|
||||
navigator.push({
|
||||
screen: 'SAML',
|
||||
title: intl.formatMessage({id: 'mobile.routes.saml', defaultMessage: 'Single SignOn'}),
|
||||
animated: true,
|
||||
backButtonTitle: '',
|
||||
navigatorStyle: {
|
||||
navBarTextColor: theme.sidebarHeaderTextColor,
|
||||
navBarBackgroundColor: theme.sidebarHeaderBg,
|
||||
navBarButtonColor: theme.sidebarHeaderTextColor,
|
||||
screenBackgroundColor: theme.centerChannelBg
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
renderEmailOption = () => {
|
||||
const config = this.props.config;
|
||||
const {config} = this.props;
|
||||
if (config.EnableSignInWithEmail === 'true' || config.EnableSignInWithUsername === 'true') {
|
||||
return (
|
||||
<Button
|
||||
key='email'
|
||||
onPress={() => preventDoubleTap(this.props.actions.goToLogin)}
|
||||
onPress={() => preventDoubleTap(this.goToLogin, this)}
|
||||
containerStyle={[GlobalStyles.signupButton, {backgroundColor: '#2389d7'}]}
|
||||
>
|
||||
<FormattedText
|
||||
|
|
@ -52,7 +85,7 @@ export default class LoginOptions extends PureComponent {
|
|||
return (
|
||||
<Button
|
||||
key='saml'
|
||||
onPress={() => preventDoubleTap(this.props.actions.goToSaml)}
|
||||
onPress={() => preventDoubleTap(this.goToSaml, this)}
|
||||
containerStyle={[GlobalStyles.signupButton, {backgroundColor: '#34a28b'}]}
|
||||
>
|
||||
<Text
|
||||
|
|
@ -70,6 +103,7 @@ export default class LoginOptions extends PureComponent {
|
|||
render() {
|
||||
return (
|
||||
<View style={[GlobalStyles.container, GlobalStyles.signupContainer]}>
|
||||
<StatusBar barStyle='light-content'/>
|
||||
<Image
|
||||
source={logo}
|
||||
/>
|
||||
|
|
@ -92,3 +126,5 @@ export default class LoginOptions extends PureComponent {
|
|||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default injectIntl(LoginOptions);
|
||||
|
|
@ -2,10 +2,8 @@
|
|||
// See License.txt for license information.
|
||||
|
||||
import {bindActionCreators} from 'redux';
|
||||
import {connect} from 'react-redux';
|
||||
|
||||
import navigationSceneConnect from '../navigationSceneConnect';
|
||||
|
||||
import {goBack} from 'app/actions/navigation';
|
||||
import {login} from 'mattermost-redux/actions/users';
|
||||
|
||||
import Mfa from './mfa';
|
||||
|
|
@ -23,10 +21,9 @@ function mapStateToProps(state) {
|
|||
function mapDispatchToProps(dispatch) {
|
||||
return {
|
||||
actions: bindActionCreators({
|
||||
goBack,
|
||||
login
|
||||
}, dispatch)
|
||||
};
|
||||
}
|
||||
|
||||
export default navigationSceneConnect(mapStateToProps, mapDispatchToProps)(Mfa);
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(Mfa);
|
||||
|
|
@ -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, {PropTypes, PureComponent} from 'react';
|
||||
import {
|
||||
ActivityIndicator,
|
||||
Image,
|
||||
|
|
@ -22,15 +22,15 @@ import logo from 'assets/images/logo.png';
|
|||
|
||||
import RequestStatus from 'mattermost-redux/constants/request_status';
|
||||
|
||||
export default class Mfa extends Component {
|
||||
export default class Mfa extends PureComponent {
|
||||
static propTypes = {
|
||||
actions: React.PropTypes.shape({
|
||||
goBack: React.PropTypes.func.isRequired,
|
||||
login: React.PropTypes.func.isRequired
|
||||
navigator: PropTypes.object,
|
||||
actions: PropTypes.shape({
|
||||
login: PropTypes.func.isRequired
|
||||
}).isRequired,
|
||||
loginId: React.PropTypes.string.isRequired,
|
||||
password: React.PropTypes.string.isRequired,
|
||||
loginRequest: React.PropTypes.object.isRequired
|
||||
loginId: PropTypes.string.isRequired,
|
||||
password: PropTypes.string.isRequired,
|
||||
loginRequest: PropTypes.object.isRequired
|
||||
};
|
||||
|
||||
constructor(props) {
|
||||
|
|
@ -52,7 +52,7 @@ export default class Mfa extends Component {
|
|||
// In case the login is successful the previous scene (login) will take care of the transition
|
||||
if (this.props.loginRequest.status === RequestStatus.STARTED &&
|
||||
nextProps.loginRequest.status === RequestStatus.FAILURE) {
|
||||
this.props.actions.goBack();
|
||||
this.props.navigator.pop({animated: true});
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -86,8 +86,10 @@ export default class Mfa extends Component {
|
|||
if (!this.state.token) {
|
||||
this.setState({
|
||||
error: {
|
||||
id: 'login_mfa.tokenReq',
|
||||
defaultMessage: 'Please enter an MFA token'
|
||||
intl: {
|
||||
id: 'login_mfa.tokenReq',
|
||||
defaultMessage: 'Please enter an MFA token'
|
||||
}
|
||||
}
|
||||
});
|
||||
return;
|
||||
|
|
@ -127,7 +129,7 @@ export default class Mfa extends Component {
|
|||
<KeyboardAvoidingView
|
||||
behavior='padding'
|
||||
style={{flex: 1}}
|
||||
keyboardVerticalOffset={0}
|
||||
keyboardVerticalOffset={5}
|
||||
>
|
||||
<TouchableWithoutFeedback onPress={this.blur}>
|
||||
<View style={[GlobalStyles.container, GlobalStyles.signupContainer]}>
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue