Merge 4.29.0 into single-server
This commit is contained in:
commit
12c1112399
|
@ -1,7 +1,3 @@
|
||||||
export default {
|
import mockDeviceInfo from 'react-native-device-info/jest/react-native-device-info-mock';
|
||||||
getModel: () => '',
|
|
||||||
getReadableVersion: () => '',
|
export default mockDeviceInfo;
|
||||||
getBundleId: () => '',
|
|
||||||
isTablet: () => false,
|
|
||||||
hasNotch: () => false
|
|
||||||
};
|
|
||||||
|
|
|
@ -1,78 +1,102 @@
|
||||||
GEM
|
GEM
|
||||||
remote: https://rubygems.org/
|
remote: https://rubygems.org/
|
||||||
specs:
|
specs:
|
||||||
CFPropertyList (3.0.2)
|
CFPropertyList (3.0.5)
|
||||||
addressable (2.7.0)
|
rexml
|
||||||
|
addressable (2.8.0)
|
||||||
public_suffix (>= 2.0.2, < 5.0)
|
public_suffix (>= 2.0.2, < 5.0)
|
||||||
|
artifactory (3.0.15)
|
||||||
atomos (0.1.3)
|
atomos (0.1.3)
|
||||||
aws-eventstream (1.0.3)
|
aws-eventstream (1.2.0)
|
||||||
aws-partitions (1.294.0)
|
aws-partitions (1.600.0)
|
||||||
aws-sdk-core (3.92.0)
|
aws-sdk-core (3.131.2)
|
||||||
aws-eventstream (~> 1.0, >= 1.0.2)
|
aws-eventstream (~> 1, >= 1.0.2)
|
||||||
aws-partitions (~> 1, >= 1.239.0)
|
aws-partitions (~> 1, >= 1.525.0)
|
||||||
aws-sigv4 (~> 1.1)
|
aws-sigv4 (~> 1.1)
|
||||||
jmespath (~> 1.0)
|
jmespath (~> 1, >= 1.6.1)
|
||||||
aws-sdk-kms (1.30.0)
|
aws-sdk-kms (1.57.0)
|
||||||
aws-sdk-core (~> 3, >= 3.71.0)
|
aws-sdk-core (~> 3, >= 3.127.0)
|
||||||
aws-sigv4 (~> 1.1)
|
aws-sigv4 (~> 1.1)
|
||||||
aws-sdk-s3 (1.61.2)
|
aws-sdk-s3 (1.114.0)
|
||||||
aws-sdk-core (~> 3, >= 3.83.0)
|
aws-sdk-core (~> 3, >= 3.127.0)
|
||||||
aws-sdk-kms (~> 1)
|
aws-sdk-kms (~> 1)
|
||||||
aws-sigv4 (~> 1.1)
|
aws-sigv4 (~> 1.4)
|
||||||
aws-sigv4 (1.1.1)
|
aws-sigv4 (1.5.0)
|
||||||
aws-eventstream (~> 1.0, >= 1.0.2)
|
aws-eventstream (~> 1, >= 1.0.2)
|
||||||
babosa (1.0.3)
|
babosa (1.0.4)
|
||||||
claide (1.0.3)
|
claide (1.1.0)
|
||||||
colored (1.2)
|
colored (1.2)
|
||||||
colored2 (3.1.2)
|
colored2 (3.1.2)
|
||||||
commander-fastlane (4.4.6)
|
commander (4.6.0)
|
||||||
highline (~> 1.7.2)
|
highline (~> 2.0.0)
|
||||||
declarative (0.0.10)
|
declarative (0.0.20)
|
||||||
declarative-option (0.1.0)
|
digest-crc (0.6.4)
|
||||||
digest-crc (0.5.1)
|
rake (>= 12.0.0, < 14.0.0)
|
||||||
domain_name (0.5.20190701)
|
domain_name (0.5.20190701)
|
||||||
unf (>= 0.0.5, < 1.0.0)
|
unf (>= 0.0.5, < 1.0.0)
|
||||||
dotenv (2.7.5)
|
dotenv (2.7.6)
|
||||||
emoji_regex (1.0.1)
|
emoji_regex (3.2.3)
|
||||||
excon (0.73.0)
|
excon (0.92.3)
|
||||||
faraday (0.17.3)
|
faraday (1.10.0)
|
||||||
multipart-post (>= 1.2, < 3)
|
faraday-em_http (~> 1.0)
|
||||||
faraday-cookie_jar (0.0.6)
|
faraday-em_synchrony (~> 1.0)
|
||||||
faraday (>= 0.7.4)
|
faraday-excon (~> 1.1)
|
||||||
|
faraday-httpclient (~> 1.0)
|
||||||
|
faraday-multipart (~> 1.0)
|
||||||
|
faraday-net_http (~> 1.0)
|
||||||
|
faraday-net_http_persistent (~> 1.0)
|
||||||
|
faraday-patron (~> 1.0)
|
||||||
|
faraday-rack (~> 1.0)
|
||||||
|
faraday-retry (~> 1.0)
|
||||||
|
ruby2_keywords (>= 0.0.4)
|
||||||
|
faraday-cookie_jar (0.0.7)
|
||||||
|
faraday (>= 0.8.0)
|
||||||
http-cookie (~> 1.0.0)
|
http-cookie (~> 1.0.0)
|
||||||
faraday_middleware (0.13.1)
|
faraday-em_http (1.0.0)
|
||||||
faraday (>= 0.7.4, < 1.0)
|
faraday-em_synchrony (1.0.0)
|
||||||
fastimage (2.1.7)
|
faraday-excon (1.1.0)
|
||||||
fastlane (2.145.0)
|
faraday-httpclient (1.0.1)
|
||||||
|
faraday-multipart (1.0.4)
|
||||||
|
multipart-post (~> 2)
|
||||||
|
faraday-net_http (1.0.1)
|
||||||
|
faraday-net_http_persistent (1.2.0)
|
||||||
|
faraday-patron (1.0.0)
|
||||||
|
faraday-rack (1.0.0)
|
||||||
|
faraday-retry (1.0.3)
|
||||||
|
faraday_middleware (1.2.0)
|
||||||
|
faraday (~> 1.0)
|
||||||
|
fastimage (2.2.6)
|
||||||
|
fastlane (2.206.2)
|
||||||
CFPropertyList (>= 2.3, < 4.0.0)
|
CFPropertyList (>= 2.3, < 4.0.0)
|
||||||
addressable (>= 2.3, < 3.0.0)
|
addressable (>= 2.8, < 3.0.0)
|
||||||
|
artifactory (~> 3.0)
|
||||||
aws-sdk-s3 (~> 1.0)
|
aws-sdk-s3 (~> 1.0)
|
||||||
babosa (>= 1.0.2, < 2.0.0)
|
babosa (>= 1.0.3, < 2.0.0)
|
||||||
bundler (>= 1.12.0, < 3.0.0)
|
bundler (>= 1.12.0, < 3.0.0)
|
||||||
colored
|
colored
|
||||||
commander-fastlane (>= 4.4.6, < 5.0.0)
|
commander (~> 4.6)
|
||||||
dotenv (>= 2.1.1, < 3.0.0)
|
dotenv (>= 2.1.1, < 3.0.0)
|
||||||
emoji_regex (>= 0.1, < 2.0)
|
emoji_regex (>= 0.1, < 4.0)
|
||||||
excon (>= 0.71.0, < 1.0.0)
|
excon (>= 0.71.0, < 1.0.0)
|
||||||
faraday (~> 0.17)
|
faraday (~> 1.0)
|
||||||
faraday-cookie_jar (~> 0.0.6)
|
faraday-cookie_jar (~> 0.0.6)
|
||||||
faraday_middleware (~> 0.13.1)
|
faraday_middleware (~> 1.0)
|
||||||
fastimage (>= 2.1.0, < 3.0.0)
|
fastimage (>= 2.1.0, < 3.0.0)
|
||||||
gh_inspector (>= 1.1.2, < 2.0.0)
|
gh_inspector (>= 1.1.2, < 2.0.0)
|
||||||
google-api-client (>= 0.29.2, < 0.37.0)
|
google-apis-androidpublisher_v3 (~> 0.3)
|
||||||
google-cloud-storage (>= 1.15.0, < 2.0.0)
|
google-apis-playcustomapp_v1 (~> 0.1)
|
||||||
highline (>= 1.7.2, < 2.0.0)
|
google-cloud-storage (~> 1.31)
|
||||||
|
highline (~> 2.0)
|
||||||
json (< 3.0.0)
|
json (< 3.0.0)
|
||||||
jwt (~> 2.1.0)
|
jwt (>= 2.1.0, < 3)
|
||||||
mini_magick (>= 4.9.4, < 5.0.0)
|
mini_magick (>= 4.9.4, < 5.0.0)
|
||||||
multi_xml (~> 0.5)
|
|
||||||
multipart-post (~> 2.0.0)
|
multipart-post (~> 2.0.0)
|
||||||
|
naturally (~> 2.2)
|
||||||
|
optparse (~> 0.1.1)
|
||||||
plist (>= 3.1.0, < 4.0.0)
|
plist (>= 3.1.0, < 4.0.0)
|
||||||
public_suffix (~> 2.0.0)
|
rubyzip (>= 2.0.0, < 3.0.0)
|
||||||
rubyzip (>= 1.3.0, < 2.0.0)
|
|
||||||
security (= 0.1.3)
|
security (= 0.1.3)
|
||||||
simctl (~> 1.6.3)
|
simctl (~> 1.6.3)
|
||||||
slack-notifier (>= 2.0.0, < 3.0.0)
|
|
||||||
terminal-notifier (>= 2.0.0, < 3.0.0)
|
terminal-notifier (>= 2.0.0, < 3.0.0)
|
||||||
terminal-table (>= 1.4.5, < 2.0.0)
|
terminal-table (>= 1.4.5, < 2.0.0)
|
||||||
tty-screen (>= 0.6.3, < 1.0.0)
|
tty-screen (>= 0.6.3, < 1.0.0)
|
||||||
|
@ -82,92 +106,106 @@ GEM
|
||||||
xcpretty (~> 0.3.0)
|
xcpretty (~> 0.3.0)
|
||||||
xcpretty-travis-formatter (>= 0.0.3)
|
xcpretty-travis-formatter (>= 0.0.3)
|
||||||
gh_inspector (1.1.3)
|
gh_inspector (1.1.3)
|
||||||
google-api-client (0.36.4)
|
google-apis-androidpublisher_v3 (0.22.0)
|
||||||
|
google-apis-core (>= 0.5, < 2.a)
|
||||||
|
google-apis-core (0.6.0)
|
||||||
addressable (~> 2.5, >= 2.5.1)
|
addressable (~> 2.5, >= 2.5.1)
|
||||||
googleauth (~> 0.9)
|
googleauth (>= 0.16.2, < 2.a)
|
||||||
httpclient (>= 2.8.1, < 3.0)
|
httpclient (>= 2.8.1, < 3.a)
|
||||||
mini_mime (~> 1.0)
|
mini_mime (~> 1.0)
|
||||||
representable (~> 3.0)
|
representable (~> 3.0)
|
||||||
retriable (>= 2.0, < 4.0)
|
retriable (>= 2.0, < 4.a)
|
||||||
signet (~> 0.12)
|
rexml
|
||||||
google-cloud-core (1.5.0)
|
webrick
|
||||||
|
google-apis-iamcredentials_v1 (0.12.0)
|
||||||
|
google-apis-core (>= 0.6, < 2.a)
|
||||||
|
google-apis-playcustomapp_v1 (0.9.0)
|
||||||
|
google-apis-core (>= 0.6, < 2.a)
|
||||||
|
google-apis-storage_v1 (0.15.0)
|
||||||
|
google-apis-core (>= 0.5, < 2.a)
|
||||||
|
google-cloud-core (1.6.0)
|
||||||
google-cloud-env (~> 1.0)
|
google-cloud-env (~> 1.0)
|
||||||
google-cloud-errors (~> 1.0)
|
google-cloud-errors (~> 1.0)
|
||||||
google-cloud-env (1.3.1)
|
google-cloud-env (1.6.0)
|
||||||
faraday (>= 0.17.3, < 2.0)
|
faraday (>= 0.17.3, < 3.0)
|
||||||
google-cloud-errors (1.0.0)
|
google-cloud-errors (1.2.0)
|
||||||
google-cloud-storage (1.25.1)
|
google-cloud-storage (1.36.2)
|
||||||
addressable (~> 2.5)
|
addressable (~> 2.8)
|
||||||
digest-crc (~> 0.4)
|
digest-crc (~> 0.4)
|
||||||
google-api-client (~> 0.33)
|
google-apis-iamcredentials_v1 (~> 0.1)
|
||||||
google-cloud-core (~> 1.2)
|
google-apis-storage_v1 (~> 0.1)
|
||||||
googleauth (~> 0.9)
|
google-cloud-core (~> 1.6)
|
||||||
|
googleauth (>= 0.16.2, < 2.a)
|
||||||
mini_mime (~> 1.0)
|
mini_mime (~> 1.0)
|
||||||
googleauth (0.11.0)
|
googleauth (1.2.0)
|
||||||
faraday (>= 0.17.3, < 2.0)
|
faraday (>= 0.17.3, < 3.a)
|
||||||
jwt (>= 1.4, < 3.0)
|
jwt (>= 1.4, < 3.0)
|
||||||
memoist (~> 0.16)
|
memoist (~> 0.16)
|
||||||
multi_json (~> 1.11)
|
multi_json (~> 1.11)
|
||||||
os (>= 0.9, < 2.0)
|
os (>= 0.9, < 2.0)
|
||||||
signet (~> 0.12)
|
signet (>= 0.16, < 2.a)
|
||||||
highline (1.7.10)
|
highline (2.0.3)
|
||||||
http-cookie (1.0.3)
|
http-cookie (1.0.5)
|
||||||
domain_name (~> 0.5)
|
domain_name (~> 0.5)
|
||||||
httpclient (2.8.3)
|
httpclient (2.8.3)
|
||||||
jmespath (1.4.0)
|
jmespath (1.6.1)
|
||||||
json (2.3.0)
|
json (2.6.2)
|
||||||
jwt (2.1.0)
|
jwt (2.4.1)
|
||||||
memoist (0.16.2)
|
memoist (0.16.2)
|
||||||
mini_magick (4.10.1)
|
mini_magick (4.11.0)
|
||||||
mini_mime (1.0.2)
|
mini_mime (1.1.2)
|
||||||
multi_json (1.14.1)
|
multi_json (1.15.0)
|
||||||
multi_xml (0.6.0)
|
|
||||||
multipart-post (2.0.0)
|
multipart-post (2.0.0)
|
||||||
nanaimo (0.2.6)
|
nanaimo (0.3.0)
|
||||||
naturally (2.2.0)
|
naturally (2.2.1)
|
||||||
os (1.1.0)
|
optparse (0.1.1)
|
||||||
plist (3.5.0)
|
os (1.1.4)
|
||||||
public_suffix (2.0.5)
|
plist (3.6.0)
|
||||||
representable (3.0.4)
|
public_suffix (4.0.7)
|
||||||
|
rake (13.0.6)
|
||||||
|
representable (3.2.0)
|
||||||
declarative (< 0.1.0)
|
declarative (< 0.1.0)
|
||||||
declarative-option (< 0.2.0)
|
trailblazer-option (>= 0.1.1, < 0.2.0)
|
||||||
uber (< 0.2.0)
|
uber (< 0.2.0)
|
||||||
retriable (3.1.2)
|
retriable (3.1.2)
|
||||||
|
rexml (3.2.5)
|
||||||
rouge (2.0.7)
|
rouge (2.0.7)
|
||||||
rubyzip (1.3.0)
|
ruby2_keywords (0.0.5)
|
||||||
|
rubyzip (2.3.2)
|
||||||
security (0.1.3)
|
security (0.1.3)
|
||||||
signet (0.14.0)
|
signet (0.17.0)
|
||||||
addressable (~> 2.3)
|
addressable (~> 2.8)
|
||||||
faraday (>= 0.17.3, < 2.0)
|
faraday (>= 0.17.5, < 3.a)
|
||||||
jwt (>= 1.5, < 3.0)
|
jwt (>= 1.5, < 3.0)
|
||||||
multi_json (~> 1.10)
|
multi_json (~> 1.10)
|
||||||
simctl (1.6.8)
|
simctl (1.6.8)
|
||||||
CFPropertyList
|
CFPropertyList
|
||||||
naturally
|
naturally
|
||||||
slack-notifier (2.3.2)
|
|
||||||
terminal-notifier (2.0.0)
|
terminal-notifier (2.0.0)
|
||||||
terminal-table (1.8.0)
|
terminal-table (1.8.0)
|
||||||
unicode-display_width (~> 1.1, >= 1.1.1)
|
unicode-display_width (~> 1.1, >= 1.1.1)
|
||||||
|
trailblazer-option (0.1.2)
|
||||||
tty-cursor (0.7.1)
|
tty-cursor (0.7.1)
|
||||||
tty-screen (0.7.1)
|
tty-screen (0.8.1)
|
||||||
tty-spinner (0.9.3)
|
tty-spinner (0.9.3)
|
||||||
tty-cursor (~> 0.7)
|
tty-cursor (~> 0.7)
|
||||||
uber (0.1.0)
|
uber (0.1.0)
|
||||||
unf (0.1.4)
|
unf (0.1.4)
|
||||||
unf_ext
|
unf_ext
|
||||||
unf_ext (0.0.7.7)
|
unf_ext (0.0.8.2)
|
||||||
unf_ext (0.0.7.7-x64-mingw32)
|
unicode-display_width (1.8.0)
|
||||||
unicode-display_width (1.7.0)
|
webrick (1.7.0)
|
||||||
word_wrap (1.0.0)
|
word_wrap (1.0.0)
|
||||||
xcodeproj (1.15.0)
|
xcodeproj (1.22.0)
|
||||||
CFPropertyList (>= 2.3.3, < 4.0)
|
CFPropertyList (>= 2.3.3, < 4.0)
|
||||||
atomos (~> 0.1.3)
|
atomos (~> 0.1.3)
|
||||||
claide (>= 1.0.2, < 2.0)
|
claide (>= 1.0.2, < 2.0)
|
||||||
colored2 (~> 3.1)
|
colored2 (~> 3.1)
|
||||||
nanaimo (~> 0.2.6)
|
nanaimo (~> 0.3.0)
|
||||||
|
rexml (~> 3.2.4)
|
||||||
xcpretty (0.3.0)
|
xcpretty (0.3.0)
|
||||||
rouge (~> 2.0.7)
|
rouge (~> 2.0.7)
|
||||||
xcpretty-travis-formatter (1.0.0)
|
xcpretty-travis-formatter (1.0.1)
|
||||||
xcpretty (~> 0.2, >= 0.0.7)
|
xcpretty (~> 0.2, >= 0.0.7)
|
||||||
|
|
||||||
PLATFORMS
|
PLATFORMS
|
||||||
|
@ -178,4 +216,4 @@ DEPENDENCIES
|
||||||
fastlane
|
fastlane
|
||||||
|
|
||||||
BUNDLED WITH
|
BUNDLED WITH
|
||||||
2.0.2
|
2.3.11
|
||||||
|
|
|
@ -144,7 +144,7 @@ android {
|
||||||
minSdkVersion rootProject.ext.minSdkVersion
|
minSdkVersion rootProject.ext.minSdkVersion
|
||||||
targetSdkVersion rootProject.ext.targetSdkVersion
|
targetSdkVersion rootProject.ext.targetSdkVersion
|
||||||
versionCode VERSIONCODE as Integer
|
versionCode VERSIONCODE as Integer
|
||||||
versionName "4.28.0"
|
versionName "4.29.0"
|
||||||
vectorDrawables.useSupportLibrary = true
|
vectorDrawables.useSupportLibrary = true
|
||||||
if (!isFoss) {
|
if (!isFoss) {
|
||||||
manifestPlaceholders = [BugsnagAPIKey: BugsnagAPIKey as String]
|
manifestPlaceholders = [BugsnagAPIKey: BugsnagAPIKey as String]
|
||||||
|
|
Binary file not shown.
|
@ -12,7 +12,6 @@ import com.facebook.react.ReactRootView;
|
||||||
import com.facebook.react.ReactActivityDelegate;
|
import com.facebook.react.ReactActivityDelegate;
|
||||||
import com.facebook.react.ReactFragmentActivity;
|
import com.facebook.react.ReactFragmentActivity;
|
||||||
|
|
||||||
import com.swmansion.gesturehandler.react.RNGestureHandlerEnabledRootView;
|
|
||||||
import com.zoontek.rnbootsplash.RNBootSplash;
|
import com.zoontek.rnbootsplash.RNBootSplash;
|
||||||
import com.google.gson.Gson;
|
import com.google.gson.Gson;
|
||||||
|
|
||||||
|
@ -51,16 +50,6 @@ public class MainActivity extends ReactFragmentActivity {
|
||||||
return "RocketChatRN";
|
return "RocketChatRN";
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
protected ReactActivityDelegate createReactActivityDelegate() {
|
|
||||||
return new ReactActivityDelegate(this, getMainComponentName()) {
|
|
||||||
@Override
|
|
||||||
protected ReactRootView createRootView() {
|
|
||||||
return new RNGestureHandlerEnabledRootView(MainActivity.this);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// from react-native-orientation
|
// from react-native-orientation
|
||||||
@Override
|
@Override
|
||||||
public void onConfigurationChanged(Configuration newConfig) {
|
public void onConfigurationChanged(Configuration newConfig) {
|
||||||
|
|
|
@ -3,21 +3,10 @@ package chat.rocket.reactnative.share;
|
||||||
import com.facebook.react.ReactActivity;
|
import com.facebook.react.ReactActivity;
|
||||||
import com.facebook.react.ReactActivityDelegate;
|
import com.facebook.react.ReactActivityDelegate;
|
||||||
import com.facebook.react.ReactRootView;
|
import com.facebook.react.ReactRootView;
|
||||||
import com.swmansion.gesturehandler.react.RNGestureHandlerEnabledRootView;
|
|
||||||
|
|
||||||
public class ShareActivity extends ReactActivity {
|
public class ShareActivity extends ReactActivity {
|
||||||
@Override
|
@Override
|
||||||
protected String getMainComponentName() {
|
protected String getMainComponentName() {
|
||||||
return "ShareRocketChatRN";
|
return "ShareRocketChatRN";
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
protected ReactActivityDelegate createReactActivityDelegate() {
|
|
||||||
return new ReactActivityDelegate(this, getMainComponentName()) {
|
|
||||||
@Override
|
|
||||||
protected ReactRootView createRootView() {
|
|
||||||
return new RNGestureHandlerEnabledRootView(ShareActivity.this);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
}
|
|
@ -14,7 +14,7 @@ buildscript {
|
||||||
targetSdkVersion = 30
|
targetSdkVersion = 30
|
||||||
ndkVersion = "20.1.5948944"
|
ndkVersion = "20.1.5948944"
|
||||||
glideVersion = "4.11.0"
|
glideVersion = "4.11.0"
|
||||||
kotlin_version = "1.3.50"
|
kotlin_version = "1.6.10"
|
||||||
supportLibVersion = "28.0.0"
|
supportLibVersion = "28.0.0"
|
||||||
libre_build = !(isPlay.toBoolean())
|
libre_build = !(isPlay.toBoolean())
|
||||||
jitsi_url = isPlay ? "https://github.com/RocketChat/jitsi-maven-repository/raw/master/releases" : "https://github.com/RocketChat/jitsi-maven-repository/raw/libre/releases"
|
jitsi_url = isPlay ? "https://github.com/RocketChat/jitsi-maven-repository/raw/master/releases" : "https://github.com/RocketChat/jitsi-maven-repository/raw/libre/releases"
|
||||||
|
|
|
@ -37,6 +37,3 @@ KEYSTORE=my-upload-key.keystore
|
||||||
KEY_ALIAS=my-key-alias
|
KEY_ALIAS=my-key-alias
|
||||||
KEYSTORE_PASSWORD=
|
KEYSTORE_PASSWORD=
|
||||||
KEY_PASSWORD=
|
KEY_PASSWORD=
|
||||||
|
|
||||||
# Version of flipper SDK to use with React Native
|
|
||||||
FLIPPER_VERSION=0.75.1
|
|
||||||
|
|
|
@ -5,7 +5,7 @@ import { connect } from 'react-redux';
|
||||||
|
|
||||||
import { SetUsernameStackParamList, StackParamList } from './definitions/navigationTypes';
|
import { SetUsernameStackParamList, StackParamList } from './definitions/navigationTypes';
|
||||||
import Navigation from './lib/navigation/appNavigation';
|
import Navigation from './lib/navigation/appNavigation';
|
||||||
import { defaultHeader, getActiveRouteName, navigationTheme } from './utils/navigation';
|
import { defaultHeader, getActiveRouteName, navigationTheme } from './lib/methods/helpers/navigation';
|
||||||
import { RootEnum } from './definitions';
|
import { RootEnum } from './definitions';
|
||||||
// Stacks
|
// Stacks
|
||||||
import AuthLoadingView from './views/AuthLoadingView';
|
import AuthLoadingView from './views/AuthLoadingView';
|
||||||
|
@ -15,7 +15,7 @@ import OutsideStack from './stacks/OutsideStack';
|
||||||
import InsideStack from './stacks/InsideStack';
|
import InsideStack from './stacks/InsideStack';
|
||||||
import MasterDetailStack from './stacks/MasterDetailStack';
|
import MasterDetailStack from './stacks/MasterDetailStack';
|
||||||
import { ThemeContext } from './theme';
|
import { ThemeContext } from './theme';
|
||||||
import { setCurrentScreen } from './utils/log';
|
import { setCurrentScreen } from './lib/methods/helpers/log';
|
||||||
|
|
||||||
// SetUsernameStack
|
// SetUsernameStack
|
||||||
const SetUsername = createStackNavigator<SetUsernameStackParamList>();
|
const SetUsername = createStackNavigator<SetUsernameStackParamList>();
|
||||||
|
|
|
@ -27,7 +27,6 @@ export const ROOM = createRequestTypes('ROOM', [
|
||||||
'LEAVE',
|
'LEAVE',
|
||||||
'DELETE',
|
'DELETE',
|
||||||
'REMOVED',
|
'REMOVED',
|
||||||
'CLOSE',
|
|
||||||
'FORWARD',
|
'FORWARD',
|
||||||
'USER_TYPING'
|
'USER_TYPING'
|
||||||
]);
|
]);
|
||||||
|
@ -54,6 +53,7 @@ export const SERVER = createRequestTypes('SERVER', [
|
||||||
]);
|
]);
|
||||||
export const METEOR = createRequestTypes('METEOR_CONNECT', [...defaultTypes, 'DISCONNECT']);
|
export const METEOR = createRequestTypes('METEOR_CONNECT', [...defaultTypes, 'DISCONNECT']);
|
||||||
export const LOGOUT = 'LOGOUT'; // logout is always success
|
export const LOGOUT = 'LOGOUT'; // logout is always success
|
||||||
|
export const DELETE_ACCOUNT = 'DELETE_ACCOUNT';
|
||||||
export const SNIPPETED_MESSAGES = createRequestTypes('SNIPPETED_MESSAGES', ['OPEN', 'READY', 'CLOSE', 'MESSAGES_RECEIVED']);
|
export const SNIPPETED_MESSAGES = createRequestTypes('SNIPPETED_MESSAGES', ['OPEN', 'READY', 'CLOSE', 'MESSAGES_RECEIVED']);
|
||||||
export const DEEP_LINKING = createRequestTypes('DEEP_LINKING', ['OPEN']);
|
export const DEEP_LINKING = createRequestTypes('DEEP_LINKING', ['OPEN']);
|
||||||
export const SORT_PREFERENCES = createRequestTypes('SORT_PREFERENCES', ['SET_ALL', 'SET']);
|
export const SORT_PREFERENCES = createRequestTypes('SORT_PREFERENCES', ['SET_ALL', 'SET']);
|
||||||
|
|
|
@ -121,3 +121,9 @@ export function setLocalAuthenticated(isLocalAuthenticated: boolean): ISetLocalA
|
||||||
isLocalAuthenticated
|
isLocalAuthenticated
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function deleteAccount(): Action {
|
||||||
|
return {
|
||||||
|
type: types.DELETE_ACCOUNT
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
|
@ -19,7 +19,6 @@ interface IBaseReturn extends Action {
|
||||||
|
|
||||||
type TSubscribeRoom = IBaseReturn;
|
type TSubscribeRoom = IBaseReturn;
|
||||||
type TUnsubscribeRoom = IBaseReturn;
|
type TUnsubscribeRoom = IBaseReturn;
|
||||||
type TCloseRoom = IBaseReturn;
|
|
||||||
|
|
||||||
type TRoom = Record<string, any>;
|
type TRoom = Record<string, any>;
|
||||||
|
|
||||||
|
@ -45,7 +44,7 @@ interface IUserTyping extends Action {
|
||||||
status: boolean;
|
status: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type TActionsRoom = TSubscribeRoom & TUnsubscribeRoom & TCloseRoom & ILeaveRoom & IDeleteRoom & IForwardRoom & IUserTyping;
|
export type TActionsRoom = TSubscribeRoom & TUnsubscribeRoom & ILeaveRoom & IDeleteRoom & IForwardRoom & IUserTyping;
|
||||||
|
|
||||||
export function subscribeRoom(rid: string): TSubscribeRoom {
|
export function subscribeRoom(rid: string): TSubscribeRoom {
|
||||||
return {
|
return {
|
||||||
|
@ -79,13 +78,6 @@ export function deleteRoom(roomType: ERoomType, room: TRoom, selected?: ISelecte
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function closeRoom(rid: string): TCloseRoom {
|
|
||||||
return {
|
|
||||||
type: ROOM.CLOSE,
|
|
||||||
rid
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export function forwardRoom(rid: string, transferData: ITransferData): IForwardRoom {
|
export function forwardRoom(rid: string, transferData: ITransferData): IForwardRoom {
|
||||||
return {
|
return {
|
||||||
type: ROOM.FORWARD,
|
type: ROOM.FORWARD,
|
||||||
|
|
|
@ -8,7 +8,7 @@ import BottomSheet, { BottomSheetBackdrop } from '@gorhom/bottom-sheet';
|
||||||
|
|
||||||
import { useDimensions, useOrientation } from '../../dimensions';
|
import { useDimensions, useOrientation } from '../../dimensions';
|
||||||
import { useTheme } from '../../theme';
|
import { useTheme } from '../../theme';
|
||||||
import { isIOS, isTablet } from '../../utils/deviceInfo';
|
import { isIOS, isTablet } from '../../lib/methods/helpers';
|
||||||
import { Handle } from './Handle';
|
import { Handle } from './Handle';
|
||||||
import { TActionSheetOptions } from './Provider';
|
import { TActionSheetOptions } from './Provider';
|
||||||
import BottomSheetContent from './BottomSheetContent';
|
import BottomSheetContent from './BottomSheetContent';
|
||||||
|
@ -101,6 +101,11 @@ const ActionSheet = React.memo(
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const onClose = () => {
|
||||||
|
toggleVisible();
|
||||||
|
data?.onClose && data?.onClose();
|
||||||
|
};
|
||||||
|
|
||||||
const renderBackdrop = useCallback(
|
const renderBackdrop = useCallback(
|
||||||
props => (
|
props => (
|
||||||
<BottomSheetBackdrop
|
<BottomSheetBackdrop
|
||||||
|
@ -116,6 +121,10 @@ const ActionSheet = React.memo(
|
||||||
|
|
||||||
const bottomSheet = isLandscape || isTablet ? styles.bottomSheet : {};
|
const bottomSheet = isLandscape || isTablet ? styles.bottomSheet : {};
|
||||||
|
|
||||||
|
// Must need this prop to avoid keyboard dismiss
|
||||||
|
// when is android tablet and the input text is focused
|
||||||
|
const androidTablet: any = isTablet && isLandscape && !isIOS ? { android_keyboardInputMode: 'adjustResize' } : {};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{children}
|
{children}
|
||||||
|
@ -130,7 +139,8 @@ const ActionSheet = React.memo(
|
||||||
enablePanDownToClose
|
enablePanDownToClose
|
||||||
style={{ ...styles.container, ...bottomSheet }}
|
style={{ ...styles.container, ...bottomSheet }}
|
||||||
backgroundStyle={{ backgroundColor: colors.focusedBackground }}
|
backgroundStyle={{ backgroundColor: colors.focusedBackground }}
|
||||||
onChange={index => index === -1 && toggleVisible()}>
|
onChange={index => index === -1 && onClose()}
|
||||||
|
{...androidTablet}>
|
||||||
<BottomSheetContent options={data?.options} hide={hide} children={data?.children} hasCancel={data?.hasCancel} />
|
<BottomSheetContent options={data?.options} hide={hide} children={data?.children} hasCancel={data?.hasCancel} />
|
||||||
</BottomSheet>
|
</BottomSheet>
|
||||||
)}
|
)}
|
||||||
|
|
|
@ -0,0 +1,141 @@
|
||||||
|
import React, { useState } from 'react';
|
||||||
|
import { StyleSheet, Text, View } from 'react-native';
|
||||||
|
|
||||||
|
import { CustomIcon, TIconsName } from '../../CustomIcon';
|
||||||
|
import i18n from '../../../i18n';
|
||||||
|
import { isIOS } from '../../../lib/methods/helpers';
|
||||||
|
import { useTheme } from '../../../theme';
|
||||||
|
import sharedStyles from '../../../views/Styles';
|
||||||
|
import Button from '../../Button';
|
||||||
|
import { FormTextInput } from '../../TextInput/FormTextInput';
|
||||||
|
import { useActionSheet } from '../Provider';
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
subtitleText: {
|
||||||
|
fontSize: 14,
|
||||||
|
...sharedStyles.textRegular,
|
||||||
|
marginBottom: 10
|
||||||
|
},
|
||||||
|
buttonSeparator: {
|
||||||
|
marginRight: 8
|
||||||
|
},
|
||||||
|
footerButtonsContainer: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
paddingTop: 16
|
||||||
|
},
|
||||||
|
titleContainerText: {
|
||||||
|
fontSize: 16,
|
||||||
|
...sharedStyles.textSemibold
|
||||||
|
},
|
||||||
|
titleContainer: {
|
||||||
|
paddingRight: 80,
|
||||||
|
marginBottom: 16,
|
||||||
|
flexDirection: 'row',
|
||||||
|
alignItems: 'center'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const FooterButtons = ({
|
||||||
|
cancelAction = () => {},
|
||||||
|
confirmAction = () => {},
|
||||||
|
cancelTitle = '',
|
||||||
|
confirmTitle = '',
|
||||||
|
disabled = false,
|
||||||
|
cancelBackgroundColor = '',
|
||||||
|
confirmBackgroundColor = ''
|
||||||
|
}): React.ReactElement => {
|
||||||
|
const { colors } = useTheme();
|
||||||
|
return (
|
||||||
|
<View style={styles.footerButtonsContainer}>
|
||||||
|
<Button
|
||||||
|
style={[styles.buttonSeparator, { flex: 1, backgroundColor: cancelBackgroundColor || colors.cancelButton }]}
|
||||||
|
color={colors.backdropColor}
|
||||||
|
title={cancelTitle}
|
||||||
|
onPress={cancelAction}
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
style={{ flex: 1, backgroundColor: confirmBackgroundColor || colors.dangerColor }}
|
||||||
|
title={confirmTitle}
|
||||||
|
onPress={confirmAction}
|
||||||
|
disabled={disabled}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const ActionSheetContentWithInputAndSubmit = ({
|
||||||
|
onSubmit = () => {},
|
||||||
|
onCancel,
|
||||||
|
title = '',
|
||||||
|
description = '',
|
||||||
|
testID = '',
|
||||||
|
secureTextEntry = true,
|
||||||
|
placeholder = '',
|
||||||
|
confirmTitle,
|
||||||
|
iconName,
|
||||||
|
iconColor,
|
||||||
|
customText,
|
||||||
|
confirmBackgroundColor,
|
||||||
|
showInput = true
|
||||||
|
}: {
|
||||||
|
onSubmit: (inputValue: string) => void;
|
||||||
|
onCancel?: () => void;
|
||||||
|
title: string;
|
||||||
|
description: string;
|
||||||
|
testID: string;
|
||||||
|
secureTextEntry?: boolean;
|
||||||
|
placeholder: string;
|
||||||
|
confirmTitle?: string;
|
||||||
|
iconName?: TIconsName;
|
||||||
|
iconColor?: string;
|
||||||
|
customText?: React.ReactElement;
|
||||||
|
confirmBackgroundColor?: string;
|
||||||
|
showInput?: boolean;
|
||||||
|
}): React.ReactElement => {
|
||||||
|
const { colors } = useTheme();
|
||||||
|
const [inputValue, setInputValue] = useState('');
|
||||||
|
const { hideActionSheet } = useActionSheet();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<View style={sharedStyles.containerScrollView} testID='action-sheet-content-with-input-and-submit'>
|
||||||
|
<>
|
||||||
|
<View style={styles.titleContainer}>
|
||||||
|
{iconName ? <CustomIcon name={iconName} size={32} color={iconColor || colors.dangerColor} /> : null}
|
||||||
|
<Text style={[styles.titleContainerText, { color: colors.passcodePrimary, paddingLeft: iconName ? 16 : 0 }]}>
|
||||||
|
{title}
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
<Text style={[styles.subtitleText, { color: colors.titleText }]}>{description}</Text>
|
||||||
|
{customText}
|
||||||
|
</>
|
||||||
|
{showInput ? (
|
||||||
|
<FormTextInput
|
||||||
|
value={inputValue}
|
||||||
|
placeholder={placeholder}
|
||||||
|
onChangeText={value => setInputValue(value)}
|
||||||
|
onSubmitEditing={() => {
|
||||||
|
// fix android animation
|
||||||
|
setTimeout(() => {
|
||||||
|
hideActionSheet();
|
||||||
|
}, 100);
|
||||||
|
if (inputValue) onSubmit(inputValue);
|
||||||
|
}}
|
||||||
|
testID={testID}
|
||||||
|
secureTextEntry={secureTextEntry}
|
||||||
|
inputStyle={{ borderWidth: 2 }}
|
||||||
|
bottomSheet={isIOS}
|
||||||
|
/>
|
||||||
|
) : null}
|
||||||
|
<FooterButtons
|
||||||
|
confirmBackgroundColor={confirmBackgroundColor || colors.actionTintColor}
|
||||||
|
cancelAction={onCancel || hideActionSheet}
|
||||||
|
confirmAction={() => onSubmit(inputValue)}
|
||||||
|
cancelTitle={i18n.t('Cancel')}
|
||||||
|
confirmTitle={confirmTitle || i18n.t('Save')}
|
||||||
|
disabled={!showInput ? false : !inputValue}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ActionSheetContentWithInputAndSubmit;
|
|
@ -53,7 +53,7 @@ const BottomSheetContent = React.memo(({ options, hasCancel, hide, children }: I
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return <BottomSheetView>{children}</BottomSheetView>;
|
return <BottomSheetView style={styles.contentContainer}>{children}</BottomSheetView>;
|
||||||
});
|
});
|
||||||
|
|
||||||
export default BottomSheetContent;
|
export default BottomSheetContent;
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { TouchableOpacity } from 'react-native';
|
import { TouchableOpacity } from 'react-native';
|
||||||
|
|
||||||
import { isAndroid } from '../../utils/deviceInfo';
|
import { isAndroid } from '../../lib/methods/helpers';
|
||||||
import Touch from '../../utils/touch';
|
import Touch from '../../lib/methods/helpers/touch';
|
||||||
|
|
||||||
// Taken from https://github.com/rgommezz/react-native-scroll-bottom-sheet#touchables
|
// Taken from https://github.com/rgommezz/react-native-scroll-bottom-sheet#touchables
|
||||||
export const Button: typeof React.Component = isAndroid ? Touch : TouchableOpacity;
|
export const Button: typeof React.Component = isAndroid ? Touch : TouchableOpacity;
|
||||||
|
|
|
@ -8,7 +8,7 @@ import { useTheme } from '../../theme';
|
||||||
export const Handle = React.memo(() => {
|
export const Handle = React.memo(() => {
|
||||||
const { theme } = useTheme();
|
const { theme } = useTheme();
|
||||||
return (
|
return (
|
||||||
<View style={[styles.handle, { backgroundColor: themes[theme].focusedBackground }]} testID='action-sheet-handle'>
|
<View style={[styles.handle]} testID='action-sheet-handle'>
|
||||||
<View style={[styles.handleIndicator, { backgroundColor: themes[theme].auxiliaryText }]} />
|
<View style={[styles.handleIndicator, { backgroundColor: themes[theme].auxiliaryText }]} />
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
|
import hoistNonReactStatics from 'hoist-non-react-statics';
|
||||||
import React, { ForwardedRef, forwardRef, useContext, useRef } from 'react';
|
import React, { ForwardedRef, forwardRef, useContext, useRef } from 'react';
|
||||||
|
|
||||||
import ActionSheet from './ActionSheet';
|
|
||||||
import { TIconsName } from '../CustomIcon';
|
import { TIconsName } from '../CustomIcon';
|
||||||
|
import ActionSheet from './ActionSheet';
|
||||||
|
|
||||||
export type TActionSheetOptionsItem = {
|
export type TActionSheetOptionsItem = {
|
||||||
title: string;
|
title: string;
|
||||||
|
@ -19,9 +20,10 @@ export type TActionSheetOptions = {
|
||||||
hasCancel?: boolean;
|
hasCancel?: boolean;
|
||||||
type?: string;
|
type?: string;
|
||||||
children?: React.ReactElement | null;
|
children?: React.ReactElement | null;
|
||||||
snaps?: string[] | number[];
|
snaps?: (string | number)[];
|
||||||
|
onClose?: () => void;
|
||||||
};
|
};
|
||||||
interface IActionSheetProvider {
|
export interface IActionSheetProvider {
|
||||||
showActionSheet: (item: TActionSheetOptions) => void;
|
showActionSheet: (item: TActionSheetOptions) => void;
|
||||||
hideActionSheet: () => void;
|
hideActionSheet: () => void;
|
||||||
}
|
}
|
||||||
|
@ -35,11 +37,15 @@ export const useActionSheet = () => useContext(context);
|
||||||
|
|
||||||
const { Provider, Consumer } = context;
|
const { Provider, Consumer } = context;
|
||||||
|
|
||||||
export const withActionSheet = (Component: React.ComponentType<any>): typeof Component =>
|
export const withActionSheet = (Component: React.ComponentType<any>): typeof Component => {
|
||||||
forwardRef((props: typeof React.Component, ref: ForwardedRef<IActionSheetProvider>) => (
|
const WithActionSheetComponent = forwardRef((props: typeof React.Component, ref: ForwardedRef<IActionSheetProvider>) => (
|
||||||
<Consumer>{(contexts: IActionSheetProvider) => <Component {...props} {...contexts} ref={ref} />}</Consumer>
|
<Consumer>{(contexts: IActionSheetProvider) => <Component {...props} {...contexts} ref={ref} />}</Consumer>
|
||||||
));
|
));
|
||||||
|
|
||||||
|
hoistNonReactStatics(WithActionSheetComponent, Component);
|
||||||
|
return WithActionSheetComponent;
|
||||||
|
};
|
||||||
|
|
||||||
export const ActionSheetProvider = React.memo(({ children }: { children: React.ReactElement | React.ReactElement[] }) => {
|
export const ActionSheetProvider = React.memo(({ children }: { children: React.ReactElement | React.ReactElement[] }) => {
|
||||||
const ref: ForwardedRef<IActionSheetProvider> = useRef(null);
|
const ref: ForwardedRef<IActionSheetProvider> = useRef(null);
|
||||||
|
|
||||||
|
|
|
@ -63,5 +63,15 @@ export default StyleSheet.create({
|
||||||
},
|
},
|
||||||
rightContainer: {
|
rightContainer: {
|
||||||
paddingLeft: 12
|
paddingLeft: 12
|
||||||
|
},
|
||||||
|
footerButtonsContainer: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
paddingTop: 16
|
||||||
|
},
|
||||||
|
buttonSeparator: {
|
||||||
|
marginRight: 8
|
||||||
|
},
|
||||||
|
contentContainer: {
|
||||||
|
flex: 1
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -3,7 +3,7 @@ import { StyleSheet, Text, View } from 'react-native';
|
||||||
|
|
||||||
import { themes } from '../lib/constants';
|
import { themes } from '../lib/constants';
|
||||||
import sharedStyles from '../views/Styles';
|
import sharedStyles from '../views/Styles';
|
||||||
import { getReadableVersion } from '../utils/deviceInfo';
|
import { getReadableVersion } from '../lib/methods/helpers';
|
||||||
import I18n from '../i18n';
|
import I18n from '../i18n';
|
||||||
import { TSupportedThemes } from '../theme';
|
import { TSupportedThemes } from '../theme';
|
||||||
|
|
||||||
|
|
|
@ -1,14 +1,13 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { View } from 'react-native';
|
import { View } from 'react-native';
|
||||||
import FastImage from '@rocket.chat/react-native-fast-image';
|
import FastImage from 'react-native-fast-image';
|
||||||
import Touchable from 'react-native-platform-touchable';
|
import Touchable from 'react-native-platform-touchable';
|
||||||
import { settings as RocketChatSettings } from '@rocket.chat/sdk';
|
import { settings as RocketChatSettings } from '@rocket.chat/sdk';
|
||||||
|
|
||||||
import { avatarURL } from '../../utils/avatar';
|
import { getAvatarURL } from '../../lib/methods/helpers/getAvatarUrl';
|
||||||
import { SubscriptionType } from '../../definitions/ISubscription';
|
import { SubscriptionType } from '../../definitions';
|
||||||
import Emoji from '../markdown/Emoji';
|
import Emoji from '../markdown/Emoji';
|
||||||
import { IAvatar } from './interfaces';
|
import { IAvatar } from './interfaces';
|
||||||
import { useTheme } from '../../theme';
|
|
||||||
|
|
||||||
const Avatar = React.memo(
|
const Avatar = React.memo(
|
||||||
({
|
({
|
||||||
|
@ -16,7 +15,8 @@ const Avatar = React.memo(
|
||||||
style,
|
style,
|
||||||
avatar,
|
avatar,
|
||||||
children,
|
children,
|
||||||
user,
|
userId,
|
||||||
|
token,
|
||||||
onPress,
|
onPress,
|
||||||
emoji,
|
emoji,
|
||||||
getCustomEmoji,
|
getCustomEmoji,
|
||||||
|
@ -31,8 +31,6 @@ const Avatar = React.memo(
|
||||||
type = SubscriptionType.DIRECT,
|
type = SubscriptionType.DIRECT,
|
||||||
externalProviderUrl
|
externalProviderUrl
|
||||||
}: IAvatar) => {
|
}: IAvatar) => {
|
||||||
const { theme } = useTheme();
|
|
||||||
|
|
||||||
if ((!text && !avatar && !emoji && !rid) || !server) {
|
if ((!text && !avatar && !emoji && !rid) || !server) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
@ -46,23 +44,17 @@ const Avatar = React.memo(
|
||||||
let image;
|
let image;
|
||||||
if (emoji) {
|
if (emoji) {
|
||||||
image = (
|
image = (
|
||||||
<Emoji
|
<Emoji baseUrl={server} getCustomEmoji={getCustomEmoji} isMessageContainsOnlyEmoji literal={emoji} style={avatarStyle} />
|
||||||
theme={theme}
|
|
||||||
baseUrl={server}
|
|
||||||
getCustomEmoji={getCustomEmoji}
|
|
||||||
isMessageContainsOnlyEmoji
|
|
||||||
literal={emoji}
|
|
||||||
style={avatarStyle}
|
|
||||||
/>
|
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
let uri = avatar;
|
let uri = avatar;
|
||||||
if (!isStatic) {
|
if (!isStatic) {
|
||||||
uri = avatarURL({
|
uri = getAvatarURL({
|
||||||
type,
|
type,
|
||||||
text,
|
text,
|
||||||
size,
|
size,
|
||||||
user,
|
userId,
|
||||||
|
token,
|
||||||
avatar,
|
avatar,
|
||||||
server,
|
server,
|
||||||
avatarETag,
|
avatarETag,
|
||||||
|
|
|
@ -1,84 +1,65 @@
|
||||||
import React from 'react';
|
|
||||||
import { connect } from 'react-redux';
|
|
||||||
import { Q } from '@nozbe/watermelondb';
|
import { Q } from '@nozbe/watermelondb';
|
||||||
|
import React, { useEffect, useRef, useState } from 'react';
|
||||||
|
import { shallowEqual, useSelector } from 'react-redux';
|
||||||
import { Observable, Subscription } from 'rxjs';
|
import { Observable, Subscription } from 'rxjs';
|
||||||
|
|
||||||
|
import { IApplicationState, TSubscriptionModel, TUserModel } from '../../definitions';
|
||||||
import database from '../../lib/database';
|
import database from '../../lib/database';
|
||||||
import { getUserSelector } from '../../selectors/login';
|
import { getUserSelector } from '../../selectors/login';
|
||||||
import { IApplicationState, TSubscriptionModel, TUserModel } from '../../definitions';
|
|
||||||
import Avatar from './Avatar';
|
import Avatar from './Avatar';
|
||||||
import { IAvatar } from './interfaces';
|
import { IAvatar } from './interfaces';
|
||||||
|
|
||||||
class AvatarContainer extends React.Component<IAvatar, any> {
|
const AvatarContainer = ({
|
||||||
private subscription?: Subscription;
|
style,
|
||||||
|
text = '',
|
||||||
|
avatar,
|
||||||
|
emoji,
|
||||||
|
size,
|
||||||
|
borderRadius,
|
||||||
|
type,
|
||||||
|
children,
|
||||||
|
onPress,
|
||||||
|
getCustomEmoji,
|
||||||
|
isStatic,
|
||||||
|
rid
|
||||||
|
}: IAvatar): React.ReactElement => {
|
||||||
|
const subscription = useRef<Subscription>();
|
||||||
|
const [avatarETag, setAvatarETag] = useState<string | undefined>('');
|
||||||
|
|
||||||
static defaultProps = {
|
const isDirect = () => type === 'd';
|
||||||
text: '',
|
|
||||||
type: 'd'
|
|
||||||
};
|
|
||||||
|
|
||||||
constructor(props: IAvatar) {
|
const server = useSelector((state: IApplicationState) => state.share.server.server || state.server.server);
|
||||||
super(props);
|
const serverVersion = useSelector((state: IApplicationState) => state.share.server.version || state.server.version);
|
||||||
this.state = { avatarETag: '' };
|
const { id, token } = useSelector(
|
||||||
this.init();
|
(state: IApplicationState) => ({
|
||||||
}
|
id: getUserSelector(state).id,
|
||||||
|
token: getUserSelector(state).token
|
||||||
|
}),
|
||||||
|
shallowEqual
|
||||||
|
);
|
||||||
|
|
||||||
componentDidUpdate(prevProps: IAvatar) {
|
const externalProviderUrl = useSelector(
|
||||||
const { text, type } = this.props;
|
(state: IApplicationState) => state.settings.Accounts_AvatarExternalProviderUrl as string
|
||||||
if (prevProps.text !== text || prevProps.type !== type) {
|
);
|
||||||
this.init();
|
const blockUnauthenticatedAccess = useSelector(
|
||||||
}
|
(state: IApplicationState) =>
|
||||||
}
|
(state.share.settings?.Accounts_AvatarBlockUnauthenticatedAccess as boolean) ??
|
||||||
|
state.settings.Accounts_AvatarBlockUnauthenticatedAccess ??
|
||||||
|
true
|
||||||
|
);
|
||||||
|
|
||||||
shouldComponentUpdate(nextProps: IAvatar, nextState: { avatarETag: string }) {
|
const init = async () => {
|
||||||
const { avatarETag } = this.state;
|
|
||||||
const { text, type, size, externalProviderUrl } = this.props;
|
|
||||||
if (nextProps.externalProviderUrl !== externalProviderUrl) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (nextState.avatarETag !== avatarETag) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (nextProps.text !== text) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (nextProps.type !== type) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (nextProps.size !== size) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
componentWillUnmount() {
|
|
||||||
if (this.subscription?.unsubscribe) {
|
|
||||||
this.subscription.unsubscribe();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
get isDirect() {
|
|
||||||
const { type } = this.props;
|
|
||||||
return type === 'd';
|
|
||||||
}
|
|
||||||
|
|
||||||
init = async () => {
|
|
||||||
const db = database.active;
|
const db = database.active;
|
||||||
const usersCollection = db.get('users');
|
const usersCollection = db.get('users');
|
||||||
const subsCollection = db.get('subscriptions');
|
const subsCollection = db.get('subscriptions');
|
||||||
|
|
||||||
let record;
|
let record;
|
||||||
try {
|
try {
|
||||||
if (this.isDirect) {
|
if (isDirect()) {
|
||||||
const { text } = this.props;
|
|
||||||
const [user] = await usersCollection.query(Q.where('username', text)).fetch();
|
const [user] = await usersCollection.query(Q.where('username', text)).fetch();
|
||||||
record = user;
|
record = user;
|
||||||
} else {
|
} else if (rid) {
|
||||||
const { rid } = this.props;
|
record = await subsCollection.find(rid);
|
||||||
if (rid) {
|
|
||||||
record = await subsCollection.find(rid);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} catch {
|
} catch {
|
||||||
// Record not found
|
// Record not found
|
||||||
|
@ -86,28 +67,46 @@ class AvatarContainer extends React.Component<IAvatar, any> {
|
||||||
|
|
||||||
if (record) {
|
if (record) {
|
||||||
const observable = record.observe() as Observable<TSubscriptionModel | TUserModel>;
|
const observable = record.observe() as Observable<TSubscriptionModel | TUserModel>;
|
||||||
this.subscription = observable.subscribe(r => {
|
subscription.current = observable.subscribe(r => {
|
||||||
const { avatarETag } = r;
|
setAvatarETag(r.avatarETag);
|
||||||
this.setState({ avatarETag });
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
render() {
|
useEffect(() => {
|
||||||
const { avatarETag } = this.state;
|
if (!avatarETag) {
|
||||||
const { serverVersion } = this.props;
|
init();
|
||||||
return <Avatar {...this.props} avatarETag={avatarETag} serverVersion={serverVersion} />;
|
}
|
||||||
}
|
return () => {
|
||||||
}
|
if (subscription?.current?.unsubscribe) {
|
||||||
|
subscription.current.unsubscribe();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}, [text, type, size, avatarETag, externalProviderUrl]);
|
||||||
|
|
||||||
const mapStateToProps = (state: IApplicationState) => ({
|
return (
|
||||||
user: getUserSelector(state),
|
<Avatar
|
||||||
server: state.share.server.server || state.server.server,
|
server={server}
|
||||||
serverVersion: state.share.server.version || state.server.version,
|
style={style}
|
||||||
blockUnauthenticatedAccess:
|
text={text}
|
||||||
(state.share.settings?.Accounts_AvatarBlockUnauthenticatedAccess as boolean) ??
|
avatar={avatar}
|
||||||
state.settings.Accounts_AvatarBlockUnauthenticatedAccess ??
|
emoji={emoji}
|
||||||
true,
|
size={size}
|
||||||
externalProviderUrl: state.settings.Accounts_AvatarExternalProviderUrl as string
|
borderRadius={borderRadius}
|
||||||
});
|
type={type}
|
||||||
export default connect(mapStateToProps)(AvatarContainer);
|
children={children}
|
||||||
|
userId={id}
|
||||||
|
token={token}
|
||||||
|
onPress={onPress}
|
||||||
|
getCustomEmoji={getCustomEmoji}
|
||||||
|
isStatic={isStatic}
|
||||||
|
rid={rid}
|
||||||
|
blockUnauthenticatedAccess={blockUnauthenticatedAccess}
|
||||||
|
externalProviderUrl={externalProviderUrl}
|
||||||
|
avatarETag={avatarETag}
|
||||||
|
serverVersion={serverVersion}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default AvatarContainer;
|
||||||
|
|
|
@ -5,23 +5,21 @@ import { TGetCustomEmoji } from '../../definitions/IEmoji';
|
||||||
export interface IAvatar {
|
export interface IAvatar {
|
||||||
server?: string;
|
server?: string;
|
||||||
style?: any;
|
style?: any;
|
||||||
text: string;
|
text?: string;
|
||||||
avatar?: string;
|
avatar?: string;
|
||||||
emoji?: string;
|
emoji?: string;
|
||||||
size?: number;
|
size?: number;
|
||||||
borderRadius?: number;
|
borderRadius?: number;
|
||||||
type?: string;
|
type?: string;
|
||||||
children?: React.ReactElement | null;
|
children?: React.ReactElement | null;
|
||||||
user?: {
|
userId?: string;
|
||||||
id?: string;
|
token?: string;
|
||||||
token?: string;
|
|
||||||
};
|
|
||||||
onPress?: () => void;
|
onPress?: () => void;
|
||||||
getCustomEmoji?: TGetCustomEmoji;
|
getCustomEmoji?: TGetCustomEmoji;
|
||||||
avatarETag?: string;
|
avatarETag?: string;
|
||||||
isStatic?: boolean | string;
|
isStatic?: boolean | string;
|
||||||
rid?: string;
|
rid?: string;
|
||||||
blockUnauthenticatedAccess?: boolean;
|
blockUnauthenticatedAccess?: boolean;
|
||||||
serverVersion: string | null;
|
serverVersion?: string | null;
|
||||||
externalProviderUrl?: string;
|
externalProviderUrl?: string;
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,7 +10,7 @@ export const IconSet = createIconSetFromIcoMoon(icoMoonConfig, 'custom', 'custom
|
||||||
|
|
||||||
export type TIconsName = keyof typeof mappedIcons;
|
export type TIconsName = keyof typeof mappedIcons;
|
||||||
|
|
||||||
interface ICustomIcon extends TextProps {
|
export interface ICustomIcon extends TextProps {
|
||||||
name: TIconsName;
|
name: TIconsName;
|
||||||
size: number;
|
size: number;
|
||||||
color: string;
|
color: string;
|
||||||
|
|
|
@ -1,6 +1,18 @@
|
||||||
export const mappedIcons = {
|
export const mappedIcons = {
|
||||||
attach: 59676,
|
'lamp-bulb': 59812,
|
||||||
link: 59752,
|
'basketball': 59776,
|
||||||
|
'percentage': 59777,
|
||||||
|
'burger': 59813,
|
||||||
|
'leaf': 59814,
|
||||||
|
'airplane': 59815,
|
||||||
|
'rocket': 59816,
|
||||||
|
'directory': 59648,
|
||||||
|
'directory-disabled': 59649,
|
||||||
|
'directory-error': 59650,
|
||||||
|
'federation-disabled': 59651,
|
||||||
|
'federation': 59652,
|
||||||
|
'attach': 59676,
|
||||||
|
'link': 59752,
|
||||||
'status-away': 59741,
|
'status-away': 59741,
|
||||||
'status-busy': 59742,
|
'status-busy': 59742,
|
||||||
'status-loading': 59743,
|
'status-loading': 59743,
|
||||||
|
@ -10,193 +22,191 @@ export const mappedIcons = {
|
||||||
'channel-auto-join': 59746,
|
'channel-auto-join': 59746,
|
||||||
'channel-move-to-team': 59747,
|
'channel-move-to-team': 59747,
|
||||||
'lock-filled': 59748,
|
'lock-filled': 59748,
|
||||||
locker: 59749,
|
'locker': 59749,
|
||||||
teams: 59751,
|
'teams': 59751,
|
||||||
shield: 59661,
|
'shield': 59661,
|
||||||
ignore: 59740,
|
'ignore': 59740,
|
||||||
'checkbox-unchecked': 59648,
|
'checkbox-unchecked': 59653,
|
||||||
'checkbox-checked': 59649,
|
'checkbox-checked': 59654,
|
||||||
'github-monochromatic': 59650,
|
'github-monochromatic': 59655,
|
||||||
'gitlab-monochromatic': 59651,
|
'gitlab-monochromatic': 59656,
|
||||||
'google-monochromatic': 59652,
|
'google-monochromatic': 59657,
|
||||||
'linkedin-monochromatic': 59653,
|
'linkedin-monochromatic': 59658,
|
||||||
'meteor-monochromatic': 59654,
|
'meteor-monochromatic': 59659,
|
||||||
'twitter-monochromatic': 59655,
|
'twitter-monochromatic': 59660,
|
||||||
administration: 59657,
|
'administration': 59662,
|
||||||
'adobe-reader-monochromatic': 59658,
|
'adobe-reader-monochromatic': 59663,
|
||||||
'all-contacts-in-channels': 59659,
|
'all-contacts-in-channels': 59664,
|
||||||
'all-contacts-in-queue': 59660,
|
'all-contacts-in-queue': 59665,
|
||||||
'apple-monochromatic': 59662,
|
'apple-monochromatic': 59666,
|
||||||
apps: 59663,
|
'apps': 59667,
|
||||||
'arrow-back': 59664,
|
'arrow-back': 59668,
|
||||||
'arrow-collapse': 59665,
|
'arrow-collapse': 59669,
|
||||||
'arrow-decrease': 59666,
|
'arrow-decrease': 59670,
|
||||||
'arrow-down-box': 59667,
|
'arrow-down-box': 59671,
|
||||||
'arrow-down-circle': 59668,
|
'arrow-down-circle': 59672,
|
||||||
'arrow-down': 59669,
|
'arrow-down': 59673,
|
||||||
'arrow-expand': 59670,
|
'arrow-expand': 59674,
|
||||||
'arrow-increase': 59671,
|
'arrow-increase': 59675,
|
||||||
'arrow-looping': 59672,
|
'arrow-looping': 59677,
|
||||||
'arrow-return': 59673,
|
'arrow-return': 59678,
|
||||||
'arrow-up-box': 59674,
|
'arrow-up-box': 59679,
|
||||||
'arrow-up': 59675,
|
'arrow-up': 59680,
|
||||||
'audio-disabled': 59677,
|
'audio-disabled': 59681,
|
||||||
'audio-unavailable': 59678,
|
'audio-unavailable': 59682,
|
||||||
audio: 59679,
|
'audio': 59683,
|
||||||
auditing: 59680,
|
'auditing': 59684,
|
||||||
auth: 59681,
|
'auth': 59685,
|
||||||
avatar: 59682,
|
'avatar': 59686,
|
||||||
backspace: 59683,
|
'backspace': 59687,
|
||||||
bold: 59684,
|
'bold': 59688,
|
||||||
book: 59685,
|
'book': 59689,
|
||||||
business: 59686,
|
'business': 59690,
|
||||||
calendar: 59687,
|
'calendar': 59691,
|
||||||
'camera-disabled': 59688,
|
'camera-disabled': 59692,
|
||||||
'camera-filled': 59689,
|
'camera-filled': 59693,
|
||||||
'camera-photo': 59690,
|
'camera-photo': 59694,
|
||||||
'camera-unavailable': 59691,
|
'camera-unavailable': 59695,
|
||||||
camera: 59692,
|
'camera': 59696,
|
||||||
'canned-response': 59693,
|
'canned-response': 59697,
|
||||||
card: 59694,
|
'card': 59698,
|
||||||
'channel-private': 59695,
|
'channel-private': 59699,
|
||||||
'channel-public': 59696,
|
'channel-public': 59700,
|
||||||
'chat-close': 59697,
|
'chat-close': 59701,
|
||||||
'chat-forward': 59698,
|
'chat-forward': 59702,
|
||||||
check: 59699,
|
'check': 59703,
|
||||||
'chevron-down': 59700,
|
'chevron-down': 59704,
|
||||||
'chevron-left-big': 59701,
|
'chevron-left-big': 59705,
|
||||||
'chevron-left': 59702,
|
'chevron-left': 59706,
|
||||||
'chevron-right': 59703,
|
'chevron-right': 59707,
|
||||||
'chevron-up': 59704,
|
'chevron-up': 59708,
|
||||||
'circle-check': 59705,
|
'circle-check': 59709,
|
||||||
clipboard: 59706,
|
'clipboard': 59710,
|
||||||
clock: 59707,
|
'clock': 59711,
|
||||||
close: 59708,
|
'close': 59712,
|
||||||
'cloud-connectivity': 59709,
|
'cloud-connectivity': 59713,
|
||||||
code: 59710,
|
'code': 59714,
|
||||||
contacts: 59711,
|
'contacts': 59715,
|
||||||
copy: 59712,
|
'copy': 59716,
|
||||||
create: 59713,
|
'create': 59717,
|
||||||
dashboard: 59714,
|
'dashboard': 59718,
|
||||||
delete: 59715,
|
'delete': 59719,
|
||||||
desktop: 59716,
|
'desktop': 59720,
|
||||||
dialpad: 59717,
|
'dialpad': 59721,
|
||||||
'directory-disabled': 59718,
|
'discussions': 59722,
|
||||||
directory: 59719,
|
'document': 59723,
|
||||||
discussions: 59720,
|
'donner': 59724,
|
||||||
document: 59721,
|
'download': 59725,
|
||||||
donner: 59722,
|
'edit': 59726,
|
||||||
download: 59723,
|
'emoji-bad-mood': 59727,
|
||||||
edit: 59724,
|
'emoji-neutral-mood': 59728,
|
||||||
'emoji-bad-mood': 59725,
|
'emoji': 59729,
|
||||||
'emoji-neutral-mood': 59726,
|
'encrypted': 59730,
|
||||||
emoji: 59727,
|
'engagement-dashboard': 59731,
|
||||||
encrypted: 59728,
|
'enterprise-feature': 59732,
|
||||||
'engagement-dashboard': 59729,
|
'facebook-monochromatic': 59733,
|
||||||
'enterprise-feature': 59730,
|
'file-document': 59734,
|
||||||
'facebook-monochromatic': 59731,
|
'file-sheet': 59735,
|
||||||
'file-document': 59732,
|
'filter': 59736,
|
||||||
'file-sheet': 59733,
|
'fingerprint': 59737,
|
||||||
filter: 59734,
|
'flag': 59738,
|
||||||
fingerprint: 59735,
|
'folder': 59739,
|
||||||
flag: 59736,
|
'game': 59753,
|
||||||
folder: 59737,
|
'giphy-monochromatic': 59754,
|
||||||
game: 59738,
|
|
||||||
'giphy-monochromatic': 59739,
|
|
||||||
'google-drive-monochromatic': 59756,
|
'google-drive-monochromatic': 59756,
|
||||||
'group-by-type': 59757,
|
'group-by-type': 59757,
|
||||||
hamburguer: 59758,
|
'hamburguer': 59758,
|
||||||
history: 59759,
|
'history': 59759,
|
||||||
home: 59760,
|
'home': 59760,
|
||||||
image: 59761,
|
'image': 59761,
|
||||||
info: 59762,
|
'info': 59762,
|
||||||
'input-clear': 59763,
|
'input-clear': 59763,
|
||||||
instance: 59764,
|
'instance': 59764,
|
||||||
italic: 59765,
|
'italic': 59765,
|
||||||
'jump-backward': 59766,
|
'jump-backward': 59766,
|
||||||
'jump-forward': 59767,
|
'jump-forward': 59767,
|
||||||
'jump-to-message': 59768,
|
'jump-to-message': 59768,
|
||||||
kebab: 59769,
|
'kebab': 59769,
|
||||||
keyboard: 59770,
|
'keyboard': 59770,
|
||||||
language: 59771,
|
'language': 59771,
|
||||||
'live-streaming': 59773,
|
'live-streaming': 59773,
|
||||||
live: 59774,
|
'live': 59774,
|
||||||
'livechat-monochromatic': 59775,
|
'livechat-monochromatic': 59775,
|
||||||
'log-view': 59778,
|
'log-view': 59778,
|
||||||
login: 59779,
|
'login': 59779,
|
||||||
logout: 59780,
|
'logout': 59780,
|
||||||
mail: 59781,
|
'mail': 59781,
|
||||||
marketplace: 59782,
|
'marketplace': 59782,
|
||||||
meatballs: 59783,
|
'meatballs': 59783,
|
||||||
mention: 59784,
|
'mention': 59784,
|
||||||
'message-disabled': 59785,
|
'message-disabled': 59785,
|
||||||
message: 59786,
|
'message': 59786,
|
||||||
'microphone-disabled': 59787,
|
'microphone-disabled': 59787,
|
||||||
microphone: 59788,
|
'microphone': 59788,
|
||||||
mobile: 59789,
|
'mobile': 59789,
|
||||||
moon: 59790,
|
'moon': 59790,
|
||||||
'move-to-the-queue': 59791,
|
'move-to-the-queue': 59791,
|
||||||
'musical-note': 59792,
|
'musical-note': 59792,
|
||||||
'new-window': 59793,
|
'new-window': 59793,
|
||||||
'notification-disabled': 59794,
|
'notification-disabled': 59794,
|
||||||
notification: 59795,
|
'notification': 59795,
|
||||||
omnichannel: 59796,
|
'omnichannel': 59796,
|
||||||
order: 59797,
|
'order': 59797,
|
||||||
'ordering-ascending': 59798,
|
'ordering-ascending': 59798,
|
||||||
'ordering-descending': 59800,
|
'ordering-descending': 59800,
|
||||||
'pause-filled': 59802,
|
'pause-filled': 59802,
|
||||||
pause: 59803,
|
'pause': 59803,
|
||||||
'phone-disabled': 59804,
|
'phone-disabled': 59804,
|
||||||
'phone-end': 59805,
|
'phone-end': 59805,
|
||||||
phone: 59806,
|
'phone': 59806,
|
||||||
'pin-map': 59807,
|
'pin-map': 59807,
|
||||||
pin: 59808,
|
'pin': 59808,
|
||||||
Pipe: 59809,
|
'Pipe': 59809,
|
||||||
'play-filled': 59810,
|
'play-filled': 59810,
|
||||||
play: 59811,
|
'play': 59811,
|
||||||
prune: 59817,
|
'prune': 59817,
|
||||||
queue: 59818,
|
'queue': 59818,
|
||||||
quote: 59819,
|
'quote': 59819,
|
||||||
'reaction-add': 59820,
|
'reaction-add': 59820,
|
||||||
record: 59821,
|
'record': 59821,
|
||||||
refresh: 59822,
|
'refresh': 59822,
|
||||||
search: 59823,
|
'search': 59823,
|
||||||
'send-filled': 59824,
|
'send-filled': 59824,
|
||||||
send: 59825,
|
'send': 59825,
|
||||||
settings: 59826,
|
'settings': 59826,
|
||||||
share: 59827,
|
'share': 59827,
|
||||||
'shield-check': 59828,
|
'shield-check': 59828,
|
||||||
'shield-alt': 59829,
|
'shield-alt': 59829,
|
||||||
signal: 59830,
|
'signal': 59830,
|
||||||
'sort-az': 59831,
|
'sort-az': 59831,
|
||||||
sort: 59832,
|
'sort': 59832,
|
||||||
'star-filled': 59833,
|
'star-filled': 59833,
|
||||||
star: 59834,
|
'star': 59834,
|
||||||
strike: 59846,
|
'strike': 59846,
|
||||||
sun: 59847,
|
'sun': 59847,
|
||||||
support: 59848,
|
'support': 59848,
|
||||||
team: 59849,
|
'team': 59849,
|
||||||
threads: 59850,
|
'threads': 59850,
|
||||||
total: 59851,
|
'total': 59851,
|
||||||
transcript: 59852,
|
'transcript': 59852,
|
||||||
underline: 59853,
|
'underline': 59853,
|
||||||
undo: 59854,
|
'undo': 59854,
|
||||||
Unlimited: 59855,
|
'Unlimited': 59855,
|
||||||
'unread-on-top-disabled': 59856,
|
'unread-on-top-disabled': 59856,
|
||||||
'unread-on-top': 59857,
|
'unread-on-top': 59857,
|
||||||
upload: 59858,
|
'upload': 59858,
|
||||||
'user-add': 59859,
|
'user-add': 59859,
|
||||||
'user-forward': 59860,
|
'user-forward': 59860,
|
||||||
user: 59861,
|
'user': 59861,
|
||||||
'view-condensed': 59862,
|
'view-condensed': 59862,
|
||||||
'view-extended': 59863,
|
'view-extended': 59863,
|
||||||
'view-medium': 59864,
|
'view-medium': 59864,
|
||||||
'waiting-on-me': 59865,
|
'waiting-on-me': 59865,
|
||||||
warning: 59866,
|
'warning': 59866,
|
||||||
'whatsapp-monochromatic': 59868,
|
'whatsapp-monochromatic': 59868,
|
||||||
'wordpress-monochromatic': 59656,
|
'wordpress-monochromatic': 59755,
|
||||||
workspaces: 59870,
|
'workspaces': 59870,
|
||||||
zip: 59871,
|
'zip': 59871,
|
||||||
add: 59872,
|
'add': 59872,
|
||||||
sms: 59753
|
'sms': 59772
|
||||||
};
|
};
|
||||||
|
|
File diff suppressed because one or more lines are too long
|
@ -1,7 +1,7 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Text, View, ViewStyle } from 'react-native';
|
import { Text, View, ViewStyle } from 'react-native';
|
||||||
|
|
||||||
import Touch from '../../utils/touch';
|
import Touch from '../../lib/methods/helpers/touch';
|
||||||
import Avatar from '../Avatar';
|
import Avatar from '../Avatar';
|
||||||
import RoomTypeIcon from '../RoomTypeIcon';
|
import RoomTypeIcon from '../RoomTypeIcon';
|
||||||
import styles, { ROW_HEIGHT } from './styles';
|
import styles, { ROW_HEIGHT } from './styles';
|
||||||
|
@ -54,7 +54,7 @@ const DirectoryItem = ({
|
||||||
<Avatar text={avatar} size={30} type={type} rid={rid} style={styles.directoryItemAvatar} />
|
<Avatar text={avatar} size={30} type={type} rid={rid} style={styles.directoryItemAvatar} />
|
||||||
<View style={styles.directoryItemTextContainer}>
|
<View style={styles.directoryItemTextContainer}>
|
||||||
<View style={styles.directoryItemTextTitle}>
|
<View style={styles.directoryItemTextTitle}>
|
||||||
<RoomTypeIcon type={type} teamMain={teamMain} />
|
{type !== 'd' ? <RoomTypeIcon type={type} teamMain={teamMain} /> : null}
|
||||||
<Text style={[styles.directoryItemName, { color: themes[theme].titleText }]} numberOfLines={1}>
|
<Text style={[styles.directoryItemName, { color: themes[theme].titleText }]} numberOfLines={1}>
|
||||||
{title}
|
{title}
|
||||||
</Text>
|
</Text>
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import FastImage from '@rocket.chat/react-native-fast-image';
|
import FastImage from 'react-native-fast-image';
|
||||||
|
|
||||||
import { ICustomEmoji } from '../../definitions/IEmoji';
|
import { ICustomEmoji } from '../../definitions/IEmoji';
|
||||||
|
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { FlatList, Text, TouchableOpacity } from 'react-native';
|
import { FlatList, Text, TouchableOpacity } from 'react-native';
|
||||||
|
|
||||||
import shortnameToUnicode from '../../utils/shortnameToUnicode';
|
import shortnameToUnicode from '../../lib/methods/helpers/shortnameToUnicode';
|
||||||
import styles from './styles';
|
import styles from './styles';
|
||||||
import CustomEmoji from './CustomEmoji';
|
import CustomEmoji from './CustomEmoji';
|
||||||
import scrollPersistTaps from '../../utils/scrollPersistTaps';
|
import scrollPersistTaps from '../../lib/methods/helpers/scrollPersistTaps';
|
||||||
import { IEmoji, IEmojiCategory } from '../../definitions/IEmoji';
|
import { IEmoji, IEmojiCategory } from '../../definitions/IEmoji';
|
||||||
|
|
||||||
const EMOJI_SIZE = 50;
|
const EMOJI_SIZE = 50;
|
||||||
|
|
|
@ -5,7 +5,7 @@ import { dequal } from 'dequal';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import orderBy from 'lodash/orderBy';
|
import orderBy from 'lodash/orderBy';
|
||||||
import { sanitizedRaw } from '@nozbe/watermelondb/RawRecord';
|
import { sanitizedRaw } from '@nozbe/watermelondb/RawRecord';
|
||||||
import { ImageStyle } from '@rocket.chat/react-native-fast-image';
|
import { ImageStyle } from 'react-native-fast-image';
|
||||||
|
|
||||||
import TabBar from './TabBar';
|
import TabBar from './TabBar';
|
||||||
import EmojiCategory from './EmojiCategory';
|
import EmojiCategory from './EmojiCategory';
|
||||||
|
@ -14,8 +14,8 @@ import categories from './categories';
|
||||||
import database from '../../lib/database';
|
import database from '../../lib/database';
|
||||||
import { emojisByCategory } from './emojis';
|
import { emojisByCategory } from './emojis';
|
||||||
import protectedFunction from '../../lib/methods/helpers/protectedFunction';
|
import protectedFunction from '../../lib/methods/helpers/protectedFunction';
|
||||||
import shortnameToUnicode from '../../utils/shortnameToUnicode';
|
import shortnameToUnicode from '../../lib/methods/helpers/shortnameToUnicode';
|
||||||
import log from '../../utils/log';
|
import log from '../../lib/methods/helpers/log';
|
||||||
import { themes } from '../../lib/constants';
|
import { themes } from '../../lib/constants';
|
||||||
import { TSupportedThemes, withTheme } from '../../theme';
|
import { TSupportedThemes, withTheme } from '../../theme';
|
||||||
import { IEmoji, TGetCustomEmoji, IApplicationState, ICustomEmojis, TFrequentlyUsedEmojiModel } from '../../definitions';
|
import { IEmoji, TGetCustomEmoji, IApplicationState, ICustomEmojis, TFrequentlyUsedEmojiModel } from '../../definitions';
|
||||||
|
|
|
@ -3,12 +3,12 @@ import { ScrollView, ScrollViewProps, StyleSheet, View } from 'react-native';
|
||||||
|
|
||||||
import { themes } from '../lib/constants';
|
import { themes } from '../lib/constants';
|
||||||
import sharedStyles from '../views/Styles';
|
import sharedStyles from '../views/Styles';
|
||||||
import scrollPersistTaps from '../utils/scrollPersistTaps';
|
import scrollPersistTaps from '../lib/methods/helpers/scrollPersistTaps';
|
||||||
import KeyboardView from './KeyboardView';
|
import KeyboardView from './KeyboardView';
|
||||||
import { useTheme } from '../theme';
|
import { useTheme } from '../theme';
|
||||||
import StatusBar from './StatusBar';
|
import StatusBar from './StatusBar';
|
||||||
import AppVersion from './AppVersion';
|
import AppVersion from './AppVersion';
|
||||||
import { isTablet } from '../utils/deviceInfo';
|
import { isTablet } from '../lib/methods/helpers';
|
||||||
import SafeAreaView from './SafeAreaView';
|
import SafeAreaView from './SafeAreaView';
|
||||||
|
|
||||||
interface IFormContainer extends ScrollViewProps {
|
interface IFormContainer extends ScrollViewProps {
|
||||||
|
|
|
@ -1,69 +0,0 @@
|
||||||
import React from 'react';
|
|
||||||
import { SafeAreaView } from 'react-native-safe-area-context';
|
|
||||||
import { StyleSheet, View } from 'react-native';
|
|
||||||
|
|
||||||
import { themes } from '../../lib/constants';
|
|
||||||
import { themedHeader } from '../../utils/navigation';
|
|
||||||
import { isIOS, isTablet } from '../../utils/deviceInfo';
|
|
||||||
import { useTheme } from '../../theme';
|
|
||||||
|
|
||||||
export const headerHeight = isIOS ? 44 : 56;
|
|
||||||
|
|
||||||
export const getHeaderHeight = (isLandscape: boolean): number => {
|
|
||||||
if (isIOS) {
|
|
||||||
if (isLandscape && !isTablet) {
|
|
||||||
return 32;
|
|
||||||
}
|
|
||||||
return 44;
|
|
||||||
}
|
|
||||||
return 56;
|
|
||||||
};
|
|
||||||
|
|
||||||
interface IHeaderTitlePosition {
|
|
||||||
insets: {
|
|
||||||
left: number;
|
|
||||||
right: number;
|
|
||||||
};
|
|
||||||
numIconsRight: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const getHeaderTitlePosition = ({
|
|
||||||
insets,
|
|
||||||
numIconsRight
|
|
||||||
}: IHeaderTitlePosition): {
|
|
||||||
left: number;
|
|
||||||
right: number;
|
|
||||||
} => ({
|
|
||||||
left: insets.left + 60,
|
|
||||||
right: insets.right + Math.max(45 * numIconsRight, 15)
|
|
||||||
});
|
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
|
||||||
container: {
|
|
||||||
height: headerHeight,
|
|
||||||
flexDirection: 'row',
|
|
||||||
justifyContent: 'center',
|
|
||||||
elevation: 4
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
interface IHeader {
|
|
||||||
headerLeft: () => React.ReactElement | null;
|
|
||||||
headerTitle: () => React.ReactElement;
|
|
||||||
headerRight: () => React.ReactElement | null;
|
|
||||||
}
|
|
||||||
|
|
||||||
const Header = ({ headerLeft, headerTitle, headerRight }: IHeader): React.ReactElement => {
|
|
||||||
const { theme } = useTheme();
|
|
||||||
return (
|
|
||||||
<SafeAreaView style={{ backgroundColor: themes[theme].headerBackground }} edges={['top', 'left', 'right']}>
|
|
||||||
<View style={[styles.container, { ...themedHeader(theme).headerStyle }]}>
|
|
||||||
{headerLeft ? headerLeft() : null}
|
|
||||||
{headerTitle ? headerTitle() : null}
|
|
||||||
{headerRight ? headerRight() : null}
|
|
||||||
</View>
|
|
||||||
</SafeAreaView>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default Header;
|
|
|
@ -1,14 +1,12 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
import { isIOS } from '../../utils/deviceInfo';
|
import { isIOS } from '../../lib/methods/helpers';
|
||||||
import I18n from '../../i18n';
|
import I18n from '../../i18n';
|
||||||
import Container from './HeaderButtonContainer';
|
import Container from './HeaderButtonContainer';
|
||||||
import Item from './HeaderButtonItem';
|
import Item, { IHeaderButtonItem } from './HeaderButtonItem';
|
||||||
|
|
||||||
interface IHeaderButtonCommon {
|
interface IHeaderButtonCommon extends IHeaderButtonItem {
|
||||||
navigation?: any; // TODO: Evaluate proper type
|
navigation?: any; // TODO: Evaluate proper type
|
||||||
onPress?: () => void;
|
|
||||||
testID?: string;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Left
|
// Left
|
||||||
|
@ -28,20 +26,20 @@ export const CloseModal = React.memo(
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
export const CancelModal = React.memo(({ onPress, testID }: Partial<IHeaderButtonCommon>) => (
|
export const CancelModal = React.memo(({ onPress, testID, ...props }: IHeaderButtonCommon) => (
|
||||||
<Container left>
|
<Container left>
|
||||||
{isIOS ? (
|
{isIOS ? (
|
||||||
<Item title={I18n.t('Cancel')} onPress={onPress} testID={testID} />
|
<Item title={I18n.t('Cancel')} onPress={onPress} testID={testID} {...props} />
|
||||||
) : (
|
) : (
|
||||||
<Item iconName='close' onPress={onPress} testID={testID} />
|
<Item iconName='close' onPress={onPress} testID={testID} {...props} />
|
||||||
)}
|
)}
|
||||||
</Container>
|
</Container>
|
||||||
));
|
));
|
||||||
|
|
||||||
// Right
|
// Right
|
||||||
export const More = React.memo(({ onPress, testID }: Partial<IHeaderButtonCommon>) => (
|
export const More = React.memo(({ onPress, testID, ...props }: IHeaderButtonCommon) => (
|
||||||
<Container>
|
<Container>
|
||||||
<Item iconName='kebab' onPress={onPress} testID={testID} />
|
<Item iconName='kebab' onPress={onPress} testID={testID} {...props} />
|
||||||
</Container>
|
</Container>
|
||||||
));
|
));
|
||||||
|
|
||||||
|
@ -58,7 +56,7 @@ export const Preferences = React.memo(({ onPress, testID, ...props }: IHeaderBut
|
||||||
));
|
));
|
||||||
|
|
||||||
export const Legal = React.memo(
|
export const Legal = React.memo(
|
||||||
({ navigation, testID, onPress = () => navigation?.navigate('LegalView') }: IHeaderButtonCommon) => (
|
({ navigation, testID, onPress = () => navigation?.navigate('LegalView'), ...props }: IHeaderButtonCommon) => (
|
||||||
<More onPress={onPress} testID={testID} />
|
<More onPress={onPress} testID={testID} {...props} />
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
|
@ -1,18 +1,18 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Platform, StyleSheet, Text } from 'react-native';
|
import { Platform, StyleSheet, Text } from 'react-native';
|
||||||
import Touchable from 'react-native-platform-touchable';
|
import { PlatformPressable } from '@react-navigation/elements';
|
||||||
|
|
||||||
import { CustomIcon, TIconsName } from '../CustomIcon';
|
import { CustomIcon, ICustomIcon, TIconsName } from '../CustomIcon';
|
||||||
import { useTheme } from '../../theme';
|
import { useTheme } from '../../theme';
|
||||||
import { themes } from '../../lib/constants';
|
|
||||||
import sharedStyles from '../../views/Styles';
|
import sharedStyles from '../../views/Styles';
|
||||||
|
|
||||||
interface IHeaderButtonItem {
|
export interface IHeaderButtonItem extends Omit<ICustomIcon, 'name' | 'size' | 'color'> {
|
||||||
title?: string;
|
title?: string;
|
||||||
iconName?: TIconsName;
|
iconName?: TIconsName;
|
||||||
onPress?: <T>(arg: T) => void;
|
onPress?: <T>(arg: T) => void;
|
||||||
testID?: string;
|
testID?: string;
|
||||||
badge?(): void;
|
badge?(): void;
|
||||||
|
color?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const BUTTON_HIT_SLOP = {
|
export const BUTTON_HIT_SLOP = {
|
||||||
|
@ -24,7 +24,7 @@ export const BUTTON_HIT_SLOP = {
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
container: {
|
container: {
|
||||||
marginHorizontal: 6
|
padding: 6
|
||||||
},
|
},
|
||||||
title: {
|
title: {
|
||||||
...Platform.select({
|
...Platform.select({
|
||||||
|
@ -39,19 +39,21 @@ const styles = StyleSheet.create({
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const Item = ({ title, iconName, onPress, testID, badge }: IHeaderButtonItem): React.ReactElement => {
|
const Item = ({ title, iconName, onPress, testID, badge, color, ...props }: IHeaderButtonItem): React.ReactElement => {
|
||||||
const { theme } = useTheme();
|
const { colors } = useTheme();
|
||||||
return (
|
return (
|
||||||
<Touchable onPress={onPress} testID={testID} hitSlop={BUTTON_HIT_SLOP} style={styles.container}>
|
<PlatformPressable onPress={onPress} testID={testID} hitSlop={BUTTON_HIT_SLOP} style={styles.container}>
|
||||||
<>
|
<>
|
||||||
{iconName ? (
|
{iconName ? (
|
||||||
<CustomIcon name={iconName} size={24} color={themes[theme].headerTintColor} />
|
<CustomIcon name={iconName} size={24} color={color || colors.headerTintColor} {...props} />
|
||||||
) : (
|
) : (
|
||||||
<Text style={[styles.title, { color: themes[theme].headerTintColor }]}>{title}</Text>
|
<Text style={[styles.title, { color: color || colors.headerTintColor }]} {...props}>
|
||||||
|
{title}
|
||||||
|
</Text>
|
||||||
)}
|
)}
|
||||||
{badge ? badge() : null}
|
{badge ? badge() : null}
|
||||||
</>
|
</>
|
||||||
</Touchable>
|
</PlatformPressable>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -7,8 +7,8 @@ const styles = StyleSheet.create({
|
||||||
badgeContainer: {
|
badgeContainer: {
|
||||||
padding: 2,
|
padding: 2,
|
||||||
position: 'absolute',
|
position: 'absolute',
|
||||||
right: -3,
|
right: 2,
|
||||||
top: -3,
|
top: 2,
|
||||||
borderRadius: 10,
|
borderRadius: 10,
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
justifyContent: 'center'
|
justifyContent: 'center'
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Image } from 'react-native';
|
import { Image } from 'react-native';
|
||||||
import { FastImageProps } from '@rocket.chat/react-native-fast-image';
|
import { FastImageProps } from 'react-native-fast-image';
|
||||||
|
|
||||||
import { types } from './types';
|
import { types } from './types';
|
||||||
|
|
||||||
|
@ -10,7 +10,7 @@ export const ImageComponent = (type?: string): React.ComponentType<Partial<Image
|
||||||
const { Image } = require('react-native');
|
const { Image } = require('react-native');
|
||||||
Component = Image;
|
Component = Image;
|
||||||
} else {
|
} else {
|
||||||
const FastImage = require('@rocket.chat/react-native-fast-image').default;
|
const FastImage = require('react-native-fast-image');
|
||||||
Component = FastImage;
|
Component = FastImage;
|
||||||
}
|
}
|
||||||
return Component;
|
return Component;
|
|
@ -0,0 +1,125 @@
|
||||||
|
import React, { useState } from 'react';
|
||||||
|
import { LayoutChangeEvent, StyleSheet, StyleProp, ViewStyle, ImageStyle, View } from 'react-native';
|
||||||
|
import { Gesture, GestureDetector } from 'react-native-gesture-handler';
|
||||||
|
import Animated, { withTiming, useSharedValue, useAnimatedStyle, withSpring } from 'react-native-reanimated';
|
||||||
|
|
||||||
|
import { useTheme } from '../../theme';
|
||||||
|
import { ImageComponent } from './ImageComponent';
|
||||||
|
|
||||||
|
interface ImageViewerProps {
|
||||||
|
style?: StyleProp<ImageStyle>;
|
||||||
|
containerStyle?: StyleProp<ViewStyle>;
|
||||||
|
imageContainerStyle?: StyleProp<ViewStyle>;
|
||||||
|
|
||||||
|
uri: string;
|
||||||
|
imageComponentType?: string;
|
||||||
|
width: number;
|
||||||
|
height: number;
|
||||||
|
onLoadEnd?: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
flex: {
|
||||||
|
flex: 1
|
||||||
|
},
|
||||||
|
image: {
|
||||||
|
flex: 1
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
export const ImageViewer = ({ uri = '', imageComponentType, width, height, ...props }: ImageViewerProps): React.ReactElement => {
|
||||||
|
const [centerX, setCenterX] = useState(0);
|
||||||
|
const [centerY, setCenterY] = useState(0);
|
||||||
|
|
||||||
|
const onLayout = ({
|
||||||
|
nativeEvent: {
|
||||||
|
layout: { x, y, width, height }
|
||||||
|
}
|
||||||
|
}: LayoutChangeEvent) => {
|
||||||
|
setCenterX(x + width / 2);
|
||||||
|
setCenterY(y + height / 2);
|
||||||
|
};
|
||||||
|
|
||||||
|
const translationX = useSharedValue<number>(0);
|
||||||
|
const translationY = useSharedValue<number>(0);
|
||||||
|
const offsetX = useSharedValue<number>(0);
|
||||||
|
const offsetY = useSharedValue<number>(0);
|
||||||
|
const scale = useSharedValue<number>(1);
|
||||||
|
const scaleOffset = useSharedValue<number>(1);
|
||||||
|
|
||||||
|
const style = useAnimatedStyle(() => ({
|
||||||
|
transform: [{ translateX: translationX.value }, { translateY: translationY.value }, { scale: scale.value }]
|
||||||
|
}));
|
||||||
|
|
||||||
|
const resetScaleAnimation = () => {
|
||||||
|
scaleOffset.value = 1;
|
||||||
|
offsetX.value = 0;
|
||||||
|
offsetY.value = 0;
|
||||||
|
scale.value = withSpring(1);
|
||||||
|
translationX.value = withSpring(0, { overshootClamping: true });
|
||||||
|
translationY.value = withSpring(0, { overshootClamping: true });
|
||||||
|
};
|
||||||
|
|
||||||
|
const clamp = (value: number, min: number, max: number) => Math.max(Math.min(value, max), min);
|
||||||
|
|
||||||
|
const pinchGesture = Gesture.Pinch()
|
||||||
|
.onUpdate(event => {
|
||||||
|
scale.value = clamp(scaleOffset.value * (event.scale > 0 ? event.scale : 1), 1, 4);
|
||||||
|
})
|
||||||
|
.onEnd(() => {
|
||||||
|
scaleOffset.value = scale.value > 0 ? scale.value : 1;
|
||||||
|
});
|
||||||
|
|
||||||
|
const panGesture = Gesture.Pan()
|
||||||
|
.maxPointers(2)
|
||||||
|
.onStart(() => {
|
||||||
|
translationX.value = offsetX.value;
|
||||||
|
translationY.value = offsetY.value;
|
||||||
|
})
|
||||||
|
.onUpdate(event => {
|
||||||
|
const scaleFactor = scale.value - 1;
|
||||||
|
translationX.value = clamp(event.translationX + offsetX.value, -scaleFactor * centerX, scaleFactor * centerX);
|
||||||
|
translationY.value = clamp(event.translationY + offsetY.value, -scaleFactor * centerY, scaleFactor * centerY);
|
||||||
|
})
|
||||||
|
.onEnd(() => {
|
||||||
|
offsetX.value = translationX.value;
|
||||||
|
offsetY.value = translationY.value;
|
||||||
|
if (scale.value === 1) resetScaleAnimation();
|
||||||
|
});
|
||||||
|
|
||||||
|
const doubleTapGesture = Gesture.Tap()
|
||||||
|
.numberOfTaps(2)
|
||||||
|
.maxDelay(120)
|
||||||
|
.maxDistance(70)
|
||||||
|
.onEnd(event => {
|
||||||
|
if (scaleOffset.value > 1) resetScaleAnimation();
|
||||||
|
else {
|
||||||
|
scale.value = withTiming(2, { duration: 200 });
|
||||||
|
translationX.value = withTiming(centerX - event.x, { duration: 200 });
|
||||||
|
offsetX.value = centerX - event.x;
|
||||||
|
scaleOffset.value = 2;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const gesture = Gesture.Simultaneous(pinchGesture, panGesture, doubleTapGesture);
|
||||||
|
|
||||||
|
const Component = ImageComponent(imageComponentType);
|
||||||
|
|
||||||
|
const { colors } = useTheme();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<View style={[styles.flex, { width, height, backgroundColor: colors.previewBackground }]}>
|
||||||
|
<GestureDetector gesture={gesture}>
|
||||||
|
<Animated.View onLayout={onLayout} style={[styles.flex, style]}>
|
||||||
|
<Component
|
||||||
|
// @ts-ignore
|
||||||
|
style={styles.image}
|
||||||
|
resizeMode='contain'
|
||||||
|
source={{ uri }}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
</Animated.View>
|
||||||
|
</GestureDetector>
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
};
|
|
@ -11,7 +11,7 @@ import sharedStyles from '../../views/Styles';
|
||||||
import { themes } from '../../lib/constants';
|
import { themes } from '../../lib/constants';
|
||||||
import { useTheme } from '../../theme';
|
import { useTheme } from '../../theme';
|
||||||
import { ROW_HEIGHT } from '../RoomItem';
|
import { ROW_HEIGHT } from '../RoomItem';
|
||||||
import { goRoom } from '../../utils/goRoom';
|
import { goRoom } from '../../lib/methods/helpers/goRoom';
|
||||||
import Navigation from '../../lib/navigation/appNavigation';
|
import Navigation from '../../lib/navigation/appNavigation';
|
||||||
import { useOrientation } from '../../dimensions';
|
import { useOrientation } from '../../dimensions';
|
||||||
import { IApplicationState, ISubscription, SubscriptionType } from '../../definitions';
|
import { IApplicationState, ISubscription, SubscriptionType } from '../../definitions';
|
||||||
|
|
|
@ -4,9 +4,9 @@ import { connect } from 'react-redux';
|
||||||
import { dequal } from 'dequal';
|
import { dequal } from 'dequal';
|
||||||
|
|
||||||
import NotifierComponent, { INotifierComponent } from './NotifierComponent';
|
import NotifierComponent, { INotifierComponent } from './NotifierComponent';
|
||||||
import EventEmitter from '../../utils/events';
|
import EventEmitter from '../../lib/methods/helpers/events';
|
||||||
import Navigation from '../../lib/navigation/appNavigation';
|
import Navigation from '../../lib/navigation/appNavigation';
|
||||||
import { getActiveRoute } from '../../utils/navigation';
|
import { getActiveRoute } from '../../lib/methods/helpers/navigation';
|
||||||
import { IApplicationState } from '../../definitions';
|
import { IApplicationState } from '../../definitions';
|
||||||
import { IRoom } from '../../reducers/room';
|
import { IRoom } from '../../reducers/room';
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { KeyboardAwareScrollView, KeyboardAwareScrollViewProps } from '@codler/react-native-keyboard-aware-scroll-view';
|
import { KeyboardAwareScrollView, KeyboardAwareScrollViewProps } from '@codler/react-native-keyboard-aware-scroll-view';
|
||||||
|
|
||||||
import scrollPersistTaps from '../utils/scrollPersistTaps';
|
import scrollPersistTaps from '../lib/methods/helpers/scrollPersistTaps';
|
||||||
|
|
||||||
interface IKeyboardViewProps extends KeyboardAwareScrollViewProps {
|
interface IKeyboardViewProps extends KeyboardAwareScrollViewProps {
|
||||||
keyboardVerticalOffset?: number;
|
keyboardVerticalOffset?: number;
|
||||||
|
|
|
@ -2,7 +2,7 @@ import React from 'react';
|
||||||
import { ScrollView, StyleSheet } from 'react-native';
|
import { ScrollView, StyleSheet } from 'react-native';
|
||||||
|
|
||||||
import { withTheme } from '../../theme';
|
import { withTheme } from '../../theme';
|
||||||
import scrollPersistTaps from '../../utils/scrollPersistTaps';
|
import scrollPersistTaps from '../../lib/methods/helpers/scrollPersistTaps';
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
container: {
|
container: {
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { I18nManager, StyleProp, StyleSheet, Text, TextStyle, View } from 'react-native';
|
import { I18nManager, StyleProp, StyleSheet, Text, TextStyle, View } from 'react-native';
|
||||||
|
|
||||||
import Touch from '../../utils/touch';
|
import Touch from '../../lib/methods/helpers/touch';
|
||||||
import { themes } from '../../lib/constants';
|
import { themes } from '../../lib/constants';
|
||||||
import sharedStyles from '../../views/Styles';
|
import sharedStyles from '../../views/Styles';
|
||||||
import { TSupportedThemes, useTheme } from '../../theme';
|
import { TSupportedThemes, useTheme } from '../../theme';
|
||||||
|
|
|
@ -1,444 +0,0 @@
|
||||||
import React from 'react';
|
|
||||||
import { Animated, Easing, Linking, StyleSheet, Text, View, ViewStyle } from 'react-native';
|
|
||||||
import { connect } from 'react-redux';
|
|
||||||
import { Base64 } from 'js-base64';
|
|
||||||
import * as AppleAuthentication from 'expo-apple-authentication';
|
|
||||||
import { StackNavigationProp } from '@react-navigation/stack';
|
|
||||||
|
|
||||||
import { TSupportedThemes, withTheme } from '../theme';
|
|
||||||
import sharedStyles from '../views/Styles';
|
|
||||||
import { themes } from '../lib/constants';
|
|
||||||
import Button from './Button';
|
|
||||||
import OrSeparator from './OrSeparator';
|
|
||||||
import Touch from '../utils/touch';
|
|
||||||
import I18n from '../i18n';
|
|
||||||
import random from '../utils/random';
|
|
||||||
import { events, logEvent } from '../utils/log';
|
|
||||||
import { CustomIcon, TIconsName } from './CustomIcon';
|
|
||||||
import { IServices } from '../selectors/login';
|
|
||||||
import { OutsideParamList } from '../stacks/types';
|
|
||||||
import { IApplicationState } from '../definitions';
|
|
||||||
import { Services } from '../lib/services';
|
|
||||||
|
|
||||||
const BUTTON_HEIGHT = 48;
|
|
||||||
const SERVICE_HEIGHT = 58;
|
|
||||||
const BORDER_RADIUS = 2;
|
|
||||||
const SERVICES_COLLAPSED_HEIGHT = 174;
|
|
||||||
|
|
||||||
const LOGIN_STYPE_POPUP = 'popup';
|
|
||||||
const LOGIN_STYPE_REDIRECT = 'redirect';
|
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
|
||||||
serviceButton: {
|
|
||||||
borderRadius: BORDER_RADIUS,
|
|
||||||
marginBottom: 10
|
|
||||||
},
|
|
||||||
serviceButtonContainer: {
|
|
||||||
borderRadius: BORDER_RADIUS,
|
|
||||||
width: '100%',
|
|
||||||
height: BUTTON_HEIGHT,
|
|
||||||
flexDirection: 'row',
|
|
||||||
alignItems: 'center',
|
|
||||||
justifyContent: 'center',
|
|
||||||
paddingHorizontal: 15
|
|
||||||
},
|
|
||||||
serviceIcon: {
|
|
||||||
position: 'absolute',
|
|
||||||
left: 15,
|
|
||||||
top: 12,
|
|
||||||
width: 24,
|
|
||||||
height: 24
|
|
||||||
},
|
|
||||||
serviceText: {
|
|
||||||
...sharedStyles.textRegular,
|
|
||||||
fontSize: 16
|
|
||||||
},
|
|
||||||
serviceName: {
|
|
||||||
...sharedStyles.textSemibold
|
|
||||||
},
|
|
||||||
options: {
|
|
||||||
marginBottom: 0
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
interface IOpenOAuth {
|
|
||||||
url: string;
|
|
||||||
ssoToken?: string;
|
|
||||||
authType?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface IItemService {
|
|
||||||
name: string;
|
|
||||||
service: string;
|
|
||||||
authType: string;
|
|
||||||
buttonColor: string;
|
|
||||||
buttonLabelColor: string;
|
|
||||||
clientConfig: { provider: string };
|
|
||||||
serverURL: string;
|
|
||||||
authorizePath: string;
|
|
||||||
clientId: string;
|
|
||||||
scope: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface IOauthProvider {
|
|
||||||
[key: string]: () => void;
|
|
||||||
facebook: () => void;
|
|
||||||
github: () => void;
|
|
||||||
gitlab: () => void;
|
|
||||||
google: () => void;
|
|
||||||
linkedin: () => void;
|
|
||||||
'meteor-developer': () => void;
|
|
||||||
twitter: () => void;
|
|
||||||
wordpress: () => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface ILoginServicesProps {
|
|
||||||
navigation: StackNavigationProp<OutsideParamList>;
|
|
||||||
server: string;
|
|
||||||
services: IServices;
|
|
||||||
Gitlab_URL: string;
|
|
||||||
CAS_enabled: boolean;
|
|
||||||
CAS_login_url: string;
|
|
||||||
separator: boolean;
|
|
||||||
theme: TSupportedThemes;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface ILoginServicesState {
|
|
||||||
collapsed: boolean;
|
|
||||||
servicesHeight: Animated.Value;
|
|
||||||
}
|
|
||||||
|
|
||||||
class LoginServices extends React.PureComponent<ILoginServicesProps, ILoginServicesState> {
|
|
||||||
private _animation?: Animated.CompositeAnimation | void;
|
|
||||||
|
|
||||||
state = {
|
|
||||||
collapsed: true,
|
|
||||||
servicesHeight: new Animated.Value(SERVICES_COLLAPSED_HEIGHT)
|
|
||||||
};
|
|
||||||
|
|
||||||
onPressFacebook = () => {
|
|
||||||
logEvent(events.ENTER_WITH_FACEBOOK);
|
|
||||||
const { services, server } = this.props;
|
|
||||||
const { clientId } = services.facebook;
|
|
||||||
const endpoint = 'https://m.facebook.com/v2.9/dialog/oauth';
|
|
||||||
const redirect_uri = `${server}/_oauth/facebook?close`;
|
|
||||||
const scope = 'email';
|
|
||||||
const state = this.getOAuthState();
|
|
||||||
const params = `?client_id=${clientId}&redirect_uri=${redirect_uri}&scope=${scope}&state=${state}&display=touch`;
|
|
||||||
this.openOAuth({ url: `${endpoint}${params}` });
|
|
||||||
};
|
|
||||||
|
|
||||||
onPressGithub = () => {
|
|
||||||
logEvent(events.ENTER_WITH_GITHUB);
|
|
||||||
const { services, server } = this.props;
|
|
||||||
const { clientId } = services.github;
|
|
||||||
const endpoint = `https://github.com/login?client_id=${clientId}&return_to=${encodeURIComponent('/login/oauth/authorize')}`;
|
|
||||||
const redirect_uri = `${server}/_oauth/github?close`;
|
|
||||||
const scope = 'user:email';
|
|
||||||
const state = this.getOAuthState();
|
|
||||||
const params = `?client_id=${clientId}&redirect_uri=${redirect_uri}&scope=${scope}&state=${state}`;
|
|
||||||
this.openOAuth({ url: `${endpoint}${encodeURIComponent(params)}` });
|
|
||||||
};
|
|
||||||
|
|
||||||
onPressGitlab = () => {
|
|
||||||
logEvent(events.ENTER_WITH_GITLAB);
|
|
||||||
const { services, server, Gitlab_URL } = this.props;
|
|
||||||
const { clientId } = services.gitlab;
|
|
||||||
const baseURL = Gitlab_URL ? Gitlab_URL.trim().replace(/\/*$/, '') : 'https://gitlab.com';
|
|
||||||
const endpoint = `${baseURL}/oauth/authorize`;
|
|
||||||
const redirect_uri = `${server}/_oauth/gitlab?close`;
|
|
||||||
const scope = 'read_user';
|
|
||||||
const state = this.getOAuthState();
|
|
||||||
const params = `?client_id=${clientId}&redirect_uri=${redirect_uri}&scope=${scope}&state=${state}&response_type=code`;
|
|
||||||
this.openOAuth({ url: `${endpoint}${params}` });
|
|
||||||
};
|
|
||||||
|
|
||||||
onPressGoogle = () => {
|
|
||||||
logEvent(events.ENTER_WITH_GOOGLE);
|
|
||||||
const { services, server } = this.props;
|
|
||||||
const { clientId } = services.google;
|
|
||||||
const endpoint = 'https://accounts.google.com/o/oauth2/auth';
|
|
||||||
const redirect_uri = `${server}/_oauth/google?close`;
|
|
||||||
const scope = 'email';
|
|
||||||
const state = this.getOAuthState(LOGIN_STYPE_REDIRECT);
|
|
||||||
const params = `?client_id=${clientId}&redirect_uri=${redirect_uri}&scope=${scope}&state=${state}&response_type=code`;
|
|
||||||
Linking.openURL(`${endpoint}${params}`);
|
|
||||||
};
|
|
||||||
|
|
||||||
onPressLinkedin = () => {
|
|
||||||
logEvent(events.ENTER_WITH_LINKEDIN);
|
|
||||||
const { services, server } = this.props;
|
|
||||||
const { clientId } = services.linkedin;
|
|
||||||
const endpoint = 'https://www.linkedin.com/oauth/v2/authorization';
|
|
||||||
const redirect_uri = `${server}/_oauth/linkedin?close`;
|
|
||||||
const scope = 'r_liteprofile,r_emailaddress';
|
|
||||||
const state = this.getOAuthState();
|
|
||||||
const params = `?client_id=${clientId}&redirect_uri=${redirect_uri}&scope=${scope}&state=${state}&response_type=code`;
|
|
||||||
this.openOAuth({ url: `${endpoint}${params}` });
|
|
||||||
};
|
|
||||||
|
|
||||||
onPressMeteor = () => {
|
|
||||||
logEvent(events.ENTER_WITH_METEOR);
|
|
||||||
const { services, server } = this.props;
|
|
||||||
const { clientId } = services['meteor-developer'];
|
|
||||||
const endpoint = 'https://www.meteor.com/oauth2/authorize';
|
|
||||||
const redirect_uri = `${server}/_oauth/meteor-developer`;
|
|
||||||
const state = this.getOAuthState();
|
|
||||||
const params = `?client_id=${clientId}&redirect_uri=${redirect_uri}&state=${state}&response_type=code`;
|
|
||||||
this.openOAuth({ url: `${endpoint}${params}` });
|
|
||||||
};
|
|
||||||
|
|
||||||
onPressTwitter = () => {
|
|
||||||
logEvent(events.ENTER_WITH_TWITTER);
|
|
||||||
const { server } = this.props;
|
|
||||||
const state = this.getOAuthState();
|
|
||||||
const url = `${server}/_oauth/twitter/?requestTokenAndRedirect=true&state=${state}`;
|
|
||||||
this.openOAuth({ url });
|
|
||||||
};
|
|
||||||
|
|
||||||
onPressWordpress = () => {
|
|
||||||
logEvent(events.ENTER_WITH_WORDPRESS);
|
|
||||||
const { services, server } = this.props;
|
|
||||||
const { clientId, serverURL } = services.wordpress;
|
|
||||||
const endpoint = `${serverURL}/oauth/authorize`;
|
|
||||||
const redirect_uri = `${server}/_oauth/wordpress?close`;
|
|
||||||
const scope = 'openid';
|
|
||||||
const state = this.getOAuthState();
|
|
||||||
const params = `?client_id=${clientId}&redirect_uri=${redirect_uri}&scope=${scope}&state=${state}&response_type=code`;
|
|
||||||
this.openOAuth({ url: `${endpoint}${params}` });
|
|
||||||
};
|
|
||||||
|
|
||||||
onPressCustomOAuth = (loginService: IItemService) => {
|
|
||||||
logEvent(events.ENTER_WITH_CUSTOM_OAUTH);
|
|
||||||
const { server } = this.props;
|
|
||||||
const { serverURL, authorizePath, clientId, scope, service } = loginService;
|
|
||||||
const redirectUri = `${server}/_oauth/${service}`;
|
|
||||||
const state = this.getOAuthState();
|
|
||||||
const params = `?client_id=${clientId}&redirect_uri=${redirectUri}&response_type=code&state=${state}&scope=${scope}`;
|
|
||||||
const domain = `${serverURL}`;
|
|
||||||
const absolutePath = `${authorizePath}${params}`;
|
|
||||||
const url = absolutePath.includes(domain) ? absolutePath : domain + absolutePath;
|
|
||||||
this.openOAuth({ url });
|
|
||||||
};
|
|
||||||
|
|
||||||
onPressSaml = (loginService: IItemService) => {
|
|
||||||
logEvent(events.ENTER_WITH_SAML);
|
|
||||||
const { server } = this.props;
|
|
||||||
const { clientConfig } = loginService;
|
|
||||||
const { provider } = clientConfig;
|
|
||||||
const ssoToken = random(17);
|
|
||||||
const url = `${server}/_saml/authorize/${provider}/${ssoToken}`;
|
|
||||||
this.openOAuth({ url, ssoToken, authType: 'saml' });
|
|
||||||
};
|
|
||||||
|
|
||||||
onPressCas = () => {
|
|
||||||
logEvent(events.ENTER_WITH_CAS);
|
|
||||||
const { server, CAS_login_url } = this.props;
|
|
||||||
const ssoToken = random(17);
|
|
||||||
const url = `${CAS_login_url}?service=${server}/_cas/${ssoToken}`;
|
|
||||||
this.openOAuth({ url, ssoToken, authType: 'cas' });
|
|
||||||
};
|
|
||||||
|
|
||||||
onPressAppleLogin = async () => {
|
|
||||||
logEvent(events.ENTER_WITH_APPLE);
|
|
||||||
try {
|
|
||||||
const { fullName, email, identityToken } = await AppleAuthentication.signInAsync({
|
|
||||||
requestedScopes: [
|
|
||||||
AppleAuthentication.AppleAuthenticationScope.FULL_NAME,
|
|
||||||
AppleAuthentication.AppleAuthenticationScope.EMAIL
|
|
||||||
]
|
|
||||||
});
|
|
||||||
await Services.loginOAuthOrSso({ fullName, email, identityToken });
|
|
||||||
} catch {
|
|
||||||
logEvent(events.ENTER_WITH_APPLE_F);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
getOAuthState = (loginStyle = LOGIN_STYPE_POPUP) => {
|
|
||||||
const credentialToken = random(43);
|
|
||||||
let obj: {
|
|
||||||
loginStyle: string;
|
|
||||||
credentialToken: string;
|
|
||||||
isCordova: boolean;
|
|
||||||
redirectUrl?: string;
|
|
||||||
} = { loginStyle, credentialToken, isCordova: true };
|
|
||||||
if (loginStyle === LOGIN_STYPE_REDIRECT) {
|
|
||||||
obj = {
|
|
||||||
...obj,
|
|
||||||
redirectUrl: 'rocketchat://auth'
|
|
||||||
};
|
|
||||||
}
|
|
||||||
return Base64.encodeURI(JSON.stringify(obj));
|
|
||||||
};
|
|
||||||
|
|
||||||
openOAuth = ({ url, ssoToken, authType = 'oauth' }: IOpenOAuth) => {
|
|
||||||
const { navigation } = this.props;
|
|
||||||
navigation.navigate('AuthenticationWebView', { url, authType, ssoToken });
|
|
||||||
};
|
|
||||||
|
|
||||||
transitionServicesTo = (height: number) => {
|
|
||||||
const { servicesHeight } = this.state;
|
|
||||||
if (this._animation) {
|
|
||||||
this._animation.stop();
|
|
||||||
}
|
|
||||||
this._animation = Animated.timing(servicesHeight, {
|
|
||||||
toValue: height,
|
|
||||||
duration: 300,
|
|
||||||
easing: Easing.inOut(Easing.quad),
|
|
||||||
useNativeDriver: false
|
|
||||||
}).start();
|
|
||||||
};
|
|
||||||
|
|
||||||
toggleServices = () => {
|
|
||||||
const { collapsed } = this.state;
|
|
||||||
const { services } = this.props;
|
|
||||||
const { length } = Object.values(services);
|
|
||||||
if (collapsed) {
|
|
||||||
this.transitionServicesTo(SERVICE_HEIGHT * length);
|
|
||||||
} else {
|
|
||||||
this.transitionServicesTo(SERVICES_COLLAPSED_HEIGHT);
|
|
||||||
}
|
|
||||||
this.setState((prevState: ILoginServicesState) => ({ collapsed: !prevState.collapsed }));
|
|
||||||
};
|
|
||||||
|
|
||||||
getSocialOauthProvider = (name: string) => {
|
|
||||||
const oauthProviders: IOauthProvider = {
|
|
||||||
facebook: this.onPressFacebook,
|
|
||||||
github: this.onPressGithub,
|
|
||||||
gitlab: this.onPressGitlab,
|
|
||||||
google: this.onPressGoogle,
|
|
||||||
linkedin: this.onPressLinkedin,
|
|
||||||
'meteor-developer': this.onPressMeteor,
|
|
||||||
twitter: this.onPressTwitter,
|
|
||||||
wordpress: this.onPressWordpress
|
|
||||||
};
|
|
||||||
return oauthProviders[name];
|
|
||||||
};
|
|
||||||
|
|
||||||
renderServicesSeparator = () => {
|
|
||||||
const { collapsed } = this.state;
|
|
||||||
const { services, separator, theme } = this.props;
|
|
||||||
const { length } = Object.values(services);
|
|
||||||
|
|
||||||
if (length > 3 && separator) {
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<Button
|
|
||||||
title={collapsed ? I18n.t('Onboarding_more_options') : I18n.t('Onboarding_less_options')}
|
|
||||||
type='secondary'
|
|
||||||
onPress={this.toggleServices}
|
|
||||||
style={styles.options}
|
|
||||||
color={themes[theme].actionTintColor}
|
|
||||||
/>
|
|
||||||
<OrSeparator theme={theme} />
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if (length > 0 && separator) {
|
|
||||||
return <OrSeparator theme={theme} />;
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
};
|
|
||||||
|
|
||||||
renderItem = (service: IItemService) => {
|
|
||||||
const { CAS_enabled, theme } = this.props;
|
|
||||||
let { name } = service;
|
|
||||||
name = name === 'meteor-developer' ? 'meteor' : name;
|
|
||||||
const icon = `${name}-monochromatic` as TIconsName;
|
|
||||||
const isSaml = service.service === 'saml';
|
|
||||||
let onPress = () => {};
|
|
||||||
|
|
||||||
switch (service.authType) {
|
|
||||||
case 'oauth': {
|
|
||||||
onPress = this.getSocialOauthProvider(service.name);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case 'oauth_custom': {
|
|
||||||
onPress = () => this.onPressCustomOAuth(service);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case 'saml': {
|
|
||||||
onPress = () => this.onPressSaml(service);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case 'cas': {
|
|
||||||
onPress = () => this.onPressCas();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case 'apple': {
|
|
||||||
onPress = () => this.onPressAppleLogin();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
name = name.charAt(0).toUpperCase() + name.slice(1);
|
|
||||||
let buttonText;
|
|
||||||
if (isSaml || (service.service === 'cas' && CAS_enabled)) {
|
|
||||||
buttonText = <Text style={[styles.serviceName, isSaml && { color: service.buttonLabelColor }]}>{name}</Text>;
|
|
||||||
} else {
|
|
||||||
buttonText = (
|
|
||||||
<>
|
|
||||||
{I18n.t('Continue_with')} <Text style={styles.serviceName}>{name}</Text>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const backgroundColor = isSaml && service.buttonColor ? service.buttonColor : themes[theme].chatComponentBackground;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Touch
|
|
||||||
key={service.name}
|
|
||||||
onPress={onPress}
|
|
||||||
style={[styles.serviceButton, { backgroundColor }]}
|
|
||||||
theme={theme}
|
|
||||||
activeOpacity={0.5}
|
|
||||||
underlayColor={themes[theme].buttonText}>
|
|
||||||
<View style={styles.serviceButtonContainer}>
|
|
||||||
{service.authType === 'oauth' || service.authType === 'apple' ? (
|
|
||||||
<CustomIcon name={icon} size={24} color={themes[theme].titleText} style={styles.serviceIcon} />
|
|
||||||
) : null}
|
|
||||||
<Text style={[styles.serviceText, { color: themes[theme].titleText }]}>{buttonText}</Text>
|
|
||||||
</View>
|
|
||||||
</Touch>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
render() {
|
|
||||||
const { servicesHeight } = this.state;
|
|
||||||
const { services, separator } = this.props;
|
|
||||||
const { length } = Object.values(services);
|
|
||||||
const style: Animated.AnimatedProps<ViewStyle> = {
|
|
||||||
overflow: 'hidden',
|
|
||||||
height: servicesHeight
|
|
||||||
};
|
|
||||||
|
|
||||||
if (length > 3 && separator) {
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<Animated.View style={style}>
|
|
||||||
{Object.values(services).map((service: IItemService) => this.renderItem(service))}
|
|
||||||
</Animated.View>
|
|
||||||
{this.renderServicesSeparator()}
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
{Object.values(services).map((service: IItemService) => this.renderItem(service))}
|
|
||||||
{this.renderServicesSeparator()}
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const mapStateToProps = (state: IApplicationState) => ({
|
|
||||||
server: state.server.server,
|
|
||||||
Gitlab_URL: state.settings.API_Gitlab_URL as string,
|
|
||||||
CAS_enabled: state.settings.CAS_enabled as boolean,
|
|
||||||
CAS_login_url: state.settings.CAS_login_url as string,
|
|
||||||
services: state.login.services as IServices
|
|
||||||
});
|
|
||||||
|
|
||||||
export default connect(mapStateToProps)(withTheme(LoginServices));
|
|
|
@ -0,0 +1,31 @@
|
||||||
|
import React from 'react';
|
||||||
|
import { Text, View } from 'react-native';
|
||||||
|
|
||||||
|
import { useTheme } from '../../theme';
|
||||||
|
import Touch from '../../lib/methods/helpers/touch';
|
||||||
|
import { CustomIcon } from '../CustomIcon';
|
||||||
|
import { IButtonService } from './interfaces';
|
||||||
|
import styles from './styles';
|
||||||
|
|
||||||
|
const ButtonService = ({ name, authType, onPress, backgroundColor, buttonText, icon }: IButtonService) => {
|
||||||
|
const { theme, colors } = useTheme();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Touch
|
||||||
|
key={name}
|
||||||
|
onPress={onPress}
|
||||||
|
style={[styles.serviceButton, { backgroundColor }]}
|
||||||
|
theme={theme}
|
||||||
|
activeOpacity={0.5}
|
||||||
|
underlayColor={colors.buttonText}>
|
||||||
|
<View style={styles.serviceButtonContainer}>
|
||||||
|
{authType === 'oauth' || authType === 'apple' ? (
|
||||||
|
<CustomIcon name={icon} size={24} color={colors.titleText} style={styles.serviceIcon} />
|
||||||
|
) : null}
|
||||||
|
<Text style={[styles.serviceText, { color: colors.titleText }]}>{buttonText}</Text>
|
||||||
|
</View>
|
||||||
|
</Touch>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ButtonService;
|
|
@ -0,0 +1,99 @@
|
||||||
|
import React from 'react';
|
||||||
|
import { storiesOf } from '@storybook/react-native';
|
||||||
|
import { Provider } from 'react-redux';
|
||||||
|
import { StyleSheet, Text, ScrollView } from 'react-native';
|
||||||
|
|
||||||
|
import { store } from '../../../storybook/stories';
|
||||||
|
import { ThemeContext } from '../../theme';
|
||||||
|
import { colors } from '../../lib/constants';
|
||||||
|
import i18n from '../../i18n';
|
||||||
|
import sharedStyles from '../../views/Styles';
|
||||||
|
import ServicesSeparator from './ServicesSeparator';
|
||||||
|
import ButtonService from './ButtonService';
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
serviceName: {
|
||||||
|
...sharedStyles.textSemibold
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const services = {
|
||||||
|
github: {
|
||||||
|
_id: 'github',
|
||||||
|
name: 'github',
|
||||||
|
clientId: 'github-123',
|
||||||
|
buttonLabelText: '',
|
||||||
|
buttonColor: '',
|
||||||
|
buttonLabelColor: '',
|
||||||
|
custom: false,
|
||||||
|
authType: 'oauth'
|
||||||
|
},
|
||||||
|
gitlab: {
|
||||||
|
_id: 'gitlab',
|
||||||
|
name: 'gitlab',
|
||||||
|
clientId: 'gitlab-123',
|
||||||
|
buttonLabelText: '',
|
||||||
|
buttonColor: '',
|
||||||
|
buttonLabelColor: '',
|
||||||
|
custom: false,
|
||||||
|
authType: 'oauth'
|
||||||
|
},
|
||||||
|
google: {
|
||||||
|
_id: 'google',
|
||||||
|
name: 'google',
|
||||||
|
clientId: 'google-123',
|
||||||
|
buttonLabelText: '',
|
||||||
|
buttonColor: '',
|
||||||
|
buttonLabelColor: '',
|
||||||
|
custom: false,
|
||||||
|
authType: 'oauth'
|
||||||
|
},
|
||||||
|
apple: {
|
||||||
|
_id: 'apple',
|
||||||
|
name: 'apple',
|
||||||
|
clientId: 'apple-123',
|
||||||
|
buttonLabelText: 'Sign in with Apple',
|
||||||
|
buttonColor: '#000',
|
||||||
|
buttonLabelColor: '#FFF',
|
||||||
|
custom: false,
|
||||||
|
authType: 'apple'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const theme = 'light';
|
||||||
|
|
||||||
|
const stories = storiesOf('Login Services', module)
|
||||||
|
.addDecorator(story => <Provider store={store}>{story()}</Provider>)
|
||||||
|
.addDecorator(story => <ThemeContext.Provider value={{ theme, colors: colors[theme] }}>{story()}</ThemeContext.Provider>)
|
||||||
|
.addDecorator(story => <ScrollView style={sharedStyles.containerScrollView}>{story()}</ScrollView>);
|
||||||
|
|
||||||
|
stories.add('ServicesSeparator', () => (
|
||||||
|
<>
|
||||||
|
<ServicesSeparator collapsed onPressButtonSeparator={() => {}} separator services={services} />
|
||||||
|
<ServicesSeparator collapsed={false} onPressButtonSeparator={() => {}} separator services={services} />
|
||||||
|
</>
|
||||||
|
));
|
||||||
|
|
||||||
|
stories.add('ServiceList', () => (
|
||||||
|
<>
|
||||||
|
{Object.values(services).map(service => {
|
||||||
|
const icon = `${service.name}-monochromatic`;
|
||||||
|
const buttonText = (
|
||||||
|
<>
|
||||||
|
{i18n.t('Continue_with')} <Text style={styles.serviceName}>{service.name}</Text>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
return (
|
||||||
|
<ButtonService
|
||||||
|
key={service._id}
|
||||||
|
onPress={() => {}}
|
||||||
|
backgroundColor={colors[theme].chatComponentBackground}
|
||||||
|
buttonText={buttonText}
|
||||||
|
icon={icon}
|
||||||
|
name={service.name}
|
||||||
|
authType={service.authType}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</>
|
||||||
|
));
|
|
@ -0,0 +1,104 @@
|
||||||
|
import React, { useRef } from 'react';
|
||||||
|
import { Text } from 'react-native';
|
||||||
|
|
||||||
|
import { useTheme } from '../../theme';
|
||||||
|
import I18n from '../../i18n';
|
||||||
|
import { TIconsName } from '../CustomIcon';
|
||||||
|
import { IItemService, IOauthProvider } from './interfaces';
|
||||||
|
import styles from './styles';
|
||||||
|
import * as ServiceLogin from './serviceLogin';
|
||||||
|
import ButtonService from './ButtonService';
|
||||||
|
|
||||||
|
const Service = React.memo(
|
||||||
|
({
|
||||||
|
CAS_enabled,
|
||||||
|
CAS_login_url,
|
||||||
|
Gitlab_URL,
|
||||||
|
server,
|
||||||
|
service
|
||||||
|
}: {
|
||||||
|
service: IItemService;
|
||||||
|
server: string;
|
||||||
|
Gitlab_URL: string;
|
||||||
|
CAS_enabled: boolean;
|
||||||
|
CAS_login_url: string;
|
||||||
|
storiesTestOnPress?: () => void;
|
||||||
|
}) => {
|
||||||
|
const { colors } = useTheme();
|
||||||
|
const onPress = useRef<any>();
|
||||||
|
const buttonText = useRef<React.ReactElement>();
|
||||||
|
const modifiedName = useRef<string>();
|
||||||
|
|
||||||
|
const { name } = service;
|
||||||
|
modifiedName.current = name === 'meteor-developer' ? 'meteor' : name;
|
||||||
|
const icon = `${modifiedName.current}-monochromatic` as TIconsName;
|
||||||
|
const isSaml = service.service === 'saml';
|
||||||
|
|
||||||
|
const getSocialOauthProvider = (name: string) => {
|
||||||
|
const oauthProviders: IOauthProvider = {
|
||||||
|
facebook: () => ServiceLogin.onPressFacebook({ service, server }),
|
||||||
|
github: () => ServiceLogin.onPressGithub({ service, server }),
|
||||||
|
gitlab: () => ServiceLogin.onPressGitlab({ service, server, urlOption: Gitlab_URL }),
|
||||||
|
google: () => ServiceLogin.onPressGoogle({ service, server }),
|
||||||
|
linkedin: () => ServiceLogin.onPressLinkedin({ service, server }),
|
||||||
|
'meteor-developer': () => ServiceLogin.onPressMeteor({ service, server }),
|
||||||
|
twitter: () => ServiceLogin.onPressTwitter({ service, server }),
|
||||||
|
wordpress: () => ServiceLogin.onPressWordpress({ service, server })
|
||||||
|
};
|
||||||
|
return oauthProviders[name];
|
||||||
|
};
|
||||||
|
|
||||||
|
switch (service.authType) {
|
||||||
|
case 'oauth': {
|
||||||
|
onPress.current = getSocialOauthProvider(service.name);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'oauth_custom': {
|
||||||
|
onPress.current = () => ServiceLogin.onPressCustomOAuth({ loginService: service, server });
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'saml': {
|
||||||
|
onPress.current = () => ServiceLogin.onPressSaml({ loginService: service, server });
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'cas': {
|
||||||
|
onPress.current = () => ServiceLogin.onPressCas({ casLoginUrl: CAS_login_url, server });
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'apple': {
|
||||||
|
onPress.current = () => ServiceLogin.onPressAppleLogin();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
modifiedName.current = modifiedName.current.charAt(0).toUpperCase() + modifiedName.current.slice(1);
|
||||||
|
if (isSaml || (service.service === 'cas' && CAS_enabled)) {
|
||||||
|
buttonText.current = (
|
||||||
|
<Text style={[styles.serviceName, isSaml && { color: service.buttonLabelColor }]}>{modifiedName.current}</Text>
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
buttonText.current = (
|
||||||
|
<>
|
||||||
|
{I18n.t('Continue_with')} <Text style={styles.serviceName}>{modifiedName.current}</Text>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const backgroundColor = isSaml && service.buttonColor ? service.buttonColor : colors.chatComponentBackground;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ButtonService
|
||||||
|
onPress={onPress.current}
|
||||||
|
backgroundColor={backgroundColor}
|
||||||
|
buttonText={buttonText.current}
|
||||||
|
icon={icon}
|
||||||
|
name={service.name}
|
||||||
|
authType={service.authType}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
export default Service;
|
|
@ -0,0 +1,35 @@
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
import Button from '../Button';
|
||||||
|
import OrSeparator from '../OrSeparator';
|
||||||
|
import { useTheme } from '../../theme';
|
||||||
|
import styles from './styles';
|
||||||
|
import I18n from '../../i18n';
|
||||||
|
import { IServicesSeparator } from './interfaces';
|
||||||
|
|
||||||
|
const ServicesSeparator = ({ services, separator, collapsed, onPress }: IServicesSeparator) => {
|
||||||
|
const { colors, theme } = useTheme();
|
||||||
|
|
||||||
|
const { length } = Object.values(services);
|
||||||
|
|
||||||
|
if (length > 3 && separator) {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Button
|
||||||
|
title={collapsed ? I18n.t('Onboarding_more_options') : I18n.t('Onboarding_less_options')}
|
||||||
|
type='secondary'
|
||||||
|
onPress={onPress}
|
||||||
|
style={styles.options}
|
||||||
|
color={colors.actionTintColor}
|
||||||
|
/>
|
||||||
|
<OrSeparator theme={theme} />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (length > 0 && separator) {
|
||||||
|
return <OrSeparator theme={theme} />;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ServicesSeparator;
|
|
@ -0,0 +1,5 @@
|
||||||
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
|
exports[`Storyshots Login Services ServiceList 1`] = `"{\\"type\\":\\"RCTScrollView\\",\\"props\\":{\\"style\\":{\\"padding\\":15,\\"paddingBottom\\":30}},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":{\\"borderRadius\\":2,\\"width\\":\\"100%\\",\\"height\\":48,\\"flexDirection\\":\\"row\\",\\"alignItems\\":\\"center\\",\\"justifyContent\\":\\"center\\",\\"paddingHorizontal\\":15}},\\"children\\":[{\\"type\\":\\"Text\\",\\"props\\":{\\"selectable\\":false,\\"allowFontScaling\\":false,\\"style\\":[{\\"fontSize\\":24,\\"color\\":\\"#0d0e12\\"},{\\"position\\":\\"absolute\\",\\"left\\":15,\\"top\\":12,\\"width\\":24,\\"height\\":24},{\\"fontFamily\\":\\"custom\\",\\"fontWeight\\":\\"normal\\",\\"fontStyle\\":\\"normal\\"},{}]},\\"children\\":[\\"\\"]},{\\"type\\":\\"Text\\",\\"props\\":{\\"style\\":[{\\"textAlign\\":\\"left\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"System\\",\\"fontWeight\\":\\"400\\",\\"fontSize\\":16},{\\"color\\":\\"#0d0e12\\"}]},\\"children\\":[\\"Continue with\\",\\" \\",{\\"type\\":\\"Text\\",\\"props\\":{\\"style\\":{\\"textAlign\\":\\"left\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"System\\",\\"fontWeight\\":\\"600\\"}},\\"children\\":[\\"github\\"]}]}]},{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":{\\"borderRadius\\":2,\\"width\\":\\"100%\\",\\"height\\":48,\\"flexDirection\\":\\"row\\",\\"alignItems\\":\\"center\\",\\"justifyContent\\":\\"center\\",\\"paddingHorizontal\\":15}},\\"children\\":[{\\"type\\":\\"Text\\",\\"props\\":{\\"selectable\\":false,\\"allowFontScaling\\":false,\\"style\\":[{\\"fontSize\\":24,\\"color\\":\\"#0d0e12\\"},{\\"position\\":\\"absolute\\",\\"left\\":15,\\"top\\":12,\\"width\\":24,\\"height\\":24},{\\"fontFamily\\":\\"custom\\",\\"fontWeight\\":\\"normal\\",\\"fontStyle\\":\\"normal\\"},{}]},\\"children\\":[\\"\\"]},{\\"type\\":\\"Text\\",\\"props\\":{\\"style\\":[{\\"textAlign\\":\\"left\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"System\\",\\"fontWeight\\":\\"400\\",\\"fontSize\\":16},{\\"color\\":\\"#0d0e12\\"}]},\\"children\\":[\\"Continue with\\",\\" \\",{\\"type\\":\\"Text\\",\\"props\\":{\\"style\\":{\\"textAlign\\":\\"left\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"System\\",\\"fontWeight\\":\\"600\\"}},\\"children\\":[\\"gitlab\\"]}]}]},{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":{\\"borderRadius\\":2,\\"width\\":\\"100%\\",\\"height\\":48,\\"flexDirection\\":\\"row\\",\\"alignItems\\":\\"center\\",\\"justifyContent\\":\\"center\\",\\"paddingHorizontal\\":15}},\\"children\\":[{\\"type\\":\\"Text\\",\\"props\\":{\\"selectable\\":false,\\"allowFontScaling\\":false,\\"style\\":[{\\"fontSize\\":24,\\"color\\":\\"#0d0e12\\"},{\\"position\\":\\"absolute\\",\\"left\\":15,\\"top\\":12,\\"width\\":24,\\"height\\":24},{\\"fontFamily\\":\\"custom\\",\\"fontWeight\\":\\"normal\\",\\"fontStyle\\":\\"normal\\"},{}]},\\"children\\":[\\"\\"]},{\\"type\\":\\"Text\\",\\"props\\":{\\"style\\":[{\\"textAlign\\":\\"left\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"System\\",\\"fontWeight\\":\\"400\\",\\"fontSize\\":16},{\\"color\\":\\"#0d0e12\\"}]},\\"children\\":[\\"Continue with\\",\\" \\",{\\"type\\":\\"Text\\",\\"props\\":{\\"style\\":{\\"textAlign\\":\\"left\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"System\\",\\"fontWeight\\":\\"600\\"}},\\"children\\":[\\"google\\"]}]}]},{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":{\\"borderRadius\\":2,\\"width\\":\\"100%\\",\\"height\\":48,\\"flexDirection\\":\\"row\\",\\"alignItems\\":\\"center\\",\\"justifyContent\\":\\"center\\",\\"paddingHorizontal\\":15}},\\"children\\":[{\\"type\\":\\"Text\\",\\"props\\":{\\"selectable\\":false,\\"allowFontScaling\\":false,\\"style\\":[{\\"fontSize\\":24,\\"color\\":\\"#0d0e12\\"},{\\"position\\":\\"absolute\\",\\"left\\":15,\\"top\\":12,\\"width\\":24,\\"height\\":24},{\\"fontFamily\\":\\"custom\\",\\"fontWeight\\":\\"normal\\",\\"fontStyle\\":\\"normal\\"},{}]},\\"children\\":[\\"\\"]},{\\"type\\":\\"Text\\",\\"props\\":{\\"style\\":[{\\"textAlign\\":\\"left\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"System\\",\\"fontWeight\\":\\"400\\",\\"fontSize\\":16},{\\"color\\":\\"#0d0e12\\"}]},\\"children\\":[\\"Continue with\\",\\" \\",{\\"type\\":\\"Text\\",\\"props\\":{\\"style\\":{\\"textAlign\\":\\"left\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"System\\",\\"fontWeight\\":\\"600\\"}},\\"children\\":[\\"apple\\"]}]}]}]}]}"`;
|
||||||
|
|
||||||
|
exports[`Storyshots Login Services ServicesSeparator 1`] = `"{\\"type\\":\\"RCTScrollView\\",\\"props\\":{\\"style\\":{\\"padding\\":15,\\"paddingBottom\\":30}},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"accessible\\":true,\\"accessibilityLabel\\":\\"More options\\",\\"focusable\\":false,\\"style\\":{\\"paddingHorizontal\\":14,\\"justifyContent\\":\\"center\\",\\"height\\":48,\\"borderRadius\\":2,\\"marginBottom\\":0,\\"backgroundColor\\":\\"#ffffff\\",\\"opacity\\":1}},\\"children\\":[{\\"type\\":\\"Text\\",\\"props\\":{\\"style\\":[{\\"textAlign\\":\\"center\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"System\\",\\"fontWeight\\":\\"500\\"},{\\"color\\":\\"#1d74f5\\",\\"fontSize\\":16},null],\\"accessibilityLabel\\":\\"More options\\"},\\"children\\":[\\"More options\\"]}]},{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":{\\"flexDirection\\":\\"row\\",\\"alignItems\\":\\"center\\",\\"marginVertical\\":24}},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":[{\\"height\\":1,\\"flex\\":1},{\\"backgroundColor\\":\\"#e1e5e8\\"}]},\\"children\\":null},{\\"type\\":\\"Text\\",\\"props\\":{\\"style\\":[{\\"fontSize\\":14,\\"marginLeft\\":14,\\"marginRight\\":14,\\"textAlign\\":\\"left\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"System\\",\\"fontWeight\\":\\"500\\"},{\\"color\\":\\"#9ca2a8\\"}]},\\"children\\":[\\"OR\\"]},{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":[{\\"height\\":1,\\"flex\\":1},{\\"backgroundColor\\":\\"#e1e5e8\\"}]},\\"children\\":null}]},{\\"type\\":\\"View\\",\\"props\\":{\\"accessible\\":true,\\"accessibilityLabel\\":\\"Less options\\",\\"focusable\\":false,\\"style\\":{\\"paddingHorizontal\\":14,\\"justifyContent\\":\\"center\\",\\"height\\":48,\\"borderRadius\\":2,\\"marginBottom\\":0,\\"backgroundColor\\":\\"#ffffff\\",\\"opacity\\":1}},\\"children\\":[{\\"type\\":\\"Text\\",\\"props\\":{\\"style\\":[{\\"textAlign\\":\\"center\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"System\\",\\"fontWeight\\":\\"500\\"},{\\"color\\":\\"#1d74f5\\",\\"fontSize\\":16},null],\\"accessibilityLabel\\":\\"Less options\\"},\\"children\\":[\\"Less options\\"]}]},{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":{\\"flexDirection\\":\\"row\\",\\"alignItems\\":\\"center\\",\\"marginVertical\\":24}},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":[{\\"height\\":1,\\"flex\\":1},{\\"backgroundColor\\":\\"#e1e5e8\\"}]},\\"children\\":null},{\\"type\\":\\"Text\\",\\"props\\":{\\"style\\":[{\\"fontSize\\":14,\\"marginLeft\\":14,\\"marginRight\\":14,\\"textAlign\\":\\"left\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"System\\",\\"fontWeight\\":\\"500\\"},{\\"color\\":\\"#9ca2a8\\"}]},\\"children\\":[\\"OR\\"]},{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":[{\\"height\\":1,\\"flex\\":1},{\\"backgroundColor\\":\\"#e1e5e8\\"}]},\\"children\\":null}]}]}]}"`;
|
|
@ -0,0 +1,84 @@
|
||||||
|
import React, { useState } from 'react';
|
||||||
|
import { shallowEqual } from 'react-redux';
|
||||||
|
import Animated, { Easing, useAnimatedStyle, useSharedValue, withTiming } from 'react-native-reanimated';
|
||||||
|
|
||||||
|
import { IServices } from '../../selectors/login';
|
||||||
|
import { useAppSelector } from '../../lib/hooks';
|
||||||
|
import { IItemService, IServiceList } from './interfaces';
|
||||||
|
import { SERVICES_COLLAPSED_HEIGHT, SERVICE_HEIGHT } from './styles';
|
||||||
|
import ServicesSeparator from './ServicesSeparator';
|
||||||
|
import Service from './Service';
|
||||||
|
|
||||||
|
const ServiceList = ({ services, CAS_enabled, CAS_login_url, Gitlab_URL, server }: IServiceList) => (
|
||||||
|
<>
|
||||||
|
{Object.values(services).map((service: IItemService) => (
|
||||||
|
<Service
|
||||||
|
key={service._id}
|
||||||
|
CAS_enabled={CAS_enabled}
|
||||||
|
CAS_login_url={CAS_login_url}
|
||||||
|
Gitlab_URL={Gitlab_URL}
|
||||||
|
server={server}
|
||||||
|
service={service}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
|
||||||
|
const LoginServices = ({ separator }: { separator: boolean }): React.ReactElement => {
|
||||||
|
const [collapsed, setCollapsed] = useState(true);
|
||||||
|
|
||||||
|
const { Gitlab_URL, CAS_enabled, CAS_login_url } = useAppSelector(
|
||||||
|
state => ({
|
||||||
|
Gitlab_URL: state.settings.API_Gitlab_URL as string,
|
||||||
|
CAS_enabled: state.settings.CAS_enabled as boolean,
|
||||||
|
CAS_login_url: state.settings.CAS_login_url as string
|
||||||
|
}),
|
||||||
|
shallowEqual
|
||||||
|
);
|
||||||
|
const server = useAppSelector(state => state.server.server);
|
||||||
|
const services = useAppSelector(state => state.login.services as IServices, shallowEqual);
|
||||||
|
const { length } = Object.values(services);
|
||||||
|
|
||||||
|
const heightButtons = useSharedValue(SERVICES_COLLAPSED_HEIGHT);
|
||||||
|
|
||||||
|
const animatedStyle = useAnimatedStyle(() => ({
|
||||||
|
overflow: 'hidden',
|
||||||
|
height: withTiming(heightButtons.value, { duration: 300, easing: Easing.inOut(Easing.quad) })
|
||||||
|
}));
|
||||||
|
|
||||||
|
const onPressButtonSeparator = () => {
|
||||||
|
heightButtons.value = collapsed ? SERVICE_HEIGHT * length : SERVICES_COLLAPSED_HEIGHT;
|
||||||
|
setCollapsed(prevState => !prevState);
|
||||||
|
};
|
||||||
|
|
||||||
|
if (length > 3 && separator) {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Animated.View style={animatedStyle}>
|
||||||
|
<ServiceList
|
||||||
|
services={services}
|
||||||
|
CAS_enabled={CAS_enabled}
|
||||||
|
CAS_login_url={CAS_login_url}
|
||||||
|
Gitlab_URL={Gitlab_URL}
|
||||||
|
server={server}
|
||||||
|
/>
|
||||||
|
</Animated.View>
|
||||||
|
<ServicesSeparator services={services} separator={separator} collapsed={collapsed} onPress={onPressButtonSeparator} />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<ServiceList
|
||||||
|
services={services}
|
||||||
|
CAS_enabled={CAS_enabled}
|
||||||
|
CAS_login_url={CAS_login_url}
|
||||||
|
Gitlab_URL={Gitlab_URL}
|
||||||
|
server={server}
|
||||||
|
/>
|
||||||
|
<ServicesSeparator services={services} separator={separator} collapsed={collapsed} onPress={onPressButtonSeparator} />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default LoginServices;
|
|
@ -0,0 +1,69 @@
|
||||||
|
import { ReactElement } from 'react';
|
||||||
|
|
||||||
|
import { IServices } from '../../selectors/login';
|
||||||
|
import { TIconsName } from '../CustomIcon';
|
||||||
|
|
||||||
|
type TAuthType = 'oauth' | 'oauth_custom' | 'saml' | 'cas' | 'apple';
|
||||||
|
|
||||||
|
type TServiceName = 'facebook' | 'github' | 'gitlab' | 'google' | 'linkedin' | 'meteor-developer' | 'twitter' | 'wordpress';
|
||||||
|
export interface IOpenOAuth {
|
||||||
|
url: string;
|
||||||
|
ssoToken?: string;
|
||||||
|
authType?: TAuthType;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IItemService {
|
||||||
|
_id: string;
|
||||||
|
name: TServiceName;
|
||||||
|
service: string;
|
||||||
|
authType: TAuthType;
|
||||||
|
buttonColor: string;
|
||||||
|
buttonLabelColor: string;
|
||||||
|
clientConfig: { provider: string };
|
||||||
|
serverURL: string;
|
||||||
|
authorizePath: string;
|
||||||
|
clientId: string;
|
||||||
|
scope: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IServiceLogin {
|
||||||
|
service: IItemService;
|
||||||
|
server: string;
|
||||||
|
urlOption?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IOauthProvider {
|
||||||
|
[key: string]: ({ service, server }: IServiceLogin) => void;
|
||||||
|
facebook: ({ service, server }: IServiceLogin) => void;
|
||||||
|
github: ({ service, server }: IServiceLogin) => void;
|
||||||
|
gitlab: ({ service, server }: IServiceLogin) => void;
|
||||||
|
google: ({ service, server }: IServiceLogin) => void;
|
||||||
|
linkedin: ({ service, server }: IServiceLogin) => void;
|
||||||
|
'meteor-developer': ({ service, server }: IServiceLogin) => void;
|
||||||
|
twitter: ({ service, server }: IServiceLogin) => void;
|
||||||
|
wordpress: ({ service, server }: IServiceLogin) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IServiceList {
|
||||||
|
services: IServices;
|
||||||
|
CAS_enabled: boolean;
|
||||||
|
CAS_login_url: string;
|
||||||
|
Gitlab_URL: string;
|
||||||
|
server: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IServicesSeparator {
|
||||||
|
services: IServices;
|
||||||
|
separator: boolean;
|
||||||
|
collapsed: boolean;
|
||||||
|
onPress(): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IButtonService {
|
||||||
|
name: string;
|
||||||
|
authType: TAuthType;
|
||||||
|
onPress: () => void;
|
||||||
|
backgroundColor: string;
|
||||||
|
buttonText: ReactElement;
|
||||||
|
icon: TIconsName;
|
||||||
|
}
|
|
@ -0,0 +1,159 @@
|
||||||
|
import * as AppleAuthentication from 'expo-apple-authentication';
|
||||||
|
import { Linking } from 'react-native';
|
||||||
|
import { Base64 } from 'js-base64';
|
||||||
|
|
||||||
|
import { Services } from '../../lib/services';
|
||||||
|
import Navigation from '../../lib/navigation/appNavigation';
|
||||||
|
import { IItemService, IOpenOAuth, IServiceLogin } from './interfaces';
|
||||||
|
import { random } from '../../lib/methods/helpers';
|
||||||
|
import { events, logEvent } from '../../lib/methods/helpers/log';
|
||||||
|
|
||||||
|
type TLoginStyle = 'popup' | 'redirect';
|
||||||
|
|
||||||
|
export const onPressFacebook = ({ service, server }: IServiceLogin) => {
|
||||||
|
logEvent(events.ENTER_WITH_FACEBOOK);
|
||||||
|
const { clientId } = service;
|
||||||
|
const endpoint = 'https://m.facebook.com/v2.9/dialog/oauth';
|
||||||
|
const redirect_uri = `${server}/_oauth/facebook?close`;
|
||||||
|
const scope = 'email';
|
||||||
|
const state = getOAuthState();
|
||||||
|
const params = `?client_id=${clientId}&redirect_uri=${redirect_uri}&scope=${scope}&state=${state}&display=touch`;
|
||||||
|
openOAuth({ url: `${endpoint}${params}` });
|
||||||
|
};
|
||||||
|
|
||||||
|
export const onPressGithub = ({ service, server }: IServiceLogin) => {
|
||||||
|
logEvent(events.ENTER_WITH_GITHUB);
|
||||||
|
const { clientId } = service;
|
||||||
|
const endpoint = `https://github.com/login?client_id=${clientId}&return_to=${encodeURIComponent('/login/oauth/authorize')}`;
|
||||||
|
const redirect_uri = `${server}/_oauth/github?close`;
|
||||||
|
const scope = 'user:email';
|
||||||
|
const state = getOAuthState();
|
||||||
|
const params = `?client_id=${clientId}&redirect_uri=${redirect_uri}&scope=${scope}&state=${state}`;
|
||||||
|
openOAuth({ url: `${endpoint}${encodeURIComponent(params)}` });
|
||||||
|
};
|
||||||
|
|
||||||
|
export const onPressGitlab = ({ service, server, urlOption }: IServiceLogin) => {
|
||||||
|
logEvent(events.ENTER_WITH_GITLAB);
|
||||||
|
const { clientId } = service;
|
||||||
|
const baseURL = urlOption ? urlOption.trim().replace(/\/*$/, '') : 'https://gitlab.com';
|
||||||
|
const endpoint = `${baseURL}/oauth/authorize`;
|
||||||
|
const redirect_uri = `${server}/_oauth/gitlab?close`;
|
||||||
|
const scope = 'read_user';
|
||||||
|
const state = getOAuthState();
|
||||||
|
const params = `?client_id=${clientId}&redirect_uri=${redirect_uri}&scope=${scope}&state=${state}&response_type=code`;
|
||||||
|
openOAuth({ url: `${endpoint}${params}` });
|
||||||
|
};
|
||||||
|
|
||||||
|
export const onPressGoogle = ({ service, server }: IServiceLogin) => {
|
||||||
|
logEvent(events.ENTER_WITH_GOOGLE);
|
||||||
|
const { clientId } = service;
|
||||||
|
const endpoint = 'https://accounts.google.com/o/oauth2/auth';
|
||||||
|
const redirect_uri = `${server}/_oauth/google?close`;
|
||||||
|
const scope = 'email';
|
||||||
|
const state = getOAuthState('redirect');
|
||||||
|
const params = `?client_id=${clientId}&redirect_uri=${redirect_uri}&scope=${scope}&state=${state}&response_type=code`;
|
||||||
|
Linking.openURL(`${endpoint}${params}`);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const onPressLinkedin = ({ service, server }: IServiceLogin) => {
|
||||||
|
logEvent(events.ENTER_WITH_LINKEDIN);
|
||||||
|
const { clientId } = service;
|
||||||
|
const endpoint = 'https://www.linkedin.com/oauth/v2/authorization';
|
||||||
|
const redirect_uri = `${server}/_oauth/linkedin?close`;
|
||||||
|
const scope = 'r_liteprofile,r_emailaddress';
|
||||||
|
const state = getOAuthState();
|
||||||
|
const params = `?client_id=${clientId}&redirect_uri=${redirect_uri}&scope=${scope}&state=${state}&response_type=code`;
|
||||||
|
openOAuth({ url: `${endpoint}${params}` });
|
||||||
|
};
|
||||||
|
|
||||||
|
export const onPressMeteor = ({ service, server }: IServiceLogin) => {
|
||||||
|
logEvent(events.ENTER_WITH_METEOR);
|
||||||
|
const { clientId } = service;
|
||||||
|
const endpoint = 'https://www.meteor.com/oauth2/authorize';
|
||||||
|
const redirect_uri = `${server}/_oauth/meteor-developer`;
|
||||||
|
const state = getOAuthState();
|
||||||
|
const params = `?client_id=${clientId}&redirect_uri=${redirect_uri}&state=${state}&response_type=code`;
|
||||||
|
openOAuth({ url: `${endpoint}${params}` });
|
||||||
|
};
|
||||||
|
|
||||||
|
export const onPressTwitter = ({ server }: IServiceLogin) => {
|
||||||
|
logEvent(events.ENTER_WITH_TWITTER);
|
||||||
|
const state = getOAuthState();
|
||||||
|
const url = `${server}/_oauth/twitter/?requestTokenAndRedirect=true&state=${state}`;
|
||||||
|
openOAuth({ url });
|
||||||
|
};
|
||||||
|
|
||||||
|
export const onPressWordpress = ({ service, server }: IServiceLogin) => {
|
||||||
|
logEvent(events.ENTER_WITH_WORDPRESS);
|
||||||
|
const { clientId, serverURL } = service;
|
||||||
|
const endpoint = `${serverURL}/oauth/authorize`;
|
||||||
|
const redirect_uri = `${server}/_oauth/wordpress?close`;
|
||||||
|
const scope = 'openid';
|
||||||
|
const state = getOAuthState();
|
||||||
|
const params = `?client_id=${clientId}&redirect_uri=${redirect_uri}&scope=${scope}&state=${state}&response_type=code`;
|
||||||
|
openOAuth({ url: `${endpoint}${params}` });
|
||||||
|
};
|
||||||
|
|
||||||
|
export const onPressCustomOAuth = ({ loginService, server }: { loginService: IItemService; server: string }) => {
|
||||||
|
logEvent(events.ENTER_WITH_CUSTOM_OAUTH);
|
||||||
|
const { serverURL, authorizePath, clientId, scope, service } = loginService;
|
||||||
|
const redirectUri = `${server}/_oauth/${service}`;
|
||||||
|
const state = getOAuthState();
|
||||||
|
const params = `?client_id=${clientId}&redirect_uri=${redirectUri}&response_type=code&state=${state}&scope=${scope}`;
|
||||||
|
const domain = `${serverURL}`;
|
||||||
|
const absolutePath = `${authorizePath}${params}`;
|
||||||
|
const url = absolutePath.includes(domain) ? absolutePath : domain + absolutePath;
|
||||||
|
openOAuth({ url });
|
||||||
|
};
|
||||||
|
|
||||||
|
export const onPressSaml = ({ loginService, server }: { loginService: IItemService; server: string }) => {
|
||||||
|
logEvent(events.ENTER_WITH_SAML);
|
||||||
|
const { clientConfig } = loginService;
|
||||||
|
const { provider } = clientConfig;
|
||||||
|
const ssoToken = random(17);
|
||||||
|
const url = `${server}/_saml/authorize/${provider}/${ssoToken}`;
|
||||||
|
openOAuth({ url, ssoToken, authType: 'saml' });
|
||||||
|
};
|
||||||
|
|
||||||
|
export const onPressCas = ({ casLoginUrl, server }: { casLoginUrl: string; server: string }) => {
|
||||||
|
logEvent(events.ENTER_WITH_CAS);
|
||||||
|
const ssoToken = random(17);
|
||||||
|
const url = `${casLoginUrl}?service=${server}/_cas/${ssoToken}`;
|
||||||
|
openOAuth({ url, ssoToken, authType: 'cas' });
|
||||||
|
};
|
||||||
|
|
||||||
|
export const onPressAppleLogin = async () => {
|
||||||
|
logEvent(events.ENTER_WITH_APPLE);
|
||||||
|
try {
|
||||||
|
const { fullName, email, identityToken } = await AppleAuthentication.signInAsync({
|
||||||
|
requestedScopes: [
|
||||||
|
AppleAuthentication.AppleAuthenticationScope.FULL_NAME,
|
||||||
|
AppleAuthentication.AppleAuthenticationScope.EMAIL
|
||||||
|
]
|
||||||
|
});
|
||||||
|
await Services.loginOAuthOrSso({ fullName, email, identityToken });
|
||||||
|
} catch {
|
||||||
|
logEvent(events.ENTER_WITH_APPLE_F);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const getOAuthState = (loginStyle: TLoginStyle = 'popup') => {
|
||||||
|
const credentialToken = random(43);
|
||||||
|
let obj: {
|
||||||
|
loginStyle: string;
|
||||||
|
credentialToken: string;
|
||||||
|
isCordova: boolean;
|
||||||
|
redirectUrl?: string;
|
||||||
|
} = { loginStyle, credentialToken, isCordova: true };
|
||||||
|
if (loginStyle === 'redirect') {
|
||||||
|
obj = {
|
||||||
|
...obj,
|
||||||
|
redirectUrl: 'rocketchat://auth'
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return Base64.encodeURI(JSON.stringify(obj));
|
||||||
|
};
|
||||||
|
|
||||||
|
const openOAuth = ({ url, ssoToken, authType = 'oauth' }: IOpenOAuth) => {
|
||||||
|
Navigation.navigate('AuthenticationWebView', { url, authType, ssoToken });
|
||||||
|
};
|
|
@ -0,0 +1,41 @@
|
||||||
|
import { StyleSheet } from 'react-native';
|
||||||
|
|
||||||
|
import sharedStyles from '../../views/Styles';
|
||||||
|
|
||||||
|
export const BUTTON_HEIGHT = 48;
|
||||||
|
export const SERVICE_HEIGHT = 58;
|
||||||
|
export const BORDER_RADIUS = 2;
|
||||||
|
export const SERVICES_COLLAPSED_HEIGHT = 174;
|
||||||
|
|
||||||
|
export default StyleSheet.create({
|
||||||
|
serviceButton: {
|
||||||
|
borderRadius: BORDER_RADIUS,
|
||||||
|
marginBottom: 10
|
||||||
|
},
|
||||||
|
serviceButtonContainer: {
|
||||||
|
borderRadius: BORDER_RADIUS,
|
||||||
|
width: '100%',
|
||||||
|
height: BUTTON_HEIGHT,
|
||||||
|
flexDirection: 'row',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
paddingHorizontal: 15
|
||||||
|
},
|
||||||
|
serviceIcon: {
|
||||||
|
position: 'absolute',
|
||||||
|
left: 15,
|
||||||
|
top: 12,
|
||||||
|
width: 24,
|
||||||
|
height: 24
|
||||||
|
},
|
||||||
|
serviceText: {
|
||||||
|
...sharedStyles.textRegular,
|
||||||
|
fontSize: 16
|
||||||
|
},
|
||||||
|
serviceName: {
|
||||||
|
...sharedStyles.textSemibold
|
||||||
|
},
|
||||||
|
options: {
|
||||||
|
marginBottom: 0
|
||||||
|
}
|
||||||
|
});
|
|
@ -4,7 +4,7 @@ import { FlatList, StyleSheet, Text, View } from 'react-native';
|
||||||
import { TSupportedThemes, useTheme } from '../../theme';
|
import { TSupportedThemes, useTheme } from '../../theme';
|
||||||
import { themes } from '../../lib/constants';
|
import { themes } from '../../lib/constants';
|
||||||
import { CustomIcon } from '../CustomIcon';
|
import { CustomIcon } from '../CustomIcon';
|
||||||
import shortnameToUnicode from '../../utils/shortnameToUnicode';
|
import shortnameToUnicode from '../../lib/methods/helpers/shortnameToUnicode';
|
||||||
import CustomEmoji from '../EmojiPicker/CustomEmoji';
|
import CustomEmoji from '../EmojiPicker/CustomEmoji';
|
||||||
import database from '../../lib/database';
|
import database from '../../lib/database';
|
||||||
import { Button } from '../ActionSheet';
|
import { Button } from '../ActionSheet';
|
||||||
|
|
|
@ -6,17 +6,18 @@ import moment from 'moment';
|
||||||
|
|
||||||
import database from '../../lib/database';
|
import database from '../../lib/database';
|
||||||
import I18n from '../../i18n';
|
import I18n from '../../i18n';
|
||||||
import log, { logEvent } from '../../utils/log';
|
import log, { logEvent } from '../../lib/methods/helpers/log';
|
||||||
import Navigation from '../../lib/navigation/appNavigation';
|
import Navigation from '../../lib/navigation/appNavigation';
|
||||||
import { getMessageTranslation } from '../message/utils';
|
import { getMessageTranslation } from '../message/utils';
|
||||||
import { LISTENER } from '../Toast';
|
import { LISTENER } from '../Toast';
|
||||||
import EventEmitter from '../../utils/events';
|
import EventEmitter from '../../lib/methods/helpers/events';
|
||||||
import { showConfirmationAlert } from '../../utils/info';
|
import { showConfirmationAlert } from '../../lib/methods/helpers/info';
|
||||||
import { TActionSheetOptionsItem, useActionSheet } from '../ActionSheet';
|
import { TActionSheetOptionsItem, useActionSheet } from '../ActionSheet';
|
||||||
import Header, { HEADER_HEIGHT, IHeader } from './Header';
|
import Header, { HEADER_HEIGHT, IHeader } from './Header';
|
||||||
import events from '../../utils/log/events';
|
import events from '../../lib/methods/helpers/log/events';
|
||||||
import { IApplicationState, ILoggedUser, TAnyMessageModel, TSubscriptionModel } from '../../definitions';
|
import { IApplicationState, ILoggedUser, TAnyMessageModel, TSubscriptionModel } from '../../definitions';
|
||||||
import { getPermalinkMessage, hasPermission } from '../../lib/methods';
|
import { getPermalinkMessage } from '../../lib/methods';
|
||||||
|
import { hasPermission } from '../../lib/methods/helpers';
|
||||||
import { Services } from '../../lib/services';
|
import { Services } from '../../lib/services';
|
||||||
|
|
||||||
export interface IMessageActionsProps {
|
export interface IMessageActionsProps {
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import FastImage from '@rocket.chat/react-native-fast-image';
|
import FastImage from 'react-native-fast-image';
|
||||||
import React, { useContext, useState } from 'react';
|
import React, { useContext, useState } from 'react';
|
||||||
import { TouchableOpacity } from 'react-native';
|
import { TouchableOpacity } from 'react-native';
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,7 @@ import React, { useContext } from 'react';
|
||||||
import { Text } from 'react-native';
|
import { Text } from 'react-native';
|
||||||
|
|
||||||
import { IEmoji } from '../../../definitions/IEmoji';
|
import { IEmoji } from '../../../definitions/IEmoji';
|
||||||
import shortnameToUnicode from '../../../utils/shortnameToUnicode';
|
import shortnameToUnicode from '../../../lib/methods/helpers/shortnameToUnicode';
|
||||||
import CustomEmoji from '../../EmojiPicker/CustomEmoji';
|
import CustomEmoji from '../../EmojiPicker/CustomEmoji';
|
||||||
import MessageboxContext from '../Context';
|
import MessageboxContext from '../Context';
|
||||||
import styles from '../styles';
|
import styles from '../styles';
|
||||||
|
|
|
@ -9,7 +9,7 @@ import styles from './styles';
|
||||||
import I18n from '../../i18n';
|
import I18n from '../../i18n';
|
||||||
import { themes } from '../../lib/constants';
|
import { themes } from '../../lib/constants';
|
||||||
import { CustomIcon } from '../CustomIcon';
|
import { CustomIcon } from '../CustomIcon';
|
||||||
import { events, logEvent } from '../../utils/log';
|
import { events, logEvent } from '../../lib/methods/helpers/log';
|
||||||
import { TSupportedThemes } from '../../theme';
|
import { TSupportedThemes } from '../../theme';
|
||||||
|
|
||||||
interface IMessageBoxRecordAudioProps {
|
interface IMessageBoxRecordAudioProps {
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { ImageOrVideo } from 'react-native-image-crop-picker';
|
import { ImageOrVideo } from 'react-native-image-crop-picker';
|
||||||
|
|
||||||
import { isIOS } from '../../utils/deviceInfo';
|
import { isIOS } from '../../lib/methods/helpers';
|
||||||
|
|
||||||
const regex = new RegExp(/\.[^/.]+$/); // Check from last '.' of the string
|
const regex = new RegExp(/\.[^/.]+$/); // Check from last '.' of the string
|
||||||
|
|
||||||
|
|
|
@ -9,16 +9,15 @@ import { Q } from '@nozbe/watermelondb';
|
||||||
import { TouchableWithoutFeedback } from 'react-native-gesture-handler';
|
import { TouchableWithoutFeedback } from 'react-native-gesture-handler';
|
||||||
|
|
||||||
import { generateTriggerId } from '../../lib/methods/actions';
|
import { generateTriggerId } from '../../lib/methods/actions';
|
||||||
import TextInput, { IThemedTextInput } from '../TextInput';
|
import { TextInput, IThemedTextInput } from '../TextInput';
|
||||||
import { userTyping as userTypingAction } from '../../actions/room';
|
import { userTyping as userTypingAction } from '../../actions/room';
|
||||||
import styles from './styles';
|
import styles from './styles';
|
||||||
import database from '../../lib/database';
|
import database from '../../lib/database';
|
||||||
import { emojis } from '../EmojiPicker/emojis';
|
import { emojis } from '../EmojiPicker/emojis';
|
||||||
import log, { events, logEvent } from '../../utils/log';
|
import log, { events, logEvent } from '../../lib/methods/helpers/log';
|
||||||
import RecordAudio from './RecordAudio';
|
import RecordAudio from './RecordAudio';
|
||||||
import I18n from '../../i18n';
|
import I18n from '../../i18n';
|
||||||
import ReplyPreview from './ReplyPreview';
|
import ReplyPreview from './ReplyPreview';
|
||||||
import debounce from '../../utils/debounce';
|
|
||||||
import { themes } from '../../lib/constants';
|
import { themes } from '../../lib/constants';
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
// eslint-disable-next-line import/extensions,import/no-unresolved
|
// eslint-disable-next-line import/extensions,import/no-unresolved
|
||||||
|
@ -26,9 +25,8 @@ import LeftButtons from './LeftButtons';
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
// eslint-disable-next-line import/extensions,import/no-unresolved
|
// eslint-disable-next-line import/extensions,import/no-unresolved
|
||||||
import RightButtons from './RightButtons';
|
import RightButtons from './RightButtons';
|
||||||
import { isAndroid, isTablet } from '../../utils/deviceInfo';
|
import { canUploadFile } from '../../lib/methods/helpers/media';
|
||||||
import { canUploadFile } from '../../utils/media';
|
import EventEmiter from '../../lib/methods/helpers/events';
|
||||||
import EventEmiter from '../../utils/events';
|
|
||||||
import { KEY_COMMAND, handleCommandShowUpload, handleCommandSubmit, handleCommandTyping } from '../../commands';
|
import { KEY_COMMAND, handleCommandShowUpload, handleCommandSubmit, handleCommandTyping } from '../../commands';
|
||||||
import getMentionRegexp from './getMentionRegexp';
|
import getMentionRegexp from './getMentionRegexp';
|
||||||
import Mentions from './Mentions';
|
import Mentions from './Mentions';
|
||||||
|
@ -47,13 +45,23 @@ import Navigation from '../../lib/navigation/appNavigation';
|
||||||
import { withActionSheet } from '../ActionSheet';
|
import { withActionSheet } from '../ActionSheet';
|
||||||
import { sanitizeLikeString } from '../../lib/database/utils';
|
import { sanitizeLikeString } from '../../lib/database/utils';
|
||||||
import { CustomIcon } from '../CustomIcon';
|
import { CustomIcon } from '../CustomIcon';
|
||||||
import { IMessage } from '../../definitions/IMessage';
|
|
||||||
import { forceJpgExtension } from './forceJpgExtension';
|
import { forceJpgExtension } from './forceJpgExtension';
|
||||||
import { IBaseScreen, IPreviewItem, IUser, TGetCustomEmoji, TSubscriptionModel, TThreadModel } from '../../definitions';
|
import {
|
||||||
|
IApplicationState,
|
||||||
|
IBaseScreen,
|
||||||
|
IPreviewItem,
|
||||||
|
IUser,
|
||||||
|
TGetCustomEmoji,
|
||||||
|
TSubscriptionModel,
|
||||||
|
TThreadModel,
|
||||||
|
IMessage
|
||||||
|
} from '../../definitions';
|
||||||
import { MasterDetailInsideStackParamList } from '../../stacks/MasterDetailStack/types';
|
import { MasterDetailInsideStackParamList } from '../../stacks/MasterDetailStack/types';
|
||||||
import { getPermalinkMessage, hasPermission, search, sendFileMessage } from '../../lib/methods';
|
import { getPermalinkMessage, search, sendFileMessage } from '../../lib/methods';
|
||||||
|
import { hasPermission, debounce, isAndroid, isTablet } from '../../lib/methods/helpers';
|
||||||
import { Services } from '../../lib/services';
|
import { Services } from '../../lib/services';
|
||||||
import { TSupportedThemes } from '../../theme';
|
import { TSupportedThemes } from '../../theme';
|
||||||
|
import { ChatsStackParamList } from '../../stacks/types';
|
||||||
|
|
||||||
if (isAndroid) {
|
if (isAndroid) {
|
||||||
require('./EmojiKeyboard');
|
require('./EmojiKeyboard');
|
||||||
|
@ -77,7 +85,7 @@ const videoPickerConfig: Options = {
|
||||||
mediaType: 'video'
|
mediaType: 'video'
|
||||||
};
|
};
|
||||||
|
|
||||||
export interface IMessageBoxProps extends IBaseScreen<MasterDetailInsideStackParamList, any> {
|
export interface IMessageBoxProps extends IBaseScreen<ChatsStackParamList & MasterDetailInsideStackParamList, any> {
|
||||||
rid: string;
|
rid: string;
|
||||||
baseUrl: string;
|
baseUrl: string;
|
||||||
message: IMessage;
|
message: IMessage;
|
||||||
|
@ -109,6 +117,7 @@ export interface IMessageBoxProps extends IBaseScreen<MasterDetailInsideStackPar
|
||||||
usedCannedResponse: string;
|
usedCannedResponse: string;
|
||||||
uploadFilePermission: string[];
|
uploadFilePermission: string[];
|
||||||
serverVersion: string;
|
serverVersion: string;
|
||||||
|
goToCannedResponses: () => void | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface IMessageBoxState {
|
interface IMessageBoxState {
|
||||||
|
@ -307,7 +316,17 @@ class MessageBox extends Component<IMessageBoxProps, IMessageBoxState> {
|
||||||
permissionToUpload
|
permissionToUpload
|
||||||
} = this.state;
|
} = this.state;
|
||||||
|
|
||||||
const { roomType, replying, editing, isFocused, message, theme, usedCannedResponse, uploadFilePermission } = this.props;
|
const {
|
||||||
|
roomType,
|
||||||
|
replying,
|
||||||
|
editing,
|
||||||
|
isFocused,
|
||||||
|
message,
|
||||||
|
theme,
|
||||||
|
usedCannedResponse,
|
||||||
|
uploadFilePermission,
|
||||||
|
goToCannedResponses
|
||||||
|
} = this.props;
|
||||||
if (nextProps.theme !== theme) {
|
if (nextProps.theme !== theme) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -359,12 +378,15 @@ class MessageBox extends Component<IMessageBoxProps, IMessageBoxState> {
|
||||||
if (nextProps.usedCannedResponse !== usedCannedResponse) {
|
if (nextProps.usedCannedResponse !== usedCannedResponse) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
if (nextProps.goToCannedResponses !== goToCannedResponses) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidUpdate(prevProps: IMessageBoxProps) {
|
componentDidUpdate(prevProps: IMessageBoxProps) {
|
||||||
const { uploadFilePermission } = this.props;
|
const { uploadFilePermission, goToCannedResponses } = this.props;
|
||||||
if (!dequal(prevProps.uploadFilePermission, uploadFilePermission)) {
|
if (!dequal(prevProps.uploadFilePermission, uploadFilePermission) || prevProps.goToCannedResponses !== goToCannedResponses) {
|
||||||
this.setOptions();
|
this.setOptions();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -785,9 +807,16 @@ class MessageBox extends Component<IMessageBoxProps, IMessageBoxState> {
|
||||||
showMessageBoxActions = () => {
|
showMessageBoxActions = () => {
|
||||||
logEvent(events.ROOM_SHOW_BOX_ACTIONS);
|
logEvent(events.ROOM_SHOW_BOX_ACTIONS);
|
||||||
const { permissionToUpload } = this.state;
|
const { permissionToUpload } = this.state;
|
||||||
const { showActionSheet } = this.props;
|
const { showActionSheet, goToCannedResponses } = this.props;
|
||||||
|
|
||||||
const options = [];
|
const options = [];
|
||||||
|
if (goToCannedResponses) {
|
||||||
|
options.push({
|
||||||
|
title: I18n.t('Canned_Responses'),
|
||||||
|
icon: 'canned-response',
|
||||||
|
onPress: () => goToCannedResponses()
|
||||||
|
});
|
||||||
|
}
|
||||||
if (permissionToUpload) {
|
if (permissionToUpload) {
|
||||||
options.push(
|
options.push(
|
||||||
{
|
{
|
||||||
|
@ -1106,7 +1135,6 @@ class MessageBox extends Component<IMessageBoxProps, IMessageBoxState> {
|
||||||
defaultValue=''
|
defaultValue=''
|
||||||
multiline
|
multiline
|
||||||
testID={`messagebox-input${tmid ? '-thread' : ''}`}
|
testID={`messagebox-input${tmid ? '-thread' : ''}`}
|
||||||
theme={theme}
|
|
||||||
{...isAndroidTablet}
|
{...isAndroidTablet}
|
||||||
/>
|
/>
|
||||||
<RightButtons
|
<RightButtons
|
||||||
|
@ -1172,7 +1200,7 @@ class MessageBox extends Component<IMessageBoxProps, IMessageBoxState> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const mapStateToProps = (state: any) => ({
|
const mapStateToProps = (state: IApplicationState) => ({
|
||||||
isMasterDetail: state.app.isMasterDetail,
|
isMasterDetail: state.app.isMasterDetail,
|
||||||
baseUrl: state.server.server,
|
baseUrl: state.server.server,
|
||||||
threadsEnabled: state.settings.Threads_enabled,
|
threadsEnabled: state.settings.Threads_enabled,
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { StyleSheet } from 'react-native';
|
import { StyleSheet } from 'react-native';
|
||||||
|
|
||||||
import { isIOS } from '../../utils/deviceInfo';
|
import { isIOS } from '../../lib/methods/helpers';
|
||||||
import sharedStyles from '../../views/Styles';
|
import sharedStyles from '../../views/Styles';
|
||||||
|
|
||||||
const MENTION_HEIGHT = 50;
|
const MENTION_HEIGHT = 50;
|
||||||
|
|
|
@ -5,7 +5,7 @@ import database from '../lib/database';
|
||||||
import protectedFunction from '../lib/methods/helpers/protectedFunction';
|
import protectedFunction from '../lib/methods/helpers/protectedFunction';
|
||||||
import { useActionSheet } from './ActionSheet';
|
import { useActionSheet } from './ActionSheet';
|
||||||
import I18n from '../i18n';
|
import I18n from '../i18n';
|
||||||
import log from '../utils/log';
|
import log from '../lib/methods/helpers/log';
|
||||||
import { TMessageModel } from '../definitions';
|
import { TMessageModel } from '../definitions';
|
||||||
import { resendMessage } from '../lib/methods';
|
import { resendMessage } from '../lib/methods';
|
||||||
|
|
||||||
|
|
|
@ -3,7 +3,7 @@ import { Text } from 'react-native';
|
||||||
|
|
||||||
import styles from './styles';
|
import styles from './styles';
|
||||||
import { themes } from '../../../lib/constants';
|
import { themes } from '../../../lib/constants';
|
||||||
import Touch from '../../../utils/touch';
|
import Touch from '../../../lib/methods/helpers/touch';
|
||||||
import { CustomIcon, TIconsName } from '../../CustomIcon';
|
import { CustomIcon, TIconsName } from '../../CustomIcon';
|
||||||
import { useTheme } from '../../../theme';
|
import { useTheme } from '../../../theme';
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,7 @@ import React, { useEffect, useState } from 'react';
|
||||||
import { Grid } from 'react-native-easy-grid';
|
import { Grid } from 'react-native-easy-grid';
|
||||||
|
|
||||||
import { themes } from '../../../lib/constants';
|
import { themes } from '../../../lib/constants';
|
||||||
import { resetAttempts } from '../../../utils/localAuthentication';
|
import { resetAttempts } from '../../../lib/methods/helpers/localAuthentication';
|
||||||
import { TYPE } from '../constants';
|
import { TYPE } from '../constants';
|
||||||
import { getDiff, getLockedUntil } from '../utils';
|
import { getDiff, getLockedUntil } from '../utils';
|
||||||
import I18n from '../../../i18n';
|
import I18n from '../../../i18n';
|
||||||
|
|
|
@ -8,7 +8,7 @@ import Base, { IBase } from './Base';
|
||||||
import Locked from './Base/Locked';
|
import Locked from './Base/Locked';
|
||||||
import { TYPE } from './constants';
|
import { TYPE } from './constants';
|
||||||
import { ATTEMPTS_KEY, LOCKED_OUT_TIMER_KEY, MAX_ATTEMPTS, PASSCODE_KEY } from '../../lib/constants';
|
import { ATTEMPTS_KEY, LOCKED_OUT_TIMER_KEY, MAX_ATTEMPTS, PASSCODE_KEY } from '../../lib/constants';
|
||||||
import { biometryAuth, resetAttempts } from '../../utils/localAuthentication';
|
import { biometryAuth, resetAttempts } from '../../lib/methods/helpers/localAuthentication';
|
||||||
import { getDiff, getLockedUntil } from './utils';
|
import { getDiff, getLockedUntil } from './utils';
|
||||||
import { useUserPreferences } from '../../lib/methods/userPreferences';
|
import { useUserPreferences } from '../../lib/methods/userPreferences';
|
||||||
import I18n from '../../i18n';
|
import I18n from '../../i18n';
|
||||||
|
|
|
@ -1,24 +1,33 @@
|
||||||
/* eslint-disable import/no-extraneous-dependencies, import/no-unresolved, import/extensions, react/prop-types, react/destructuring-assignment */
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Dimensions, View } from 'react-native';
|
import { Dimensions, SafeAreaView } from 'react-native';
|
||||||
import { storiesOf } from '@storybook/react-native';
|
import { storiesOf } from '@storybook/react-native';
|
||||||
import { Provider } from 'react-redux';
|
import { Provider } from 'react-redux';
|
||||||
|
import { SafeAreaProvider } from 'react-native-safe-area-context';
|
||||||
|
import { Header, HeaderBackground } from '@react-navigation/elements';
|
||||||
|
|
||||||
import Header from '../Header';
|
|
||||||
import { longText } from '../../../storybook/utils';
|
import { longText } from '../../../storybook/utils';
|
||||||
import { ThemeContext } from '../../theme';
|
import { ThemeContext } from '../../theme';
|
||||||
import { store } from '../../../storybook/stories';
|
import { store } from '../../../storybook/stories';
|
||||||
|
import { colors, themes } from '../../lib/constants';
|
||||||
import RoomHeaderComponent from './RoomHeader';
|
import RoomHeaderComponent from './RoomHeader';
|
||||||
|
|
||||||
const stories = storiesOf('RoomHeader', module).addDecorator(story => <Provider store={store}>{story()}</Provider>);
|
const stories = storiesOf('RoomHeader', module)
|
||||||
|
.addDecorator(story => <Provider store={store}>{story()}</Provider>)
|
||||||
// TODO: refactor after react-navigation v6
|
.addDecorator(story => <SafeAreaProvider>{story()}</SafeAreaProvider>);
|
||||||
const HeaderExample = ({ title }) => (
|
|
||||||
<Header headerTitle={() => <View style={{ flex: 1, paddingHorizontal: 12 }}>{title()}</View>} />
|
|
||||||
);
|
|
||||||
|
|
||||||
const { width, height } = Dimensions.get('window');
|
const { width, height } = Dimensions.get('window');
|
||||||
|
|
||||||
|
const HeaderExample = ({ title, theme = 'light' }) => (
|
||||||
|
<SafeAreaView>
|
||||||
|
<Header
|
||||||
|
title=''
|
||||||
|
headerTitle={title}
|
||||||
|
headerTitleAlign='left'
|
||||||
|
headerBackground={() => <HeaderBackground style={{ backgroundColor: themes[theme].headerBackground }} />}
|
||||||
|
/>
|
||||||
|
</SafeAreaView>
|
||||||
|
);
|
||||||
|
|
||||||
const RoomHeader = ({ ...props }) => (
|
const RoomHeader = ({ ...props }) => (
|
||||||
<RoomHeaderComponent
|
<RoomHeaderComponent
|
||||||
width={width}
|
width={width}
|
||||||
|
@ -27,6 +36,8 @@ const RoomHeader = ({ ...props }) => (
|
||||||
type='p'
|
type='p'
|
||||||
testID={props.title}
|
testID={props.title}
|
||||||
onPress={() => alert('header pressed!')}
|
onPress={() => alert('header pressed!')}
|
||||||
|
status={props.status}
|
||||||
|
usersTyping={props.usersTyping}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
@ -82,8 +93,8 @@ stories.add('thread', () => (
|
||||||
));
|
));
|
||||||
|
|
||||||
const ThemeStory = ({ theme }) => (
|
const ThemeStory = ({ theme }) => (
|
||||||
<ThemeContext.Provider value={{ theme }}>
|
<ThemeContext.Provider value={{ theme, colors: colors[theme] }}>
|
||||||
<HeaderExample title={() => <RoomHeader subtitle='subtitle' />} />
|
<HeaderExample title={() => <RoomHeader subtitle='subtitle' />} theme={theme} />
|
||||||
</ThemeContext.Provider>
|
</ThemeContext.Provider>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -3,7 +3,6 @@ import { StyleSheet, Text, TouchableOpacity, View } from 'react-native';
|
||||||
|
|
||||||
import I18n from '../../i18n';
|
import I18n from '../../i18n';
|
||||||
import sharedStyles from '../../views/Styles';
|
import sharedStyles from '../../views/Styles';
|
||||||
import { themes } from '../../lib/constants';
|
|
||||||
import { MarkdownPreview } from '../markdown';
|
import { MarkdownPreview } from '../markdown';
|
||||||
import RoomTypeIcon from '../RoomTypeIcon';
|
import RoomTypeIcon from '../RoomTypeIcon';
|
||||||
import { TUserStatus, IOmnichannelSource } from '../../definitions';
|
import { TUserStatus, IOmnichannelSource } from '../../definitions';
|
||||||
|
@ -44,39 +43,39 @@ const styles = StyleSheet.create({
|
||||||
|
|
||||||
type TRoomHeaderSubTitle = {
|
type TRoomHeaderSubTitle = {
|
||||||
usersTyping: [];
|
usersTyping: [];
|
||||||
subtitle: string;
|
subtitle?: string;
|
||||||
renderFunc?: () => React.ReactElement;
|
renderFunc?: () => React.ReactElement;
|
||||||
scale: number;
|
scale: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
type TRoomHeaderHeaderTitle = {
|
type TRoomHeaderHeaderTitle = {
|
||||||
title: string;
|
title?: string;
|
||||||
tmid: string;
|
tmid?: string;
|
||||||
prid: string;
|
prid?: string;
|
||||||
scale: number;
|
scale: number;
|
||||||
testID: string;
|
testID?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
interface IRoomHeader {
|
interface IRoomHeader {
|
||||||
title: string;
|
title?: string;
|
||||||
subtitle: string;
|
subtitle?: string;
|
||||||
type: string;
|
type: string;
|
||||||
width: number;
|
width: number;
|
||||||
height: number;
|
height: number;
|
||||||
prid: string;
|
prid?: string;
|
||||||
tmid: string;
|
tmid?: string;
|
||||||
teamMain: boolean;
|
teamMain?: boolean;
|
||||||
status: TUserStatus;
|
status: TUserStatus;
|
||||||
usersTyping: [];
|
usersTyping: [];
|
||||||
isGroupChat: boolean;
|
isGroupChat?: boolean;
|
||||||
parentTitle: string;
|
parentTitle?: string;
|
||||||
onPress: () => void;
|
onPress: Function;
|
||||||
testID: string;
|
testID?: string;
|
||||||
sourceType?: IOmnichannelSource;
|
sourceType?: IOmnichannelSource;
|
||||||
}
|
}
|
||||||
|
|
||||||
const SubTitle = React.memo(({ usersTyping, subtitle, renderFunc, scale }: TRoomHeaderSubTitle) => {
|
const SubTitle = React.memo(({ usersTyping, subtitle, renderFunc, scale }: TRoomHeaderSubTitle) => {
|
||||||
const { theme } = useTheme();
|
const { colors } = useTheme();
|
||||||
const fontSize = getSubTitleSize(scale);
|
const fontSize = getSubTitleSize(scale);
|
||||||
// typing
|
// typing
|
||||||
if (usersTyping.length) {
|
if (usersTyping.length) {
|
||||||
|
@ -87,7 +86,7 @@ const SubTitle = React.memo(({ usersTyping, subtitle, renderFunc, scale }: TRoom
|
||||||
usersText = usersTyping.join(', ');
|
usersText = usersTyping.join(', ');
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<Text style={[styles.subtitle, { fontSize, color: themes[theme].auxiliaryText }]} numberOfLines={1}>
|
<Text style={[styles.subtitle, { fontSize, color: colors.auxiliaryText }]} numberOfLines={1}>
|
||||||
<Text style={styles.typingUsers}>{usersText} </Text>
|
<Text style={styles.typingUsers}>{usersText} </Text>
|
||||||
{usersTyping.length > 1 ? I18n.t('are_typing') : I18n.t('is_typing')}...
|
{usersTyping.length > 1 ? I18n.t('are_typing') : I18n.t('is_typing')}...
|
||||||
</Text>
|
</Text>
|
||||||
|
@ -101,15 +100,15 @@ const SubTitle = React.memo(({ usersTyping, subtitle, renderFunc, scale }: TRoom
|
||||||
|
|
||||||
// subtitle
|
// subtitle
|
||||||
if (subtitle) {
|
if (subtitle) {
|
||||||
return <MarkdownPreview msg={subtitle} style={[styles.subtitle, { fontSize, color: themes[theme].auxiliaryText }]} />;
|
return <MarkdownPreview msg={subtitle} style={[styles.subtitle, { fontSize, color: colors.auxiliaryText }]} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
});
|
});
|
||||||
|
|
||||||
const HeaderTitle = React.memo(({ title, tmid, prid, scale, testID }: TRoomHeaderHeaderTitle) => {
|
const HeaderTitle = React.memo(({ title, tmid, prid, scale, testID }: TRoomHeaderHeaderTitle) => {
|
||||||
const { theme } = useTheme();
|
const { colors } = useTheme();
|
||||||
const titleStyle = { fontSize: TITLE_SIZE * scale, color: themes[theme].headerTitleColor };
|
const titleStyle = { fontSize: TITLE_SIZE * scale, color: colors.headerTitleColor };
|
||||||
if (!tmid && !prid) {
|
if (!tmid && !prid) {
|
||||||
return (
|
return (
|
||||||
<Text style={[styles.title, titleStyle]} numberOfLines={1} testID={testID}>
|
<Text style={[styles.title, titleStyle]} numberOfLines={1} testID={testID}>
|
||||||
|
@ -139,7 +138,7 @@ const Header = React.memo(
|
||||||
usersTyping = [],
|
usersTyping = [],
|
||||||
sourceType
|
sourceType
|
||||||
}: IRoomHeader) => {
|
}: IRoomHeader) => {
|
||||||
const { theme } = useTheme();
|
const { colors } = useTheme();
|
||||||
const portrait = height > width;
|
const portrait = height > width;
|
||||||
let scale = 1;
|
let scale = 1;
|
||||||
|
|
||||||
|
@ -154,7 +153,7 @@ const Header = React.memo(
|
||||||
renderFunc = () => (
|
renderFunc = () => (
|
||||||
<View style={styles.titleContainer}>
|
<View style={styles.titleContainer}>
|
||||||
<RoomTypeIcon type={prid ? 'discussion' : type} isGroupChat={isGroupChat} status={status} teamMain={teamMain} />
|
<RoomTypeIcon type={prid ? 'discussion' : type} isGroupChat={isGroupChat} status={status} teamMain={teamMain} />
|
||||||
<Text style={[styles.subtitle, { color: themes[theme].auxiliaryText }]} numberOfLines={1}>
|
<Text style={[styles.subtitle, { color: colors.auxiliaryText }]} numberOfLines={1}>
|
||||||
{parentTitle}
|
{parentTitle}
|
||||||
</Text>
|
</Text>
|
||||||
</View>
|
</View>
|
||||||
|
|
File diff suppressed because one or more lines are too long
|
@ -1,117 +1,56 @@
|
||||||
import { dequal } from 'dequal';
|
import React from 'react';
|
||||||
import React, { Component } from 'react';
|
import { shallowEqual, useSelector } from 'react-redux';
|
||||||
import { connect } from 'react-redux';
|
|
||||||
|
|
||||||
import { IApplicationState, TUserStatus, IOmnichannelSource } from '../../definitions';
|
import { IApplicationState, TUserStatus, IOmnichannelSource, IVisitor } from '../../definitions';
|
||||||
import { withDimensions } from '../../dimensions';
|
import { useDimensions } from '../../dimensions';
|
||||||
import I18n from '../../i18n';
|
import I18n from '../../i18n';
|
||||||
import RoomHeader from './RoomHeader';
|
import RoomHeader from './RoomHeader';
|
||||||
|
|
||||||
interface IRoomHeaderContainerProps {
|
interface IRoomHeaderContainerProps {
|
||||||
title: string;
|
title?: string;
|
||||||
subtitle: string;
|
subtitle?: string;
|
||||||
type: string;
|
type: string;
|
||||||
prid: string;
|
prid?: string;
|
||||||
tmid: string;
|
tmid?: string;
|
||||||
teamMain: boolean;
|
teamMain?: boolean;
|
||||||
usersTyping: [];
|
roomUserId?: string | null;
|
||||||
status: TUserStatus;
|
onPress: Function;
|
||||||
statusText: string;
|
parentTitle?: string;
|
||||||
connecting: boolean;
|
isGroupChat?: boolean;
|
||||||
connected: boolean;
|
testID?: string;
|
||||||
roomUserId: string;
|
|
||||||
widthOffset: number;
|
|
||||||
onPress(): void;
|
|
||||||
width: number;
|
|
||||||
height: number;
|
|
||||||
parentTitle: string;
|
|
||||||
isGroupChat: boolean;
|
|
||||||
testID: string;
|
|
||||||
sourceType?: IOmnichannelSource;
|
sourceType?: IOmnichannelSource;
|
||||||
|
visitor?: IVisitor;
|
||||||
}
|
}
|
||||||
|
|
||||||
class RoomHeaderContainer extends Component<IRoomHeaderContainerProps, any> {
|
const RoomHeaderContainer = React.memo(
|
||||||
shouldComponentUpdate(nextProps: IRoomHeaderContainerProps) {
|
({
|
||||||
const {
|
isGroupChat,
|
||||||
type,
|
onPress,
|
||||||
title,
|
parentTitle,
|
||||||
subtitle,
|
prid,
|
||||||
status,
|
roomUserId,
|
||||||
statusText,
|
subtitle: subtitleProp,
|
||||||
connecting,
|
teamMain,
|
||||||
connected,
|
testID,
|
||||||
onPress,
|
title,
|
||||||
usersTyping,
|
tmid,
|
||||||
width,
|
type,
|
||||||
height,
|
sourceType,
|
||||||
teamMain,
|
visitor
|
||||||
sourceType
|
}: IRoomHeaderContainerProps) => {
|
||||||
} = this.props;
|
let subtitle: string | undefined;
|
||||||
if (nextProps.type !== type) {
|
let status: TUserStatus = 'offline';
|
||||||
return true;
|
let statusText: string | undefined;
|
||||||
}
|
const { width, height } = useDimensions();
|
||||||
if (nextProps.title !== title) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (nextProps.subtitle !== subtitle) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (nextProps.status !== status) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (nextProps.statusText !== statusText) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (nextProps.connecting !== connecting) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (nextProps.connected !== connected) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (nextProps.width !== width) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (nextProps.height !== height) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (!dequal(nextProps.usersTyping, usersTyping)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (!dequal(nextProps.sourceType, sourceType)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (nextProps.onPress !== onPress) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (nextProps.teamMain !== teamMain) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
const connecting = useSelector((state: IApplicationState) => state.meteor.connecting || state.server.loading);
|
||||||
const {
|
const usersTyping = useSelector((state: IApplicationState) => state.usersTyping, shallowEqual);
|
||||||
title,
|
const connected = useSelector((state: IApplicationState) => state.meteor.connected);
|
||||||
subtitle: subtitleProp,
|
const activeUser = useSelector(
|
||||||
type,
|
(state: IApplicationState) => (roomUserId ? state.activeUsers?.[roomUserId] : undefined),
|
||||||
teamMain,
|
shallowEqual
|
||||||
prid,
|
);
|
||||||
tmid,
|
|
||||||
status = 'offline',
|
|
||||||
statusText,
|
|
||||||
connecting,
|
|
||||||
connected,
|
|
||||||
usersTyping,
|
|
||||||
onPress,
|
|
||||||
width,
|
|
||||||
height,
|
|
||||||
parentTitle,
|
|
||||||
isGroupChat,
|
|
||||||
testID,
|
|
||||||
sourceType
|
|
||||||
} = this.props;
|
|
||||||
|
|
||||||
let subtitle;
|
|
||||||
if (connecting) {
|
if (connecting) {
|
||||||
subtitle = I18n.t('Connecting');
|
subtitle = I18n.t('Connecting');
|
||||||
} else if (!connected) {
|
} else if (!connected) {
|
||||||
|
@ -120,6 +59,17 @@ class RoomHeaderContainer extends Component<IRoomHeaderContainerProps, any> {
|
||||||
subtitle = subtitleProp;
|
subtitle = subtitleProp;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (connected) {
|
||||||
|
if ((type === 'd' || (tmid && roomUserId)) && activeUser) {
|
||||||
|
const { status: statusActiveUser, statusText: statusTextActiveUser } = activeUser;
|
||||||
|
status = statusActiveUser;
|
||||||
|
statusText = statusTextActiveUser;
|
||||||
|
} else if (type === 'l' && visitor?.status) {
|
||||||
|
const { status: statusVisitor } = visitor;
|
||||||
|
status = statusVisitor;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<RoomHeader
|
<RoomHeader
|
||||||
prid={prid}
|
prid={prid}
|
||||||
|
@ -140,28 +90,6 @@ class RoomHeaderContainer extends Component<IRoomHeaderContainerProps, any> {
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
);
|
||||||
|
|
||||||
const mapStateToProps = (state: IApplicationState, ownProps: any) => {
|
export default RoomHeaderContainer;
|
||||||
let statusText = '';
|
|
||||||
let status = 'offline';
|
|
||||||
const { roomUserId, type, visitor = {}, tmid } = ownProps;
|
|
||||||
|
|
||||||
if (state.meteor.connected) {
|
|
||||||
if ((type === 'd' || (tmid && roomUserId)) && state.activeUsers[roomUserId]) {
|
|
||||||
({ status, statusText } = state.activeUsers[roomUserId]);
|
|
||||||
} else if (type === 'l' && visitor?.status) {
|
|
||||||
({ status } = visitor);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
connecting: state.meteor.connecting || state.server.loading,
|
|
||||||
connected: state.meteor.connected,
|
|
||||||
usersTyping: state.usersTyping,
|
|
||||||
status: status as TUserStatus,
|
|
||||||
statusText
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
export default connect(mapStateToProps)(withDimensions(RoomHeaderContainer));
|
|
||||||
|
|
|
@ -1,25 +1,32 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Animated, View } from 'react-native';
|
import { View } from 'react-native';
|
||||||
|
import Animated, {
|
||||||
|
useAnimatedStyle,
|
||||||
|
interpolate,
|
||||||
|
withSpring,
|
||||||
|
runOnJS,
|
||||||
|
useAnimatedReaction,
|
||||||
|
useSharedValue
|
||||||
|
} from 'react-native-reanimated';
|
||||||
import { RectButton } from 'react-native-gesture-handler';
|
import { RectButton } from 'react-native-gesture-handler';
|
||||||
|
import * as Haptics from 'expo-haptics';
|
||||||
|
|
||||||
import { isRTL } from '../../i18n';
|
|
||||||
import { CustomIcon } from '../CustomIcon';
|
import { CustomIcon } from '../CustomIcon';
|
||||||
import { DisplayMode, themes } from '../../lib/constants';
|
import { DisplayMode } from '../../lib/constants';
|
||||||
import styles, { ACTION_WIDTH, LONG_SWIPE, ROW_HEIGHT_CONDENSED } from './styles';
|
import styles, { ACTION_WIDTH, LONG_SWIPE, ROW_HEIGHT_CONDENSED } from './styles';
|
||||||
import { ILeftActionsProps, IRightActionsProps } from './interfaces';
|
import { ILeftActionsProps, IRightActionsProps } from './interfaces';
|
||||||
|
import { useTheme } from '../../theme';
|
||||||
|
import I18n from '../../i18n';
|
||||||
|
|
||||||
const reverse = new Animated.Value(isRTL() ? -1 : 1);
|
|
||||||
const CONDENSED_ICON_SIZE = 24;
|
const CONDENSED_ICON_SIZE = 24;
|
||||||
const EXPANDED_ICON_SIZE = 28;
|
const EXPANDED_ICON_SIZE = 28;
|
||||||
|
|
||||||
export const LeftActions = React.memo(({ theme, transX, isRead, width, onToggleReadPress, displayMode }: ILeftActionsProps) => {
|
export const LeftActions = React.memo(({ transX, isRead, width, onToggleReadPress, displayMode }: ILeftActionsProps) => {
|
||||||
const translateX = Animated.multiply(
|
const { colors } = useTheme();
|
||||||
transX.interpolate({
|
|
||||||
inputRange: [0, ACTION_WIDTH],
|
const animatedStyles = useAnimatedStyle(() => ({
|
||||||
outputRange: [-ACTION_WIDTH, 0]
|
transform: [{ translateX: transX.value }]
|
||||||
}),
|
}));
|
||||||
reverse
|
|
||||||
);
|
|
||||||
|
|
||||||
const isCondensed = displayMode === DisplayMode.Condensed;
|
const isCondensed = displayMode === DisplayMode.Condensed;
|
||||||
const viewHeight = isCondensed ? { height: ROW_HEIGHT_CONDENSED } : null;
|
const viewHeight = isCondensed ? { height: ROW_HEIGHT_CONDENSED } : null;
|
||||||
|
@ -29,20 +36,16 @@ export const LeftActions = React.memo(({ theme, transX, isRead, width, onToggleR
|
||||||
<Animated.View
|
<Animated.View
|
||||||
style={[
|
style={[
|
||||||
styles.actionLeftButtonContainer,
|
styles.actionLeftButtonContainer,
|
||||||
{
|
{ width: width * 2, backgroundColor: colors.tintColor, right: '100%' },
|
||||||
right: width - ACTION_WIDTH,
|
viewHeight,
|
||||||
width,
|
animatedStyles
|
||||||
transform: [{ translateX }],
|
|
||||||
backgroundColor: themes[theme].tintColor
|
|
||||||
},
|
|
||||||
viewHeight
|
|
||||||
]}>
|
]}>
|
||||||
<View style={[styles.actionLeftButtonContainer, viewHeight]}>
|
<View style={[styles.actionLeftButtonContainer, viewHeight]}>
|
||||||
<RectButton style={styles.actionButton} onPress={onToggleReadPress}>
|
<RectButton style={styles.actionButton} onPress={onToggleReadPress}>
|
||||||
<CustomIcon
|
<CustomIcon
|
||||||
size={isCondensed ? CONDENSED_ICON_SIZE : EXPANDED_ICON_SIZE}
|
size={isCondensed ? CONDENSED_ICON_SIZE : EXPANDED_ICON_SIZE}
|
||||||
name={isRead ? 'flag' : 'check'}
|
name={isRead ? 'flag' : 'check'}
|
||||||
color={themes[theme].buttonText}
|
color={colors.buttonText}
|
||||||
/>
|
/>
|
||||||
</RectButton>
|
</RectButton>
|
||||||
</View>
|
</View>
|
||||||
|
@ -51,64 +54,102 @@ export const LeftActions = React.memo(({ theme, transX, isRead, width, onToggleR
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
export const RightActions = React.memo(
|
export const RightActions = React.memo(({ transX, favorite, width, toggleFav, onHidePress, displayMode }: IRightActionsProps) => {
|
||||||
({ transX, favorite, width, toggleFav, onHidePress, theme, displayMode }: IRightActionsProps) => {
|
const { colors } = useTheme();
|
||||||
const translateXFav = Animated.multiply(
|
|
||||||
transX.interpolate({
|
|
||||||
inputRange: [-width / 2, -ACTION_WIDTH * 2, 0],
|
|
||||||
outputRange: [width / 2, width - ACTION_WIDTH * 2, width]
|
|
||||||
}),
|
|
||||||
reverse
|
|
||||||
);
|
|
||||||
const translateXHide = Animated.multiply(
|
|
||||||
transX.interpolate({
|
|
||||||
inputRange: [-width, -LONG_SWIPE, -ACTION_WIDTH * 2, 0],
|
|
||||||
outputRange: [0, width - LONG_SWIPE, width - ACTION_WIDTH, width]
|
|
||||||
}),
|
|
||||||
reverse
|
|
||||||
);
|
|
||||||
|
|
||||||
const isCondensed = displayMode === DisplayMode.Condensed;
|
const animatedFavStyles = useAnimatedStyle(() => ({ transform: [{ translateX: transX.value }] }));
|
||||||
const viewHeight = isCondensed ? { height: ROW_HEIGHT_CONDENSED } : null;
|
|
||||||
|
|
||||||
return (
|
const translateXHide = useSharedValue(0);
|
||||||
<View style={[styles.actionsLeftContainer, viewHeight]} pointerEvents='box-none'>
|
|
||||||
<Animated.View
|
const triggerHideAnimation = (toValue: number) => {
|
||||||
style={[
|
Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light);
|
||||||
styles.actionRightButtonContainer,
|
translateXHide.value = withSpring(toValue, { overshootClamping: true, mass: 0.7 });
|
||||||
{
|
};
|
||||||
width,
|
|
||||||
transform: [{ translateX: translateXFav }],
|
useAnimatedReaction(
|
||||||
backgroundColor: themes[theme].hideBackground
|
() => transX.value,
|
||||||
},
|
(currentTransX, previousTransX) => {
|
||||||
viewHeight
|
// Triggers the animation and hapticFeedback if swipe reaches/unreaches the threshold.
|
||||||
]}>
|
if (I18n.isRTL) {
|
||||||
<RectButton style={[styles.actionButton, { backgroundColor: themes[theme].favoriteBackground }]} onPress={toggleFav}>
|
if (previousTransX && currentTransX > LONG_SWIPE && previousTransX <= LONG_SWIPE) {
|
||||||
<CustomIcon
|
runOnJS(triggerHideAnimation)(ACTION_WIDTH);
|
||||||
size={isCondensed ? CONDENSED_ICON_SIZE : EXPANDED_ICON_SIZE}
|
} else if (previousTransX && currentTransX <= LONG_SWIPE && previousTransX > LONG_SWIPE) {
|
||||||
name={favorite ? 'star-filled' : 'star'}
|
runOnJS(triggerHideAnimation)(0);
|
||||||
color={themes[theme].buttonText}
|
}
|
||||||
/>
|
} else if (previousTransX && currentTransX < -LONG_SWIPE && previousTransX >= -LONG_SWIPE) {
|
||||||
</RectButton>
|
runOnJS(triggerHideAnimation)(-ACTION_WIDTH);
|
||||||
</Animated.View>
|
} else if (previousTransX && currentTransX >= -LONG_SWIPE && previousTransX < -LONG_SWIPE) {
|
||||||
<Animated.View
|
runOnJS(triggerHideAnimation)(0);
|
||||||
style={[
|
}
|
||||||
styles.actionRightButtonContainer,
|
}
|
||||||
{
|
);
|
||||||
width,
|
|
||||||
transform: [{ translateX: translateXHide }]
|
const animatedHideStyles = useAnimatedStyle(() => {
|
||||||
},
|
if (I18n.isRTL) {
|
||||||
isCondensed && { height: ROW_HEIGHT_CONDENSED }
|
if (transX.value < LONG_SWIPE && transX.value >= 2 * ACTION_WIDTH) {
|
||||||
]}>
|
const parallaxSwipe = interpolate(
|
||||||
<RectButton style={[styles.actionButton, { backgroundColor: themes[theme].hideBackground }]} onPress={onHidePress}>
|
transX.value,
|
||||||
<CustomIcon
|
[2 * ACTION_WIDTH, LONG_SWIPE],
|
||||||
size={isCondensed ? CONDENSED_ICON_SIZE : EXPANDED_ICON_SIZE}
|
[ACTION_WIDTH, ACTION_WIDTH + 0.1 * transX.value]
|
||||||
name='unread-on-top-disabled'
|
);
|
||||||
color={themes[theme].buttonText}
|
return { transform: [{ translateX: parallaxSwipe + translateXHide.value }] };
|
||||||
/>
|
}
|
||||||
</RectButton>
|
return { transform: [{ translateX: transX.value - ACTION_WIDTH + translateXHide.value }] };
|
||||||
</Animated.View>
|
}
|
||||||
</View>
|
if (transX.value > -LONG_SWIPE && transX.value <= -2 * ACTION_WIDTH) {
|
||||||
);
|
const parallaxSwipe = interpolate(
|
||||||
}
|
transX.value,
|
||||||
);
|
[-2 * ACTION_WIDTH, -LONG_SWIPE],
|
||||||
|
[-ACTION_WIDTH, -ACTION_WIDTH + 0.1 * transX.value]
|
||||||
|
);
|
||||||
|
return { transform: [{ translateX: parallaxSwipe + translateXHide.value }] };
|
||||||
|
}
|
||||||
|
return { transform: [{ translateX: transX.value + ACTION_WIDTH + translateXHide.value }] };
|
||||||
|
});
|
||||||
|
|
||||||
|
const isCondensed = displayMode === DisplayMode.Condensed;
|
||||||
|
const viewHeight = isCondensed ? { height: ROW_HEIGHT_CONDENSED } : null;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<View style={[styles.actionsLeftContainer, viewHeight]} pointerEvents='box-none'>
|
||||||
|
<Animated.View
|
||||||
|
style={[
|
||||||
|
styles.actionRightButtonContainer,
|
||||||
|
{
|
||||||
|
width,
|
||||||
|
backgroundColor: colors.favoriteBackground,
|
||||||
|
left: '100%'
|
||||||
|
},
|
||||||
|
viewHeight,
|
||||||
|
animatedFavStyles
|
||||||
|
]}>
|
||||||
|
<RectButton style={[styles.actionButton, { backgroundColor: colors.favoriteBackground }]} onPress={toggleFav}>
|
||||||
|
<CustomIcon
|
||||||
|
size={isCondensed ? CONDENSED_ICON_SIZE : EXPANDED_ICON_SIZE}
|
||||||
|
name={favorite ? 'star-filled' : 'star'}
|
||||||
|
color={colors.buttonText}
|
||||||
|
/>
|
||||||
|
</RectButton>
|
||||||
|
</Animated.View>
|
||||||
|
<Animated.View
|
||||||
|
style={[
|
||||||
|
styles.actionRightButtonContainer,
|
||||||
|
{
|
||||||
|
width: width * 2,
|
||||||
|
backgroundColor: colors.hideBackground,
|
||||||
|
left: '100%'
|
||||||
|
},
|
||||||
|
isCondensed && { height: ROW_HEIGHT_CONDENSED },
|
||||||
|
animatedHideStyles
|
||||||
|
]}>
|
||||||
|
<RectButton style={[styles.actionButton, { backgroundColor: colors.hideBackground }]} onPress={onHidePress}>
|
||||||
|
<CustomIcon
|
||||||
|
size={isCondensed ? CONDENSED_ICON_SIZE : EXPANDED_ICON_SIZE}
|
||||||
|
name='unread-on-top-disabled'
|
||||||
|
color={colors.buttonText}
|
||||||
|
/>
|
||||||
|
</RectButton>
|
||||||
|
</Animated.View>
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { View } from 'react-native';
|
import { View } from 'react-native';
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
|
|
||||||
import Avatar from '../Avatar';
|
import Avatar from '../Avatar';
|
||||||
import { DisplayMode } from '../../lib/constants';
|
import { DisplayMode } from '../../lib/constants';
|
||||||
import TypeIcon from './TypeIcon';
|
import TypeIcon from './TypeIcon';
|
||||||
import styles from './styles';
|
import styles from './styles';
|
||||||
|
import { IIconOrAvatar } from './interfaces';
|
||||||
|
|
||||||
const IconOrAvatar = ({
|
const IconOrAvatar = ({
|
||||||
avatar,
|
avatar,
|
||||||
|
@ -17,10 +17,9 @@ const IconOrAvatar = ({
|
||||||
isGroupChat,
|
isGroupChat,
|
||||||
teamMain,
|
teamMain,
|
||||||
showLastMessage,
|
showLastMessage,
|
||||||
theme,
|
|
||||||
displayMode,
|
displayMode,
|
||||||
sourceType
|
sourceType
|
||||||
}) => {
|
}: IIconOrAvatar): React.ReactElement | null => {
|
||||||
if (showAvatar) {
|
if (showAvatar) {
|
||||||
return (
|
return (
|
||||||
<Avatar text={avatar} size={displayMode === DisplayMode.Condensed ? 36 : 48} type={type} style={styles.avatar} rid={rid} />
|
<Avatar text={avatar} size={displayMode === DisplayMode.Condensed ? 36 : 48} type={type} style={styles.avatar} rid={rid} />
|
||||||
|
@ -35,7 +34,6 @@ const IconOrAvatar = ({
|
||||||
prid={prid}
|
prid={prid}
|
||||||
status={status}
|
status={status}
|
||||||
isGroupChat={isGroupChat}
|
isGroupChat={isGroupChat}
|
||||||
theme={theme}
|
|
||||||
teamMain={teamMain}
|
teamMain={teamMain}
|
||||||
size={24}
|
size={24}
|
||||||
style={{ marginRight: 12 }}
|
style={{ marginRight: 12 }}
|
||||||
|
@ -48,18 +46,4 @@ const IconOrAvatar = ({
|
||||||
return null;
|
return null;
|
||||||
};
|
};
|
||||||
|
|
||||||
IconOrAvatar.propTypes = {
|
|
||||||
avatar: PropTypes.string,
|
|
||||||
type: PropTypes.string,
|
|
||||||
theme: PropTypes.string,
|
|
||||||
rid: PropTypes.string,
|
|
||||||
showAvatar: PropTypes.bool,
|
|
||||||
displayMode: PropTypes.string,
|
|
||||||
prid: PropTypes.string,
|
|
||||||
status: PropTypes.string,
|
|
||||||
isGroupChat: PropTypes.bool,
|
|
||||||
teamMain: PropTypes.bool,
|
|
||||||
showLastMessage: PropTypes.bool
|
|
||||||
};
|
|
||||||
|
|
||||||
export default IconOrAvatar;
|
export default IconOrAvatar;
|
|
@ -4,8 +4,9 @@ import { dequal } from 'dequal';
|
||||||
import I18n from '../../i18n';
|
import I18n from '../../i18n';
|
||||||
import styles from './styles';
|
import styles from './styles';
|
||||||
import { MarkdownPreview } from '../markdown';
|
import { MarkdownPreview } from '../markdown';
|
||||||
import { E2E_MESSAGE_TYPE, E2E_STATUS, themes } from '../../lib/constants';
|
import { E2E_MESSAGE_TYPE, E2E_STATUS } from '../../lib/constants';
|
||||||
import { ILastMessageProps } from './interfaces';
|
import { ILastMessageProps } from './interfaces';
|
||||||
|
import { useTheme } from '../../theme';
|
||||||
|
|
||||||
const formatMsg = ({ lastMessage, type, showLastMessage, username, useRealName }: Partial<ILastMessageProps>) => {
|
const formatMsg = ({ lastMessage, type, showLastMessage, username, useRealName }: Partial<ILastMessageProps>) => {
|
||||||
if (!showLastMessage) {
|
if (!showLastMessage) {
|
||||||
|
@ -46,8 +47,9 @@ const formatMsg = ({ lastMessage, type, showLastMessage, username, useRealName }
|
||||||
|
|
||||||
const arePropsEqual = (oldProps: any, newProps: any) => dequal(oldProps, newProps);
|
const arePropsEqual = (oldProps: any, newProps: any) => dequal(oldProps, newProps);
|
||||||
|
|
||||||
const LastMessage = React.memo(
|
const LastMessage = React.memo(({ lastMessage, type, showLastMessage, username, alert, useRealName }: ILastMessageProps) => {
|
||||||
({ lastMessage, type, showLastMessage, username, alert, useRealName, theme }: ILastMessageProps) => (
|
const { colors } = useTheme();
|
||||||
|
return (
|
||||||
<MarkdownPreview
|
<MarkdownPreview
|
||||||
msg={formatMsg({
|
msg={formatMsg({
|
||||||
lastMessage,
|
lastMessage,
|
||||||
|
@ -56,12 +58,11 @@ const LastMessage = React.memo(
|
||||||
username,
|
username,
|
||||||
useRealName
|
useRealName
|
||||||
})}
|
})}
|
||||||
style={[styles.markdownText, { color: alert ? themes[theme].bodyText : themes[theme].auxiliaryText }]}
|
style={[styles.markdownText, { color: alert ? colors.bodyText : colors.auxiliaryText }]}
|
||||||
numberOfLines={2}
|
numberOfLines={2}
|
||||||
testID='room-item-last-message'
|
testID='room-item-last-message'
|
||||||
/>
|
/>
|
||||||
),
|
);
|
||||||
arePropsEqual
|
}, arePropsEqual);
|
||||||
);
|
|
||||||
|
|
||||||
export default LastMessage;
|
export default LastMessage;
|
||||||
|
|
|
@ -25,7 +25,6 @@ const RoomItem = ({
|
||||||
showLastMessage,
|
showLastMessage,
|
||||||
status = 'offline',
|
status = 'offline',
|
||||||
useRealName,
|
useRealName,
|
||||||
theme,
|
|
||||||
isFocused,
|
isFocused,
|
||||||
isGroupChat,
|
isGroupChat,
|
||||||
isRead,
|
isRead,
|
||||||
|
@ -52,7 +51,8 @@ const RoomItem = ({
|
||||||
autoJoin,
|
autoJoin,
|
||||||
showAvatar,
|
showAvatar,
|
||||||
displayMode,
|
displayMode,
|
||||||
sourceType
|
sourceType,
|
||||||
|
hideMentionStatus
|
||||||
}: IRoomItemProps) => (
|
}: IRoomItemProps) => (
|
||||||
<Touchable
|
<Touchable
|
||||||
onPress={onPress}
|
onPress={onPress}
|
||||||
|
@ -66,15 +66,13 @@ const RoomItem = ({
|
||||||
hideChannel={hideChannel}
|
hideChannel={hideChannel}
|
||||||
testID={testID}
|
testID={testID}
|
||||||
type={type}
|
type={type}
|
||||||
theme={theme}
|
isFocused={!!isFocused}
|
||||||
isFocused={isFocused}
|
|
||||||
swipeEnabled={swipeEnabled}
|
swipeEnabled={swipeEnabled}
|
||||||
displayMode={displayMode}>
|
displayMode={displayMode}>
|
||||||
<Wrapper
|
<Wrapper
|
||||||
accessibilityLabel={accessibilityLabel}
|
accessibilityLabel={accessibilityLabel}
|
||||||
avatar={avatar}
|
avatar={avatar}
|
||||||
type={type}
|
type={type}
|
||||||
theme={theme}
|
|
||||||
rid={rid}
|
rid={rid}
|
||||||
prid={prid}
|
prid={prid}
|
||||||
status={status}
|
status={status}
|
||||||
|
@ -82,7 +80,7 @@ const RoomItem = ({
|
||||||
teamMain={teamMain}
|
teamMain={teamMain}
|
||||||
displayMode={displayMode}
|
displayMode={displayMode}
|
||||||
showAvatar={showAvatar}
|
showAvatar={showAvatar}
|
||||||
showLastMessage={showLastMessage}
|
showLastMessage={!!showLastMessage}
|
||||||
sourceType={sourceType}>
|
sourceType={sourceType}>
|
||||||
{showLastMessage && displayMode === DisplayMode.Expanded ? (
|
{showLastMessage && displayMode === DisplayMode.Expanded ? (
|
||||||
<>
|
<>
|
||||||
|
@ -97,19 +95,18 @@ const RoomItem = ({
|
||||||
sourceType={sourceType}
|
sourceType={sourceType}
|
||||||
/>
|
/>
|
||||||
) : null}
|
) : null}
|
||||||
<Title name={name} theme={theme} hideUnreadStatus={hideUnreadStatus} alert={alert} />
|
<Title name={name} hideUnreadStatus={hideUnreadStatus} alert={alert} />
|
||||||
{autoJoin ? <Tag testID='auto-join-tag' name={I18n.t('Auto-join')} /> : null}
|
{autoJoin ? <Tag testID='auto-join-tag' name={I18n.t('Auto-join')} /> : null}
|
||||||
<UpdatedAt date={date} theme={theme} hideUnreadStatus={hideUnreadStatus} alert={alert} />
|
<UpdatedAt date={date} hideUnreadStatus={hideUnreadStatus} alert={alert} />
|
||||||
</View>
|
</View>
|
||||||
<View style={styles.row}>
|
<View style={styles.row}>
|
||||||
<LastMessage
|
<LastMessage
|
||||||
lastMessage={lastMessage}
|
lastMessage={lastMessage}
|
||||||
type={type}
|
type={type}
|
||||||
showLastMessage={showLastMessage}
|
showLastMessage={showLastMessage}
|
||||||
username={username}
|
username={username || ''}
|
||||||
alert={alert && !hideUnreadStatus}
|
alert={alert && !hideUnreadStatus}
|
||||||
useRealName={useRealName}
|
useRealName={useRealName}
|
||||||
theme={theme}
|
|
||||||
/>
|
/>
|
||||||
<UnreadBadge
|
<UnreadBadge
|
||||||
unread={unread}
|
unread={unread}
|
||||||
|
@ -118,6 +115,8 @@ const RoomItem = ({
|
||||||
tunread={tunread}
|
tunread={tunread}
|
||||||
tunreadUser={tunreadUser}
|
tunreadUser={tunreadUser}
|
||||||
tunreadGroup={tunreadGroup}
|
tunreadGroup={tunreadGroup}
|
||||||
|
hideMentionStatus={hideMentionStatus}
|
||||||
|
hideUnreadStatus={hideUnreadStatus}
|
||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
</>
|
</>
|
||||||
|
@ -133,10 +132,10 @@ const RoomItem = ({
|
||||||
style={{ marginRight: 8 }}
|
style={{ marginRight: 8 }}
|
||||||
sourceType={sourceType}
|
sourceType={sourceType}
|
||||||
/>
|
/>
|
||||||
<Title name={name} theme={theme} hideUnreadStatus={hideUnreadStatus} alert={alert} />
|
<Title name={name} hideUnreadStatus={hideUnreadStatus} alert={alert} />
|
||||||
{autoJoin ? <Tag name={I18n.t('Auto-join')} /> : null}
|
{autoJoin ? <Tag name={I18n.t('Auto-join')} /> : null}
|
||||||
<View style={styles.wrapUpdatedAndBadge}>
|
<View style={styles.wrapUpdatedAndBadge}>
|
||||||
<UpdatedAt date={date} theme={theme} hideUnreadStatus={hideUnreadStatus} alert={alert} />
|
<UpdatedAt date={date} hideUnreadStatus={hideUnreadStatus} alert={alert} />
|
||||||
<UnreadBadge
|
<UnreadBadge
|
||||||
unread={unread}
|
unread={unread}
|
||||||
userMentions={userMentions}
|
userMentions={userMentions}
|
||||||
|
@ -144,6 +143,8 @@ const RoomItem = ({
|
||||||
tunread={tunread}
|
tunread={tunread}
|
||||||
tunreadUser={tunreadUser}
|
tunreadUser={tunreadUser}
|
||||||
tunreadGroup={tunreadGroup}
|
tunreadGroup={tunreadGroup}
|
||||||
|
hideMentionStatus={hideMentionStatus}
|
||||||
|
hideUnreadStatus={hideUnreadStatus}
|
||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
|
|
|
@ -2,16 +2,19 @@ import React from 'react';
|
||||||
import { Text } from 'react-native';
|
import { Text } from 'react-native';
|
||||||
|
|
||||||
import styles from './styles';
|
import styles from './styles';
|
||||||
import { themes } from '../../lib/constants';
|
|
||||||
import { ITitleProps } from './interfaces';
|
import { ITitleProps } from './interfaces';
|
||||||
|
import { useTheme } from '../../theme';
|
||||||
|
|
||||||
const Title = React.memo(({ name, theme, hideUnreadStatus, alert }: ITitleProps) => (
|
const Title = React.memo(({ name, hideUnreadStatus, alert }: ITitleProps) => {
|
||||||
<Text
|
const { colors } = useTheme();
|
||||||
style={[styles.title, alert && !hideUnreadStatus && styles.alert, { color: themes[theme].titleText }]}
|
return (
|
||||||
ellipsizeMode='tail'
|
<Text
|
||||||
numberOfLines={1}>
|
style={[styles.title, alert && !hideUnreadStatus && styles.alert, { color: colors.titleText }]}
|
||||||
{name}
|
ellipsizeMode='tail'
|
||||||
</Text>
|
numberOfLines={1}>
|
||||||
));
|
{name}
|
||||||
|
</Text>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
export default Title;
|
export default Title;
|
||||||
|
|
|
@ -1,207 +1,98 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Animated } from 'react-native';
|
import Animated, {
|
||||||
|
useAnimatedGestureHandler,
|
||||||
|
useSharedValue,
|
||||||
|
useAnimatedStyle,
|
||||||
|
withSpring,
|
||||||
|
runOnJS
|
||||||
|
} from 'react-native-reanimated';
|
||||||
import {
|
import {
|
||||||
GestureEvent,
|
|
||||||
HandlerStateChangeEventPayload,
|
|
||||||
LongPressGestureHandler,
|
LongPressGestureHandler,
|
||||||
PanGestureHandler,
|
PanGestureHandler,
|
||||||
PanGestureHandlerEventPayload,
|
State,
|
||||||
State
|
HandlerStateChangeEventPayload,
|
||||||
|
PanGestureHandlerEventPayload
|
||||||
} from 'react-native-gesture-handler';
|
} from 'react-native-gesture-handler';
|
||||||
|
|
||||||
import Touch from '../../utils/touch';
|
import Touch from '../../lib/methods/helpers/touch';
|
||||||
import { ACTION_WIDTH, LONG_SWIPE, SMALL_SWIPE } from './styles';
|
import { ACTION_WIDTH, LONG_SWIPE, SMALL_SWIPE } from './styles';
|
||||||
import { isRTL } from '../../i18n';
|
|
||||||
import { themes } from '../../lib/constants';
|
|
||||||
import { LeftActions, RightActions } from './Actions';
|
import { LeftActions, RightActions } from './Actions';
|
||||||
import { ITouchableProps } from './interfaces';
|
import { ITouchableProps } from './interfaces';
|
||||||
|
import { useTheme } from '../../theme';
|
||||||
|
import I18n from '../../i18n';
|
||||||
|
|
||||||
class Touchable extends React.Component<ITouchableProps, any> {
|
const Touchable = ({
|
||||||
private dragX: Animated.Value;
|
children,
|
||||||
private rowOffSet: Animated.Value;
|
type,
|
||||||
private reverse: Animated.Value;
|
onPress,
|
||||||
private transX: Animated.AnimatedAddition;
|
onLongPress,
|
||||||
private transXReverse: Animated.AnimatedMultiplication;
|
testID,
|
||||||
private _onGestureEvent: (event: GestureEvent<PanGestureHandlerEventPayload>) => void;
|
width,
|
||||||
private _value: number;
|
favorite,
|
||||||
|
isRead,
|
||||||
|
rid,
|
||||||
|
toggleFav,
|
||||||
|
toggleRead,
|
||||||
|
hideChannel,
|
||||||
|
isFocused,
|
||||||
|
swipeEnabled,
|
||||||
|
displayMode
|
||||||
|
}: ITouchableProps): React.ReactElement => {
|
||||||
|
const { theme, colors } = useTheme();
|
||||||
|
|
||||||
constructor(props: ITouchableProps) {
|
const rowOffSet = useSharedValue(0);
|
||||||
super(props);
|
const transX = useSharedValue(0);
|
||||||
this.dragX = new Animated.Value(0);
|
const rowState = useSharedValue(0); // 0: closed, 1: right opened, -1: left opened
|
||||||
this.rowOffSet = new Animated.Value(0);
|
let _value = 0;
|
||||||
this.reverse = new Animated.Value(isRTL() ? -1 : 1);
|
|
||||||
this.transX = Animated.add(this.rowOffSet, this.dragX);
|
|
||||||
this.transXReverse = Animated.multiply(this.transX, this.reverse);
|
|
||||||
this.state = {
|
|
||||||
rowState: 0 // 0: closed, 1: right opened, -1: left opened
|
|
||||||
};
|
|
||||||
this._onGestureEvent = Animated.event([{ nativeEvent: { translationX: this.dragX } }], { useNativeDriver: true });
|
|
||||||
this._value = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
_onHandlerStateChange = ({ nativeEvent }: { nativeEvent: HandlerStateChangeEventPayload & PanGestureHandlerEventPayload }) => {
|
const close = () => {
|
||||||
if (nativeEvent.oldState === State.ACTIVE) {
|
rowState.value = 0;
|
||||||
this._handleRelease(nativeEvent);
|
transX.value = withSpring(0, { overshootClamping: true });
|
||||||
}
|
rowOffSet.value = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
onLongPressHandlerStateChange = ({ nativeEvent }: { nativeEvent: HandlerStateChangeEventPayload }) => {
|
const handleToggleFav = () => {
|
||||||
if (nativeEvent.state === State.ACTIVE) {
|
|
||||||
this.onLongPress();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
_handleRelease = (nativeEvent: PanGestureHandlerEventPayload) => {
|
|
||||||
const { translationX } = nativeEvent;
|
|
||||||
const { rowState } = this.state;
|
|
||||||
this._value += translationX;
|
|
||||||
|
|
||||||
let toValue = 0;
|
|
||||||
if (rowState === 0) {
|
|
||||||
// if no option is opened
|
|
||||||
if (translationX > 0 && translationX < LONG_SWIPE) {
|
|
||||||
// open leading option if he swipe right but not enough to trigger action
|
|
||||||
if (isRTL()) {
|
|
||||||
toValue = 2 * ACTION_WIDTH;
|
|
||||||
} else {
|
|
||||||
toValue = ACTION_WIDTH;
|
|
||||||
}
|
|
||||||
this.setState({ rowState: -1 });
|
|
||||||
} else if (translationX >= LONG_SWIPE) {
|
|
||||||
toValue = 0;
|
|
||||||
if (isRTL()) {
|
|
||||||
this.hideChannel();
|
|
||||||
} else {
|
|
||||||
this.toggleRead();
|
|
||||||
}
|
|
||||||
} else if (translationX < 0 && translationX > -LONG_SWIPE) {
|
|
||||||
// open trailing option if he swipe left
|
|
||||||
if (isRTL()) {
|
|
||||||
toValue = -ACTION_WIDTH;
|
|
||||||
} else {
|
|
||||||
toValue = -2 * ACTION_WIDTH;
|
|
||||||
}
|
|
||||||
this.setState({ rowState: 1 });
|
|
||||||
} else if (translationX <= -LONG_SWIPE) {
|
|
||||||
toValue = 0;
|
|
||||||
this.setState({ rowState: 0 });
|
|
||||||
if (isRTL()) {
|
|
||||||
this.toggleRead();
|
|
||||||
} else {
|
|
||||||
this.hideChannel();
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
toValue = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (rowState === -1) {
|
|
||||||
// if left option is opened
|
|
||||||
if (this._value < SMALL_SWIPE) {
|
|
||||||
toValue = 0;
|
|
||||||
this.setState({ rowState: 0 });
|
|
||||||
} else if (this._value > LONG_SWIPE) {
|
|
||||||
toValue = 0;
|
|
||||||
this.setState({ rowState: 0 });
|
|
||||||
if (isRTL()) {
|
|
||||||
this.hideChannel();
|
|
||||||
} else {
|
|
||||||
this.toggleRead();
|
|
||||||
}
|
|
||||||
} else if (isRTL()) {
|
|
||||||
toValue = 2 * ACTION_WIDTH;
|
|
||||||
} else {
|
|
||||||
toValue = ACTION_WIDTH;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (rowState === 1) {
|
|
||||||
// if right option is opened
|
|
||||||
if (this._value > -2 * SMALL_SWIPE) {
|
|
||||||
toValue = 0;
|
|
||||||
this.setState({ rowState: 0 });
|
|
||||||
} else if (this._value < -LONG_SWIPE) {
|
|
||||||
toValue = 0;
|
|
||||||
this.setState({ rowState: 0 });
|
|
||||||
if (isRTL()) {
|
|
||||||
this.toggleRead();
|
|
||||||
} else {
|
|
||||||
this.hideChannel();
|
|
||||||
}
|
|
||||||
} else if (isRTL()) {
|
|
||||||
toValue = -ACTION_WIDTH;
|
|
||||||
} else {
|
|
||||||
toValue = -2 * ACTION_WIDTH;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
this._animateRow(toValue);
|
|
||||||
};
|
|
||||||
|
|
||||||
_animateRow = (toValue: number) => {
|
|
||||||
this.rowOffSet.setValue(this._value);
|
|
||||||
this._value = toValue;
|
|
||||||
this.dragX.setValue(0);
|
|
||||||
Animated.spring(this.rowOffSet, {
|
|
||||||
toValue,
|
|
||||||
bounciness: 0,
|
|
||||||
useNativeDriver: true
|
|
||||||
}).start();
|
|
||||||
};
|
|
||||||
|
|
||||||
close = () => {
|
|
||||||
this.setState({ rowState: 0 });
|
|
||||||
this._animateRow(0);
|
|
||||||
};
|
|
||||||
|
|
||||||
toggleFav = () => {
|
|
||||||
const { toggleFav, rid, favorite } = this.props;
|
|
||||||
if (toggleFav) {
|
if (toggleFav) {
|
||||||
toggleFav(rid, favorite);
|
toggleFav(rid, favorite);
|
||||||
}
|
}
|
||||||
this.close();
|
close();
|
||||||
};
|
};
|
||||||
|
|
||||||
toggleRead = () => {
|
const handleToggleRead = () => {
|
||||||
const { toggleRead, rid, isRead } = this.props;
|
|
||||||
if (toggleRead) {
|
if (toggleRead) {
|
||||||
toggleRead(rid, isRead);
|
toggleRead(rid, isRead);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
hideChannel = () => {
|
const handleHideChannel = () => {
|
||||||
const { hideChannel, rid, type } = this.props;
|
|
||||||
if (hideChannel) {
|
if (hideChannel) {
|
||||||
hideChannel(rid, type);
|
hideChannel(rid, type);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
onToggleReadPress = () => {
|
const onToggleReadPress = () => {
|
||||||
this.toggleRead();
|
handleToggleRead();
|
||||||
this.close();
|
close();
|
||||||
};
|
};
|
||||||
|
|
||||||
onHidePress = () => {
|
const onHidePress = () => {
|
||||||
this.hideChannel();
|
handleHideChannel();
|
||||||
this.close();
|
close();
|
||||||
};
|
};
|
||||||
|
|
||||||
onPress = () => {
|
const handlePress = () => {
|
||||||
const { rowState } = this.state;
|
if (rowState.value !== 0) {
|
||||||
if (rowState !== 0) {
|
close();
|
||||||
this.close();
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const { onPress } = this.props;
|
|
||||||
if (onPress) {
|
if (onPress) {
|
||||||
onPress();
|
onPress();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
onLongPress = () => {
|
const handleLongPress = () => {
|
||||||
const { rowState } = this.state;
|
if (rowState.value !== 0) {
|
||||||
const { onLongPress } = this.props;
|
close();
|
||||||
if (rowState !== 0) {
|
|
||||||
this.close();
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -210,55 +101,139 @@ class Touchable extends React.Component<ITouchableProps, any> {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
render() {
|
const onLongPressHandlerStateChange = ({ nativeEvent }: { nativeEvent: HandlerStateChangeEventPayload }) => {
|
||||||
const { testID, isRead, width, favorite, children, theme, isFocused, swipeEnabled, displayMode } = this.props;
|
if (nativeEvent.state === State.ACTIVE) {
|
||||||
|
handleLongPress();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
const handleRelease = (event: PanGestureHandlerEventPayload) => {
|
||||||
<LongPressGestureHandler onHandlerStateChange={this.onLongPressHandlerStateChange}>
|
const { translationX } = event;
|
||||||
<Animated.View>
|
_value += translationX;
|
||||||
<PanGestureHandler
|
let toValue = 0;
|
||||||
minDeltaX={20}
|
if (rowState.value === 0) {
|
||||||
onGestureEvent={this._onGestureEvent}
|
// if no option is opened
|
||||||
onHandlerStateChange={this._onHandlerStateChange}
|
if (translationX > 0 && translationX < LONG_SWIPE) {
|
||||||
enabled={swipeEnabled}>
|
if (I18n.isRTL) {
|
||||||
<Animated.View>
|
toValue = 2 * ACTION_WIDTH;
|
||||||
<LeftActions
|
} else {
|
||||||
transX={this.transXReverse}
|
toValue = ACTION_WIDTH;
|
||||||
isRead={isRead}
|
}
|
||||||
width={width}
|
rowState.value = -1;
|
||||||
onToggleReadPress={this.onToggleReadPress}
|
} else if (translationX >= LONG_SWIPE) {
|
||||||
|
toValue = 0;
|
||||||
|
if (I18n.isRTL) {
|
||||||
|
handleHideChannel();
|
||||||
|
} else {
|
||||||
|
handleToggleRead();
|
||||||
|
}
|
||||||
|
} else if (translationX < 0 && translationX > -LONG_SWIPE) {
|
||||||
|
// open trailing option if he swipe left
|
||||||
|
if (I18n.isRTL) {
|
||||||
|
toValue = -ACTION_WIDTH;
|
||||||
|
} else {
|
||||||
|
toValue = -2 * ACTION_WIDTH;
|
||||||
|
}
|
||||||
|
rowState.value = 1;
|
||||||
|
} else if (translationX <= -LONG_SWIPE) {
|
||||||
|
toValue = 0;
|
||||||
|
rowState.value = 1;
|
||||||
|
if (I18n.isRTL) {
|
||||||
|
handleToggleRead();
|
||||||
|
} else {
|
||||||
|
handleHideChannel();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
toValue = 0;
|
||||||
|
}
|
||||||
|
} else if (rowState.value === -1) {
|
||||||
|
// if left option is opened
|
||||||
|
if (_value < SMALL_SWIPE) {
|
||||||
|
toValue = 0;
|
||||||
|
rowState.value = 0;
|
||||||
|
} else if (_value > LONG_SWIPE) {
|
||||||
|
toValue = 0;
|
||||||
|
rowState.value = 0;
|
||||||
|
if (I18n.isRTL) {
|
||||||
|
handleHideChannel();
|
||||||
|
} else {
|
||||||
|
handleToggleRead();
|
||||||
|
}
|
||||||
|
} else if (I18n.isRTL) {
|
||||||
|
toValue = 2 * ACTION_WIDTH;
|
||||||
|
} else {
|
||||||
|
toValue = ACTION_WIDTH;
|
||||||
|
}
|
||||||
|
} else if (rowState.value === 1) {
|
||||||
|
// if right option is opened
|
||||||
|
if (_value > -2 * SMALL_SWIPE) {
|
||||||
|
toValue = 0;
|
||||||
|
rowState.value = 0;
|
||||||
|
} else if (_value < -LONG_SWIPE) {
|
||||||
|
if (I18n.isRTL) {
|
||||||
|
handleToggleRead();
|
||||||
|
} else {
|
||||||
|
handleHideChannel();
|
||||||
|
}
|
||||||
|
} else if (I18n.isRTL) {
|
||||||
|
toValue = -ACTION_WIDTH;
|
||||||
|
} else {
|
||||||
|
toValue = -2 * ACTION_WIDTH;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
transX.value = withSpring(toValue, { overshootClamping: true });
|
||||||
|
rowOffSet.value = toValue;
|
||||||
|
_value = toValue;
|
||||||
|
};
|
||||||
|
|
||||||
|
const onGestureEvent = useAnimatedGestureHandler({
|
||||||
|
onActive: event => {
|
||||||
|
transX.value = event.translationX + rowOffSet.value;
|
||||||
|
if (transX.value > 2 * width) transX.value = 2 * width;
|
||||||
|
},
|
||||||
|
onEnd: event => {
|
||||||
|
runOnJS(handleRelease)(event);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const animatedStyles = useAnimatedStyle(() => ({ transform: [{ translateX: transX.value }] }));
|
||||||
|
|
||||||
|
return (
|
||||||
|
<LongPressGestureHandler onHandlerStateChange={onLongPressHandlerStateChange}>
|
||||||
|
<Animated.View>
|
||||||
|
<PanGestureHandler activeOffsetX={[-20, 20]} onGestureEvent={onGestureEvent} enabled={swipeEnabled}>
|
||||||
|
<Animated.View>
|
||||||
|
<LeftActions
|
||||||
|
transX={transX}
|
||||||
|
isRead={isRead}
|
||||||
|
width={width}
|
||||||
|
onToggleReadPress={onToggleReadPress}
|
||||||
|
displayMode={displayMode}
|
||||||
|
/>
|
||||||
|
<RightActions
|
||||||
|
transX={transX}
|
||||||
|
favorite={favorite}
|
||||||
|
width={width}
|
||||||
|
toggleFav={handleToggleFav}
|
||||||
|
onHidePress={onHidePress}
|
||||||
|
displayMode={displayMode}
|
||||||
|
/>
|
||||||
|
<Animated.View style={animatedStyles}>
|
||||||
|
<Touch
|
||||||
|
onPress={handlePress}
|
||||||
theme={theme}
|
theme={theme}
|
||||||
displayMode={displayMode}
|
testID={testID}
|
||||||
/>
|
|
||||||
<RightActions
|
|
||||||
transX={this.transXReverse}
|
|
||||||
favorite={favorite}
|
|
||||||
width={width}
|
|
||||||
toggleFav={this.toggleFav}
|
|
||||||
onHidePress={this.onHidePress}
|
|
||||||
theme={theme}
|
|
||||||
displayMode={displayMode}
|
|
||||||
/>
|
|
||||||
<Animated.View
|
|
||||||
style={{
|
style={{
|
||||||
transform: [{ translateX: this.transX }]
|
backgroundColor: isFocused ? colors.chatComponentBackground : colors.backgroundColor
|
||||||
}}>
|
}}>
|
||||||
<Touch
|
{children}
|
||||||
onPress={this.onPress}
|
</Touch>
|
||||||
theme={theme}
|
|
||||||
testID={testID}
|
|
||||||
style={{
|
|
||||||
backgroundColor: isFocused ? themes[theme].chatComponentBackground : themes[theme].backgroundColor
|
|
||||||
}}>
|
|
||||||
{children}
|
|
||||||
</Touch>
|
|
||||||
</Animated.View>
|
|
||||||
</Animated.View>
|
</Animated.View>
|
||||||
</PanGestureHandler>
|
</Animated.View>
|
||||||
</Animated.View>
|
</PanGestureHandler>
|
||||||
</LongPressGestureHandler>
|
</Animated.View>
|
||||||
);
|
</LongPressGestureHandler>
|
||||||
}
|
);
|
||||||
}
|
};
|
||||||
|
|
||||||
export default Touchable;
|
export default Touchable;
|
||||||
|
|
|
@ -2,11 +2,13 @@ import React from 'react';
|
||||||
import { Text } from 'react-native';
|
import { Text } from 'react-native';
|
||||||
|
|
||||||
import styles from './styles';
|
import styles from './styles';
|
||||||
import { themes } from '../../lib/constants';
|
import { capitalize } from '../../lib/methods/helpers/room';
|
||||||
import { capitalize } from '../../utils/room';
|
|
||||||
import { IUpdatedAtProps } from './interfaces';
|
import { IUpdatedAtProps } from './interfaces';
|
||||||
|
import { useTheme } from '../../theme';
|
||||||
|
|
||||||
|
const UpdatedAt = React.memo(({ date, hideUnreadStatus, alert }: IUpdatedAtProps) => {
|
||||||
|
const { colors } = useTheme();
|
||||||
|
|
||||||
const UpdatedAt = React.memo(({ date, theme, hideUnreadStatus, alert }: IUpdatedAtProps) => {
|
|
||||||
if (!date) {
|
if (!date) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
@ -15,13 +17,13 @@ const UpdatedAt = React.memo(({ date, theme, hideUnreadStatus, alert }: IUpdated
|
||||||
style={[
|
style={[
|
||||||
styles.date,
|
styles.date,
|
||||||
{
|
{
|
||||||
color: themes[theme].auxiliaryText
|
color: colors.auxiliaryText
|
||||||
},
|
},
|
||||||
alert &&
|
alert &&
|
||||||
!hideUnreadStatus && [
|
!hideUnreadStatus && [
|
||||||
styles.updateAlert,
|
styles.updateAlert,
|
||||||
{
|
{
|
||||||
color: themes[theme].tintColor
|
color: colors.tintColor
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
]}
|
]}
|
||||||
|
|
|
@ -1,26 +1,30 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { View } from 'react-native';
|
import { View } from 'react-native';
|
||||||
|
|
||||||
import { DisplayMode, themes } from '../../lib/constants';
|
import { DisplayMode } from '../../lib/constants';
|
||||||
|
import { useTheme } from '../../theme';
|
||||||
import IconOrAvatar from './IconOrAvatar';
|
import IconOrAvatar from './IconOrAvatar';
|
||||||
import { IWrapperProps } from './interfaces';
|
import { IWrapperProps } from './interfaces';
|
||||||
import styles from './styles';
|
import styles from './styles';
|
||||||
|
|
||||||
const Wrapper = ({ accessibilityLabel, theme, children, displayMode, ...props }: IWrapperProps): React.ReactElement => (
|
const Wrapper = ({ accessibilityLabel, children, displayMode, ...props }: IWrapperProps): React.ReactElement => {
|
||||||
<View
|
const { colors } = useTheme();
|
||||||
style={[styles.container, displayMode === DisplayMode.Condensed && styles.containerCondensed]}
|
return (
|
||||||
accessibilityLabel={accessibilityLabel}>
|
|
||||||
<IconOrAvatar theme={theme} displayMode={displayMode} {...props} />
|
|
||||||
<View
|
<View
|
||||||
style={[
|
style={[styles.container, displayMode === DisplayMode.Condensed && styles.containerCondensed]}
|
||||||
styles.centerContainer,
|
accessibilityLabel={accessibilityLabel}>
|
||||||
{
|
<IconOrAvatar displayMode={displayMode} {...props} />
|
||||||
borderColor: themes[theme].separatorColor
|
<View
|
||||||
}
|
style={[
|
||||||
]}>
|
styles.centerContainer,
|
||||||
{children}
|
{
|
||||||
|
borderColor: colors.separatorColor
|
||||||
|
}
|
||||||
|
]}>
|
||||||
|
{children}
|
||||||
|
</View>
|
||||||
</View>
|
</View>
|
||||||
</View>
|
);
|
||||||
);
|
};
|
||||||
|
|
||||||
export default Wrapper;
|
export default Wrapper;
|
||||||
|
|
|
@ -1,172 +1,115 @@
|
||||||
import React from 'react';
|
import React, { useEffect, useReducer, useRef } from 'react';
|
||||||
import { connect } from 'react-redux';
|
import { Subscription } from 'rxjs';
|
||||||
|
|
||||||
import I18n from '../../i18n';
|
import I18n from '../../i18n';
|
||||||
import { ROW_HEIGHT, ROW_HEIGHT_CONDENSED } from './styles';
|
import { useAppSelector } from '../../lib/hooks';
|
||||||
import { formatDate } from '../../utils/room';
|
import { getUserPresence } from '../../lib/methods';
|
||||||
import RoomItem from './RoomItem';
|
import { isGroupChat } from '../../lib/methods/helpers';
|
||||||
import { ISubscription, TUserStatus } from '../../definitions';
|
import { formatDate } from '../../lib/methods/helpers/room';
|
||||||
import { IRoomItemContainerProps } from './interfaces';
|
import { IRoomItemContainerProps } from './interfaces';
|
||||||
|
import RoomItem from './RoomItem';
|
||||||
|
import { ROW_HEIGHT, ROW_HEIGHT_CONDENSED } from './styles';
|
||||||
|
|
||||||
export { ROW_HEIGHT, ROW_HEIGHT_CONDENSED };
|
export { ROW_HEIGHT, ROW_HEIGHT_CONDENSED };
|
||||||
|
|
||||||
const attrs = [
|
const attrs = ['width', 'isFocused', 'showLastMessage', 'autoJoin', 'showAvatar', 'displayMode'];
|
||||||
'width',
|
|
||||||
'status',
|
|
||||||
'connected',
|
|
||||||
'theme',
|
|
||||||
'isFocused',
|
|
||||||
'forceUpdate',
|
|
||||||
'showLastMessage',
|
|
||||||
'autoJoin',
|
|
||||||
'showAvatar',
|
|
||||||
'displayMode'
|
|
||||||
];
|
|
||||||
|
|
||||||
class RoomItemContainer extends React.Component<IRoomItemContainerProps, any> {
|
const RoomItemContainer = React.memo(
|
||||||
private roomSubscription: ISubscription | undefined;
|
({
|
||||||
|
item,
|
||||||
static defaultProps: Partial<IRoomItemContainerProps> = {
|
id,
|
||||||
status: 'offline',
|
onPress,
|
||||||
getUserPresence: () => {},
|
onLongPress,
|
||||||
getRoomTitle: () => 'title',
|
width,
|
||||||
getRoomAvatar: () => '',
|
toggleFav,
|
||||||
getIsGroupChat: () => false,
|
toggleRead,
|
||||||
getIsRead: () => false,
|
hideChannel,
|
||||||
swipeEnabled: true
|
isFocused,
|
||||||
};
|
showLastMessage,
|
||||||
|
username,
|
||||||
constructor(props: IRoomItemContainerProps) {
|
useRealName,
|
||||||
super(props);
|
autoJoin,
|
||||||
this.init();
|
showAvatar,
|
||||||
}
|
displayMode,
|
||||||
|
getRoomTitle = () => 'title',
|
||||||
componentDidMount() {
|
getRoomAvatar = () => '',
|
||||||
const { connected, getUserPresence, id } = this.props;
|
getIsRead = () => false,
|
||||||
if (connected && this.isDirect) {
|
swipeEnabled = true
|
||||||
getUserPresence(id);
|
}: IRoomItemContainerProps) => {
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
shouldComponentUpdate(nextProps: IRoomItemContainerProps) {
|
|
||||||
const { props } = this;
|
|
||||||
return !attrs.every(key => props[key] === nextProps[key]);
|
|
||||||
}
|
|
||||||
|
|
||||||
componentDidUpdate(prevProps: IRoomItemContainerProps) {
|
|
||||||
const { connected, getUserPresence, id } = this.props;
|
|
||||||
if (prevProps.connected !== connected && connected && this.isDirect) {
|
|
||||||
getUserPresence(id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
componentWillUnmount() {
|
|
||||||
if (this.roomSubscription?.unsubscribe) {
|
|
||||||
this.roomSubscription.unsubscribe();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
get isGroupChat() {
|
|
||||||
const { item, getIsGroupChat } = this.props;
|
|
||||||
return getIsGroupChat(item);
|
|
||||||
}
|
|
||||||
|
|
||||||
get isDirect() {
|
|
||||||
const {
|
|
||||||
item: { t },
|
|
||||||
id
|
|
||||||
} = this.props;
|
|
||||||
return t === 'd' && id && !this.isGroupChat;
|
|
||||||
}
|
|
||||||
|
|
||||||
init = () => {
|
|
||||||
const { item } = this.props;
|
|
||||||
if (item?.observe) {
|
|
||||||
const observable = item.observe();
|
|
||||||
this.roomSubscription = observable?.subscribe?.(() => {
|
|
||||||
this.forceUpdate();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
onPress = () => {
|
|
||||||
const { item, onPress } = this.props;
|
|
||||||
return onPress(item);
|
|
||||||
};
|
|
||||||
|
|
||||||
onLongPress = () => {
|
|
||||||
const { item, onLongPress } = this.props;
|
|
||||||
if (onLongPress) {
|
|
||||||
return onLongPress(item);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
render() {
|
|
||||||
const {
|
|
||||||
item,
|
|
||||||
getRoomTitle,
|
|
||||||
getRoomAvatar,
|
|
||||||
getIsRead,
|
|
||||||
width,
|
|
||||||
toggleFav,
|
|
||||||
toggleRead,
|
|
||||||
hideChannel,
|
|
||||||
theme,
|
|
||||||
isFocused,
|
|
||||||
status,
|
|
||||||
showLastMessage,
|
|
||||||
username,
|
|
||||||
useRealName,
|
|
||||||
swipeEnabled,
|
|
||||||
autoJoin,
|
|
||||||
showAvatar,
|
|
||||||
displayMode
|
|
||||||
} = this.props;
|
|
||||||
const name = getRoomTitle(item);
|
const name = getRoomTitle(item);
|
||||||
const testID = `rooms-list-view-item-${name}`;
|
const testID = `rooms-list-view-item-${name}`;
|
||||||
const avatar = getRoomAvatar(item);
|
const avatar = getRoomAvatar(item);
|
||||||
const isRead = getIsRead(item);
|
const isRead = getIsRead(item);
|
||||||
const date = item.roomUpdatedAt && formatDate(item.roomUpdatedAt);
|
const date = item.roomUpdatedAt && formatDate(item.roomUpdatedAt);
|
||||||
const alert = item.alert || item.tunread?.length;
|
const alert = item.alert || item.tunread?.length;
|
||||||
|
const connected = useAppSelector(state => state.meteor.connected);
|
||||||
|
const userStatus = useAppSelector(state => state.activeUsers[id || '']?.status);
|
||||||
|
const [_, forceUpdate] = useReducer(x => x + 1, 1);
|
||||||
|
const roomSubscription = useRef<Subscription | null>(null);
|
||||||
|
|
||||||
let accessibilityLabel = name;
|
useEffect(() => {
|
||||||
|
const init = () => {
|
||||||
|
if (item?.observe) {
|
||||||
|
const observable = item.observe();
|
||||||
|
roomSubscription.current = observable?.subscribe?.(() => {
|
||||||
|
if (_) forceUpdate();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
init();
|
||||||
|
|
||||||
|
return () => roomSubscription.current?.unsubscribe();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const isDirect = !!(item.t === 'd' && id && !isGroupChat(item));
|
||||||
|
if (connected && isDirect) {
|
||||||
|
getUserPresence(id);
|
||||||
|
}
|
||||||
|
}, [connected]);
|
||||||
|
|
||||||
|
const handleOnPress = () => onPress(item);
|
||||||
|
|
||||||
|
const handleOnLongPress = () => onLongPress && onLongPress(item);
|
||||||
|
|
||||||
|
let accessibilityLabel = '';
|
||||||
if (item.unread === 1) {
|
if (item.unread === 1) {
|
||||||
accessibilityLabel += `, ${item.unread} ${I18n.t('alert')}`;
|
accessibilityLabel = `, ${item.unread} ${I18n.t('alert')}`;
|
||||||
} else if (item.unread > 1) {
|
} else if (item.unread > 1) {
|
||||||
accessibilityLabel += `, ${item.unread} ${I18n.t('alerts')}`;
|
accessibilityLabel = `, ${item.unread} ${I18n.t('alerts')}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (item.userMentions > 0) {
|
if (item.userMentions > 0) {
|
||||||
accessibilityLabel += `, ${I18n.t('you_were_mentioned')}`;
|
accessibilityLabel = `, ${I18n.t('you_were_mentioned')}`;
|
||||||
|
}
|
||||||
|
if (date) {
|
||||||
|
accessibilityLabel = `, ${I18n.t('last_message')} ${date}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (date) {
|
const status = item.t === 'l' ? item.visitor?.status || item.v?.status : userStatus;
|
||||||
accessibilityLabel += `, ${I18n.t('last_message')} ${date}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<RoomItem
|
<RoomItem
|
||||||
name={name}
|
name={name}
|
||||||
avatar={avatar}
|
avatar={avatar}
|
||||||
isGroupChat={this.isGroupChat}
|
isGroupChat={isGroupChat(item)}
|
||||||
isRead={isRead}
|
isRead={isRead}
|
||||||
onPress={this.onPress}
|
onPress={handleOnPress}
|
||||||
onLongPress={this.onLongPress}
|
onLongPress={handleOnLongPress}
|
||||||
date={date}
|
date={date}
|
||||||
accessibilityLabel={accessibilityLabel}
|
accessibilityLabel={accessibilityLabel}
|
||||||
width={width}
|
width={width}
|
||||||
favorite={item.f}
|
favorite={item.f}
|
||||||
toggleFav={toggleFav}
|
|
||||||
rid={item.rid}
|
rid={item.rid}
|
||||||
|
toggleFav={toggleFav}
|
||||||
toggleRead={toggleRead}
|
toggleRead={toggleRead}
|
||||||
hideChannel={hideChannel}
|
hideChannel={hideChannel}
|
||||||
testID={testID}
|
testID={testID}
|
||||||
type={item.t}
|
type={item.t}
|
||||||
theme={theme}
|
|
||||||
isFocused={isFocused}
|
isFocused={isFocused}
|
||||||
prid={item.prid}
|
prid={item.prid}
|
||||||
status={status}
|
status={status}
|
||||||
hideUnreadStatus={item.hideUnreadStatus}
|
hideUnreadStatus={item.hideUnreadStatus}
|
||||||
|
hideMentionStatus={item.hideMentionStatus}
|
||||||
alert={alert}
|
alert={alert}
|
||||||
lastMessage={item.lastMessage}
|
lastMessage={item.lastMessage}
|
||||||
showLastMessage={showLastMessage}
|
showLastMessage={showLastMessage}
|
||||||
|
@ -186,23 +129,8 @@ class RoomItemContainer extends React.Component<IRoomItemContainerProps, any> {
|
||||||
sourceType={item.source}
|
sourceType={item.source}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
},
|
||||||
}
|
(props, nextProps) => attrs.every(key => props[key] === nextProps[key])
|
||||||
|
);
|
||||||
|
|
||||||
const mapStateToProps = (state: any, ownProps: any) => {
|
export default RoomItemContainer;
|
||||||
let status = 'loading';
|
|
||||||
const { id, type, visitor = {} } = ownProps;
|
|
||||||
if (state.meteor.connected) {
|
|
||||||
if (type === 'd') {
|
|
||||||
status = state.activeUsers[id]?.status || 'loading';
|
|
||||||
} else if (type === 'l' && visitor?.status) {
|
|
||||||
({ status } = visitor);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
connected: state.meteor.connected,
|
|
||||||
status: status as TUserStatus
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
export default connect(mapStateToProps)(RoomItemContainer);
|
|
||||||
|
|
|
@ -1,12 +1,11 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Animated } from 'react-native';
|
import Animated from 'react-native-reanimated';
|
||||||
|
|
||||||
import { TSupportedThemes } from '../../theme';
|
import { TSupportedThemes } from '../../theme';
|
||||||
import { TUserStatus, ILastMessage, SubscriptionType, IOmnichannelSource } from '../../definitions';
|
import { TUserStatus, ILastMessage, SubscriptionType, IOmnichannelSource } from '../../definitions';
|
||||||
|
|
||||||
export interface ILeftActionsProps {
|
export interface ILeftActionsProps {
|
||||||
theme: TSupportedThemes;
|
transX: Animated.SharedValue<number>;
|
||||||
transX: Animated.AnimatedAddition | Animated.AnimatedMultiplication;
|
|
||||||
isRead: boolean;
|
isRead: boolean;
|
||||||
width: number;
|
width: number;
|
||||||
onToggleReadPress(): void;
|
onToggleReadPress(): void;
|
||||||
|
@ -14,8 +13,7 @@ export interface ILeftActionsProps {
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IRightActionsProps {
|
export interface IRightActionsProps {
|
||||||
theme: TSupportedThemes;
|
transX: Animated.SharedValue<number>;
|
||||||
transX: Animated.AnimatedAddition | Animated.AnimatedMultiplication;
|
|
||||||
favorite: boolean;
|
favorite: boolean;
|
||||||
width: number;
|
width: number;
|
||||||
toggleFav(): void;
|
toggleFav(): void;
|
||||||
|
@ -25,14 +23,12 @@ export interface IRightActionsProps {
|
||||||
|
|
||||||
export interface ITitleProps {
|
export interface ITitleProps {
|
||||||
name: string;
|
name: string;
|
||||||
theme: TSupportedThemes;
|
|
||||||
hideUnreadStatus: boolean;
|
hideUnreadStatus: boolean;
|
||||||
alert: boolean;
|
alert: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IUpdatedAtProps {
|
export interface IUpdatedAtProps {
|
||||||
date: string;
|
date: string;
|
||||||
theme: TSupportedThemes;
|
|
||||||
hideUnreadStatus: boolean;
|
hideUnreadStatus: boolean;
|
||||||
alert: boolean;
|
alert: boolean;
|
||||||
}
|
}
|
||||||
|
@ -41,13 +37,12 @@ export interface IWrapperProps {
|
||||||
accessibilityLabel: string;
|
accessibilityLabel: string;
|
||||||
avatar: string;
|
avatar: string;
|
||||||
type: string;
|
type: string;
|
||||||
theme: TSupportedThemes;
|
|
||||||
rid: string;
|
rid: string;
|
||||||
children: React.ReactElement;
|
children: React.ReactElement;
|
||||||
displayMode: string;
|
displayMode: string;
|
||||||
prid: string;
|
prid: string;
|
||||||
showLastMessage: boolean;
|
showLastMessage: boolean;
|
||||||
status: string;
|
status: TUserStatus;
|
||||||
isGroupChat: boolean;
|
isGroupChat: boolean;
|
||||||
teamMain: boolean;
|
teamMain: boolean;
|
||||||
showAvatar: boolean;
|
showAvatar: boolean;
|
||||||
|
@ -66,48 +61,43 @@ export interface ITypeIconProps {
|
||||||
sourceType: IOmnichannelSource;
|
sourceType: IOmnichannelSource;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IRoomItemContainerProps {
|
interface IRoomItemTouchables {
|
||||||
[key: string]: string | boolean | Function | number;
|
toggleFav?: (rid: string, favorite: boolean) => Promise<void>;
|
||||||
item: any;
|
toggleRead?: (rid: string, tIsRead: boolean) => Promise<void>;
|
||||||
showLastMessage: boolean;
|
hideChannel?: (rid: string, type: SubscriptionType) => Promise<void>;
|
||||||
id: string;
|
onPress: (item?: any) => void;
|
||||||
onPress: (item: any) => void;
|
onLongPress?: (item?: any) => void;
|
||||||
onLongPress: (item: any) => Promise<void>;
|
|
||||||
username: string;
|
|
||||||
width: number;
|
|
||||||
status: TUserStatus;
|
|
||||||
toggleFav(): void;
|
|
||||||
toggleRead(): void;
|
|
||||||
hideChannel(): void;
|
|
||||||
useRealName: boolean;
|
|
||||||
getUserPresence: (uid: string) => void;
|
|
||||||
connected: boolean;
|
|
||||||
theme: TSupportedThemes;
|
|
||||||
isFocused: boolean;
|
|
||||||
getRoomTitle: (item: any) => string;
|
|
||||||
getRoomAvatar: (item: any) => string;
|
|
||||||
getIsGroupChat: (item: any) => boolean;
|
|
||||||
getIsRead: (item: any) => boolean;
|
|
||||||
swipeEnabled: boolean;
|
|
||||||
autoJoin: boolean;
|
|
||||||
showAvatar: boolean;
|
|
||||||
displayMode: string;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IRoomItemProps {
|
interface IBaseRoomItem extends IRoomItemTouchables {
|
||||||
|
[key: string]: any;
|
||||||
|
showLastMessage?: boolean;
|
||||||
|
useRealName: boolean;
|
||||||
|
isFocused?: boolean;
|
||||||
|
displayMode: string;
|
||||||
|
showAvatar: boolean;
|
||||||
|
swipeEnabled: boolean;
|
||||||
|
autoJoin?: boolean;
|
||||||
|
width: number;
|
||||||
|
username?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IRoomItemContainerProps extends IBaseRoomItem {
|
||||||
|
item: any;
|
||||||
|
id?: string;
|
||||||
|
getRoomTitle: (item: any) => string;
|
||||||
|
getRoomAvatar: (item: any) => string;
|
||||||
|
getIsRead?: (item: any) => boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IRoomItemProps extends IBaseRoomItem {
|
||||||
rid: string;
|
rid: string;
|
||||||
type: SubscriptionType;
|
type: SubscriptionType;
|
||||||
prid: string;
|
prid: string;
|
||||||
name: string;
|
name: string;
|
||||||
avatar: string;
|
avatar: string;
|
||||||
showLastMessage: boolean;
|
|
||||||
username: string;
|
|
||||||
testID: string;
|
testID: string;
|
||||||
width: number;
|
|
||||||
status: TUserStatus;
|
status: TUserStatus;
|
||||||
useRealName: boolean;
|
|
||||||
theme: TSupportedThemes;
|
|
||||||
isFocused: boolean;
|
|
||||||
isGroupChat: boolean;
|
isGroupChat: boolean;
|
||||||
isRead: boolean;
|
isRead: boolean;
|
||||||
teamMain: boolean;
|
teamMain: boolean;
|
||||||
|
@ -123,21 +113,12 @@ export interface IRoomItemProps {
|
||||||
tunread: [];
|
tunread: [];
|
||||||
tunreadUser: [];
|
tunreadUser: [];
|
||||||
tunreadGroup: [];
|
tunreadGroup: [];
|
||||||
swipeEnabled: boolean;
|
|
||||||
toggleFav(): void;
|
|
||||||
toggleRead(): void;
|
|
||||||
onPress(): void;
|
|
||||||
onLongPress(): void;
|
|
||||||
hideChannel(): void;
|
|
||||||
autoJoin: boolean;
|
|
||||||
size?: number;
|
size?: number;
|
||||||
showAvatar: boolean;
|
|
||||||
displayMode: string;
|
|
||||||
sourceType: IOmnichannelSource;
|
sourceType: IOmnichannelSource;
|
||||||
|
hideMentionStatus?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ILastMessageProps {
|
export interface ILastMessageProps {
|
||||||
theme: TSupportedThemes;
|
|
||||||
lastMessage: ILastMessage;
|
lastMessage: ILastMessage;
|
||||||
type: SubscriptionType;
|
type: SubscriptionType;
|
||||||
showLastMessage: boolean;
|
showLastMessage: boolean;
|
||||||
|
@ -146,21 +127,29 @@ export interface ILastMessageProps {
|
||||||
alert: boolean;
|
alert: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ITouchableProps {
|
export interface ITouchableProps extends IRoomItemTouchables {
|
||||||
children: JSX.Element;
|
children: JSX.Element;
|
||||||
type: string;
|
type: SubscriptionType;
|
||||||
onPress(): void;
|
|
||||||
onLongPress(): void;
|
|
||||||
testID: string;
|
testID: string;
|
||||||
width: number;
|
width: number;
|
||||||
favorite: boolean;
|
favorite: boolean;
|
||||||
isRead: boolean;
|
isRead: boolean;
|
||||||
rid: string;
|
rid: string;
|
||||||
toggleFav: Function;
|
|
||||||
toggleRead: Function;
|
|
||||||
hideChannel: Function;
|
|
||||||
theme: TSupportedThemes;
|
|
||||||
isFocused: boolean;
|
isFocused: boolean;
|
||||||
swipeEnabled: boolean;
|
swipeEnabled: boolean;
|
||||||
displayMode: string;
|
displayMode: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface IIconOrAvatar {
|
||||||
|
avatar: string;
|
||||||
|
type: string;
|
||||||
|
rid: string;
|
||||||
|
showAvatar: boolean;
|
||||||
|
displayMode: string;
|
||||||
|
prid: string;
|
||||||
|
status: TUserStatus;
|
||||||
|
isGroupChat: boolean;
|
||||||
|
teamMain: boolean;
|
||||||
|
showLastMessage: boolean;
|
||||||
|
sourceType: IOmnichannelSource;
|
||||||
|
}
|
||||||
|
|
|
@ -6,7 +6,7 @@ export const ROW_HEIGHT = 75 * PixelRatio.getFontScale();
|
||||||
export const ROW_HEIGHT_CONDENSED = 60 * PixelRatio.getFontScale();
|
export const ROW_HEIGHT_CONDENSED = 60 * PixelRatio.getFontScale();
|
||||||
export const ACTION_WIDTH = 80;
|
export const ACTION_WIDTH = 80;
|
||||||
export const SMALL_SWIPE = ACTION_WIDTH / 2;
|
export const SMALL_SWIPE = ACTION_WIDTH / 2;
|
||||||
export const LONG_SWIPE = ACTION_WIDTH * 3;
|
export const LONG_SWIPE = ACTION_WIDTH * 2.5;
|
||||||
|
|
||||||
export default StyleSheet.create({
|
export default StyleSheet.create({
|
||||||
flex: {
|
flex: {
|
||||||
|
|
|
@ -0,0 +1,7 @@
|
||||||
|
import { storiesOf } from '@storybook/react-native';
|
||||||
|
import React from 'react';
|
||||||
|
import SearchBox from './index';
|
||||||
|
|
||||||
|
const stories = storiesOf('SearchBox', module);
|
||||||
|
|
||||||
|
stories.add('Item', () => <SearchBox />);
|
|
@ -0,0 +1,45 @@
|
||||||
|
import React from 'react';
|
||||||
|
import { fireEvent, render } from '@testing-library/react-native';
|
||||||
|
import { TextInputProps } from 'react-native';
|
||||||
|
|
||||||
|
import SearchBox from '.';
|
||||||
|
|
||||||
|
const onChangeTextMock = jest.fn();
|
||||||
|
|
||||||
|
const testSearchInputs = {
|
||||||
|
onChangeText: onChangeTextMock,
|
||||||
|
testID: 'search-box-text-input'
|
||||||
|
};
|
||||||
|
|
||||||
|
const Render = ({ onChangeText, testID }: TextInputProps) => <SearchBox testID={testID} onChangeText={onChangeText} />;
|
||||||
|
|
||||||
|
describe('SearchBox', () => {
|
||||||
|
it('should render the searchbox component', () => {
|
||||||
|
const { findByTestId } = render(<Render onChangeText={testSearchInputs.onChangeText} testID={testSearchInputs.testID} />);
|
||||||
|
|
||||||
|
expect(findByTestId('searchbox')).toBeTruthy();
|
||||||
|
});
|
||||||
|
it('should not render clear-input icon', async () => {
|
||||||
|
const { queryByTestId } = render(<Render onChangeText={testSearchInputs.onChangeText} testID={testSearchInputs.testID} />);
|
||||||
|
const clearInput = await queryByTestId('clear-text-input');
|
||||||
|
expect(clearInput).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should input new value with onChangeText function', async () => {
|
||||||
|
const { findByTestId } = render(<Render onChangeText={onChangeTextMock} testID={testSearchInputs.testID} />);
|
||||||
|
|
||||||
|
const component = await findByTestId(testSearchInputs.testID);
|
||||||
|
fireEvent.changeText(component, 'new-input-value');
|
||||||
|
expect(onChangeTextMock).toHaveBeenCalledWith('new-input-value');
|
||||||
|
});
|
||||||
|
|
||||||
|
// we need skip this test for now, until discovery how handle with functions effect
|
||||||
|
// https://github.com/callstack/react-native-testing-library/issues/978
|
||||||
|
it.skip('should clear input when call onCancelSearch function', async () => {
|
||||||
|
const { findByTestId } = render(<Render testID={'input-with-value'} onChangeText={onChangeTextMock} />);
|
||||||
|
|
||||||
|
const component = await findByTestId('clear-text-input');
|
||||||
|
fireEvent.press(component, 'input-with-value');
|
||||||
|
expect(onChangeTextMock).toHaveBeenCalledWith('input-with-value');
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,3 @@
|
||||||
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
|
exports[`Storyshots SearchBox Item 1`] = `"{\\"type\\":\\"View\\",\\"props\\":{\\"testID\\":\\"searchbox\\"},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":[{\\"marginBottom\\":10},{\\"margin\\":16,\\"marginBottom\\":16}]},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":{\\"position\\":\\"relative\\"}},\\"children\\":[{\\"type\\":\\"TextInput\\",\\"props\\":{\\"allowFontScaling\\":true,\\"rejectResponderTermination\\":true,\\"underlineColorAndroid\\":\\"transparent\\",\\"style\\":[{\\"color\\":\\"#0d0e12\\"},[{\\"textAlign\\":\\"left\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"System\\",\\"fontWeight\\":\\"400\\",\\"height\\":48,\\"fontSize\\":16,\\"padding\\":14,\\"borderWidth\\":0.5,\\"borderRadius\\":2},null,{\\"paddingRight\\":45},{\\"backgroundColor\\":\\"#ffffff\\",\\"borderColor\\":\\"#cbcbcc\\",\\"color\\":\\"#0d0e12\\"},null,null],{\\"textAlign\\":\\"auto\\"}],\\"placeholderTextColor\\":\\"#9ca2a8\\",\\"keyboardAppearance\\":\\"light\\",\\"autoCorrect\\":false,\\"autoCapitalize\\":\\"none\\",\\"accessibilityLabel\\":\\"Search\\",\\"placeholder\\":\\"Search\\",\\"value\\":\\"\\",\\"blurOnSubmit\\":true,\\"returnKeyType\\":\\"search\\"},\\"children\\":null},{\\"type\\":\\"Text\\",\\"props\\":{\\"selectable\\":false,\\"allowFontScaling\\":false,\\"style\\":[{\\"fontSize\\":20,\\"color\\":\\"#2f343d\\"},[{\\"position\\":\\"absolute\\",\\"top\\":14},{\\"right\\":15}],{\\"fontFamily\\":\\"custom\\",\\"fontWeight\\":\\"normal\\",\\"fontStyle\\":\\"normal\\"},{}]},\\"children\\":[\\"\\"]}]}]}]}"`;
|
|
@ -0,0 +1,43 @@
|
||||||
|
import React, { useCallback, useState } from 'react';
|
||||||
|
import { StyleSheet, TextInputProps, View } from 'react-native';
|
||||||
|
|
||||||
|
import I18n from '../../i18n';
|
||||||
|
import { FormTextInput } from '../TextInput';
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
inputContainer: {
|
||||||
|
margin: 16,
|
||||||
|
marginBottom: 16
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const SearchBox = ({ onChangeText, onSubmitEditing, testID }: TextInputProps): JSX.Element => {
|
||||||
|
const [text, setText] = useState('');
|
||||||
|
|
||||||
|
const internalOnChangeText = useCallback(value => {
|
||||||
|
setText(value);
|
||||||
|
onChangeText?.(value);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<View testID='searchbox'>
|
||||||
|
<FormTextInput
|
||||||
|
autoCapitalize='none'
|
||||||
|
autoCorrect={false}
|
||||||
|
blurOnSubmit
|
||||||
|
placeholder={I18n.t('Search')}
|
||||||
|
returnKeyType='search'
|
||||||
|
underlineColorAndroid='transparent'
|
||||||
|
containerStyle={styles.inputContainer}
|
||||||
|
onChangeText={internalOnChangeText}
|
||||||
|
onSubmitEditing={onSubmitEditing}
|
||||||
|
value={text}
|
||||||
|
testID={testID}
|
||||||
|
onClearInput={() => internalOnChangeText('')}
|
||||||
|
iconRight={'search'}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default SearchBox;
|
|
@ -5,8 +5,8 @@ import I18n from '../i18n';
|
||||||
import { useTheme } from '../theme';
|
import { useTheme } from '../theme';
|
||||||
import sharedStyles from '../views/Styles';
|
import sharedStyles from '../views/Styles';
|
||||||
import { themes } from '../lib/constants';
|
import { themes } from '../lib/constants';
|
||||||
import TextInput from './TextInput';
|
import { TextInput } from './TextInput';
|
||||||
import { isIOS, isTablet } from '../utils/deviceInfo';
|
import { isIOS, isTablet } from '../lib/methods/helpers';
|
||||||
import { useOrientation } from '../dimensions';
|
import { useOrientation } from '../dimensions';
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
|
@ -39,7 +39,6 @@ const SearchHeader = ({ onSearchChangeText, testID }: ISearchHeaderProps): JSX.E
|
||||||
style={[styles.title, isLight && { color: themes[theme].headerTitleColor }, { fontSize: titleFontSize }]}
|
style={[styles.title, isLight && { color: themes[theme].headerTitleColor }, { fontSize: titleFontSize }]}
|
||||||
placeholder={I18n.t('Search')}
|
placeholder={I18n.t('Search')}
|
||||||
onChangeText={onSearchChangeText}
|
onChangeText={onSearchChangeText}
|
||||||
theme={theme}
|
|
||||||
testID={testID}
|
testID={testID}
|
||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
|
|
|
@ -1,13 +1,13 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
// @ts-ignore // TODO: Remove on react-native update
|
// @ts-ignore // TODO: Remove on react-native update
|
||||||
import { Pressable, Text, View } from 'react-native';
|
import { Pressable, Text, View } from 'react-native';
|
||||||
import FastImage from '@rocket.chat/react-native-fast-image';
|
import FastImage from 'react-native-fast-image';
|
||||||
|
|
||||||
import { IServerInfo } from '../../definitions';
|
import { IServerInfo } from '../../definitions';
|
||||||
import Check from '../Check';
|
import Check from '../Check';
|
||||||
import styles, { ROW_HEIGHT } from './styles';
|
import styles, { ROW_HEIGHT } from './styles';
|
||||||
import { themes } from '../../lib/constants';
|
import { themes } from '../../lib/constants';
|
||||||
import { isIOS } from '../../utils/deviceInfo';
|
import { isIOS } from '../../lib/methods/helpers';
|
||||||
import { useTheme } from '../../theme';
|
import { useTheme } from '../../theme';
|
||||||
|
|
||||||
export { ROW_HEIGHT };
|
export { ROW_HEIGHT };
|
||||||
|
|
|
@ -0,0 +1,54 @@
|
||||||
|
import React from 'react';
|
||||||
|
import { render } from '@testing-library/react-native';
|
||||||
|
|
||||||
|
import { FormTextInput } from '.';
|
||||||
|
|
||||||
|
const FormTextInputID = 'form-text-input-id';
|
||||||
|
|
||||||
|
describe('FormTextInput', () => {
|
||||||
|
test('should render the component', async () => {
|
||||||
|
const { findByTestId } = render(<FormTextInput testID={FormTextInputID} />);
|
||||||
|
const component = await findByTestId('form-text-input-id');
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should render the component with left icon', async () => {
|
||||||
|
const { findByTestId } = render(<FormTextInput testID={FormTextInputID} iconLeft='user' />);
|
||||||
|
const component = await findByTestId(`${FormTextInputID}-icon-left`);
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should render the component with right icon', async () => {
|
||||||
|
const { findByTestId } = render(<FormTextInput testID={FormTextInputID} iconRight='user' />);
|
||||||
|
const component = await findByTestId(`${FormTextInputID}-icon-right`);
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should render the component with password icon', async () => {
|
||||||
|
const { findByTestId } = render(<FormTextInput testID={FormTextInputID} secureTextEntry />);
|
||||||
|
const component = await findByTestId(`${FormTextInputID}-icon-password`);
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should render the component with loading', async () => {
|
||||||
|
const { findByTestId } = render(<FormTextInput testID={FormTextInputID} loading />);
|
||||||
|
const component = await findByTestId(`${FormTextInputID}-loading`);
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should render the component with label', async () => {
|
||||||
|
const { findByText } = render(<FormTextInput testID={FormTextInputID} label='form text input' />);
|
||||||
|
const component = await findByText('form text input');
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should render the component with error', async () => {
|
||||||
|
const error = {
|
||||||
|
reason: 'An error occurred'
|
||||||
|
};
|
||||||
|
|
||||||
|
const { findByText } = render(<FormTextInput testID={FormTextInputID} error={error} />);
|
||||||
|
const component = await findByText(error.reason);
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
|
@ -1,13 +1,13 @@
|
||||||
import React from 'react';
|
import { BottomSheetTextInput } from '@gorhom/bottom-sheet';
|
||||||
import { StyleProp, StyleSheet, Text, TextInputProps, TextInput as RNTextInput, TextStyle, View, ViewStyle } from 'react-native';
|
import React, { useState } from 'react';
|
||||||
|
import { StyleProp, StyleSheet, Text, TextInput as RNTextInput, TextInputProps, TextStyle, View, ViewStyle } from 'react-native';
|
||||||
import Touchable from 'react-native-platform-touchable';
|
import Touchable from 'react-native-platform-touchable';
|
||||||
|
|
||||||
|
import { useTheme } from '../../theme';
|
||||||
import sharedStyles from '../../views/Styles';
|
import sharedStyles from '../../views/Styles';
|
||||||
import TextInput from './index';
|
|
||||||
import { themes } from '../../lib/constants';
|
|
||||||
import { CustomIcon, TIconsName } from '../CustomIcon';
|
|
||||||
import ActivityIndicator from '../ActivityIndicator';
|
import ActivityIndicator from '../ActivityIndicator';
|
||||||
import { TSupportedThemes } from '../../theme';
|
import { CustomIcon, TIconsName } from '../CustomIcon';
|
||||||
|
import { TextInput } from './TextInput';
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
error: {
|
error: {
|
||||||
|
@ -58,134 +58,118 @@ export interface IRCTextInputProps extends TextInputProps {
|
||||||
containerStyle?: StyleProp<ViewStyle>;
|
containerStyle?: StyleProp<ViewStyle>;
|
||||||
inputStyle?: StyleProp<TextStyle>;
|
inputStyle?: StyleProp<TextStyle>;
|
||||||
inputRef?: React.Ref<RNTextInput>;
|
inputRef?: React.Ref<RNTextInput>;
|
||||||
testID?: string;
|
|
||||||
iconLeft?: TIconsName;
|
iconLeft?: TIconsName;
|
||||||
iconRight?: TIconsName;
|
iconRight?: TIconsName;
|
||||||
left?: JSX.Element;
|
left?: JSX.Element;
|
||||||
onIconRightPress?(): void;
|
bottomSheet?: boolean;
|
||||||
theme: TSupportedThemes;
|
onClearInput?: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface IRCTextInputState {
|
export const FormTextInput = ({
|
||||||
showPassword: boolean;
|
label,
|
||||||
}
|
error,
|
||||||
|
loading,
|
||||||
|
containerStyle,
|
||||||
|
inputStyle,
|
||||||
|
inputRef,
|
||||||
|
iconLeft,
|
||||||
|
iconRight,
|
||||||
|
onClearInput,
|
||||||
|
value,
|
||||||
|
left,
|
||||||
|
testID,
|
||||||
|
secureTextEntry,
|
||||||
|
bottomSheet,
|
||||||
|
placeholder,
|
||||||
|
...inputProps
|
||||||
|
}: IRCTextInputProps): React.ReactElement => {
|
||||||
|
const { colors } = useTheme();
|
||||||
|
const [showPassword, setShowPassword] = useState(false);
|
||||||
|
const showClearInput = onClearInput && value && value.length > 0;
|
||||||
|
const Input = bottomSheet ? BottomSheetTextInput : TextInput;
|
||||||
|
return (
|
||||||
|
<View style={[styles.inputContainer, containerStyle]}>
|
||||||
|
{label ? (
|
||||||
|
<Text style={[styles.label, { color: colors.titleText }, error?.error && { color: colors.dangerColor }]}>{label}</Text>
|
||||||
|
) : null}
|
||||||
|
|
||||||
export default class FormTextInput extends React.PureComponent<IRCTextInputProps, IRCTextInputState> {
|
<View style={styles.wrap}>
|
||||||
static defaultProps = {
|
<Input
|
||||||
error: {},
|
style={[
|
||||||
theme: 'light'
|
styles.input,
|
||||||
};
|
iconLeft && styles.inputIconLeft,
|
||||||
|
(secureTextEntry || iconRight) && styles.inputIconRight,
|
||||||
state = {
|
{
|
||||||
showPassword: false
|
backgroundColor: colors.backgroundColor,
|
||||||
};
|
borderColor: colors.separatorColor,
|
||||||
|
color: colors.titleText
|
||||||
get iconLeft() {
|
},
|
||||||
const { testID, iconLeft, theme } = this.props;
|
error?.error && {
|
||||||
return iconLeft ? (
|
color: colors.dangerColor,
|
||||||
<CustomIcon
|
borderColor: colors.dangerColor
|
||||||
name={iconLeft}
|
},
|
||||||
testID={testID ? `${testID}-icon-left` : undefined}
|
inputStyle
|
||||||
size={20}
|
]}
|
||||||
color={themes[theme].bodyText}
|
// @ts-ignore ref error
|
||||||
style={[styles.iconContainer, styles.iconLeft]}
|
ref={inputRef}
|
||||||
/>
|
autoCorrect={false}
|
||||||
) : null;
|
autoCapitalize='none'
|
||||||
}
|
underlineColorAndroid='transparent'
|
||||||
|
secureTextEntry={secureTextEntry && !showPassword}
|
||||||
get iconRight() {
|
testID={testID}
|
||||||
const { iconRight, onIconRightPress, theme } = this.props;
|
accessibilityLabel={placeholder}
|
||||||
return iconRight ? (
|
placeholder={placeholder}
|
||||||
<Touchable onPress={onIconRightPress} style={[styles.iconContainer, styles.iconRight]}>
|
value={value}
|
||||||
<CustomIcon name={iconRight} size={20} color={themes[theme].bodyText} />
|
{...inputProps}
|
||||||
</Touchable>
|
|
||||||
) : null;
|
|
||||||
}
|
|
||||||
|
|
||||||
get iconPassword() {
|
|
||||||
const { showPassword } = this.state;
|
|
||||||
const { testID, theme } = this.props;
|
|
||||||
return (
|
|
||||||
<Touchable onPress={this.tooglePassword} style={[styles.iconContainer, styles.iconRight]}>
|
|
||||||
<CustomIcon
|
|
||||||
name={showPassword ? 'unread-on-top' : 'unread-on-top-disabled'}
|
|
||||||
testID={testID ? `${testID}-icon-right` : undefined}
|
|
||||||
size={20}
|
|
||||||
color={themes[theme].auxiliaryText}
|
|
||||||
/>
|
/>
|
||||||
</Touchable>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
get loading() {
|
{iconLeft ? (
|
||||||
const { theme } = this.props;
|
<CustomIcon
|
||||||
return <ActivityIndicator style={[styles.iconContainer, styles.iconRight]} color={themes[theme].bodyText} />;
|
name={iconLeft}
|
||||||
}
|
testID={testID ? `${testID}-icon-left` : undefined}
|
||||||
|
size={20}
|
||||||
tooglePassword = () => {
|
color={colors.auxiliaryText}
|
||||||
this.setState(prevState => ({ showPassword: !prevState.showPassword }));
|
style={[styles.iconContainer, styles.iconLeft]}
|
||||||
};
|
|
||||||
|
|
||||||
render() {
|
|
||||||
const { showPassword } = this.state;
|
|
||||||
const {
|
|
||||||
label,
|
|
||||||
left,
|
|
||||||
error,
|
|
||||||
loading,
|
|
||||||
secureTextEntry,
|
|
||||||
containerStyle,
|
|
||||||
inputRef,
|
|
||||||
iconLeft,
|
|
||||||
iconRight,
|
|
||||||
inputStyle,
|
|
||||||
testID,
|
|
||||||
placeholder,
|
|
||||||
theme,
|
|
||||||
...inputProps
|
|
||||||
} = this.props;
|
|
||||||
const { dangerColor } = themes[theme];
|
|
||||||
return (
|
|
||||||
<View style={[styles.inputContainer, containerStyle]}>
|
|
||||||
{label ? (
|
|
||||||
<Text style={[styles.label, { color: themes[theme].titleText }, error?.error && { color: dangerColor }]}>{label}</Text>
|
|
||||||
) : null}
|
|
||||||
<View style={styles.wrap}>
|
|
||||||
<TextInput
|
|
||||||
style={[
|
|
||||||
styles.input,
|
|
||||||
iconLeft && styles.inputIconLeft,
|
|
||||||
(secureTextEntry || iconRight) && styles.inputIconRight,
|
|
||||||
{
|
|
||||||
backgroundColor: themes[theme].backgroundColor,
|
|
||||||
borderColor: themes[theme].separatorColor,
|
|
||||||
color: themes[theme].titleText
|
|
||||||
},
|
|
||||||
error?.error && {
|
|
||||||
color: dangerColor,
|
|
||||||
borderColor: dangerColor
|
|
||||||
},
|
|
||||||
inputStyle
|
|
||||||
]}
|
|
||||||
ref={inputRef}
|
|
||||||
autoCorrect={false}
|
|
||||||
autoCapitalize='none'
|
|
||||||
underlineColorAndroid='transparent'
|
|
||||||
secureTextEntry={secureTextEntry && !showPassword}
|
|
||||||
testID={testID}
|
|
||||||
accessibilityLabel={placeholder}
|
|
||||||
placeholder={placeholder}
|
|
||||||
theme={theme}
|
|
||||||
{...inputProps}
|
|
||||||
/>
|
/>
|
||||||
{iconLeft ? this.iconLeft : null}
|
) : null}
|
||||||
{iconRight ? this.iconRight : null}
|
|
||||||
{secureTextEntry ? this.iconPassword : null}
|
{showClearInput ? (
|
||||||
{loading ? this.loading : null}
|
<Touchable onPress={onClearInput} style={[styles.iconContainer, styles.iconRight]} testID='clear-text-input'>
|
||||||
{left}
|
<CustomIcon name='input-clear' size={20} color={colors.auxiliaryTintColor} />
|
||||||
</View>
|
</Touchable>
|
||||||
{error && error.reason ? <Text style={[styles.error, { color: dangerColor }]}>{error.reason}</Text> : null}
|
) : null}
|
||||||
|
|
||||||
|
{iconRight && !showClearInput ? (
|
||||||
|
<CustomIcon
|
||||||
|
name={iconRight}
|
||||||
|
testID={testID ? `${testID}-icon-right` : undefined}
|
||||||
|
size={20}
|
||||||
|
color={colors.bodyText}
|
||||||
|
style={[styles.iconContainer, styles.iconRight]}
|
||||||
|
/>
|
||||||
|
) : null}
|
||||||
|
|
||||||
|
{secureTextEntry ? (
|
||||||
|
<Touchable onPress={() => setShowPassword(!showPassword)} style={[styles.iconContainer, styles.iconRight]}>
|
||||||
|
<CustomIcon
|
||||||
|
name={showPassword ? 'unread-on-top' : 'unread-on-top-disabled'}
|
||||||
|
testID={testID ? `${testID}-icon-password` : undefined}
|
||||||
|
size={20}
|
||||||
|
color={colors.auxiliaryText}
|
||||||
|
/>
|
||||||
|
</Touchable>
|
||||||
|
) : null}
|
||||||
|
|
||||||
|
{loading ? (
|
||||||
|
<ActivityIndicator
|
||||||
|
style={[styles.iconContainer, styles.iconRight]}
|
||||||
|
color={colors.bodyText}
|
||||||
|
testID={testID ? `${testID}-loading` : undefined}
|
||||||
|
/>
|
||||||
|
) : null}
|
||||||
|
{left}
|
||||||
</View>
|
</View>
|
||||||
);
|
{error && error.reason ? <Text style={[styles.error, { color: colors.dangerColor }]}>{error.reason}</Text> : null}
|
||||||
}
|
</View>
|
||||||
}
|
);
|
||||||
|
};
|
||||||
|
|
|
@ -3,7 +3,7 @@ import React from 'react';
|
||||||
import { storiesOf } from '@storybook/react-native';
|
import { storiesOf } from '@storybook/react-native';
|
||||||
|
|
||||||
import { View, StyleSheet } from 'react-native';
|
import { View, StyleSheet } from 'react-native';
|
||||||
import FormTextInput from './FormTextInput';
|
import { FormTextInput } from '.';
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
paddingHorizontal: {
|
paddingHorizontal: {
|
||||||
|
@ -18,14 +18,12 @@ const item = {
|
||||||
longText: 'https://open.rocket.chat/images/logo/android-chrome-512x512.png'
|
longText: 'https://open.rocket.chat/images/logo/android-chrome-512x512.png'
|
||||||
};
|
};
|
||||||
|
|
||||||
const theme = 'light';
|
|
||||||
|
|
||||||
stories.add('Short and Long Text', () => (
|
stories.add('Short and Long Text', () => (
|
||||||
<>
|
<>
|
||||||
<View style={styles.paddingHorizontal}>
|
<View style={styles.paddingHorizontal}>
|
||||||
<FormTextInput label='Short Text' placeholder='placeholder' value={item.name} theme={theme} />
|
<FormTextInput label='Short Text' placeholder='placeholder' value={item.name} />
|
||||||
|
|
||||||
<FormTextInput label='Long Text' placeholder='placeholder' value={item.longText} theme={theme} />
|
<FormTextInput label='Long Text' placeholder='placeholder' value={item.longText} />
|
||||||
</View>
|
</View>
|
||||||
</>
|
</>
|
||||||
));
|
));
|
||||||
|
|
|
@ -0,0 +1,29 @@
|
||||||
|
import React from 'react';
|
||||||
|
import { I18nManager, StyleProp, StyleSheet, TextInput as RNTextInput, TextStyle } from 'react-native';
|
||||||
|
|
||||||
|
import { IRCTextInputProps } from './FormTextInput';
|
||||||
|
import { themes } from '../../lib/constants';
|
||||||
|
import { useTheme } from '../../theme';
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
input: {
|
||||||
|
...(I18nManager.isRTL ? { textAlign: 'right' } : { textAlign: 'auto' })
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
export interface IThemedTextInput extends IRCTextInputProps {
|
||||||
|
style: StyleProp<TextStyle>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const TextInput = React.forwardRef<RNTextInput, IThemedTextInput>(({ style, ...props }, ref) => {
|
||||||
|
const { theme } = useTheme();
|
||||||
|
return (
|
||||||
|
<RNTextInput
|
||||||
|
ref={ref}
|
||||||
|
style={[{ color: themes[theme].titleText }, style, styles.input]}
|
||||||
|
placeholderTextColor={themes[theme].auxiliaryText}
|
||||||
|
keyboardAppearance={theme === 'light' ? 'light' : 'dark'}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
});
|
|
@ -0,0 +1,2 @@
|
||||||
|
export * from './TextInput';
|
||||||
|
export * from './FormTextInput';
|
|
@ -1,29 +0,0 @@
|
||||||
import React from 'react';
|
|
||||||
import { I18nManager, StyleProp, StyleSheet, TextInput, TextStyle } from 'react-native';
|
|
||||||
|
|
||||||
import { IRCTextInputProps } from './FormTextInput';
|
|
||||||
import { themes } from '../../lib/constants';
|
|
||||||
import { TSupportedThemes } from '../../theme';
|
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
|
||||||
input: {
|
|
||||||
...(I18nManager.isRTL ? { textAlign: 'right' } : { textAlign: 'auto' })
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
export interface IThemedTextInput extends IRCTextInputProps {
|
|
||||||
style: StyleProp<TextStyle>;
|
|
||||||
theme: TSupportedThemes;
|
|
||||||
}
|
|
||||||
|
|
||||||
const ThemedTextInput = React.forwardRef<TextInput, IThemedTextInput>(({ style, theme, ...props }, ref) => (
|
|
||||||
<TextInput
|
|
||||||
ref={ref}
|
|
||||||
style={[{ color: themes[theme].titleText }, style, styles.input]}
|
|
||||||
placeholderTextColor={themes[theme].auxiliaryText}
|
|
||||||
keyboardAppearance={theme === 'light' ? 'light' : 'dark'}
|
|
||||||
{...props}
|
|
||||||
/>
|
|
||||||
));
|
|
||||||
|
|
||||||
export default ThemedTextInput;
|
|
|
@ -1,11 +1,10 @@
|
||||||
import React from 'react';
|
import React, { useEffect } from 'react';
|
||||||
import { StyleSheet } from 'react-native';
|
import { StyleSheet } from 'react-native';
|
||||||
import EasyToast from 'react-native-easy-toast';
|
import EasyToast from 'react-native-easy-toast';
|
||||||
|
|
||||||
import { themes } from '../lib/constants';
|
import EventEmitter from '../lib/methods/helpers/events';
|
||||||
|
import { useTheme } from '../theme';
|
||||||
import sharedStyles from '../views/Styles';
|
import sharedStyles from '../views/Styles';
|
||||||
import EventEmitter from '../utils/events';
|
|
||||||
import { TSupportedThemes, withTheme } from '../theme';
|
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
toast: {
|
toast: {
|
||||||
|
@ -21,54 +20,37 @@ const styles = StyleSheet.create({
|
||||||
|
|
||||||
export const LISTENER = 'Toast';
|
export const LISTENER = 'Toast';
|
||||||
|
|
||||||
interface IToastProps {
|
let listener: Function;
|
||||||
theme?: TSupportedThemes;
|
let toast: EasyToast | null | undefined;
|
||||||
}
|
|
||||||
|
|
||||||
class Toast extends React.Component<IToastProps, any> {
|
const Toast = (): React.ReactElement => {
|
||||||
private listener?: Function;
|
const { colors } = useTheme();
|
||||||
|
|
||||||
private toast: EasyToast | null | undefined;
|
useEffect(() => {
|
||||||
|
listener = EventEmitter.addEventListener(LISTENER, showToast);
|
||||||
|
return () => {
|
||||||
|
EventEmitter.removeListener(LISTENER, listener);
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
componentDidMount() {
|
const getToastRef = (newToast: EasyToast | null) => (toast = newToast);
|
||||||
this.listener = EventEmitter.addEventListener(LISTENER, this.showToast);
|
|
||||||
}
|
|
||||||
|
|
||||||
shouldComponentUpdate(nextProps: any) {
|
const showToast = ({ message }: { message: string }) => {
|
||||||
const { theme } = this.props;
|
if (toast && toast.show) {
|
||||||
if (nextProps.theme !== theme) {
|
toast.show(message, 1000);
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
componentWillUnmount() {
|
|
||||||
if (this.listener) {
|
|
||||||
EventEmitter.removeListener(LISTENER, this.listener);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
getToastRef = (toast: EasyToast | null) => (this.toast = toast);
|
|
||||||
|
|
||||||
showToast = ({ message }: { message: string }) => {
|
|
||||||
if (this.toast && this.toast.show) {
|
|
||||||
this.toast.show(message, 1000);
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
render() {
|
return (
|
||||||
const { theme } = this.props;
|
<EasyToast
|
||||||
return (
|
ref={getToastRef}
|
||||||
<EasyToast
|
// @ts-ignore
|
||||||
ref={this.getToastRef}
|
position='center'
|
||||||
// @ts-ignore
|
style={[styles.toast, { backgroundColor: colors.toastBackground }]}
|
||||||
position='center'
|
textStyle={[styles.text, { color: colors.buttonText }]}
|
||||||
style={[styles.toast, { backgroundColor: themes[theme!].toastBackground }]}
|
opacity={0.9}
|
||||||
textStyle={[styles.text, { color: themes[theme!].buttonText }]}
|
/>
|
||||||
opacity={0.9}
|
);
|
||||||
/>
|
};
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default withTheme(Toast);
|
export default Toast;
|
||||||
|
|
|
@ -6,9 +6,9 @@ import Modal from 'react-native-modal';
|
||||||
import useDeepCompareEffect from 'use-deep-compare-effect';
|
import useDeepCompareEffect from 'use-deep-compare-effect';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
|
|
||||||
import FormTextInput from '../TextInput/FormTextInput';
|
import { FormTextInput } from '../TextInput';
|
||||||
import I18n from '../../i18n';
|
import I18n from '../../i18n';
|
||||||
import EventEmitter from '../../utils/events';
|
import EventEmitter from '../../lib/methods/helpers/events';
|
||||||
import { useTheme } from '../../theme';
|
import { useTheme } from '../../theme';
|
||||||
import { themes } from '../../lib/constants';
|
import { themes } from '../../lib/constants';
|
||||||
import Button from '../Button';
|
import Button from '../Button';
|
||||||
|
@ -116,7 +116,6 @@ const TwoFactor = React.memo(({ isMasterDetail }: { isMasterDetail: boolean }) =
|
||||||
{method?.text ? <Text style={[styles.subtitle, { color }]}>{I18n.t(method.text)}</Text> : null}
|
{method?.text ? <Text style={[styles.subtitle, { color }]}>{I18n.t(method.text)}</Text> : null}
|
||||||
<FormTextInput
|
<FormTextInput
|
||||||
value={code}
|
value={code}
|
||||||
theme={theme}
|
|
||||||
inputRef={(e: any) => InteractionManager.runAfterInteractions(() => e?.getNativeRef()?.focus())}
|
inputRef={(e: any) => InteractionManager.runAfterInteractions(() => e?.getNativeRef()?.focus())}
|
||||||
returnKeyType='send'
|
returnKeyType='send'
|
||||||
autoCapitalize='none'
|
autoCapitalize='none'
|
||||||
|
|
|
@ -10,7 +10,7 @@ import { textParser } from './utils';
|
||||||
import { themes } from '../../lib/constants';
|
import { themes } from '../../lib/constants';
|
||||||
import sharedStyles from '../../views/Styles';
|
import sharedStyles from '../../views/Styles';
|
||||||
import { CustomIcon } from '../CustomIcon';
|
import { CustomIcon } from '../CustomIcon';
|
||||||
import { isAndroid } from '../../utils/deviceInfo';
|
import { isAndroid } from '../../lib/methods/helpers';
|
||||||
import { useTheme } from '../../theme';
|
import { useTheme } from '../../theme';
|
||||||
import ActivityIndicator from '../ActivityIndicator';
|
import ActivityIndicator from '../ActivityIndicator';
|
||||||
import { IDatePicker } from './interfaces';
|
import { IDatePicker } from './interfaces';
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { StyleSheet, View } from 'react-native';
|
import { StyleSheet, View } from 'react-native';
|
||||||
import FastImage from '@rocket.chat/react-native-fast-image';
|
import FastImage from 'react-native-fast-image';
|
||||||
import { BlockContext } from '@rocket.chat/ui-kit';
|
import { BlockContext } from '@rocket.chat/ui-kit';
|
||||||
|
|
||||||
import ImageContainer from '../message/Image';
|
import ImageContainer from '../message/Image';
|
||||||
|
|
|
@ -1,52 +1,52 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Text, View } from 'react-native';
|
import { Text, View } from 'react-native';
|
||||||
import Touchable from 'react-native-platform-touchable';
|
import Touchable from 'react-native-platform-touchable';
|
||||||
import FastImage from '@rocket.chat/react-native-fast-image';
|
import FastImage from 'react-native-fast-image';
|
||||||
|
|
||||||
import { themes } from '../../../lib/constants';
|
|
||||||
import { textParser } from '../utils';
|
import { textParser } from '../utils';
|
||||||
import { CustomIcon } from '../../CustomIcon';
|
import { CustomIcon } from '../../CustomIcon';
|
||||||
import styles from './styles';
|
import styles from './styles';
|
||||||
import { IItemData } from '.';
|
import { IItemData } from '.';
|
||||||
import { TSupportedThemes } from '../../../theme';
|
import { useTheme } from '../../../theme';
|
||||||
|
|
||||||
interface IChip {
|
interface IChip {
|
||||||
item: IItemData;
|
item: IItemData;
|
||||||
onSelect: (item: IItemData) => void;
|
onSelect: (item: IItemData) => void;
|
||||||
style?: object;
|
style?: object;
|
||||||
theme: TSupportedThemes;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
interface IChips {
|
interface IChips {
|
||||||
items: IItemData[];
|
items: IItemData[];
|
||||||
onSelect: (item: IItemData) => void;
|
onSelect: (item: IItemData) => void;
|
||||||
style?: object;
|
style?: object;
|
||||||
theme: TSupportedThemes;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const keyExtractor = (item: IItemData) => item.value.toString();
|
const keyExtractor = (item: IItemData) => item.value.toString();
|
||||||
|
|
||||||
const Chip = ({ item, onSelect, style, theme }: IChip) => (
|
const Chip = ({ item, onSelect, style }: IChip) => {
|
||||||
<Touchable
|
const { colors } = useTheme();
|
||||||
key={item.value}
|
return (
|
||||||
onPress={() => onSelect(item)}
|
<Touchable
|
||||||
style={[styles.chip, { backgroundColor: themes[theme].auxiliaryBackground }, style]}
|
key={item.value}
|
||||||
background={Touchable.Ripple(themes[theme].bannerBackground)}>
|
onPress={() => onSelect(item)}
|
||||||
<>
|
style={[styles.chip, { backgroundColor: colors.auxiliaryBackground }, style]}
|
||||||
{item.imageUrl ? <FastImage style={styles.chipImage} source={{ uri: item.imageUrl }} /> : null}
|
background={Touchable.Ripple(colors.bannerBackground)}>
|
||||||
<Text numberOfLines={1} style={[styles.chipText, { color: themes[theme].titleText }]}>
|
<>
|
||||||
{textParser([item.text])}
|
{item.imageUrl ? <FastImage style={styles.chipImage} source={{ uri: item.imageUrl }} /> : null}
|
||||||
</Text>
|
<Text numberOfLines={1} style={[styles.chipText, { color: colors.titleText }]}>
|
||||||
<CustomIcon name='close' size={16} color={themes[theme].auxiliaryText} />
|
{textParser([item.text])}
|
||||||
</>
|
</Text>
|
||||||
</Touchable>
|
<CustomIcon name='close' size={16} color={colors.auxiliaryText} />
|
||||||
);
|
</>
|
||||||
|
</Touchable>
|
||||||
|
);
|
||||||
|
};
|
||||||
Chip.propTypes = {};
|
Chip.propTypes = {};
|
||||||
|
|
||||||
const Chips = ({ items, onSelect, style, theme }: IChips) => (
|
const Chips = ({ items, onSelect, style }: IChips) => (
|
||||||
<View style={styles.chips}>
|
<View style={styles.chips}>
|
||||||
{items.map(item => (
|
{items.map(item => (
|
||||||
<Chip key={keyExtractor(item)} item={item} onSelect={onSelect} style={style} theme={theme} />
|
<Chip key={keyExtractor(item)} item={item} onSelect={onSelect} style={style} />
|
||||||
))}
|
))}
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
|
|
|
@ -3,15 +3,13 @@ import { Text, View } from 'react-native';
|
||||||
import Touchable from 'react-native-platform-touchable';
|
import Touchable from 'react-native-platform-touchable';
|
||||||
|
|
||||||
import { CustomIcon } from '../../CustomIcon';
|
import { CustomIcon } from '../../CustomIcon';
|
||||||
import { themes } from '../../../lib/constants';
|
|
||||||
import ActivityIndicator from '../../ActivityIndicator';
|
import ActivityIndicator from '../../ActivityIndicator';
|
||||||
import styles from './styles';
|
import styles from './styles';
|
||||||
import { TSupportedThemes } from '../../../theme';
|
import { useTheme } from '../../../theme';
|
||||||
|
|
||||||
interface IInput {
|
interface IInput {
|
||||||
children?: JSX.Element;
|
children?: JSX.Element;
|
||||||
onPress: () => void;
|
onPress: () => void;
|
||||||
theme: TSupportedThemes;
|
|
||||||
inputStyle?: object;
|
inputStyle?: object;
|
||||||
disabled?: boolean | null;
|
disabled?: boolean | null;
|
||||||
placeholder?: string;
|
placeholder?: string;
|
||||||
|
@ -19,21 +17,23 @@ interface IInput {
|
||||||
innerInputStyle?: object;
|
innerInputStyle?: object;
|
||||||
}
|
}
|
||||||
|
|
||||||
const Input = ({ children, onPress, theme, loading, inputStyle, placeholder, disabled, innerInputStyle }: IInput) => (
|
const Input = ({ children, onPress, loading, inputStyle, placeholder, disabled, innerInputStyle }: IInput) => {
|
||||||
<Touchable
|
const { colors } = useTheme();
|
||||||
onPress={onPress}
|
return (
|
||||||
style={[{ backgroundColor: themes[theme].backgroundColor }, inputStyle]}
|
<Touchable
|
||||||
background={Touchable.Ripple(themes[theme].bannerBackground)}
|
onPress={onPress}
|
||||||
disabled={disabled}>
|
style={[{ backgroundColor: colors.backgroundColor }, inputStyle]}
|
||||||
<View style={[styles.input, { borderColor: themes[theme].separatorColor }, innerInputStyle]}>
|
background={Touchable.Ripple(colors.bannerBackground)}
|
||||||
{placeholder ? <Text style={[styles.pickerText, { color: themes[theme].auxiliaryText }]}>{placeholder}</Text> : children}
|
disabled={disabled}>
|
||||||
{loading ? (
|
<View style={[styles.input, { borderColor: colors.separatorColor }, innerInputStyle]}>
|
||||||
<ActivityIndicator style={[styles.loading, styles.icon]} />
|
{placeholder ? <Text style={[styles.pickerText, { color: colors.auxiliaryText }]}>{placeholder}</Text> : children}
|
||||||
) : (
|
{loading ? (
|
||||||
<CustomIcon name='chevron-down' size={22} color={themes[theme].auxiliaryText} style={styles.icon} />
|
<ActivityIndicator style={styles.icon} />
|
||||||
)}
|
) : (
|
||||||
</View>
|
<CustomIcon name='chevron-down' size={22} color={colors.auxiliaryText} style={styles.icon} />
|
||||||
</Touchable>
|
)}
|
||||||
);
|
</View>
|
||||||
|
</Touchable>
|
||||||
|
);
|
||||||
|
};
|
||||||
export default Input;
|
export default Input;
|
||||||
|
|
|
@ -1,61 +1,54 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { FlatList, Text } from 'react-native';
|
import { Text } from 'react-native';
|
||||||
import Touchable from 'react-native-platform-touchable';
|
import Touchable from 'react-native-platform-touchable';
|
||||||
import FastImage from '@rocket.chat/react-native-fast-image';
|
import FastImage from 'react-native-fast-image';
|
||||||
|
import { FlatList } from 'react-native-gesture-handler';
|
||||||
|
|
||||||
import Check from '../../Check';
|
import Check from '../../Check';
|
||||||
import * as List from '../../List';
|
import * as List from '../../List';
|
||||||
import { textParser } from '../utils';
|
import { textParser } from '../utils';
|
||||||
import { themes } from '../../../lib/constants';
|
|
||||||
import styles from './styles';
|
import styles from './styles';
|
||||||
import { IItemData } from '.';
|
import { IItemData } from '.';
|
||||||
import { TSupportedThemes } from '../../../theme';
|
import { useTheme } from '../../../theme';
|
||||||
|
|
||||||
interface IItem {
|
interface IItem {
|
||||||
item: IItemData;
|
item: IItemData;
|
||||||
selected?: string;
|
selected?: string;
|
||||||
onSelect: Function;
|
onSelect: Function;
|
||||||
theme: TSupportedThemes;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
interface IItems {
|
interface IItems {
|
||||||
items: IItemData[];
|
items: IItemData[];
|
||||||
selected: string[];
|
selected: string[];
|
||||||
onSelect: Function;
|
onSelect: Function;
|
||||||
theme: TSupportedThemes;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const keyExtractor = (item: IItemData) => item.value.toString();
|
const keyExtractor = (item: IItemData) => item.value?.name || item.text?.text;
|
||||||
|
|
||||||
// RectButton doesn't work on modal (Android)
|
// RectButton doesn't work on modal (Android)
|
||||||
const Item = ({ item, selected, onSelect, theme }: IItem) => {
|
const Item = ({ item, selected, onSelect }: IItem) => {
|
||||||
const itemName = item.value?.name || item.text.text.toLowerCase();
|
const itemName = item.value?.name || item.text.text.toLowerCase();
|
||||||
|
const { colors } = useTheme();
|
||||||
return (
|
return (
|
||||||
<Touchable
|
<Touchable testID={`multi-select-item-${itemName}`} key={itemName} onPress={() => onSelect(item)} style={[styles.item]}>
|
||||||
testID={`multi-select-item-${itemName}`}
|
|
||||||
key={itemName}
|
|
||||||
onPress={() => onSelect(item)}
|
|
||||||
style={[styles.item, { backgroundColor: themes[theme].backgroundColor }]}>
|
|
||||||
<>
|
<>
|
||||||
{item.imageUrl ? <FastImage style={styles.itemImage} source={{ uri: item.imageUrl }} /> : null}
|
{item.imageUrl ? <FastImage style={styles.itemImage} source={{ uri: item.imageUrl }} /> : null}
|
||||||
<Text style={{ color: themes[theme].titleText }}>{textParser([item.text])}</Text>
|
<Text style={{ color: colors.titleText }}>{textParser([item.text])}</Text>
|
||||||
{selected ? <Check /> : null}
|
{selected ? <Check /> : null}
|
||||||
</>
|
</>
|
||||||
</Touchable>
|
</Touchable>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const Items = ({ items, selected, onSelect, theme }: IItems) => (
|
const Items = ({ items, selected, onSelect }: IItems) => (
|
||||||
<FlatList
|
<FlatList
|
||||||
data={items}
|
data={items}
|
||||||
style={[styles.items, { backgroundColor: themes[theme].backgroundColor }]}
|
style={[styles.items]}
|
||||||
contentContainerStyle={[styles.itemContent, { backgroundColor: themes[theme].backgroundColor }]}
|
contentContainerStyle={[styles.itemContent]}
|
||||||
keyboardShouldPersistTaps='always'
|
keyboardShouldPersistTaps='always'
|
||||||
ItemSeparatorComponent={List.Separator}
|
ItemSeparatorComponent={List.Separator}
|
||||||
keyExtractor={keyExtractor}
|
keyExtractor={keyExtractor}
|
||||||
renderItem={({ item }) => (
|
renderItem={({ item }) => <Item item={item} onSelect={onSelect} selected={selected.find(s => s === item.value)} />}
|
||||||
<Item item={item} onSelect={onSelect} theme={theme} selected={selected.find(s => s === item.value)} />
|
|
||||||
)}
|
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue