forked from Ivasoft/mattermost-mobile
Compare commits
31 Commits
voice-mess
...
gitpod
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
70f51889e5 | ||
|
|
11242def6a | ||
|
|
bb051b83b9 | ||
|
|
c82c634523 | ||
|
|
0be105e8fb | ||
|
|
e5d2e1d364 | ||
|
|
cae9dc21d2 | ||
|
|
6b2d8ceff3 | ||
|
|
b90eaefcc0 | ||
|
|
5b695d17b2 | ||
|
|
64f582952c | ||
|
|
388a72e2b6 | ||
|
|
6c89f461c0 | ||
|
|
756e842aa4 | ||
|
|
4f4f96ff24 | ||
|
|
b086776c33 | ||
|
|
52215b7749 | ||
|
|
084d342e1d | ||
|
|
f000014809 | ||
|
|
fa923a7dfd | ||
|
|
a23b156f7a | ||
|
|
5c7e89d7de | ||
|
|
e1c74124e2 | ||
|
|
391c120db9 | ||
|
|
380b375411 | ||
|
|
d201035a89 | ||
|
|
3e7ebfe95c | ||
|
|
4c4d5475f3 | ||
|
|
2b4b7c7e92 | ||
|
|
681b6b0b0f | ||
|
|
e905f7df29 |
@@ -1,20 +1,20 @@
|
||||
version: 2.1
|
||||
orbs:
|
||||
owasp: entur/owasp@0.0.10
|
||||
node: circleci/node@5.0.2
|
||||
node: circleci/node@5.0.3
|
||||
|
||||
executors:
|
||||
android:
|
||||
parameters:
|
||||
resource_class:
|
||||
default: large
|
||||
default: xlarge
|
||||
type: string
|
||||
environment:
|
||||
NODE_OPTIONS: --max_old_space_size=12000
|
||||
NODE_ENV: production
|
||||
BABEL_ENV: production
|
||||
docker:
|
||||
- image: cimg/android:2022.03-node
|
||||
- image: cimg/android:2022.09.2-node
|
||||
working_directory: ~/mattermost-mobile
|
||||
resource_class: <<parameters.resource_class>>
|
||||
|
||||
@@ -105,7 +105,7 @@ commands:
|
||||
description: "Get JavaScript dependencies"
|
||||
steps:
|
||||
- node/install:
|
||||
node-version: '16.14.2'
|
||||
node-version: '18.7.0'
|
||||
- restore_cache:
|
||||
name: Restore npm cache
|
||||
key: v2-npm-{{ checksum "package.json" }}-{{ arch }}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
{
|
||||
"extends": [
|
||||
"plugin:mattermost/react",
|
||||
"./eslint/eslint-mattermost",
|
||||
"./eslint/eslint-react",
|
||||
"plugin:@typescript-eslint/eslint-recommended",
|
||||
"plugin:@typescript-eslint/recommended",
|
||||
"plugin:react-hooks/recommended"
|
||||
@@ -8,7 +9,6 @@
|
||||
"parser": "@typescript-eslint/parser",
|
||||
"plugins": [
|
||||
"@typescript-eslint",
|
||||
"mattermost",
|
||||
"import"
|
||||
],
|
||||
"settings": {
|
||||
|
||||
@@ -67,4 +67,4 @@ untyped-import
|
||||
untyped-type-import
|
||||
|
||||
[version]
|
||||
^0.176.3
|
||||
^0.182.0
|
||||
|
||||
13
.github/codeql/codeql-config.yml
vendored
Normal file
13
.github/codeql/codeql-config.yml
vendored
Normal file
@@ -0,0 +1,13 @@
|
||||
name: "CodeQL config"
|
||||
|
||||
query-filters:
|
||||
- exclude:
|
||||
problem.severity:
|
||||
- warning
|
||||
- recommendation
|
||||
- exclude:
|
||||
id: js/insecure-randomness
|
||||
|
||||
paths-ignore:
|
||||
- test
|
||||
- '**/*.test.*'
|
||||
23
.github/workflows/codeql-analysis.yml
vendored
23
.github/workflows/codeql-analysis.yml
vendored
@@ -9,8 +9,13 @@ on:
|
||||
schedule:
|
||||
- cron: '0 0 * * 0'
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
analyze:
|
||||
permissions:
|
||||
security-events: write
|
||||
name: Analyze
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
@@ -18,26 +23,20 @@ jobs:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
language: [ 'javascript' ]
|
||||
# CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ]
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v3
|
||||
|
||||
# Initializes the CodeQL tools for scanning.
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@v1
|
||||
uses: github/codeql-action/init@v2
|
||||
with:
|
||||
languages: ${{ matrix.language }}
|
||||
# If you wish to specify custom queries, you can do so here or in a config file.
|
||||
# By default, queries listed here will override any specified in a config file.
|
||||
# Prefix the list here with "+" to use these queries and those in the config file.
|
||||
# queries: ./path/to/local/query, your-org/your-repo/queries@main
|
||||
config-file: ./.github/codeql/codeql-config.yml
|
||||
|
||||
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
|
||||
# If this step fails, then you should remove it and run the build manually (see below)
|
||||
# Autobuild attempts to build any compiled languages
|
||||
- name: Autobuild
|
||||
uses: github/codeql-action/autobuild@v1
|
||||
uses: github/codeql-action/autobuild@v2
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@v1
|
||||
uses: github/codeql-action/analyze@v2
|
||||
|
||||
2
.gitignore
vendored
2
.gitignore
vendored
@@ -44,6 +44,8 @@ ios/.xcode.env.local
|
||||
.gradle
|
||||
local.properties
|
||||
*.iml
|
||||
*.hprof
|
||||
.cxx/
|
||||
android/app/bin
|
||||
android/app/build
|
||||
android/build
|
||||
|
||||
159
.gitpod.Dockerfile
vendored
Normal file
159
.gitpod.Dockerfile
vendored
Normal file
@@ -0,0 +1,159 @@
|
||||
FROM gitpod/workspace-full-vnc
|
||||
|
||||
ENV CYPRESS_CACHE_FOLDER=/workspace/.cypress-cache
|
||||
|
||||
# Install Cypress dependencies.
|
||||
RUN sudo apt-get update \
|
||||
&& sudo DEBIAN_FRONTEND=noninteractive apt-get install -y \
|
||||
libgtk2.0-0 \
|
||||
libgtk-3-0 \
|
||||
libnotify-dev \
|
||||
libgconf-2-4 \
|
||||
libnss3 \
|
||||
libxss1 \
|
||||
libasound2 \
|
||||
libxtst6 \
|
||||
xauth \
|
||||
xvfb \
|
||||
&& sudo rm -rf /var/lib/apt/lists/*
|
||||
|
||||
RUN mkdir -p /workspace/persist/.cache/go-build
|
||||
ENV GOCACHE=/workspace/persist/.cache/go-build
|
||||
|
||||
ENV MM_SERVICESETTINGS_ENABLEDEVELOPER=true
|
||||
|
||||
# Copied from https://github.com/react-native-community/docker-android/blob/master/Dockerfile
|
||||
|
||||
LABEL Description="This image provides a base Android development environment for React Native, and may be used to run tests."
|
||||
|
||||
ENV DEBIAN_FRONTEND=noninteractive
|
||||
|
||||
# set default build arguments
|
||||
# https://developer.android.com/studio#command-tools
|
||||
ARG SDK_VERSION=commandlinetools-linux-8512546_latest.zip
|
||||
ARG ANDROID_BUILD_VERSION=31
|
||||
ARG ANDROID_TOOLS_VERSION=31.0.0
|
||||
ARG BUCK_VERSION=2022.05.05.01
|
||||
# Buck doesn't support versions beyond NDK 21
|
||||
# Therefore we need to diverge the NDK version and set NDK_HOME
|
||||
# for Buck to pick it up correctly.
|
||||
ARG NDK_VERSION_BUCK=21.4.7075529
|
||||
ARG NDK_VERSION_GRADLE=23.1.7779620
|
||||
ARG NODE_VERSION=14.x
|
||||
ARG WATCHMAN_VERSION=4.9.0
|
||||
ARG CMAKE_VERSION=3.18.1
|
||||
|
||||
# set default environment variables, please don't remove old env for compatibilty issue
|
||||
ENV ADB_INSTALL_TIMEOUT=10
|
||||
ENV ANDROID_HOME=/opt/android
|
||||
ENV ANDROID_SDK_ROOT=${ANDROID_HOME}
|
||||
ENV ANDROID_NDK_BUCK=${ANDROID_HOME}/ndk/$NDK_VERSION_BUCK
|
||||
ENV ANDROID_NDK_GRADLE=${ANDROID_HOME}/ndk/$NDK_VERSION_GRADLE
|
||||
# this is needed for Buck to be able to recognize NDK 21
|
||||
ENV NDK_HOME=${ANDROID_HOME}/ndk/$NDK_VERSION_BUCK
|
||||
ENV JAVA_HOME=/usr/lib/jvm/java-11-openjdk-amd64
|
||||
ENV CMAKE_BIN_PATH=${ANDROID_HOME}/cmake/$CMAKE_VERSION/bin
|
||||
|
||||
ENV PATH=${CMAKE_BIN_PATH}:${ANDROID_HOME}/cmdline-tools/latest/bin:${ANDROID_HOME}/emulator:${ANDROID_HOME}/platform-tools:${ANDROID_HOME}/tools:${ANDROID_HOME}/tools/bin:/opt/buck/bin/:${PATH}
|
||||
|
||||
# Install system dependencies
|
||||
RUN apt update -qq && apt install -qq -y --no-install-recommends \
|
||||
apt-transport-https \
|
||||
curl \
|
||||
file \
|
||||
gcc \
|
||||
git \
|
||||
g++ \
|
||||
gnupg2 \
|
||||
libc++1-10 \
|
||||
libgl1 \
|
||||
libtcmalloc-minimal4 \
|
||||
make \
|
||||
openjdk-11-jdk-headless \
|
||||
openssh-client \
|
||||
patch \
|
||||
python3 \
|
||||
python3-distutils \
|
||||
rsync \
|
||||
ruby \
|
||||
ruby-dev \
|
||||
tzdata \
|
||||
unzip \
|
||||
sudo \
|
||||
ninja-build \
|
||||
zip \
|
||||
# Dev libraries requested by Hermes
|
||||
libicu-dev \
|
||||
# Emulator & video bridge dependencies
|
||||
libc6 \
|
||||
libdbus-1-3 \
|
||||
libfontconfig1 \
|
||||
libgcc1 \
|
||||
libpulse0 \
|
||||
libtinfo5 \
|
||||
libx11-6 \
|
||||
libxcb1 \
|
||||
libxdamage1 \
|
||||
libnss3 \
|
||||
libxcomposite1 \
|
||||
libxcursor1 \
|
||||
libxi6 \
|
||||
libxext6 \
|
||||
libxfixes3 \
|
||||
zlib1g \
|
||||
libgl1 \
|
||||
pulseaudio \
|
||||
socat \
|
||||
&& gem install bundler \
|
||||
&& rm -rf /var/lib/apt/lists/*;
|
||||
|
||||
# install nodejs and yarn packages from nodesource
|
||||
RUN curl -sL https://deb.nodesource.com/setup_${NODE_VERSION} | bash - \
|
||||
&& apt-get update -qq \
|
||||
&& apt-get install -qq -y --no-install-recommends nodejs \
|
||||
&& npm i -g yarn \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# download and install buck using the java11 pex from Jitpack
|
||||
RUN curl -L https://jitpack.io/com/github/facebook/buck/v${BUCK_VERSION}/buck-v${BUCK_VERSION}-java11.pex -o /tmp/buck.pex \
|
||||
&& mv /tmp/buck.pex /usr/local/bin/buck \
|
||||
&& chmod +x /usr/local/bin/buck
|
||||
|
||||
# Full reference at https://dl.google.com/android/repository/repository2-1.xml
|
||||
# download and unpack android
|
||||
# workaround buck clang version detection by symlinking
|
||||
RUN curl -sS https://dl.google.com/android/repository/${SDK_VERSION} -o /tmp/sdk.zip \
|
||||
&& mkdir -p ${ANDROID_HOME}/cmdline-tools \
|
||||
&& unzip -q -d ${ANDROID_HOME}/cmdline-tools /tmp/sdk.zip \
|
||||
&& mv ${ANDROID_HOME}/cmdline-tools/cmdline-tools ${ANDROID_HOME}/cmdline-tools/latest \
|
||||
&& rm /tmp/sdk.zip \
|
||||
&& yes | sdkmanager --licenses \
|
||||
&& yes | sdkmanager "platform-tools" \
|
||||
"emulator" \
|
||||
"platforms;android-$ANDROID_BUILD_VERSION" \
|
||||
"build-tools;$ANDROID_TOOLS_VERSION" \
|
||||
"cmake;$CMAKE_VERSION" \
|
||||
"system-images;android-21;google_apis;armeabi-v7a" \
|
||||
"ndk;$NDK_VERSION_BUCK" \
|
||||
"ndk;$NDK_VERSION_GRADLE" \
|
||||
&& rm -rf ${ANDROID_HOME}/.android \
|
||||
&& chmod 777 -R /opt/android \
|
||||
&& ln -s ${ANDROID_NDK_BUCK}/toolchains/llvm/prebuilt/linux-x86_64/lib64/clang/9.0.9 ${ANDROID_NDK_BUCK}/toolchains/llvm/prebuilt/linux-x86_64/lib64/clang/9.0.8
|
||||
|
||||
|
||||
|
||||
# Copied from https://github.com/gengjiawen/ci-sample/blob/master/.gitpod.Dockerfile
|
||||
|
||||
# FROM reactnativecommunity/react-native-android
|
||||
|
||||
### Gitpod user ###
|
||||
# '-l': see https://docs.docker.com/develop/develop-images/dockerfile_best-practices/#user
|
||||
RUN useradd -l -u 33333 -G sudo -md /home/gitpod -s /bin/bash -p gitpod gitpod \
|
||||
# passwordless sudo for users in the 'sudo' group
|
||||
&& sed -i.bkp -e 's/%sudo\s\+ALL=(ALL\(:ALL\)\?)\s\+ALL/%sudo ALL=NOPASSWD:ALL/g' /etc/sudoers
|
||||
|
||||
# Install custom tools, runtimes, etc.
|
||||
# For example "bastet", a command-line tetris clone:
|
||||
# RUN brew install bastet
|
||||
#
|
||||
# More information: https://www.gitpod.io/docs/config-docker/
|
||||
6
.gitpod.yml
Normal file
6
.gitpod.yml
Normal file
@@ -0,0 +1,6 @@
|
||||
image:
|
||||
file: .gitpod.Dockerfile
|
||||
|
||||
tasks:
|
||||
- init: npm install
|
||||
- command: npm run android
|
||||
101
NOTICE.txt
101
NOTICE.txt
@@ -224,18 +224,18 @@ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH RE
|
||||
|
||||
## @nozbe/watermelondb
|
||||
|
||||
This product contains 'cameraroll' by Bartol Karuza.
|
||||
This product contains '@nozbe/watermelondb' by Nozbe.
|
||||
|
||||
React-native native module that provides access to the local camera roll or photo library
|
||||
Build powerful React and React Native apps that scale from hundreds to tens of thousands of records and remain fast ⚡️
|
||||
|
||||
* HOMEPAGE:
|
||||
* https://github.com/react-native-community/react-native-cameraroll
|
||||
* https://github.com/Nozbe/WatermelonDB/
|
||||
|
||||
* LICENSE: MIT
|
||||
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2020 Elias Nahum
|
||||
Copyright (c) Nozbe
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
@@ -293,50 +293,14 @@ SOFTWARE.
|
||||
|
||||
---
|
||||
|
||||
## @react-native-community/art
|
||||
## @react-native-cameraroll/react-native-cameraroll
|
||||
|
||||
This product contains '@react-native-community/art' by react-native-art.
|
||||
|
||||
React Native module that allows you to draw vector graphics
|
||||
|
||||
* HOMEPAGE:
|
||||
* https://github.com/react-native-art/art
|
||||
|
||||
* LICENSE: MIT
|
||||
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2019 react-native-community
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
|
||||
---
|
||||
|
||||
## @react-native-community/cameraroll
|
||||
|
||||
This product contains 'cameraroll' by Bartol Karuza.
|
||||
This product contains 'react-native-cameraroll' by Bartol Karuza.
|
||||
|
||||
React-native native module that provides access to the local camera roll or photo library
|
||||
|
||||
* HOMEPAGE:
|
||||
* https://github.com/react-native-community/react-native-cameraroll
|
||||
* https://github.com/react-native-cameraroll/react-native-cameraroll
|
||||
|
||||
* LICENSE: MIT
|
||||
|
||||
@@ -365,14 +329,14 @@ SOFTWARE.
|
||||
|
||||
---
|
||||
|
||||
## @react-native-community/clipboard
|
||||
## @react-native-clipboard/clipboard
|
||||
|
||||
This product contains '@react-native-community/clipboard' by React Native Community.
|
||||
This product contains '@react-native-clipboard/clipboard' by React Native Community.
|
||||
|
||||
React Native Clipboard API for both iOS and Android
|
||||
|
||||
* HOMEPAGE:
|
||||
* https://github.com/react-native-community/clipboard
|
||||
* https://github.com/react-native-clipboard/clipboard
|
||||
|
||||
* LICENSE: MIT
|
||||
|
||||
@@ -2350,20 +2314,20 @@ SOFTWARE.
|
||||
|
||||
---
|
||||
|
||||
## react-native-neomorph-shadows
|
||||
## react-native-shadow-2
|
||||
|
||||
This product contains a modified version of 'react-native-neomorph-shadows' by Daniel.
|
||||
This product contains a modified version of 'react-native-shadow-2' by Henrique Bruno Fantauzzi de Almeida.
|
||||
|
||||
Shadows and neumorphism/neomorphism for iOS & Android (like iOS).
|
||||
Cross-platform shadow for React Native. Supports Android, iOS, Web and Expo.
|
||||
|
||||
* HOMEPAGE:
|
||||
* https://github.com/tokkozhin/react-native-neomorph-shadows
|
||||
* https://github.com/SrBrahma/react-native-shadow-2
|
||||
|
||||
* LICENSE: MIT
|
||||
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2020 tokkozhin
|
||||
Copyright (c) 2021 Henrique Bruno Fantauzzi de Almeida
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
@@ -2957,41 +2921,6 @@ The above copyright notice and this permission notice shall be included in all c
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
|
||||
---
|
||||
|
||||
## rn-placeholder
|
||||
|
||||
This product contains a modified version of 'rn-placeholder' by Marvin FRACHET.
|
||||
|
||||
Display some placeholder stuff before rendering your text or media content in React Native
|
||||
|
||||
* HOMEPAGE:
|
||||
* https://github.com/mfrachet/rn-placeholder
|
||||
|
||||
* LICENSE: MIT
|
||||
|
||||
Copyright (c) 2004-Today Marvin Frachet
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of this software and associated documentation files (the
|
||||
"Software"), to deal in the Software without restriction, including
|
||||
without limitation the rights to use, copy, modify, merge, publish,
|
||||
distribute, sublicense, and/or sell copies of the Software, and to
|
||||
permit persons to whom the Software is furnished to do so, subject to
|
||||
the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be
|
||||
included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
|
||||
---
|
||||
|
||||
## semver
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
apply plugin: "com.android.application"
|
||||
apply plugin: 'kotlin-android'
|
||||
import com.android.build.OutputFile
|
||||
import org.apache.tools.ant.taskdefs.condition.Os
|
||||
|
||||
/**
|
||||
* The react.gradle file registers a task for each build variant (e.g. bundleDebugJsAndAssets
|
||||
@@ -144,33 +145,21 @@ android {
|
||||
applicationId "com.mattermost.rnbeta"
|
||||
minSdkVersion rootProject.ext.minSdkVersion
|
||||
targetSdkVersion rootProject.ext.targetSdkVersion
|
||||
versionCode 426
|
||||
versionCode 428
|
||||
versionName "2.0.0"
|
||||
testBuildType System.getProperty('testBuildType', 'debug')
|
||||
testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner'
|
||||
buildConfigField "boolean", "IS_NEW_ARCHITECTURE_ENABLED", isNewArchitectureEnabled().toString()
|
||||
|
||||
if (isNewArchitectureEnabled()) {
|
||||
// We configure the NDK build only if you decide to opt-in for the New Architecture.
|
||||
// We configure the CMake build only if you decide to opt-in for the New Architecture.
|
||||
externalNativeBuild {
|
||||
ndkBuild {
|
||||
arguments "APP_PLATFORM=android-21",
|
||||
"APP_STL=c++_shared",
|
||||
"NDK_TOOLCHAIN_VERSION=clang",
|
||||
"GENERATED_SRC_DIR=$buildDir/generated/source",
|
||||
"PROJECT_BUILD_DIR=$buildDir",
|
||||
"REACT_ANDROID_DIR=$rootDir/../node_modules/react-native/ReactAndroid",
|
||||
"REACT_ANDROID_BUILD_DIR=$rootDir/../node_modules/react-native/ReactAndroid/build",
|
||||
"NODE_MODULES_DIR=$rootDir/../node_modules"
|
||||
cFlags "-Wall", "-Werror", "-fexceptions", "-frtti", "-DWITH_INSPECTOR=1"
|
||||
cppFlags "-std=c++17"
|
||||
// Make sure this target name is the same you specify inside the
|
||||
// src/main/jni/Android.mk file for the `LOCAL_MODULE` variable.
|
||||
targets "rndiffapp_appmodules"
|
||||
// Fix for windows limit on number of character in file paths and in command lines
|
||||
if (Os.isFamily(Os.FAMILY_WINDOWS)) {
|
||||
arguments "NDK_APP_SHORT_COMMANDS=true"
|
||||
}
|
||||
cmake {
|
||||
arguments "-DPROJECT_BUILD_DIR=$buildDir",
|
||||
"-DREACT_ANDROID_DIR=$rootDir/../node_modules/react-native/ReactAndroid",
|
||||
"-DREACT_ANDROID_BUILD_DIR=$rootDir/../node_modules/react-native/ReactAndroid/build",
|
||||
"-DNODE_MODULES_DIR=$rootDir/../node_modules",
|
||||
"-DANDROID_STL=c++_shared"
|
||||
}
|
||||
}
|
||||
if (!enableSeparateBuildPerCPUArchitecture) {
|
||||
@@ -184,8 +173,8 @@ android {
|
||||
if (isNewArchitectureEnabled()) {
|
||||
// We configure the NDK build only if you decide to opt-in for the New Architecture.
|
||||
externalNativeBuild {
|
||||
ndkBuild {
|
||||
path "$projectDir/src/main/jni/Android.mk"
|
||||
cmake {
|
||||
path "$projectDir/src/main/jni/CMakeLists.txt"
|
||||
}
|
||||
}
|
||||
def reactAndroidProjectDir = project(':ReactAndroid').projectDir
|
||||
@@ -207,15 +196,15 @@ android {
|
||||
preReleaseBuild.dependsOn(packageReactNdkReleaseLibs)
|
||||
|
||||
// Due to a bug inside AGP, we have to explicitly set a dependency
|
||||
// between configureNdkBuild* tasks and the preBuild tasks.
|
||||
// between configureCMakeDebug* tasks and the preBuild tasks.
|
||||
// This can be removed once this is solved: https://issuetracker.google.com/issues/207403732
|
||||
configureNdkBuildRelease.dependsOn(preReleaseBuild)
|
||||
configureNdkBuildDebug.dependsOn(preDebugBuild)
|
||||
configureCMakeDebugRelease.dependsOn(preReleaseBuild)
|
||||
configureCMakeDebugDebug.dependsOn(preDebugBuild)
|
||||
reactNativeArchitectures().each { architecture ->
|
||||
tasks.findByName("configureNdkBuildDebug[${architecture}]")?.configure {
|
||||
tasks.findByName("configureCMakeDebugDebug[${architecture}]")?.configure {
|
||||
dependsOn("preDebugBuild")
|
||||
}
|
||||
tasks.findByName("configureNdkBuildRelease[${architecture}]")?.configure {
|
||||
tasks.findByName("configureCMakeDebugRelease[${architecture}]")?.configure {
|
||||
dependsOn("preReleaseBuild")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,8 @@
|
||||
<uses-permission-sdk-23 android:name="android.permission.VIBRATE"/>
|
||||
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
|
||||
<uses-permission android:name="android.permission.CAMERA"/>
|
||||
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES"/>
|
||||
<uses-permission android:name="android.permission.READ_MEDIA_VIDEO"/>
|
||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
|
||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
|
||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||
|
||||
@@ -1,48 +0,0 @@
|
||||
THIS_DIR := $(call my-dir)
|
||||
|
||||
include $(REACT_ANDROID_DIR)/Android-prebuilt.mk
|
||||
|
||||
# If you wish to add a custom TurboModule or Fabric component in your app you
|
||||
# will have to include the following autogenerated makefile.
|
||||
# include $(GENERATED_SRC_DIR)/codegen/jni/Android.mk
|
||||
include $(CLEAR_VARS)
|
||||
|
||||
LOCAL_PATH := $(THIS_DIR)
|
||||
|
||||
# You can customize the name of your application .so file here.
|
||||
LOCAL_MODULE := mattermost_appmodules
|
||||
|
||||
LOCAL_C_INCLUDES := $(LOCAL_PATH)
|
||||
LOCAL_SRC_FILES := $(wildcard $(LOCAL_PATH)/*.cpp)
|
||||
LOCAL_EXPORT_C_INCLUDES := $(LOCAL_PATH)
|
||||
|
||||
# If you wish to add a custom TurboModule or Fabric component in your app you
|
||||
# will have to uncomment those lines to include the generated source
|
||||
# files from the codegen (placed in $(GENERATED_SRC_DIR)/codegen/jni)
|
||||
#
|
||||
# LOCAL_C_INCLUDES += $(GENERATED_SRC_DIR)/codegen/jni
|
||||
# LOCAL_SRC_FILES += $(wildcard $(GENERATED_SRC_DIR)/codegen/jni/*.cpp)
|
||||
# LOCAL_EXPORT_C_INCLUDES += $(GENERATED_SRC_DIR)/codegen/jni
|
||||
|
||||
# Here you should add any native library you wish to depend on.
|
||||
LOCAL_SHARED_LIBRARIES := \
|
||||
libfabricjni \
|
||||
libfbjni \
|
||||
libfolly_runtime \
|
||||
libglog \
|
||||
libjsi \
|
||||
libreact_codegen_rncore \
|
||||
libreact_debug \
|
||||
libreact_nativemodule_core \
|
||||
libreact_render_componentregistry \
|
||||
libreact_render_core \
|
||||
libreact_render_debug \
|
||||
libreact_render_graphics \
|
||||
librrc_view \
|
||||
libruntimeexecutor \
|
||||
libturbomodulejsijni \
|
||||
libyoga
|
||||
|
||||
LOCAL_CFLAGS := -DLOG_TAG=\"ReactNative\" -fexceptions -frtti -std=c++17 -Wall
|
||||
|
||||
include $(BUILD_SHARED_LIBRARY)
|
||||
7
android/app/src/main/jni/CMakeLists.txt
Normal file
7
android/app/src/main/jni/CMakeLists.txt
Normal file
@@ -0,0 +1,7 @@
|
||||
cmake_minimum_required(VERSION 3.13)
|
||||
|
||||
# Define the library name here.
|
||||
project(rndiffapp_appmodules)
|
||||
|
||||
# This file includes all the necessary to let you build your application with the New Architecture.
|
||||
include(${REACT_ANDROID_DIR}/cmake-utils/ReactNative-application.cmake)
|
||||
@@ -1,12 +1,13 @@
|
||||
#include "MainApplicationModuleProvider.h"
|
||||
|
||||
#include <rncli.h>
|
||||
#include <rncore.h>
|
||||
|
||||
namespace facebook {
|
||||
namespace react {
|
||||
|
||||
std::shared_ptr<TurboModule> MainApplicationModuleProvider(
|
||||
const std::string moduleName,
|
||||
const std::string &moduleName,
|
||||
const JavaTurboModule::InitParams ¶ms) {
|
||||
// Here you can provide your own module provider for TurboModules coming from
|
||||
// either your application or from external libraries. The approach to follow
|
||||
@@ -17,6 +18,13 @@ std::shared_ptr<TurboModule> MainApplicationModuleProvider(
|
||||
// return module;
|
||||
// }
|
||||
// return rncore_ModuleProvider(moduleName, params);
|
||||
|
||||
// Module providers autolinked by RN CLI
|
||||
auto rncli_module = rncli_ModuleProvider(moduleName, params);
|
||||
if (rncli_module != nullptr) {
|
||||
return rncli_module;
|
||||
}
|
||||
|
||||
return rncore_ModuleProvider(moduleName, params);
|
||||
}
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ namespace facebook {
|
||||
namespace react {
|
||||
|
||||
std::shared_ptr<TurboModule> MainApplicationModuleProvider(
|
||||
const std::string moduleName,
|
||||
const std::string &moduleName,
|
||||
const JavaTurboModule::InitParams ¶ms);
|
||||
|
||||
} // namespace react
|
||||
|
||||
@@ -22,24 +22,24 @@ void MainApplicationTurboModuleManagerDelegate::registerNatives() {
|
||||
|
||||
std::shared_ptr<TurboModule>
|
||||
MainApplicationTurboModuleManagerDelegate::getTurboModule(
|
||||
const std::string name,
|
||||
const std::shared_ptr<CallInvoker> jsInvoker) {
|
||||
const std::string &name,
|
||||
const std::shared_ptr<CallInvoker> &jsInvoker) {
|
||||
// Not implemented yet: provide pure-C++ NativeModules here.
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::shared_ptr<TurboModule>
|
||||
MainApplicationTurboModuleManagerDelegate::getTurboModule(
|
||||
const std::string name,
|
||||
const std::string &name,
|
||||
const JavaTurboModule::InitParams ¶ms) {
|
||||
return MainApplicationModuleProvider(name, params);
|
||||
}
|
||||
|
||||
bool MainApplicationTurboModuleManagerDelegate::canCreateTurboModule(
|
||||
std::string name) {
|
||||
const std::string &name) {
|
||||
return getTurboModule(name, nullptr) != nullptr ||
|
||||
getTurboModule(name, {.moduleName = name}) != nullptr;
|
||||
}
|
||||
|
||||
} // namespace react
|
||||
} // namespace facebook
|
||||
} // namespace facebook
|
||||
|
||||
@@ -14,25 +14,25 @@ class MainApplicationTurboModuleManagerDelegate
|
||||
public:
|
||||
// Adapt it to the package you used for your Java class.
|
||||
static constexpr auto kJavaDescriptor =
|
||||
"Lcom/mattermost/newarchitecture/modules/MainApplicationTurboModuleManagerDelegate;";
|
||||
"Lcom/rndiffapp/newarchitecture/modules/MainApplicationTurboModuleManagerDelegate;";
|
||||
|
||||
static jni::local_ref<jhybriddata> initHybrid(jni::alias_ref<jhybridobject>);
|
||||
|
||||
static void registerNatives();
|
||||
|
||||
std::shared_ptr<TurboModule> getTurboModule(
|
||||
const std::string name,
|
||||
const std::shared_ptr<CallInvoker> jsInvoker) override;
|
||||
const std::string &name,
|
||||
const std::shared_ptr<CallInvoker> &jsInvoker) override;
|
||||
std::shared_ptr<TurboModule> getTurboModule(
|
||||
const std::string name,
|
||||
const std::string &name,
|
||||
const JavaTurboModule::InitParams ¶ms) override;
|
||||
|
||||
/**
|
||||
* Test-only method. Allows user to verify whether a TurboModule can be
|
||||
* created by instances of this class.
|
||||
*/
|
||||
bool canCreateTurboModule(std::string name);
|
||||
bool canCreateTurboModule(const std::string &name);
|
||||
};
|
||||
|
||||
} // namespace react
|
||||
} // namespace facebook
|
||||
} // namespace facebook
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
#include <fbjni/fbjni.h>
|
||||
#include <react/renderer/componentregistry/ComponentDescriptorProviderRegistry.h>
|
||||
#include <react/renderer/components/rncore/ComponentDescriptors.h>
|
||||
#include <rncli.h>
|
||||
|
||||
namespace facebook {
|
||||
namespace react {
|
||||
@@ -14,6 +15,9 @@ std::shared_ptr<ComponentDescriptorProviderRegistry const>
|
||||
MainComponentsRegistry::sharedProviderRegistry() {
|
||||
auto providerRegistry = CoreComponentsRegistry::sharedProviderRegistry();
|
||||
|
||||
// Autolinked providers registered by RN CLI
|
||||
rncli_registerProviders(providerRegistry);
|
||||
|
||||
// Custom Fabric Components go here. You can register custom
|
||||
// components coming from your App or from 3rd party libraries here.
|
||||
//
|
||||
@@ -58,4 +62,4 @@ void MainComponentsRegistry::registerNatives() {
|
||||
}
|
||||
|
||||
} // namespace react
|
||||
} // namespace facebook
|
||||
} // namespace facebook
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
import org.apache.tools.ant.taskdefs.condition.Os
|
||||
|
||||
// Top-level build file where you can add configuration options common to all sub-projects/modules.
|
||||
|
||||
buildscript {
|
||||
@@ -27,7 +25,7 @@ buildscript {
|
||||
google()
|
||||
}
|
||||
dependencies {
|
||||
classpath("com.android.tools.build:gradle:7.1.1")
|
||||
classpath("com.android.tools.build:gradle:7.2.1")
|
||||
classpath("com.facebook.react:react-native-gradle-plugin")
|
||||
classpath("de.undercouch:gradle-download-task:5.0.1")
|
||||
classpath('com.google.gms:google-services:4.3.10')
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
# Specifies the JVM arguments used for the daemon process.
|
||||
# The setting is particularly useful for tweaking memory settings.
|
||||
# Default value: -Xmx512m -XX:MaxMetaspaceSize=256m
|
||||
org.gradle.jvmargs=-Xmx2048m -XX:MaxMetaspaceSize=1g
|
||||
org.gradle.jvmargs=-Xmx2048m -XX:MaxMetaspaceSize=512m
|
||||
|
||||
# When configured, Gradle will run in incubating parallel mode.
|
||||
# This option should only be used with decoupled projects. More details, visit
|
||||
|
||||
BIN
android/gradle/wrapper/gradle-wrapper.jar
vendored
BIN
android/gradle/wrapper/gradle-wrapper.jar
vendored
Binary file not shown.
@@ -2,4 +2,4 @@ distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.3-all.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.5.1-all.zip
|
||||
|
||||
@@ -17,10 +17,10 @@ import NetworkManager from '@managers/network_manager';
|
||||
import {prepareMyChannelsForTeam, getChannelById, getChannelByName, getMyChannel, getChannelInfo, queryMyChannelSettingsByIds, getMembersCountByChannelsId} from '@queries/servers/channel';
|
||||
import {queryPreferencesByCategoryAndName} from '@queries/servers/preference';
|
||||
import {getCommonSystemValues, getConfig, getCurrentTeamId, getCurrentUserId, getLicense, setCurrentChannelId} from '@queries/servers/system';
|
||||
import {prepareMyTeams, getNthLastChannelFromTeam, getMyTeamById, getTeamById, getTeamByName, queryMyTeams} from '@queries/servers/team';
|
||||
import {getNthLastChannelFromTeam, getMyTeamById, getTeamByName, queryMyTeams} from '@queries/servers/team';
|
||||
import {getCurrentUser} from '@queries/servers/user';
|
||||
import EphemeralStore from '@store/ephemeral_store';
|
||||
import {generateChannelNameFromDisplayName, getDirectChannelName, isArchived, isDMorGM} from '@utils/channel';
|
||||
import {generateChannelNameFromDisplayName, getDirectChannelName, isDMorGM} from '@utils/channel';
|
||||
import {isTablet} from '@utils/helpers';
|
||||
import {logError, logInfo} from '@utils/log';
|
||||
import {showMuteChannelSnackbar} from '@utils/snack_bar';
|
||||
@@ -32,14 +32,11 @@ import {fetchPostsForChannel} from './post';
|
||||
import {setDirectChannelVisible} from './preference';
|
||||
import {fetchRolesIfNeeded} from './role';
|
||||
import {forceLogoutIfNecessary} from './session';
|
||||
import {addUserToTeam, fetchTeamByName, removeUserFromTeam} from './team';
|
||||
import {addCurrentUserToTeam, fetchTeamByName, removeCurrentUserFromTeam} from './team';
|
||||
import {fetchProfilesInGroupChannels, fetchProfilesPerChannels, fetchUsersByIds, updateUsersNoLongerVisible} from './user';
|
||||
|
||||
import type {Client} from '@client/rest';
|
||||
import type ChannelModel from '@typings/database/models/servers/channel';
|
||||
import type MyChannelModel from '@typings/database/models/servers/my_channel';
|
||||
import type MyTeamModel from '@typings/database/models/servers/my_team';
|
||||
import type TeamModel from '@typings/database/models/servers/team';
|
||||
|
||||
export type MyChannelsRequest = {
|
||||
categories?: CategoryWithChannels[];
|
||||
@@ -531,11 +528,12 @@ export async function fetchDirectChannelsInfo(serverUrl: string, directChannels:
|
||||
return fetchMissingDirectChannelsInfo(serverUrl, channels, currentUser?.locale, teammateDisplayNameSetting, currentUser?.id);
|
||||
}
|
||||
|
||||
export async function joinChannel(serverUrl: string, userId: string, teamId: string, channelId?: string, channelName?: string, fetchOnly = false) {
|
||||
export async function joinChannel(serverUrl: string, teamId: string, channelId?: string, channelName?: string, fetchOnly = false) {
|
||||
const operator = DatabaseManager.serverDatabases[serverUrl]?.operator;
|
||||
if (!operator) {
|
||||
return {error: `${serverUrl} database not found`};
|
||||
}
|
||||
const database = operator.database;
|
||||
|
||||
let client: Client;
|
||||
try {
|
||||
@@ -544,6 +542,8 @@ export async function joinChannel(serverUrl: string, userId: string, teamId: str
|
||||
return {error};
|
||||
}
|
||||
|
||||
const userId = await getCurrentUserId(database);
|
||||
|
||||
let member: ChannelMembership | undefined;
|
||||
let channel: Channel | undefined;
|
||||
try {
|
||||
@@ -614,8 +614,7 @@ export async function joinChannelIfNeeded(serverUrl: string, channelId: string)
|
||||
return {error: undefined};
|
||||
}
|
||||
|
||||
const userId = await getCurrentUserId(database);
|
||||
return joinChannel(serverUrl, userId, '', channelId);
|
||||
return joinChannel(serverUrl, '', channelId);
|
||||
} catch (error) {
|
||||
return {error};
|
||||
}
|
||||
@@ -634,167 +633,77 @@ export async function markChannelAsRead(serverUrl: string, channelId: string) {
|
||||
|
||||
export async function switchToChannelByName(serverUrl: string, channelName: string, teamName: string, errorHandler: (intl: IntlShape) => void, intl: IntlShape) {
|
||||
let database;
|
||||
let operator;
|
||||
try {
|
||||
const result = DatabaseManager.getServerDatabaseAndOperator(serverUrl);
|
||||
database = result.database;
|
||||
operator = result.operator;
|
||||
} catch (e) {
|
||||
return {error: `${serverUrl} database not found`};
|
||||
}
|
||||
|
||||
const onError = (joinedTeam: boolean, teamId?: string) => {
|
||||
errorHandler(intl);
|
||||
if (joinedTeam && teamId) {
|
||||
removeCurrentUserFromTeam(serverUrl, teamId, false);
|
||||
}
|
||||
};
|
||||
|
||||
let joinedTeam = false;
|
||||
let teamId = '';
|
||||
try {
|
||||
let myChannel: MyChannelModel | ChannelMembership | undefined;
|
||||
let team: TeamModel | Team | undefined;
|
||||
let myTeam: MyTeamModel | TeamMembership | undefined;
|
||||
let name = teamName;
|
||||
const roles: string [] = [];
|
||||
const system = await getCommonSystemValues(database);
|
||||
const currentTeam = await getTeamById(database, system.currentTeamId);
|
||||
|
||||
if (name === PERMALINK_GENERIC_TEAM_NAME_REDIRECT) {
|
||||
name = currentTeam!.name;
|
||||
if (teamName === PERMALINK_GENERIC_TEAM_NAME_REDIRECT) {
|
||||
teamId = await getCurrentTeamId(database);
|
||||
} else {
|
||||
team = await getTeamByName(database, teamName);
|
||||
}
|
||||
const team = await getTeamByName(database, teamName);
|
||||
const isTeamMember = team ? await getMyTeamById(database, team.id) : false;
|
||||
teamId = team?.id || '';
|
||||
|
||||
if (!team) {
|
||||
const fetchTeam = await fetchTeamByName(serverUrl, name, true);
|
||||
if (fetchTeam.error) {
|
||||
errorHandler(intl);
|
||||
return {error: fetchTeam.error};
|
||||
if (!isTeamMember) {
|
||||
const fetchRequest = await fetchTeamByName(serverUrl, teamName);
|
||||
if (!fetchRequest.team) {
|
||||
onError(joinedTeam);
|
||||
return {error: fetchRequest.error || 'no team received'};
|
||||
}
|
||||
const {error} = await addCurrentUserToTeam(serverUrl, fetchRequest.team.id);
|
||||
if (error) {
|
||||
onError(joinedTeam);
|
||||
return {error};
|
||||
}
|
||||
teamId = fetchRequest.team.id;
|
||||
joinedTeam = true;
|
||||
}
|
||||
|
||||
team = fetchTeam.team!;
|
||||
}
|
||||
|
||||
let joinedNewTeam = false;
|
||||
myTeam = await getMyTeamById(database, team.id);
|
||||
if (!myTeam) {
|
||||
const added = await addUserToTeam(serverUrl, team.id, system.currentUserId, true);
|
||||
if (added.error) {
|
||||
errorHandler(intl);
|
||||
return {error: added.error};
|
||||
const channel = await getChannelByName(database, teamId, channelName);
|
||||
const isChannelMember = channel ? await getMyChannel(database, channel.id) : false;
|
||||
let channelId = channel?.id || '';
|
||||
if (!isChannelMember) {
|
||||
const fetchRequest = await fetchChannelByName(serverUrl, teamId, channelName, true);
|
||||
if (!fetchRequest.channel) {
|
||||
onError(joinedTeam, teamId);
|
||||
return {error: fetchRequest.error || 'cannot fetch channel'};
|
||||
}
|
||||
myTeam = added.member!;
|
||||
roles.push(...myTeam.roles.split(' '));
|
||||
joinedNewTeam = true;
|
||||
}
|
||||
|
||||
if (!myTeam) {
|
||||
errorHandler(intl);
|
||||
return {error: 'Could not fetch team member'};
|
||||
}
|
||||
|
||||
let channel: Channel | ChannelModel | undefined = await getChannelByName(database, team.id, channelName);
|
||||
if (!channel) {
|
||||
const chReq = await fetchChannelByName(serverUrl, team.id, channelName, true);
|
||||
if (chReq.error) {
|
||||
errorHandler(intl);
|
||||
return {error: chReq.error};
|
||||
}
|
||||
channel = chReq.channel;
|
||||
}
|
||||
|
||||
if (!channel) {
|
||||
errorHandler(intl);
|
||||
return {error: 'Could not fetch channel'};
|
||||
}
|
||||
|
||||
if (isArchived(channel) && system.config.ExperimentalViewArchivedChannels !== 'true') {
|
||||
errorHandler(intl);
|
||||
return {error: 'Channel is archived'};
|
||||
}
|
||||
|
||||
myChannel = await getMyChannel(database, channel.id);
|
||||
|
||||
if (!myChannel) {
|
||||
const channelTeamId = 'team_id' in channel ? channel.team_id : channel.teamId;
|
||||
const req = await fetchMyChannel(serverUrl, channelTeamId || team.id, channel.id, true);
|
||||
myChannel = req.memberships?.[0];
|
||||
}
|
||||
|
||||
if (!myChannel) {
|
||||
if (channel.type === General.PRIVATE_CHANNEL) {
|
||||
const displayName = 'display_name' in channel ? channel.display_name : channel.displayName;
|
||||
const {join} = await privateChannelJoinPrompt(displayName, intl);
|
||||
if (fetchRequest.channel.type === General.PRIVATE_CHANNEL) {
|
||||
const {join} = await privateChannelJoinPrompt(fetchRequest.channel.display_name, intl);
|
||||
if (!join) {
|
||||
if (joinedNewTeam) {
|
||||
await removeUserFromTeam(serverUrl, team.id, system.currentUserId, true);
|
||||
}
|
||||
errorHandler(intl);
|
||||
onError(joinedTeam, teamId);
|
||||
return {error: 'Refused to join Private channel'};
|
||||
}
|
||||
logInfo('joining channel', displayName, channel.id);
|
||||
const result = await joinChannel(serverUrl, system.currentUserId, team.id, channel.id, undefined, true);
|
||||
if (result.error || !result.channel) {
|
||||
if (joinedNewTeam) {
|
||||
await removeUserFromTeam(serverUrl, team.id, system.currentUserId, true);
|
||||
}
|
||||
|
||||
errorHandler(intl);
|
||||
return {error: result.error};
|
||||
}
|
||||
|
||||
myChannel = result.member!;
|
||||
roles.push(...myChannel.roles.split(' '));
|
||||
}
|
||||
|
||||
logInfo('joining channel', fetchRequest.channel.display_name, fetchRequest.channel.id);
|
||||
const joinRequest = await joinChannel(serverUrl, teamId, undefined, channelName, false);
|
||||
if (!joinRequest.channel) {
|
||||
onError(joinedTeam, teamId);
|
||||
return {error: joinRequest.error || 'no channel returned from join'};
|
||||
}
|
||||
|
||||
channelId = fetchRequest.channel.id;
|
||||
}
|
||||
|
||||
if (!myChannel) {
|
||||
errorHandler(intl);
|
||||
return {error: 'could not fetch channel member'};
|
||||
}
|
||||
|
||||
const modelPromises: Array<Promise<Model[]>> = [];
|
||||
if (!(team instanceof Model)) {
|
||||
modelPromises.push(...prepareMyTeams(operator, [team], [(myTeam as TeamMembership)]));
|
||||
} else if (!(myTeam instanceof Model)) {
|
||||
const mt: MyTeam[] = [{
|
||||
id: myTeam.team_id,
|
||||
roles: myTeam.roles,
|
||||
}];
|
||||
modelPromises.push(
|
||||
operator.handleMyTeam({myTeams: mt, prepareRecordsOnly: true}),
|
||||
operator.handleTeamMemberships({teamMemberships: [myTeam], prepareRecordsOnly: true}),
|
||||
);
|
||||
}
|
||||
|
||||
// We are checking both, so this may become an issue
|
||||
if (!(myChannel instanceof Model) && !(channel instanceof Model)) {
|
||||
modelPromises.push(...await prepareMyChannelsForTeam(operator, team.id, [channel], [myChannel]));
|
||||
}
|
||||
|
||||
let teamId;
|
||||
if (team.id !== system.currentTeamId) {
|
||||
teamId = team.id;
|
||||
}
|
||||
|
||||
let channelId;
|
||||
if (channel.id !== system.currentChannelId) {
|
||||
channelId = channel.id;
|
||||
}
|
||||
|
||||
if (modelPromises.length) {
|
||||
const models = await Promise.all(modelPromises);
|
||||
await operator.batchRecords(models.flat());
|
||||
}
|
||||
|
||||
if (teamId) {
|
||||
fetchMyChannelsForTeam(serverUrl, teamId, true, 0, false, true);
|
||||
}
|
||||
|
||||
if (teamId || channelId) {
|
||||
await switchToChannelById(serverUrl, channel.id, team.id);
|
||||
}
|
||||
|
||||
if (roles.length) {
|
||||
fetchRolesIfNeeded(serverUrl, roles);
|
||||
}
|
||||
|
||||
switchToChannelById(serverUrl, channelId, teamId);
|
||||
return {error: undefined};
|
||||
} catch (error) {
|
||||
errorHandler(intl);
|
||||
onError(joinedTeam, teamId);
|
||||
return {error};
|
||||
}
|
||||
}
|
||||
@@ -1147,7 +1056,7 @@ export async function switchToLastChannel(serverUrl: string, teamId?: string) {
|
||||
}
|
||||
}
|
||||
|
||||
export async function searchChannels(serverUrl: string, term: string, isSearch = false) {
|
||||
export async function searchChannels(serverUrl: string, term: string, teamId: string, isSearch = false) {
|
||||
const database = DatabaseManager.serverDatabases[serverUrl]?.database;
|
||||
if (!database) {
|
||||
return {error: `${serverUrl} database not found`};
|
||||
@@ -1161,9 +1070,8 @@ export async function searchChannels(serverUrl: string, term: string, isSearch =
|
||||
}
|
||||
|
||||
try {
|
||||
const currentTeamId = await getCurrentTeamId(database);
|
||||
const autoCompleteFunc = isSearch ? client.autocompleteChannelsForSearch : client.autocompleteChannels;
|
||||
const channels = await autoCompleteFunc(currentTeamId, term);
|
||||
const channels = await autoCompleteFunc(teamId, term);
|
||||
return {channels};
|
||||
} catch (error) {
|
||||
return {error};
|
||||
|
||||
@@ -11,7 +11,7 @@ import {fetchRoles} from '@actions/remote/role';
|
||||
import {fetchConfigAndLicense} from '@actions/remote/systems';
|
||||
import {fetchAllTeams, fetchMyTeams, fetchTeamsChannelsAndUnreadPosts, MyTeamsRequest} from '@actions/remote/team';
|
||||
import {fetchNewThreads} from '@actions/remote/thread';
|
||||
import {fetchMe, MyUserRequest, updateAllUsersSince} from '@actions/remote/user';
|
||||
import {autoUpdateTimezone, fetchMe, MyUserRequest, updateAllUsersSince} from '@actions/remote/user';
|
||||
import {gqlAllChannels} from '@client/graphQL/entry';
|
||||
import {General, Preferences, Screens} from '@constants';
|
||||
import {SYSTEM_IDENTIFIERS} from '@constants/database';
|
||||
@@ -65,6 +65,12 @@ export type EntryResponse = {
|
||||
const FETCH_MISSING_DM_TIMEOUT = 2500;
|
||||
export const FETCH_UNREADS_TIMEOUT = 2500;
|
||||
|
||||
export const getRemoveTeamIds = async (database: Database, teamData: MyTeamsRequest) => {
|
||||
const myTeams = await queryMyTeams(database).fetch();
|
||||
const joinedTeams = new Set(teamData.memberships?.filter((m) => m.delete_at === 0).map((m) => m.team_id));
|
||||
return myTeams.filter((m) => !joinedTeams.has(m.id)).map((m) => m.id);
|
||||
};
|
||||
|
||||
export const teamsToRemove = async (serverUrl: string, removeTeamIds?: string[]) => {
|
||||
const operator = DatabaseManager.serverDatabases[serverUrl]?.operator;
|
||||
if (!operator) {
|
||||
@@ -162,7 +168,6 @@ export const fetchAppEntryData = async (serverUrl: string, sinceArg: number, ini
|
||||
fetchMe(serverUrl, fetchOnly),
|
||||
];
|
||||
|
||||
const removeTeamIds: string[] = [];
|
||||
const resolution = await Promise.all(promises);
|
||||
const [teamData, , meData] = resolution;
|
||||
let [, chData] = resolution;
|
||||
@@ -179,10 +184,7 @@ export const fetchAppEntryData = async (serverUrl: string, sinceArg: number, ini
|
||||
}
|
||||
}
|
||||
|
||||
const removedFromTeam = teamData.memberships?.filter((m) => m.delete_at > 0);
|
||||
if (removedFromTeam?.length) {
|
||||
removeTeamIds.push(...removedFromTeam.map((m) => m.team_id));
|
||||
}
|
||||
const removeTeamIds = await getRemoveTeamIds(database, teamData);
|
||||
|
||||
let data: AppEntryData = {
|
||||
initialTeamId,
|
||||
@@ -195,10 +197,6 @@ export const fetchAppEntryData = async (serverUrl: string, sinceArg: number, ini
|
||||
};
|
||||
|
||||
if (teamData.teams?.length === 0 && !teamData.error) {
|
||||
// User is no longer a member of any team
|
||||
const myTeams = await queryMyTeams(database).fetch();
|
||||
removeTeamIds.push(...(myTeams.map((myTeam) => myTeam.id) || []));
|
||||
|
||||
return {
|
||||
...data,
|
||||
initialTeamId: '',
|
||||
@@ -383,6 +381,7 @@ export const syncOtherServers = async (serverUrl: string) => {
|
||||
if (server.url !== serverUrl && server.lastActiveAt > 0) {
|
||||
registerDeviceToken(server.url);
|
||||
syncAllChannelMembersAndThreads(server.url);
|
||||
autoUpdateTimezone(server.url);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ import {fetchGroupsForMember} from '@actions/remote/groups';
|
||||
import {fetchPostsForUnreadChannels} from '@actions/remote/post';
|
||||
import {MyTeamsRequest} from '@actions/remote/team';
|
||||
import {fetchNewThreads} from '@actions/remote/thread';
|
||||
import {updateAllUsersSince} from '@actions/remote/user';
|
||||
import {autoUpdateTimezone, updateAllUsersSince} from '@actions/remote/user';
|
||||
import {gqlEntry, gqlEntryChannels, gqlOtherChannels} from '@client/graphQL/entry';
|
||||
import {Preferences} from '@constants';
|
||||
import DatabaseManager from '@database/manager';
|
||||
@@ -19,16 +19,14 @@ import {queryAllChannels, queryAllChannelsForTeam} from '@queries/servers/channe
|
||||
import {prepareModels, truncateCrtRelatedTables} from '@queries/servers/entry';
|
||||
import {getHasCRTChanged} from '@queries/servers/preference';
|
||||
import {getConfig} from '@queries/servers/system';
|
||||
import {queryMyTeams} from '@queries/servers/team';
|
||||
import {filterAndTransformRoles, getMemberChannelsFromGQLQuery, getMemberTeamsFromGQLQuery, gqlToClientChannelMembership, gqlToClientPreference, gqlToClientSidebarCategory, gqlToClientTeamMembership, gqlToClientUser} from '@utils/graphql';
|
||||
import {logDebug} from '@utils/log';
|
||||
import {processIsCRTEnabled} from '@utils/thread';
|
||||
|
||||
import {teamsToRemove, FETCH_UNREADS_TIMEOUT, entryRest, EntryResponse, entryInitialChannelId, restDeferredAppEntryActions} from './common';
|
||||
import {teamsToRemove, FETCH_UNREADS_TIMEOUT, entryRest, EntryResponse, entryInitialChannelId, restDeferredAppEntryActions, getRemoveTeamIds} from './common';
|
||||
|
||||
import type ClientError from '@client/rest/error';
|
||||
import type ChannelModel from '@typings/database/models/servers/channel';
|
||||
import type TeamModel from '@typings/database/models/servers/team';
|
||||
|
||||
export async function deferredAppEntryGraphQLActions(
|
||||
serverUrl: string,
|
||||
@@ -227,23 +225,9 @@ export const entryGQL = async (serverUrl: string, currentTeamId?: string, curren
|
||||
const roles = filterAndTransformRoles(gqlRoles);
|
||||
|
||||
const initialChannelId = await entryInitialChannelId(database, currentChannelId, currentTeamId, initialTeamId, meData.user.id, chData?.channels, chData?.memberships);
|
||||
let removeTeams: TeamModel[] = [];
|
||||
const removeChannels = await getRemoveChannels(database, chData, initialTeamId, true);
|
||||
|
||||
const removeTeamIds = [];
|
||||
|
||||
const removedFromTeam = teamData.memberships?.filter((m) => m.delete_at > 0);
|
||||
if (removedFromTeam?.length) {
|
||||
removeTeamIds.push(...removedFromTeam.map((m) => m.team_id));
|
||||
}
|
||||
|
||||
if (teamData.teams?.length === 0) {
|
||||
// User is no longer a member of any team
|
||||
const myTeams = await queryMyTeams(database).fetch();
|
||||
removeTeamIds.push(...(myTeams?.map((myTeam) => myTeam.id) || []));
|
||||
}
|
||||
|
||||
removeTeams = await teamsToRemove(serverUrl, removeTeamIds);
|
||||
const removeTeamIds = await getRemoveTeamIds(database, teamData);
|
||||
const removeTeams = await teamsToRemove(serverUrl, removeTeamIds);
|
||||
|
||||
const modelPromises = await prepareModels({operator, initialTeamId, removeTeams, removeChannels, teamData, chData, prefData, meData}, true);
|
||||
if (roles.length) {
|
||||
@@ -285,5 +269,7 @@ export async function deferredAppEntryActions(
|
||||
result = restDeferredAppEntryActions(serverUrl, since, currentUserId, currentUserLocale, preferences, config, license, teamData, chData, initialTeamId, initialChannelId);
|
||||
}
|
||||
|
||||
autoUpdateTimezone(serverUrl);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -19,18 +19,16 @@ import EphemeralStore from '@store/ephemeral_store';
|
||||
import {logWarning, logError} from '@utils/log';
|
||||
import {scheduleExpiredNotification} from '@utils/notification';
|
||||
import {getCSRFFromCookie} from '@utils/security';
|
||||
import {getDeviceTimezone, isTimezoneEnabled} from '@utils/timezone';
|
||||
|
||||
import {loginEntry} from './entry';
|
||||
import {fetchDataRetentionPolicy} from './systems';
|
||||
import {autoUpdateTimezone} from './user';
|
||||
|
||||
import type ClientError from '@client/rest/error';
|
||||
import type {LoginArgs} from '@typings/database/database';
|
||||
|
||||
const HTTP_UNAUTHORIZED = 401;
|
||||
|
||||
export const completeLogin = async (serverUrl: string, user: UserProfile) => {
|
||||
export const completeLogin = async (serverUrl: string) => {
|
||||
const operator = DatabaseManager.serverDatabases[serverUrl]?.operator;
|
||||
if (!operator) {
|
||||
return {error: `${serverUrl} database not found`};
|
||||
@@ -43,12 +41,6 @@ export const completeLogin = async (serverUrl: string, user: UserProfile) => {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Set timezone
|
||||
if (isTimezoneEnabled(config)) {
|
||||
const timezone = getDeviceTimezone();
|
||||
await autoUpdateTimezone(serverUrl, {deviceTimezone: timezone, userId: user.id});
|
||||
}
|
||||
|
||||
// Data retention
|
||||
if (config?.DataRetentionEnableMessageDeletion === 'true' && license?.IsLicensed === 'true' && license?.DataRetention === 'true') {
|
||||
fetchDataRetentionPolicy(serverUrl);
|
||||
@@ -165,7 +157,7 @@ export const login = async (serverUrl: string, {ldapOnly = false, loginId, mfaTo
|
||||
|
||||
try {
|
||||
const {error, hasTeams, time} = await loginEntry({serverUrl, user});
|
||||
completeLogin(serverUrl, user);
|
||||
completeLogin(serverUrl);
|
||||
return {error: error as ClientError, failed: false, hasTeams, time};
|
||||
} catch (error) {
|
||||
return {error: error as ClientError, failed: false, time: 0};
|
||||
@@ -309,7 +301,7 @@ export const ssoLogin = async (serverUrl: string, serverDisplayName: string, ser
|
||||
|
||||
try {
|
||||
const {error, hasTeams, time} = await loginEntry({serverUrl, user, deviceToken});
|
||||
completeLogin(serverUrl, user);
|
||||
completeLogin(serverUrl);
|
||||
return {error: error as ClientError, failed: false, hasTeams, time};
|
||||
} catch (error) {
|
||||
return {error: error as ClientError, failed: false, time: 0};
|
||||
|
||||
@@ -63,7 +63,7 @@ export const fetchConfigAndLicense = async (serverUrl: string, fetchOnly = false
|
||||
]);
|
||||
|
||||
if (!fetchOnly) {
|
||||
storeConfigAndLicense(serverUrl, config, license);
|
||||
await storeConfigAndLicense(serverUrl, config, license);
|
||||
}
|
||||
|
||||
return {config, license};
|
||||
|
||||
@@ -55,6 +55,7 @@ export async function addUserToTeam(serverUrl: string, teamId: string, userId: s
|
||||
|
||||
try {
|
||||
EphemeralStore.startAddingToTeam(teamId);
|
||||
const team = await client.getTeam(teamId);
|
||||
const member = await client.addToTeam(teamId, userId);
|
||||
|
||||
if (!fetchOnly) {
|
||||
@@ -68,6 +69,7 @@ export async function addUserToTeam(serverUrl: string, teamId: string, userId: s
|
||||
}];
|
||||
|
||||
const models: Model[] = (await Promise.all([
|
||||
operator.handleTeam({teams: [team], prepareRecordsOnly: true}),
|
||||
operator.handleMyTeam({myTeams, prepareRecordsOnly: true}),
|
||||
operator.handleTeamMemberships({teamMemberships: [member], prepareRecordsOnly: true}),
|
||||
...await prepareMyChannelsForTeam(operator, teamId, channels || [], channelMembers || []),
|
||||
@@ -248,6 +250,16 @@ export async function fetchTeamByName(serverUrl: string, teamName: string, fetch
|
||||
}
|
||||
}
|
||||
|
||||
export const removeCurrentUserFromTeam = async (serverUrl: string, teamId: string, fetchOnly = false) => {
|
||||
try {
|
||||
const {database} = DatabaseManager.getServerDatabaseAndOperator(serverUrl);
|
||||
const userId = await getCurrentUserId(database);
|
||||
return removeUserFromTeam(serverUrl, teamId, userId, fetchOnly);
|
||||
} catch (error) {
|
||||
return {error};
|
||||
}
|
||||
};
|
||||
|
||||
export const removeUserFromTeam = async (serverUrl: string, teamId: string, userId: string, fetchOnly = false) => {
|
||||
let client;
|
||||
try {
|
||||
|
||||
@@ -15,9 +15,10 @@ import {debounce} from '@helpers/api/general';
|
||||
import NetworkManager from '@managers/network_manager';
|
||||
import {getMembersCountByChannelsId, queryChannelsByTypes} from '@queries/servers/channel';
|
||||
import {queryGroupsByNames} from '@queries/servers/group';
|
||||
import {getCurrentTeamId, getCurrentUserId} from '@queries/servers/system';
|
||||
import {getCurrentUser, getUserById, prepareUsers, queryAllUsers, queryUsersById, queryUsersByIdsOrUsernames, queryUsersByUsername} from '@queries/servers/user';
|
||||
import {getConfig, getCurrentUserId} from '@queries/servers/system';
|
||||
import {getCurrentUser, prepareUsers, queryAllUsers, queryUsersById, queryUsersByIdsOrUsernames, queryUsersByUsername} from '@queries/servers/user';
|
||||
import {logError} from '@utils/log';
|
||||
import {getDeviceTimezone, isTimezoneEnabled} from '@utils/timezone';
|
||||
import {getUserTimezoneProps, removeUserFromList} from '@utils/user';
|
||||
|
||||
import {fetchGroupsByNames} from './groups';
|
||||
@@ -817,7 +818,7 @@ export const uploadUserProfileImage = async (serverUrl: string, localPath: strin
|
||||
return {error: undefined};
|
||||
};
|
||||
|
||||
export const searchUsers = async (serverUrl: string, term: string, channelId?: string) => {
|
||||
export const searchUsers = async (serverUrl: string, term: string, teamId: string, channelId?: string) => {
|
||||
const database = DatabaseManager.serverDatabases[serverUrl]?.database;
|
||||
if (!database) {
|
||||
return {error: `${serverUrl} database not found`};
|
||||
@@ -831,8 +832,7 @@ export const searchUsers = async (serverUrl: string, term: string, channelId?: s
|
||||
}
|
||||
|
||||
try {
|
||||
const currentTeamId = await getCurrentTeamId(database);
|
||||
const users = await client.autocompleteUsers(term, currentTeamId, channelId);
|
||||
const users = await client.autocompleteUsers(term, teamId, channelId);
|
||||
return {users};
|
||||
} catch (error) {
|
||||
return {error};
|
||||
@@ -850,7 +850,7 @@ export const buildProfileImageUrl = (serverUrl: string, userId: string, timestam
|
||||
return client.getProfilePictureUrl(userId, timestamp);
|
||||
};
|
||||
|
||||
export const autoUpdateTimezone = async (serverUrl: string, {deviceTimezone, userId}: {deviceTimezone: string; userId: string}) => {
|
||||
export const autoUpdateTimezone = async (serverUrl: string) => {
|
||||
let database;
|
||||
try {
|
||||
const result = DatabaseManager.getServerDatabaseAndOperator(serverUrl);
|
||||
@@ -859,12 +859,16 @@ export const autoUpdateTimezone = async (serverUrl: string, {deviceTimezone, use
|
||||
return {error: `${serverUrl} database not found`};
|
||||
}
|
||||
|
||||
const currentUser = await getUserById(database, userId);
|
||||
const config = await getConfig(database);
|
||||
const currentUser = await getCurrentUser(database);
|
||||
|
||||
if (!currentUser) {
|
||||
if (!currentUser || !config || !isTimezoneEnabled(config)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Set timezone
|
||||
const deviceTimezone = getDeviceTimezone();
|
||||
|
||||
const currentTimezone = getUserTimezoneProps(currentUser);
|
||||
const newTimezoneExists = currentTimezone.automaticTimezone !== deviceTimezone;
|
||||
|
||||
|
||||
@@ -3,8 +3,8 @@
|
||||
exports[`@components/app_version should match snapshot 1`] = `
|
||||
<View
|
||||
animatedStyle={
|
||||
Object {
|
||||
"value": Object {
|
||||
{
|
||||
"value": {
|
||||
"opacity": 1,
|
||||
},
|
||||
}
|
||||
@@ -12,14 +12,14 @@ exports[`@components/app_version should match snapshot 1`] = `
|
||||
collapsable={false}
|
||||
pointerEvents="none"
|
||||
style={
|
||||
Object {
|
||||
{
|
||||
"opacity": 1,
|
||||
}
|
||||
}
|
||||
>
|
||||
<View
|
||||
style={
|
||||
Object {
|
||||
{
|
||||
"bottom": 0,
|
||||
"marginBottom": 12,
|
||||
"marginLeft": 20,
|
||||
@@ -29,7 +29,7 @@ exports[`@components/app_version should match snapshot 1`] = `
|
||||
>
|
||||
<Text
|
||||
style={
|
||||
Object {
|
||||
{
|
||||
"fontSize": 12,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {debounce} from 'lodash';
|
||||
import React, {useCallback, useEffect, useMemo, useState} from 'react';
|
||||
import React, {useCallback, useEffect, useMemo, useRef, useState} from 'react';
|
||||
import {Platform, SectionList, SectionListData, SectionListRenderItemInfo, StyleProp, ViewStyle} from 'react-native';
|
||||
|
||||
import {searchGroupsByName, searchGroupsByNameInChannel, searchGroupsByNameInTeam} from '@actions/local/group';
|
||||
@@ -175,9 +175,35 @@ const makeSections = (teamMembers: Array<UserProfile | UserModel>, usersInChanne
|
||||
return newSections;
|
||||
};
|
||||
|
||||
const searchGroups = async (serverUrl: string, matchTerm: string, useGroupMentions: boolean, isChannelConstrained: boolean, isTeamConstrained: boolean, channelId?: string, teamId?: string) => {
|
||||
try {
|
||||
if (useGroupMentions && matchTerm && matchTerm !== '') {
|
||||
let g = emptyGroupList;
|
||||
|
||||
if (isChannelConstrained) {
|
||||
// If the channel is constrained, we only show groups for that channel
|
||||
if (channelId) {
|
||||
g = await searchGroupsByNameInChannel(serverUrl, matchTerm, channelId);
|
||||
}
|
||||
} else if (isTeamConstrained) {
|
||||
// If there is no channel constraint, but a team constraint - only show groups for team
|
||||
g = await searchGroupsByNameInTeam(serverUrl, matchTerm, teamId!);
|
||||
} else {
|
||||
// No constraints? Search all groups
|
||||
g = await searchGroupsByName(serverUrl, matchTerm || '');
|
||||
}
|
||||
|
||||
return g.length ? g : emptyGroupList;
|
||||
}
|
||||
return emptyGroupList;
|
||||
} catch (error) {
|
||||
return emptyGroupList;
|
||||
}
|
||||
};
|
||||
|
||||
type Props = {
|
||||
channelId?: string;
|
||||
teamId?: string;
|
||||
teamId: string;
|
||||
cursorPosition: number;
|
||||
isSearch: boolean;
|
||||
updateValue: (v: string) => void;
|
||||
@@ -232,9 +258,22 @@ const AtMention = ({
|
||||
const [localUsers, setLocalUsers] = useState<UserModel[]>();
|
||||
const [filteredLocalUsers, setFilteredLocalUsers] = useState(emptyUserlList);
|
||||
|
||||
const runSearch = useMemo(() => debounce(async (sUrl: string, term: string, cId?: string) => {
|
||||
setLoading(true);
|
||||
const {users: receivedUsers, error} = await searchUsers(sUrl, term, cId);
|
||||
const latestSearchAt = useRef(0);
|
||||
|
||||
const runSearch = useMemo(() => debounce(async (sUrl: string, term: string, groupMentions: boolean, channelConstrained: boolean, teamConstrained: boolean, tId: string, cId?: string) => {
|
||||
const searchAt = Date.now();
|
||||
latestSearchAt.current = searchAt;
|
||||
|
||||
const [{users: receivedUsers, error}, groupsResult] = await Promise.all([
|
||||
searchUsers(sUrl, term, tId, cId),
|
||||
searchGroups(sUrl, term, groupMentions, channelConstrained, teamConstrained, cId, tId),
|
||||
]);
|
||||
|
||||
if (latestSearchAt.current > searchAt) {
|
||||
return;
|
||||
}
|
||||
|
||||
setGroups(groupsResult);
|
||||
|
||||
setUseLocal(Boolean(error));
|
||||
if (error) {
|
||||
@@ -243,6 +282,10 @@ const AtMention = ({
|
||||
fallbackUsers = await getAllUsers(sUrl);
|
||||
setLocalUsers(fallbackUsers);
|
||||
}
|
||||
if (latestSearchAt.current > searchAt) {
|
||||
return;
|
||||
}
|
||||
|
||||
const filteredUsers = filterResults(fallbackUsers, term);
|
||||
setFilteredLocalUsers(filteredUsers.length ? filteredUsers : emptyUserlList);
|
||||
} else if (receivedUsers) {
|
||||
@@ -270,8 +313,12 @@ const AtMention = ({
|
||||
const resetState = () => {
|
||||
setUsersInChannel(emptyUserlList);
|
||||
setUsersOutOfChannel(emptyUserlList);
|
||||
setGroups(emptyGroupList);
|
||||
setFilteredLocalUsers(emptyUserlList);
|
||||
setSections(emptySectionList);
|
||||
setNoResultsTerm(null);
|
||||
latestSearchAt.current = Date.now();
|
||||
setLoading(false);
|
||||
runSearch.cancel();
|
||||
};
|
||||
|
||||
@@ -285,7 +332,7 @@ const AtMention = ({
|
||||
completedDraft = mentionPart.replace(AT_MENTION_REGEX, `@${mention} `);
|
||||
}
|
||||
|
||||
const newCursorPosition = completedDraft.length - 1;
|
||||
const newCursorPosition = completedDraft.length;
|
||||
|
||||
if (value.length > cursorPosition) {
|
||||
completedDraft += value.substring(cursorPosition);
|
||||
@@ -297,6 +344,7 @@ const AtMention = ({
|
||||
onShowingChange(false);
|
||||
setNoResultsTerm(mention);
|
||||
setSections(emptySectionList);
|
||||
latestSearchAt.current = Date.now();
|
||||
}, [value, localCursorPosition, isSearch]);
|
||||
|
||||
const renderSpecialMentions = useCallback((item: SpecialMention) => {
|
||||
@@ -361,39 +409,6 @@ const AtMention = ({
|
||||
}
|
||||
}, [cursorPosition]);
|
||||
|
||||
useEffect(() => {
|
||||
if (useGroupMentions && matchTerm && matchTerm !== '') {
|
||||
// If the channel is constrained, we only show groups for that channel
|
||||
if (isChannelConstrained && channelId) {
|
||||
searchGroupsByNameInChannel(serverUrl, matchTerm, channelId).then((g) => {
|
||||
setGroups(g.length ? g : emptyGroupList);
|
||||
}).catch(() => {
|
||||
setGroups(emptyGroupList);
|
||||
});
|
||||
}
|
||||
|
||||
// If there is no channel constraint, but a team constraint - only show groups for team
|
||||
if (isTeamConstrained && !isChannelConstrained) {
|
||||
searchGroupsByNameInTeam(serverUrl, matchTerm, teamId!).then((g) => {
|
||||
setGroups(g.length ? g : emptyGroupList);
|
||||
}).catch(() => {
|
||||
setGroups(emptyGroupList);
|
||||
});
|
||||
}
|
||||
|
||||
// No constraints? Search all groups
|
||||
if (!isTeamConstrained && !isChannelConstrained) {
|
||||
searchGroupsByName(serverUrl, matchTerm || '').then((g) => {
|
||||
setGroups(Array.isArray(g) ? g : emptyGroupList);
|
||||
}).catch(() => {
|
||||
setGroups(emptyGroupList);
|
||||
});
|
||||
}
|
||||
} else {
|
||||
setGroups(emptyGroupList);
|
||||
}
|
||||
}, [matchTerm, useGroupMentions]);
|
||||
|
||||
useEffect(() => {
|
||||
if (matchTerm === null) {
|
||||
resetState();
|
||||
@@ -406,10 +421,14 @@ const AtMention = ({
|
||||
}
|
||||
|
||||
setNoResultsTerm(null);
|
||||
runSearch(serverUrl, matchTerm, channelId);
|
||||
}, [matchTerm]);
|
||||
setLoading(true);
|
||||
runSearch(serverUrl, matchTerm, useGroupMentions, isChannelConstrained, isTeamConstrained, teamId, channelId);
|
||||
}, [matchTerm, teamId, useGroupMentions, isChannelConstrained, isTeamConstrained]);
|
||||
|
||||
useEffect(() => {
|
||||
if (noResultsTerm && !loading) {
|
||||
return;
|
||||
}
|
||||
const showSpecialMentions = useChannelMentions && matchTerm != null && checkSpecialMentions(matchTerm);
|
||||
const buildMemberSection = isSearch || (!channelId && teamMembers.length > 0);
|
||||
let newSections;
|
||||
|
||||
@@ -18,8 +18,11 @@ import AtMention from './at_mention';
|
||||
import type {WithDatabaseArgs} from '@typings/database/database';
|
||||
import type TeamModel from '@typings/database/models/servers/team';
|
||||
|
||||
type OwnProps = {channelId?: string}
|
||||
const enhanced = withObservables([], ({database, channelId}: WithDatabaseArgs & OwnProps) => {
|
||||
type OwnProps = {
|
||||
channelId?: string;
|
||||
teamId?: string;
|
||||
}
|
||||
const enhanced = withObservables(['teamId'], ({database, channelId, teamId}: WithDatabaseArgs & OwnProps) => {
|
||||
const currentUser = observeCurrentUser(database);
|
||||
|
||||
const hasLicense = observeLicense(database).pipe(
|
||||
@@ -51,20 +54,19 @@ const enhanced = withObservables([], ({database, channelId}: WithDatabaseArgs &
|
||||
useGroupMentions = of$(false);
|
||||
isChannelConstrained = of$(false);
|
||||
isTeamConstrained = of$(false);
|
||||
team = observeCurrentTeam(database);
|
||||
team = teamId ? observeTeam(database, teamId) : observeCurrentTeam(database);
|
||||
}
|
||||
|
||||
isTeamConstrained = team.pipe(
|
||||
switchMap((t) => of$(Boolean(t?.isGroupConstrained))),
|
||||
);
|
||||
const teamId = team.pipe(switchMap((t) => of$(t?.id)));
|
||||
|
||||
return {
|
||||
isChannelConstrained,
|
||||
isTeamConstrained,
|
||||
useChannelMentions,
|
||||
useGroupMentions,
|
||||
teamId,
|
||||
teamId: team.pipe(switchMap((t) => of$(t?.id))),
|
||||
};
|
||||
});
|
||||
|
||||
|
||||
@@ -61,6 +61,7 @@ type Props = {
|
||||
availableSpace: SharedValue<number>;
|
||||
inPost?: boolean;
|
||||
growDown?: boolean;
|
||||
teamId?: string;
|
||||
containerStyle?: StyleProp<ViewStyle>;
|
||||
}
|
||||
|
||||
@@ -81,6 +82,7 @@ const Autocomplete = ({
|
||||
inPost = false,
|
||||
growDown = false,
|
||||
containerStyle,
|
||||
teamId,
|
||||
}: Props) => {
|
||||
const theme = useTheme();
|
||||
const isTablet = useIsTablet();
|
||||
@@ -152,6 +154,7 @@ const Autocomplete = ({
|
||||
nestedScrollEnabled={nestedScrollEnabled}
|
||||
isSearch={isSearch}
|
||||
channelId={channelId}
|
||||
teamId={teamId}
|
||||
/>
|
||||
<ChannelMention
|
||||
cursorPosition={cursorPosition}
|
||||
@@ -161,6 +164,8 @@ const Autocomplete = ({
|
||||
value={value || ''}
|
||||
nestedScrollEnabled={nestedScrollEnabled}
|
||||
isSearch={isSearch}
|
||||
channelId={channelId}
|
||||
teamId={teamId}
|
||||
/>
|
||||
{!isSearch &&
|
||||
<EmojiSuggestion
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {debounce} from 'lodash';
|
||||
import React, {useCallback, useEffect, useMemo, useState} from 'react';
|
||||
import React, {useCallback, useEffect, useMemo, useRef, useState} from 'react';
|
||||
import {Platform, SectionList, SectionListData, SectionListRenderItemInfo, StyleProp, ViewStyle} from 'react-native';
|
||||
|
||||
import {searchChannels} from '@actions/remote/channel';
|
||||
@@ -11,11 +11,8 @@ import ChannelMentionItem from '@components/autocomplete/channel_mention_item';
|
||||
import {General} from '@constants';
|
||||
import {CHANNEL_MENTION_REGEX, CHANNEL_MENTION_SEARCH_REGEX} from '@constants/autocomplete';
|
||||
import {useServerUrl} from '@context/server';
|
||||
import DatabaseManager from '@database/manager';
|
||||
import useDidUpdate from '@hooks/did_update';
|
||||
import {t} from '@i18n';
|
||||
import {queryAllChannelsForTeam} from '@queries/servers/channel';
|
||||
import {getCurrentTeamId} from '@queries/servers/system';
|
||||
import {hasTrailingSpaces} from '@utils/helpers';
|
||||
|
||||
import type ChannelModel from '@typings/database/models/servers/channel';
|
||||
@@ -25,32 +22,6 @@ const keyExtractor = (item: Channel) => {
|
||||
return item.id;
|
||||
};
|
||||
|
||||
const getMatchTermForChannelMention = (() => {
|
||||
let lastMatchTerm: string | null = null;
|
||||
let lastValue: string;
|
||||
let lastIsSearch: boolean;
|
||||
return (value: string, isSearch: boolean) => {
|
||||
if (value !== lastValue || isSearch !== lastIsSearch) {
|
||||
const regex = isSearch ? CHANNEL_MENTION_SEARCH_REGEX : CHANNEL_MENTION_REGEX;
|
||||
const match = value.match(regex);
|
||||
lastValue = value;
|
||||
lastIsSearch = isSearch;
|
||||
if (match) {
|
||||
if (isSearch) {
|
||||
lastMatchTerm = match[1].toLowerCase();
|
||||
} else if (match.index && match.index > 0 && value[match.index - 1] === '~') {
|
||||
lastMatchTerm = null;
|
||||
} else {
|
||||
lastMatchTerm = match[2].toLowerCase();
|
||||
}
|
||||
} else {
|
||||
lastMatchTerm = null;
|
||||
}
|
||||
}
|
||||
return lastMatchTerm;
|
||||
};
|
||||
})();
|
||||
|
||||
const reduceChannelsForSearch = (channels: Array<Channel | ChannelModel>, members: MyChannelModel[]) => {
|
||||
const memberIds = new Set(members.map((m) => m.id));
|
||||
return channels.reduce<Array<Array<Channel | ChannelModel>>>(([pubC, priC, dms], c) => {
|
||||
@@ -83,7 +54,7 @@ const reduceChannelsForAutocomplete = (channels: Array<Channel | ChannelModel>,
|
||||
}, [[], []]);
|
||||
};
|
||||
|
||||
const makeSections = (channels: Array<Channel | ChannelModel>, myMembers: MyChannelModel[], isSearch = false) => {
|
||||
const makeSections = (channels: Array<Channel | ChannelModel>, myMembers: MyChannelModel[], loading: boolean, isSearch = false) => {
|
||||
const newSections = [];
|
||||
if (isSearch) {
|
||||
const [publicChannels, privateChannels, directAndGroupMessages] = reduceChannelsForSearch(channels, myMembers);
|
||||
@@ -128,7 +99,7 @@ const makeSections = (channels: Array<Channel | ChannelModel>, myMembers: MyChan
|
||||
});
|
||||
}
|
||||
|
||||
if (otherChannels.length) {
|
||||
if (otherChannels.length || (!myChannels.length && loading)) {
|
||||
newSections.push({
|
||||
id: t('suggestion.mention.morechannels'),
|
||||
defaultMessage: 'Other Channels',
|
||||
@@ -164,18 +135,11 @@ type Props = {
|
||||
value: string;
|
||||
nestedScrollEnabled: boolean;
|
||||
listStyle: StyleProp<ViewStyle>;
|
||||
matchTerm: string;
|
||||
localChannels: ChannelModel[];
|
||||
teamId: string;
|
||||
}
|
||||
|
||||
const getAllChannels = async (serverUrl: string) => {
|
||||
const database = DatabaseManager.serverDatabases[serverUrl]?.database;
|
||||
if (!database) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const teamId = await getCurrentTeamId(database);
|
||||
return queryAllChannelsForTeam(database, teamId).fetch();
|
||||
};
|
||||
|
||||
const emptySections: Array<SectionListData<Channel>> = [];
|
||||
const emptyChannels: Array<Channel | ChannelModel> = [];
|
||||
|
||||
@@ -188,47 +152,45 @@ const ChannelMention = ({
|
||||
value,
|
||||
nestedScrollEnabled,
|
||||
listStyle,
|
||||
matchTerm,
|
||||
localChannels,
|
||||
teamId,
|
||||
}: Props) => {
|
||||
const serverUrl = useServerUrl();
|
||||
|
||||
const [sections, setSections] = useState<Array<SectionListData<(Channel | ChannelModel)>>>(emptySections);
|
||||
const [channels, setChannels] = useState<Array<ChannelModel | Channel>>(emptyChannels);
|
||||
const [remoteChannels, setRemoteChannels] = useState<Array<ChannelModel | Channel>>(emptyChannels);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [noResultsTerm, setNoResultsTerm] = useState<string|null>(null);
|
||||
const [localCursorPosition, setLocalCursorPosition] = useState(cursorPosition); // To avoid errors due to delay between value changes and cursor position changes.
|
||||
const [useLocal, setUseLocal] = useState(true);
|
||||
const [localChannels, setlocalChannels] = useState<ChannelModel[]>();
|
||||
const [filteredLocalChannels, setFilteredLocalChannels] = useState(emptyChannels);
|
||||
|
||||
const runSearch = useMemo(() => debounce(async (sUrl: string, term: string) => {
|
||||
setLoading(true);
|
||||
const {channels: receivedChannels, error} = await searchChannels(sUrl, term, isSearch);
|
||||
setUseLocal(Boolean(error));
|
||||
const latestSearchAt = useRef(0);
|
||||
|
||||
if (error) {
|
||||
let fallbackChannels = localChannels;
|
||||
if (!fallbackChannels) {
|
||||
fallbackChannels = await getAllChannels(sUrl);
|
||||
setlocalChannels(fallbackChannels);
|
||||
}
|
||||
const filteredChannels = filterResults(fallbackChannels, term);
|
||||
setFilteredLocalChannels(filteredChannels.length ? filteredChannels : emptyChannels);
|
||||
} else if (receivedChannels) {
|
||||
let channelsToStore: Array<Channel | ChannelModel> = receivedChannels;
|
||||
if (hasTrailingSpaces(term)) {
|
||||
channelsToStore = filterResults(receivedChannels, term);
|
||||
}
|
||||
setChannels(channelsToStore.length ? channelsToStore : emptyChannels);
|
||||
const runSearch = useMemo(() => debounce(async (sUrl: string, term: string, tId: string) => {
|
||||
const searchAt = Date.now();
|
||||
latestSearchAt.current = searchAt;
|
||||
|
||||
const {channels: receivedChannels} = await searchChannels(sUrl, term, tId, isSearch);
|
||||
|
||||
if (latestSearchAt.current > searchAt) {
|
||||
return;
|
||||
}
|
||||
let channelsToStore: Array<Channel | ChannelModel> = receivedChannels || [];
|
||||
if (hasTrailingSpaces(term)) {
|
||||
channelsToStore = filterResults(receivedChannels || [], term);
|
||||
}
|
||||
setRemoteChannels(channelsToStore.length ? channelsToStore : emptyChannels);
|
||||
|
||||
setLoading(false);
|
||||
}, 200), []);
|
||||
|
||||
const matchTerm = getMatchTermForChannelMention(value.substring(0, localCursorPosition), isSearch);
|
||||
const resetState = () => {
|
||||
setFilteredLocalChannels(emptyChannels);
|
||||
setChannels(emptyChannels);
|
||||
latestSearchAt.current = Date.now();
|
||||
setRemoteChannels(emptyChannels);
|
||||
setSections(emptySections);
|
||||
setNoResultsTerm(null);
|
||||
runSearch.cancel();
|
||||
setLoading(false);
|
||||
};
|
||||
|
||||
const completeMention = useCallback((mention: string) => {
|
||||
@@ -264,8 +226,11 @@ const ChannelMention = ({
|
||||
}
|
||||
|
||||
onShowingChange(false);
|
||||
setLoading(false);
|
||||
setNoResultsTerm(mention);
|
||||
setSections(emptySections);
|
||||
setRemoteChannels(emptyChannels);
|
||||
latestSearchAt.current = Date.now();
|
||||
}, [value, localCursorPosition, isSearch]);
|
||||
|
||||
const renderItem = useCallback(({item}: SectionListRenderItemInfo<Channel | ChannelModel>) => {
|
||||
@@ -306,21 +271,41 @@ const ChannelMention = ({
|
||||
}
|
||||
|
||||
setNoResultsTerm(null);
|
||||
runSearch(serverUrl, matchTerm);
|
||||
}, [matchTerm]);
|
||||
setLoading(true);
|
||||
runSearch(serverUrl, matchTerm, teamId);
|
||||
}, [matchTerm, teamId]);
|
||||
|
||||
const channels = useMemo(() => {
|
||||
const ids = new Set(localChannels.map((c) => c.id));
|
||||
return [...localChannels, ...remoteChannels.filter((c) => !ids.has(c.id))].sort((a, b) => {
|
||||
const aDisplay = 'display_name' in a ? a.display_name : a.displayName;
|
||||
const bDisplay = 'display_name' in b ? b.display_name : b.displayName;
|
||||
const displayResult = aDisplay.localeCompare(bDisplay);
|
||||
if (displayResult === 0) {
|
||||
return a.name.localeCompare(b.name);
|
||||
}
|
||||
return displayResult;
|
||||
});
|
||||
}, [localChannels, remoteChannels]);
|
||||
|
||||
useDidUpdate(() => {
|
||||
const newSections = makeSections(useLocal ? filteredLocalChannels : channels, myMembers, isSearch);
|
||||
if (noResultsTerm && !loading) {
|
||||
return;
|
||||
}
|
||||
const newSections = makeSections(channels, myMembers, loading, isSearch);
|
||||
const nSections = newSections.length;
|
||||
|
||||
if (!loading && !nSections && noResultsTerm == null) {
|
||||
setNoResultsTerm(matchTerm);
|
||||
}
|
||||
if (nSections) {
|
||||
setNoResultsTerm(null);
|
||||
}
|
||||
setSections(newSections.length ? newSections : emptySections);
|
||||
onShowingChange(Boolean(nSections));
|
||||
}, [channels, myMembers, loading]);
|
||||
|
||||
if (sections.length === 0 || noResultsTerm != null) {
|
||||
if (!loading && (sections.length === 0 || noResultsTerm != null)) {
|
||||
// If we are not in an active state or the mention has been completed return null so nothing is rendered
|
||||
// other components are not blocked.
|
||||
return null;
|
||||
|
||||
@@ -3,17 +3,90 @@
|
||||
|
||||
import {withDatabase} from '@nozbe/watermelondb/DatabaseProvider';
|
||||
import withObservables from '@nozbe/with-observables';
|
||||
import {of as of$} from 'rxjs';
|
||||
import {switchMap} from 'rxjs/operators';
|
||||
|
||||
import {queryAllMyChannel} from '@queries/servers/channel';
|
||||
import {CHANNEL_MENTION_REGEX, CHANNEL_MENTION_SEARCH_REGEX} from '@constants/autocomplete';
|
||||
import {observeChannel, queryAllMyChannel, queryChannelsForAutocomplete} from '@queries/servers/channel';
|
||||
import {observeCurrentTeamId} from '@queries/servers/system';
|
||||
|
||||
import ChannelMention from './channel_mention';
|
||||
|
||||
import type {WithDatabaseArgs} from '@typings/database/database';
|
||||
import type ChannelModel from '@typings/database/models/servers/channel';
|
||||
|
||||
const enhanced = withObservables([], ({database}: WithDatabaseArgs) => {
|
||||
const getMatchTermForChannelMention = (() => {
|
||||
let lastMatchTerm: string | null = null;
|
||||
let lastValue: string;
|
||||
let lastIsSearch: boolean;
|
||||
return (value: string, isSearch: boolean) => {
|
||||
if (value !== lastValue || isSearch !== lastIsSearch) {
|
||||
const regex = isSearch ? CHANNEL_MENTION_SEARCH_REGEX : CHANNEL_MENTION_REGEX;
|
||||
const match = value.match(regex);
|
||||
lastValue = value;
|
||||
lastIsSearch = isSearch;
|
||||
if (match) {
|
||||
if (isSearch) {
|
||||
lastMatchTerm = match[1].toLowerCase();
|
||||
} else if (match.index && match.index > 0 && value[match.index - 1] === '~') {
|
||||
lastMatchTerm = null;
|
||||
} else {
|
||||
lastMatchTerm = match[2].toLowerCase();
|
||||
}
|
||||
} else {
|
||||
lastMatchTerm = null;
|
||||
}
|
||||
}
|
||||
return lastMatchTerm;
|
||||
};
|
||||
})();
|
||||
|
||||
type WithTeamIdProps = {
|
||||
teamId?: string;
|
||||
channelId?: string;
|
||||
} & WithDatabaseArgs;
|
||||
|
||||
type OwnProps = {
|
||||
value: string;
|
||||
isSearch: boolean;
|
||||
cursorPosition: number;
|
||||
teamId: string;
|
||||
} & WithDatabaseArgs;
|
||||
|
||||
const emptyChannelList: ChannelModel[] = [];
|
||||
|
||||
const withMembers = withObservables([], ({database}: WithDatabaseArgs) => {
|
||||
return {
|
||||
myMembers: queryAllMyChannel(database).observe(),
|
||||
};
|
||||
});
|
||||
|
||||
export default withDatabase(enhanced(ChannelMention));
|
||||
const withTeamId = withObservables(['teamId', 'channelId'], ({teamId, channelId, database}: WithTeamIdProps) => {
|
||||
let currentTeamId;
|
||||
if (teamId) {
|
||||
currentTeamId = of$(teamId);
|
||||
} else if (channelId) {
|
||||
currentTeamId = observeChannel(database, channelId).pipe(switchMap((c) => {
|
||||
return c?.teamId ? of$(c.teamId) : observeCurrentTeamId(database);
|
||||
}));
|
||||
} else {
|
||||
currentTeamId = observeCurrentTeamId(database);
|
||||
}
|
||||
|
||||
return {
|
||||
teamId: currentTeamId,
|
||||
};
|
||||
});
|
||||
|
||||
const enhanced = withObservables(['value', 'isSearch', 'teamId', 'cursorPosition'], ({value, isSearch, teamId, cursorPosition, database}: OwnProps) => {
|
||||
const matchTerm = getMatchTermForChannelMention(value.substring(0, cursorPosition), isSearch);
|
||||
|
||||
const localChannels = matchTerm === null ? of$(emptyChannelList) : queryChannelsForAutocomplete(database, matchTerm, isSearch, teamId).observe();
|
||||
|
||||
return {
|
||||
matchTerm: of$(matchTerm),
|
||||
localChannels,
|
||||
};
|
||||
});
|
||||
|
||||
export default withDatabase(withMembers(withTeamId(enhanced(ChannelMention))));
|
||||
|
||||
@@ -1899,7 +1899,7 @@ export class AppCommandParser {
|
||||
if (input[0] === '@') {
|
||||
input = input.substring(1);
|
||||
}
|
||||
const res = await searchUsers(this.serverUrl, input, this.channelID);
|
||||
const res = await searchUsers(this.serverUrl, input, this.teamID, this.channelID);
|
||||
return getUserSuggestions(res.users);
|
||||
};
|
||||
|
||||
@@ -1908,7 +1908,7 @@ export class AppCommandParser {
|
||||
if (input[0] === '~') {
|
||||
input = input.substring(1);
|
||||
}
|
||||
const res = await searchChannels(this.serverUrl, input);
|
||||
const res = await searchChannels(this.serverUrl, input, this.teamID);
|
||||
return getChannelSuggestions(res.channels);
|
||||
};
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ export async function inTextMentionSuggestions(serverUrl: string, pretext: strin
|
||||
const incompleteLessLastWord = separatedWords.slice(0, -1).join(' ');
|
||||
const lastWord = separatedWords[separatedWords.length - 1];
|
||||
if (lastWord.startsWith('@')) {
|
||||
const res = await searchUsers(serverUrl, lastWord.substring(1), channelID);
|
||||
const res = await searchUsers(serverUrl, lastWord.substring(1), teamID, channelID);
|
||||
const users = await getUserSuggestions(res.users);
|
||||
users.forEach((u) => {
|
||||
let complete = incompleteLessLastWord ? incompleteLessLastWord + ' ' + u.Complete : u.Complete;
|
||||
@@ -23,7 +23,7 @@ export async function inTextMentionSuggestions(serverUrl: string, pretext: strin
|
||||
}
|
||||
|
||||
if (lastWord.startsWith('~') && !lastWord.startsWith('~~')) {
|
||||
const res = await searchChannels(serverUrl, lastWord.substring(1));
|
||||
const res = await searchChannels(serverUrl, lastWord.substring(1), teamID);
|
||||
const channels = await getChannelSuggestions(res.channels);
|
||||
channels.forEach((c) => {
|
||||
let complete = incompleteLessLastWord ? incompleteLessLastWord + ' ' + c.Complete : c.Complete;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import Clipboard from '@react-native-community/clipboard';
|
||||
import Clipboard from '@react-native-clipboard/clipboard';
|
||||
import React, {useCallback} from 'react';
|
||||
import {useIntl} from 'react-intl';
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import Clipboard from '@react-native-community/clipboard';
|
||||
import Clipboard from '@react-native-clipboard/clipboard';
|
||||
import React, {useCallback} from 'react';
|
||||
import {useIntl} from 'react-intl';
|
||||
|
||||
|
||||
@@ -13,15 +13,15 @@ exports[`components/channel_list/categories/body/channel_item should match snaps
|
||||
onResponderTerminationRequest={[Function]}
|
||||
onStartShouldSetResponder={[Function]}
|
||||
style={
|
||||
Object {
|
||||
{
|
||||
"opacity": 1,
|
||||
}
|
||||
}
|
||||
>
|
||||
<View
|
||||
style={
|
||||
Array [
|
||||
Object {
|
||||
[
|
||||
{
|
||||
"alignItems": "center",
|
||||
"flexDirection": "row",
|
||||
"minHeight": 40,
|
||||
@@ -29,7 +29,7 @@ exports[`components/channel_list/categories/body/channel_item should match snaps
|
||||
},
|
||||
false,
|
||||
undefined,
|
||||
Object {
|
||||
{
|
||||
"minHeight": 40,
|
||||
},
|
||||
]
|
||||
@@ -38,7 +38,7 @@ exports[`components/channel_list/categories/body/channel_item should match snaps
|
||||
>
|
||||
<View
|
||||
style={
|
||||
Object {
|
||||
{
|
||||
"flex": 1,
|
||||
"flexDirection": "row",
|
||||
}
|
||||
@@ -46,12 +46,12 @@ exports[`components/channel_list/categories/body/channel_item should match snaps
|
||||
>
|
||||
<View
|
||||
style={
|
||||
Array [
|
||||
Object {
|
||||
[
|
||||
{
|
||||
"alignItems": "center",
|
||||
"justifyContent": "center",
|
||||
},
|
||||
Object {
|
||||
{
|
||||
"height": 24,
|
||||
"width": 24,
|
||||
},
|
||||
@@ -63,13 +63,13 @@ exports[`components/channel_list/categories/body/channel_item should match snaps
|
||||
<Icon
|
||||
name="globe"
|
||||
style={
|
||||
Array [
|
||||
Object {
|
||||
[
|
||||
{
|
||||
"color": "rgba(255,255,255,0.4)",
|
||||
},
|
||||
undefined,
|
||||
undefined,
|
||||
Object {
|
||||
{
|
||||
"fontSize": 24,
|
||||
"left": 1,
|
||||
},
|
||||
@@ -83,14 +83,14 @@ exports[`components/channel_list/categories/body/channel_item should match snaps
|
||||
ellipsizeMode="tail"
|
||||
numberOfLines={1}
|
||||
style={
|
||||
Array [
|
||||
Object {
|
||||
[
|
||||
{
|
||||
"fontFamily": "OpenSans",
|
||||
"fontSize": 16,
|
||||
"fontWeight": "400",
|
||||
"lineHeight": 24,
|
||||
},
|
||||
Object {
|
||||
{
|
||||
"color": "rgba(255,255,255,0.72)",
|
||||
"marginTop": -1,
|
||||
"paddingLeft": 12,
|
||||
@@ -126,15 +126,15 @@ exports[`components/channel_list/categories/body/channel_item should match snaps
|
||||
onResponderTerminationRequest={[Function]}
|
||||
onStartShouldSetResponder={[Function]}
|
||||
style={
|
||||
Object {
|
||||
{
|
||||
"opacity": 1,
|
||||
}
|
||||
}
|
||||
>
|
||||
<View
|
||||
style={
|
||||
Array [
|
||||
Object {
|
||||
[
|
||||
{
|
||||
"alignItems": "center",
|
||||
"flexDirection": "row",
|
||||
"minHeight": 40,
|
||||
@@ -142,7 +142,7 @@ exports[`components/channel_list/categories/body/channel_item should match snaps
|
||||
},
|
||||
false,
|
||||
undefined,
|
||||
Object {
|
||||
{
|
||||
"minHeight": 40,
|
||||
},
|
||||
]
|
||||
@@ -151,7 +151,7 @@ exports[`components/channel_list/categories/body/channel_item should match snaps
|
||||
>
|
||||
<View
|
||||
style={
|
||||
Object {
|
||||
{
|
||||
"flex": 1,
|
||||
"flexDirection": "row",
|
||||
}
|
||||
@@ -159,12 +159,12 @@ exports[`components/channel_list/categories/body/channel_item should match snaps
|
||||
>
|
||||
<View
|
||||
style={
|
||||
Array [
|
||||
Object {
|
||||
[
|
||||
{
|
||||
"alignItems": "center",
|
||||
"justifyContent": "center",
|
||||
},
|
||||
Object {
|
||||
{
|
||||
"height": 24,
|
||||
"width": 24,
|
||||
},
|
||||
@@ -176,13 +176,13 @@ exports[`components/channel_list/categories/body/channel_item should match snaps
|
||||
<Icon
|
||||
name="pencil-outline"
|
||||
style={
|
||||
Array [
|
||||
Object {
|
||||
[
|
||||
{
|
||||
"color": "rgba(255,255,255,0.4)",
|
||||
},
|
||||
undefined,
|
||||
undefined,
|
||||
Object {
|
||||
{
|
||||
"fontSize": 24,
|
||||
"left": 2,
|
||||
},
|
||||
@@ -196,14 +196,14 @@ exports[`components/channel_list/categories/body/channel_item should match snaps
|
||||
ellipsizeMode="tail"
|
||||
numberOfLines={1}
|
||||
style={
|
||||
Array [
|
||||
Object {
|
||||
[
|
||||
{
|
||||
"fontFamily": "OpenSans",
|
||||
"fontSize": 16,
|
||||
"fontWeight": "400",
|
||||
"lineHeight": 24,
|
||||
},
|
||||
Object {
|
||||
{
|
||||
"color": "rgba(255,255,255,0.72)",
|
||||
"marginTop": -1,
|
||||
"paddingLeft": 12,
|
||||
@@ -226,14 +226,14 @@ exports[`components/channel_list/categories/body/channel_item should match snaps
|
||||
name="phone-in-talk"
|
||||
size={16}
|
||||
style={
|
||||
Array [
|
||||
Object {
|
||||
[
|
||||
{
|
||||
"fontFamily": "OpenSans",
|
||||
"fontSize": 16,
|
||||
"fontWeight": "400",
|
||||
"lineHeight": 24,
|
||||
},
|
||||
Object {
|
||||
{
|
||||
"color": "rgba(255,255,255,0.72)",
|
||||
"marginTop": -1,
|
||||
"paddingLeft": 12,
|
||||
@@ -244,9 +244,8 @@ exports[`components/channel_list/categories/body/channel_item should match snaps
|
||||
null,
|
||||
false,
|
||||
false,
|
||||
Object {
|
||||
"flex": 1,
|
||||
"marginRight": 20,
|
||||
{
|
||||
"paddingRight": 0,
|
||||
"textAlign": "right",
|
||||
},
|
||||
]
|
||||
@@ -269,15 +268,15 @@ exports[`components/channel_list/categories/body/channel_item should match snaps
|
||||
onResponderTerminationRequest={[Function]}
|
||||
onStartShouldSetResponder={[Function]}
|
||||
style={
|
||||
Object {
|
||||
{
|
||||
"opacity": 1,
|
||||
}
|
||||
}
|
||||
>
|
||||
<View
|
||||
style={
|
||||
Array [
|
||||
Object {
|
||||
[
|
||||
{
|
||||
"alignItems": "center",
|
||||
"flexDirection": "row",
|
||||
"minHeight": 40,
|
||||
@@ -285,7 +284,7 @@ exports[`components/channel_list/categories/body/channel_item should match snaps
|
||||
},
|
||||
false,
|
||||
undefined,
|
||||
Object {
|
||||
{
|
||||
"minHeight": 40,
|
||||
},
|
||||
]
|
||||
@@ -294,7 +293,7 @@ exports[`components/channel_list/categories/body/channel_item should match snaps
|
||||
>
|
||||
<View
|
||||
style={
|
||||
Object {
|
||||
{
|
||||
"flex": 1,
|
||||
"flexDirection": "row",
|
||||
}
|
||||
@@ -302,12 +301,12 @@ exports[`components/channel_list/categories/body/channel_item should match snaps
|
||||
>
|
||||
<View
|
||||
style={
|
||||
Array [
|
||||
Object {
|
||||
[
|
||||
{
|
||||
"alignItems": "center",
|
||||
"justifyContent": "center",
|
||||
},
|
||||
Object {
|
||||
{
|
||||
"height": 24,
|
||||
"width": 24,
|
||||
},
|
||||
@@ -319,13 +318,13 @@ exports[`components/channel_list/categories/body/channel_item should match snaps
|
||||
<Icon
|
||||
name="pencil-outline"
|
||||
style={
|
||||
Array [
|
||||
Object {
|
||||
[
|
||||
{
|
||||
"color": "rgba(255,255,255,0.4)",
|
||||
},
|
||||
undefined,
|
||||
undefined,
|
||||
Object {
|
||||
{
|
||||
"fontSize": 24,
|
||||
"left": 2,
|
||||
},
|
||||
@@ -339,14 +338,14 @@ exports[`components/channel_list/categories/body/channel_item should match snaps
|
||||
ellipsizeMode="tail"
|
||||
numberOfLines={1}
|
||||
style={
|
||||
Array [
|
||||
Object {
|
||||
[
|
||||
{
|
||||
"fontFamily": "OpenSans",
|
||||
"fontSize": 16,
|
||||
"fontWeight": "400",
|
||||
"lineHeight": 24,
|
||||
},
|
||||
Object {
|
||||
{
|
||||
"color": "rgba(255,255,255,0.72)",
|
||||
"marginTop": -1,
|
||||
"paddingLeft": 12,
|
||||
|
||||
@@ -116,9 +116,8 @@ export const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => ({
|
||||
top: 5,
|
||||
},
|
||||
hasCall: {
|
||||
flex: 1,
|
||||
textAlign: 'right',
|
||||
marginRight: 20,
|
||||
paddingRight: 0,
|
||||
},
|
||||
}));
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import Clipboard from '@react-native-community/clipboard';
|
||||
import Clipboard from '@react-native-clipboard/clipboard';
|
||||
import React, {useCallback} from 'react';
|
||||
|
||||
import {BaseOption} from '@components/common_post_options';
|
||||
|
||||
@@ -13,7 +13,7 @@ exports[`components/custom_status/clear_button should match snapshot 1`] = `
|
||||
onResponderTerminationRequest={[Function]}
|
||||
onStartShouldSetResponder={[Function]}
|
||||
style={
|
||||
Object {
|
||||
{
|
||||
"alignItems": "center",
|
||||
"flex": 1,
|
||||
"height": 40,
|
||||
@@ -28,7 +28,7 @@ exports[`components/custom_status/clear_button should match snapshot 1`] = `
|
||||
name="close-circle"
|
||||
size={20}
|
||||
style={
|
||||
Object {
|
||||
{
|
||||
"borderRadius": 1000,
|
||||
"color": "rgba(63,67,80,0.52)",
|
||||
}
|
||||
|
||||
@@ -6,9 +6,9 @@ exports[`components/custom_status/custom_status_emoji should match snapshot 1`]
|
||||
>
|
||||
<Text
|
||||
style={
|
||||
Array [
|
||||
[
|
||||
undefined,
|
||||
Object {
|
||||
{
|
||||
"color": "#000",
|
||||
"fontSize": 16,
|
||||
},
|
||||
@@ -26,9 +26,9 @@ exports[`components/custom_status/custom_status_emoji should match snapshot with
|
||||
>
|
||||
<Text
|
||||
style={
|
||||
Array [
|
||||
[
|
||||
undefined,
|
||||
Object {
|
||||
{
|
||||
"color": "#000",
|
||||
"fontSize": 34,
|
||||
},
|
||||
|
||||
@@ -3,8 +3,8 @@
|
||||
exports[`components/custom_status/custom_status_text should match snapshot 1`] = `
|
||||
<Text
|
||||
style={
|
||||
Array [
|
||||
Object {
|
||||
[
|
||||
{
|
||||
"color": "rgba(63,67,80,0.5)",
|
||||
"fontSize": 17,
|
||||
"includeFontPadding": false,
|
||||
@@ -21,8 +21,8 @@ exports[`components/custom_status/custom_status_text should match snapshot 1`] =
|
||||
exports[`components/custom_status/custom_status_text should match snapshot with empty text 1`] = `
|
||||
<Text
|
||||
style={
|
||||
Array [
|
||||
Object {
|
||||
[
|
||||
{
|
||||
"color": "rgba(63,67,80,0.5)",
|
||||
"fontSize": 17,
|
||||
"includeFontPadding": false,
|
||||
|
||||
@@ -3,15 +3,15 @@
|
||||
exports[`ErrorText should match snapshot 1`] = `
|
||||
<Text
|
||||
style={
|
||||
Array [
|
||||
Object {
|
||||
[
|
||||
{
|
||||
"color": "#d24b4e",
|
||||
"fontSize": 12,
|
||||
"marginBottom": 15,
|
||||
"marginTop": 15,
|
||||
"textAlign": "left",
|
||||
},
|
||||
Object {
|
||||
{
|
||||
"fontSize": 14,
|
||||
"marginHorizontal": 15,
|
||||
},
|
||||
|
||||
@@ -30,7 +30,8 @@ type FileProps = {
|
||||
onPress: (index: number) => void;
|
||||
publicLinkEnabled: boolean;
|
||||
channelName?: string;
|
||||
onOptionsPress?: (index: number) => void;
|
||||
onOptionsPress?: (fileInfo: FileInfo) => void;
|
||||
optionSelected?: boolean;
|
||||
wrapperWidth?: number;
|
||||
showDate?: boolean;
|
||||
updateFileForGallery: (idx: number, file: FileInfo) => void;
|
||||
@@ -74,6 +75,7 @@ const File = ({
|
||||
nonVisibleImagesCount = 0,
|
||||
onOptionsPress,
|
||||
onPress,
|
||||
optionSelected,
|
||||
publicLinkEnabled,
|
||||
showDate = false,
|
||||
updateFileForGallery,
|
||||
@@ -94,19 +96,15 @@ const File = ({
|
||||
const {styles, onGestureEvent, ref} = useGalleryItem(galleryIdentifier, index, handlePreviewPress);
|
||||
|
||||
const handleOnOptionsPress = useCallback(() => {
|
||||
onOptionsPress?.(index);
|
||||
}, [index, onOptionsPress]);
|
||||
onOptionsPress?.(file);
|
||||
}, [file, onOptionsPress]);
|
||||
|
||||
const renderOptionsButton = () => {
|
||||
if (onOptionsPress) {
|
||||
return (
|
||||
<FileOptionsIcon
|
||||
onPress={handleOnOptionsPress}
|
||||
/>
|
||||
);
|
||||
}
|
||||
return null;
|
||||
};
|
||||
const optionsButton = (
|
||||
<FileOptionsIcon
|
||||
onPress={handleOnOptionsPress}
|
||||
selected={optionSelected}
|
||||
/>
|
||||
);
|
||||
|
||||
const fileInfo = (
|
||||
<FileInfo
|
||||
@@ -174,7 +172,7 @@ const File = ({
|
||||
{fileIcon}
|
||||
</View>
|
||||
{fileInfo}
|
||||
{renderOptionsButton()}
|
||||
{onOptionsPress && optionsButton}
|
||||
</View>
|
||||
);
|
||||
};
|
||||
@@ -189,7 +187,7 @@ const File = ({
|
||||
<View style={[style.fileWrapper]}>
|
||||
{renderDocumentFile}
|
||||
{fileInfo}
|
||||
{renderOptionsButton()}
|
||||
{onOptionsPress && optionsButton}
|
||||
</View>
|
||||
);
|
||||
} else {
|
||||
|
||||
@@ -2,32 +2,38 @@
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import React from 'react';
|
||||
import {TouchableOpacity, StyleSheet} from 'react-native';
|
||||
import {TouchableOpacity} from 'react-native';
|
||||
|
||||
import CompassIcon from '@components/compass_icon';
|
||||
import {useTheme} from '@context/theme';
|
||||
import {changeOpacity} from '@utils/theme';
|
||||
import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme';
|
||||
|
||||
type Props = {
|
||||
onPress: () => void;
|
||||
selected?: boolean;
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
threeDotContainer: {
|
||||
alignItems: 'flex-end',
|
||||
marginHorizontal: 20,
|
||||
},
|
||||
const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => {
|
||||
return {
|
||||
threeDotContainer: {
|
||||
alignItems: 'flex-end',
|
||||
borderRadius: 4,
|
||||
marginHorizontal: 20,
|
||||
padding: 7,
|
||||
},
|
||||
selected: {
|
||||
backgroundColor: changeOpacity(theme.buttonBg, 0.08),
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
const hitSlop = {top: 5, bottom: 5, left: 5, right: 5};
|
||||
|
||||
export default function FileOptionsIcon({onPress}: Props) {
|
||||
export default function FileOptionsIcon({onPress, selected = false}: Props) {
|
||||
const theme = useTheme();
|
||||
const styles = getStyleSheet(theme);
|
||||
return (
|
||||
<TouchableOpacity
|
||||
onPress={onPress}
|
||||
style={styles.threeDotContainer}
|
||||
hitSlop={hitSlop}
|
||||
style={[styles.threeDotContainer, selected ? styles.selected : null]}
|
||||
>
|
||||
<CompassIcon
|
||||
name='dots-horizontal'
|
||||
|
||||
@@ -6,7 +6,6 @@ import Svg, {
|
||||
Mask,
|
||||
G,
|
||||
} from 'react-native-svg';
|
||||
import {EMaskUnits} from 'react-native-svg/src/elements/Mask';
|
||||
|
||||
type Props = {
|
||||
theme: Theme;
|
||||
@@ -60,7 +59,7 @@ function SvgComponent({theme}: Props) {
|
||||
/>
|
||||
<Mask
|
||||
id='a'
|
||||
maskUnits={EMaskUnits.USER_SPACE_ON_USE}
|
||||
maskUnits='userSpaceOnUse'
|
||||
x={76}
|
||||
y={43}
|
||||
width={134}
|
||||
|
||||
@@ -6,7 +6,6 @@ import Svg, {
|
||||
Mask,
|
||||
G,
|
||||
} from 'react-native-svg';
|
||||
import {EMaskUnits} from 'react-native-svg/src/elements/Mask';
|
||||
|
||||
type Props = {
|
||||
theme: Theme;
|
||||
@@ -60,7 +59,7 @@ function SvgComponent({theme}: Props) {
|
||||
/>
|
||||
<Mask
|
||||
id='a'
|
||||
maskUnits={EMaskUnits.USER_SPACE_ON_USE}
|
||||
maskUnits='userSpaceOnUse'
|
||||
x={76}
|
||||
y={43}
|
||||
width={134}
|
||||
|
||||
@@ -11,7 +11,6 @@ import Svg, {
|
||||
Use,
|
||||
Image,
|
||||
} from 'react-native-svg';
|
||||
import {EMaskUnits} from 'react-native-svg/src/elements/Mask';
|
||||
|
||||
type Props = {
|
||||
theme: Theme;
|
||||
@@ -31,7 +30,7 @@ function SvgComponent({theme}: Props) {
|
||||
/>
|
||||
<Mask
|
||||
id='a'
|
||||
maskUnits={EMaskUnits.USER_SPACE_ON_USE}
|
||||
maskUnits='userSpaceOnUse'
|
||||
x={3}
|
||||
y={0}
|
||||
width={117}
|
||||
@@ -53,7 +52,7 @@ function SvgComponent({theme}: Props) {
|
||||
/>
|
||||
<Mask
|
||||
id='b'
|
||||
maskUnits={EMaskUnits.USER_SPACE_ON_USE}
|
||||
maskUnits='userSpaceOnUse'
|
||||
x={32}
|
||||
y={42}
|
||||
width={71}
|
||||
@@ -102,7 +101,7 @@ function SvgComponent({theme}: Props) {
|
||||
/>
|
||||
<Mask
|
||||
id='c'
|
||||
maskUnits={EMaskUnits.USER_SPACE_ON_USE}
|
||||
maskUnits='userSpaceOnUse'
|
||||
x={25}
|
||||
y={3}
|
||||
width={53}
|
||||
@@ -125,7 +124,7 @@ function SvgComponent({theme}: Props) {
|
||||
/>
|
||||
<Mask
|
||||
id='d'
|
||||
maskUnits={EMaskUnits.USER_SPACE_ON_USE}
|
||||
maskUnits='userSpaceOnUse'
|
||||
x={71}
|
||||
y={30}
|
||||
width={51}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
exports[`Loading Error should match snapshot 1`] = `
|
||||
<View
|
||||
style={
|
||||
Object {
|
||||
{
|
||||
"alignItems": "center",
|
||||
"flex": 1,
|
||||
"justifyContent": "center",
|
||||
@@ -13,7 +13,7 @@ exports[`Loading Error should match snapshot 1`] = `
|
||||
>
|
||||
<View
|
||||
style={
|
||||
Object {
|
||||
{
|
||||
"alignItems": "center",
|
||||
"backgroundColor": "rgba(255,255,255,0.08)",
|
||||
"borderRadius": 60,
|
||||
@@ -26,7 +26,7 @@ exports[`Loading Error should match snapshot 1`] = `
|
||||
<Icon
|
||||
name="alert-circle-outline"
|
||||
style={
|
||||
Object {
|
||||
{
|
||||
"color": "rgba(255,255,255,0.48)",
|
||||
"fontSize": 72,
|
||||
"lineHeight": 72,
|
||||
@@ -36,14 +36,14 @@ exports[`Loading Error should match snapshot 1`] = `
|
||||
</View>
|
||||
<Text
|
||||
style={
|
||||
Array [
|
||||
Object {
|
||||
[
|
||||
{
|
||||
"fontFamily": "Metropolis-SemiBold",
|
||||
"fontSize": 20,
|
||||
"fontWeight": "600",
|
||||
"lineHeight": 28,
|
||||
},
|
||||
Object {
|
||||
{
|
||||
"color": "#ffffff",
|
||||
"marginTop": 20,
|
||||
"textAlign": "center",
|
||||
@@ -55,14 +55,14 @@ exports[`Loading Error should match snapshot 1`] = `
|
||||
</Text>
|
||||
<Text
|
||||
style={
|
||||
Array [
|
||||
Object {
|
||||
[
|
||||
{
|
||||
"fontFamily": "OpenSans",
|
||||
"fontSize": 16,
|
||||
"fontWeight": "400",
|
||||
"lineHeight": 24,
|
||||
},
|
||||
Object {
|
||||
{
|
||||
"color": "#ffffff",
|
||||
"marginTop": 4,
|
||||
"textAlign": "center",
|
||||
@@ -84,7 +84,7 @@ exports[`Loading Error should match snapshot 1`] = `
|
||||
onResponderTerminationRequest={[Function]}
|
||||
onStartShouldSetResponder={[Function]}
|
||||
style={
|
||||
Object {
|
||||
{
|
||||
"alignItems": "center",
|
||||
"backgroundColor": "#ffffff",
|
||||
"borderRadius": 4,
|
||||
@@ -100,8 +100,8 @@ exports[`Loading Error should match snapshot 1`] = `
|
||||
>
|
||||
<Text
|
||||
style={
|
||||
Array [
|
||||
Object {
|
||||
[
|
||||
{
|
||||
"alignItems": "center",
|
||||
"fontFamily": "OpenSans-SemiBold",
|
||||
"fontWeight": "600",
|
||||
@@ -109,12 +109,12 @@ exports[`Loading Error should match snapshot 1`] = `
|
||||
"padding": 1,
|
||||
"textAlignVertical": "center",
|
||||
},
|
||||
Object {
|
||||
{
|
||||
"fontSize": 16,
|
||||
"lineHeight": 18,
|
||||
"marginTop": 1,
|
||||
},
|
||||
Object {
|
||||
{
|
||||
"color": "#1c58d9",
|
||||
},
|
||||
]
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
import {useManagedConfig} from '@mattermost/react-native-emm';
|
||||
import {Database} from '@nozbe/watermelondb';
|
||||
import Clipboard from '@react-native-community/clipboard';
|
||||
import Clipboard from '@react-native-clipboard/clipboard';
|
||||
import React, {useCallback, useEffect, useMemo} from 'react';
|
||||
import {useIntl} from 'react-intl';
|
||||
import {GestureResponderEvent, Keyboard, StyleProp, StyleSheet, Text, TextStyle, View} from 'react-native';
|
||||
|
||||
@@ -21,7 +21,6 @@ type ChannelMentionProps = {
|
||||
channelName: string;
|
||||
channels: ChannelModel[];
|
||||
currentTeamId: string;
|
||||
currentUserId: string;
|
||||
linkStyle: StyleProp<TextStyle>;
|
||||
team: TeamModel;
|
||||
textStyle: StyleProp<TextStyle>;
|
||||
@@ -57,7 +56,7 @@ function getChannelFromChannelName(name: string, channels: ChannelModel[], chann
|
||||
}
|
||||
|
||||
const ChannelMention = ({
|
||||
channelMentions, channelName, channels, currentTeamId, currentUserId,
|
||||
channelMentions, channelName, channels, currentTeamId,
|
||||
linkStyle, team, textStyle,
|
||||
}: ChannelMentionProps) => {
|
||||
const intl = useIntl();
|
||||
@@ -68,7 +67,7 @@ const ChannelMention = ({
|
||||
let c = channel;
|
||||
|
||||
if (!c?.id && c?.display_name) {
|
||||
const result = await joinChannel(serverUrl, currentUserId, currentTeamId, undefined, channelName);
|
||||
const result = await joinChannel(serverUrl, currentTeamId, undefined, channelName);
|
||||
if (result.error || !result.channel) {
|
||||
const joinFailedMessage = {
|
||||
id: t('mobile.join_channel.error'),
|
||||
|
||||
@@ -6,7 +6,7 @@ import withObservables from '@nozbe/with-observables';
|
||||
import {switchMap} from 'rxjs/operators';
|
||||
|
||||
import {queryAllChannelsForTeam} from '@queries/servers/channel';
|
||||
import {observeCurrentTeamId, observeCurrentUserId} from '@queries/servers/system';
|
||||
import {observeCurrentTeamId} from '@queries/servers/system';
|
||||
import {observeTeam} from '@queries/servers/team';
|
||||
|
||||
import ChannelMention from './channel_mention';
|
||||
@@ -17,7 +17,6 @@ export type ChannelMentions = Record<string, {id?: string; display_name: string;
|
||||
|
||||
const enhance = withObservables([], ({database}: WithDatabaseArgs) => {
|
||||
const currentTeamId = observeCurrentTeamId(database);
|
||||
const currentUserId = observeCurrentUserId(database);
|
||||
const channels = currentTeamId.pipe(
|
||||
switchMap((id) => queryAllChannelsForTeam(database, id).observeWithColumns(['display_name'])),
|
||||
);
|
||||
@@ -28,7 +27,6 @@ const enhance = withObservables([], ({database}: WithDatabaseArgs) => {
|
||||
return {
|
||||
channels,
|
||||
currentTeamId,
|
||||
currentUserId,
|
||||
team,
|
||||
};
|
||||
});
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {useManagedConfig} from '@mattermost/react-native-emm';
|
||||
import Clipboard from '@react-native-community/clipboard';
|
||||
import Clipboard from '@react-native-clipboard/clipboard';
|
||||
import React, {useCallback} from 'react';
|
||||
import {useIntl} from 'react-intl';
|
||||
import {Keyboard, StyleSheet, Text, TextStyle, TouchableOpacity, View} from 'react-native';
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {useManagedConfig} from '@mattermost/react-native-emm';
|
||||
import Clipboard from '@react-native-community/clipboard';
|
||||
import Clipboard from '@react-native-clipboard/clipboard';
|
||||
import React, {useCallback, useMemo, useRef, useState} from 'react';
|
||||
import {useIntl} from 'react-intl';
|
||||
import {Alert, Platform, StyleProp, Text, TextStyle, TouchableWithoutFeedback, View} from 'react-native';
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {useManagedConfig} from '@mattermost/react-native-emm';
|
||||
import Clipboard from '@react-native-community/clipboard';
|
||||
import Clipboard from '@react-native-clipboard/clipboard';
|
||||
import React, {useCallback, useMemo} from 'react';
|
||||
import {useIntl} from 'react-intl';
|
||||
import {Keyboard, View, Text, StyleSheet, Platform} from 'react-native';
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {useManagedConfig} from '@mattermost/react-native-emm';
|
||||
import Clipboard from '@react-native-community/clipboard';
|
||||
import Clipboard from '@react-native-clipboard/clipboard';
|
||||
import React, {Children, ReactElement, useCallback} from 'react';
|
||||
import {useIntl} from 'react-intl';
|
||||
import {Alert, StyleSheet, Text, View} from 'react-native';
|
||||
|
||||
@@ -24,6 +24,7 @@ type Props = {
|
||||
updatePostInputTop: (top: number) => void;
|
||||
updateValue: (value: string) => void;
|
||||
value: string;
|
||||
setIsFocused: (isFocused: boolean) => void;
|
||||
}
|
||||
|
||||
const emptyFileList: FileInfo[] = [];
|
||||
@@ -47,6 +48,7 @@ export default function DraftHandler(props: Props) {
|
||||
updatePostInputTop,
|
||||
updateValue,
|
||||
value,
|
||||
setIsFocused,
|
||||
} = props;
|
||||
|
||||
const serverUrl = useServerUrl();
|
||||
@@ -144,6 +146,7 @@ export default function DraftHandler(props: Props) {
|
||||
updateCursorPosition={updateCursorPosition}
|
||||
updatePostInputTop={updatePostInputTop}
|
||||
updateValue={updateValue}
|
||||
setIsFocused={setIsFocused}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -36,6 +36,7 @@ type Props = {
|
||||
updateValue: (value: string) => void;
|
||||
addFiles: (files: FileInfo[]) => void;
|
||||
updatePostInputTop: (top: number) => void;
|
||||
setIsFocused: (isFocused: boolean) => void;
|
||||
}
|
||||
|
||||
const SAFE_AREA_VIEW_EDGES: Edge[] = ['left', 'right'];
|
||||
@@ -94,6 +95,7 @@ export default function DraftInput({
|
||||
updateCursorPosition,
|
||||
cursorPosition,
|
||||
updatePostInputTop,
|
||||
setIsFocused,
|
||||
}: Props) {
|
||||
const theme = useTheme();
|
||||
|
||||
@@ -142,6 +144,7 @@ export default function DraftInput({
|
||||
value={value}
|
||||
addFiles={addFiles}
|
||||
sendMessage={sendMessage}
|
||||
setIsFocused={setIsFocused}
|
||||
/>
|
||||
<Uploads
|
||||
currentUserId={currentUserId}
|
||||
|
||||
@@ -57,6 +57,7 @@ function PostDraft({
|
||||
const [value, setValue] = useState(message);
|
||||
const [cursorPosition, setCursorPosition] = useState(message.length);
|
||||
const [postInputTop, setPostInputTop] = useState(0);
|
||||
const [isFocused, setIsFocused] = useState(false);
|
||||
const isTablet = useIsTablet();
|
||||
const keyboardHeight = useKeyboardHeight(keyboardTracker);
|
||||
const insets = useSafeAreaInsets();
|
||||
@@ -110,10 +111,11 @@ function PostDraft({
|
||||
updatePostInputTop={setPostInputTop}
|
||||
updateValue={setValue}
|
||||
value={value}
|
||||
setIsFocused={setIsFocused}
|
||||
/>
|
||||
);
|
||||
|
||||
const autoComplete = (
|
||||
const autoComplete = isFocused ? (
|
||||
<Autocomplete
|
||||
position={animatedAutocompletePosition}
|
||||
updateValue={setValue}
|
||||
@@ -126,7 +128,7 @@ function PostDraft({
|
||||
inPost={true}
|
||||
availableSpace={animatedAutocompleteAvailableSpace}
|
||||
/>
|
||||
);
|
||||
) : null;
|
||||
|
||||
if (Platform.OS === 'android') {
|
||||
return (
|
||||
|
||||
@@ -39,6 +39,7 @@ type Props = {
|
||||
cursorPosition: number;
|
||||
updateCursorPosition: (pos: number) => void;
|
||||
sendMessage: () => void;
|
||||
setIsFocused: (isFocused: boolean) => void;
|
||||
}
|
||||
|
||||
const showPasteFilesErrorDialog = (intl: IntlShape) => {
|
||||
@@ -108,6 +109,7 @@ export default function PostInput({
|
||||
cursorPosition,
|
||||
updateCursorPosition,
|
||||
sendMessage,
|
||||
setIsFocused,
|
||||
}: Props) {
|
||||
const intl = useIntl();
|
||||
const isTablet = useIsTablet();
|
||||
@@ -140,7 +142,12 @@ export default function PostInput({
|
||||
|
||||
const onBlur = useCallback(() => {
|
||||
updateDraftMessage(serverUrl, channelId, rootId, value);
|
||||
}, [channelId, rootId, value]);
|
||||
setIsFocused(false);
|
||||
}, [channelId, rootId, value, setIsFocused]);
|
||||
|
||||
const onFocus = useCallback(() => {
|
||||
setIsFocused(true);
|
||||
}, [setIsFocused]);
|
||||
|
||||
const checkMessageLength = useCallback((newValue: string) => {
|
||||
const valueLength = newValue.trim().length;
|
||||
@@ -308,6 +315,7 @@ export default function PostInput({
|
||||
placeholderTextColor={changeOpacity(theme.centerChannelColor, 0.5)}
|
||||
multiline={true}
|
||||
onBlur={onBlur}
|
||||
onFocus={onFocus}
|
||||
blurOnSubmit={false}
|
||||
underlineColorAndroid='transparent'
|
||||
keyboardType={keyboardType}
|
||||
|
||||
@@ -29,6 +29,7 @@ type Props = {
|
||||
testID?: string;
|
||||
channelId: string;
|
||||
rootId: string;
|
||||
setIsFocused: (isFocused: boolean) => void;
|
||||
|
||||
// From database
|
||||
currentUserId: string;
|
||||
@@ -73,6 +74,7 @@ export default function SendHandler({
|
||||
uploadFileError,
|
||||
updateCursorPosition,
|
||||
updatePostInputTop,
|
||||
setIsFocused,
|
||||
}: Props) {
|
||||
const intl = useIntl();
|
||||
const serverUrl = useServerUrl();
|
||||
@@ -290,6 +292,7 @@ export default function SendHandler({
|
||||
canSend={canSend()}
|
||||
maxMessageLength={maxMessageLength}
|
||||
updatePostInputTop={updatePostInputTop}
|
||||
setIsFocused={setIsFocused}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
exports[`AttachmentFooter it matches snapshot when both footer and footer_icon are provided 1`] = `
|
||||
<View
|
||||
style={
|
||||
Object {
|
||||
{
|
||||
"flex": 1,
|
||||
"flexDirection": "row",
|
||||
"marginTop": 5,
|
||||
@@ -12,11 +12,11 @@ exports[`AttachmentFooter it matches snapshot when both footer and footer_icon a
|
||||
>
|
||||
<View
|
||||
style={
|
||||
Array [
|
||||
Object {
|
||||
[
|
||||
{
|
||||
"overflow": "hidden",
|
||||
},
|
||||
Object {
|
||||
{
|
||||
"height": 12,
|
||||
"marginRight": 5,
|
||||
"marginTop": 1,
|
||||
@@ -26,14 +26,15 @@ exports[`AttachmentFooter it matches snapshot when both footer and footer_icon a
|
||||
}
|
||||
>
|
||||
<FastImageView
|
||||
defaultSource={null}
|
||||
resizeMode="cover"
|
||||
source={
|
||||
Object {
|
||||
{
|
||||
"uri": "https://images.com/image.png",
|
||||
}
|
||||
}
|
||||
style={
|
||||
Object {
|
||||
{
|
||||
"bottom": 0,
|
||||
"left": 0,
|
||||
"position": "absolute",
|
||||
@@ -47,7 +48,7 @@ exports[`AttachmentFooter it matches snapshot when both footer and footer_icon a
|
||||
ellipsizeMode="tail"
|
||||
numberOfLines={1}
|
||||
style={
|
||||
Object {
|
||||
{
|
||||
"color": "rgba(63,67,80,0.5)",
|
||||
"fontSize": 11,
|
||||
}
|
||||
@@ -61,7 +62,7 @@ exports[`AttachmentFooter it matches snapshot when both footer and footer_icon a
|
||||
exports[`AttachmentFooter it matches snapshot when footer text is provided 1`] = `
|
||||
<View
|
||||
style={
|
||||
Object {
|
||||
{
|
||||
"flex": 1,
|
||||
"flexDirection": "row",
|
||||
"marginTop": 5,
|
||||
@@ -72,7 +73,7 @@ exports[`AttachmentFooter it matches snapshot when footer text is provided 1`] =
|
||||
ellipsizeMode="tail"
|
||||
numberOfLines={1}
|
||||
style={
|
||||
Object {
|
||||
{
|
||||
"color": "rgba(63,67,80,0.5)",
|
||||
"fontSize": 11,
|
||||
}
|
||||
|
||||
@@ -29,6 +29,7 @@ type HeaderProps = {
|
||||
enablePostUsernameOverride: boolean;
|
||||
isAutoResponse: boolean;
|
||||
isCRTEnabled?: boolean;
|
||||
isCustomStatusEnabled: boolean;
|
||||
isEphemeral: boolean;
|
||||
isMilitaryTime: boolean;
|
||||
isPendingOrFailed: boolean;
|
||||
@@ -76,7 +77,7 @@ const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => {
|
||||
|
||||
const Header = (props: HeaderProps) => {
|
||||
const {
|
||||
author, commentCount = 0, currentUser, enablePostUsernameOverride, isAutoResponse, isCRTEnabled,
|
||||
author, commentCount = 0, currentUser, enablePostUsernameOverride, isAutoResponse, isCRTEnabled, isCustomStatusEnabled,
|
||||
isEphemeral, isMilitaryTime, isPendingOrFailed, isPostPriorityEnabled, isSystemPost, isTimezoneEnabled, isWebHook,
|
||||
location, post, rootPostAuthor, shouldRenderReplyButton, teammateNameDisplay,
|
||||
} = props;
|
||||
@@ -90,7 +91,7 @@ const Header = (props: HeaderProps) => {
|
||||
const customStatus = getUserCustomStatus(author);
|
||||
const customStatusExpired = isCustomStatusExpired(author);
|
||||
const showCustomStatusEmoji = Boolean(
|
||||
displayName && customStatus &&
|
||||
isCustomStatusEnabled && displayName && customStatus &&
|
||||
!(isSystemPost || author.isBot || isAutoResponse || isWebHook),
|
||||
);
|
||||
|
||||
|
||||
@@ -34,6 +34,7 @@ const withHeaderProps = withObservables(
|
||||
const isMilitaryTime = preferences.pipe(map((prefs) => getPreferenceAsBool(prefs, Preferences.CATEGORY_DISPLAY_SETTINGS, 'use_military_time', false)));
|
||||
const teammateNameDisplay = observeTeammateNameDisplay(database);
|
||||
const commentCount = queryPostReplies(database, post.rootId || post.id).observeCount();
|
||||
const isCustomStatusEnabled = observeConfigBooleanValue(database, 'EnableCustomUserStatuses');
|
||||
const rootPostAuthor = differentThreadSequence ? post.root.observe().pipe(switchMap((root) => {
|
||||
if (root.length) {
|
||||
return root[0].author.observe();
|
||||
@@ -46,6 +47,7 @@ const withHeaderProps = withObservables(
|
||||
author,
|
||||
commentCount,
|
||||
enablePostUsernameOverride,
|
||||
isCustomStatusEnabled,
|
||||
isMilitaryTime,
|
||||
isTimezoneEnabled,
|
||||
rootPostAuthor,
|
||||
|
||||
@@ -3,15 +3,15 @@
|
||||
exports[`renderSystemMessage uses renderer for Channel Display Name update 1`] = `
|
||||
<View
|
||||
style={
|
||||
Object {
|
||||
{
|
||||
"marginBottom": 5,
|
||||
}
|
||||
}
|
||||
>
|
||||
<View
|
||||
style={
|
||||
Array [
|
||||
Object {
|
||||
[
|
||||
{
|
||||
"alignItems": "flex-start",
|
||||
"flexDirection": "row",
|
||||
"flexWrap": "wrap",
|
||||
@@ -23,29 +23,29 @@ exports[`renderSystemMessage uses renderer for Channel Display Name update 1`] =
|
||||
<Text>
|
||||
<Text
|
||||
style={
|
||||
Array [
|
||||
Object {
|
||||
[
|
||||
{
|
||||
"color": "rgba(63,67,80,0.6)",
|
||||
"fontFamily": "OpenSans",
|
||||
"fontSize": 16,
|
||||
"fontWeight": "400",
|
||||
"lineHeight": 24,
|
||||
},
|
||||
Object {
|
||||
{
|
||||
"opacity": 1,
|
||||
},
|
||||
]
|
||||
}
|
||||
>
|
||||
<Text
|
||||
style={Array []}
|
||||
style={[]}
|
||||
>
|
||||
@username
|
||||
</Text>
|
||||
</Text>
|
||||
<Text
|
||||
style={
|
||||
Object {
|
||||
{
|
||||
"color": "rgba(63,67,80,0.6)",
|
||||
"fontFamily": "OpenSans",
|
||||
"fontSize": 16,
|
||||
@@ -65,15 +65,15 @@ exports[`renderSystemMessage uses renderer for Channel Display Name update 1`] =
|
||||
exports[`renderSystemMessage uses renderer for Channel Header update 1`] = `
|
||||
<View
|
||||
style={
|
||||
Object {
|
||||
{
|
||||
"marginBottom": 5,
|
||||
}
|
||||
}
|
||||
>
|
||||
<View
|
||||
style={
|
||||
Array [
|
||||
Object {
|
||||
[
|
||||
{
|
||||
"alignItems": "flex-start",
|
||||
"flexDirection": "row",
|
||||
"flexWrap": "wrap",
|
||||
@@ -85,29 +85,29 @@ exports[`renderSystemMessage uses renderer for Channel Header update 1`] = `
|
||||
<Text>
|
||||
<Text
|
||||
style={
|
||||
Array [
|
||||
Object {
|
||||
[
|
||||
{
|
||||
"color": "rgba(63,67,80,0.6)",
|
||||
"fontFamily": "OpenSans",
|
||||
"fontSize": 16,
|
||||
"fontWeight": "400",
|
||||
"lineHeight": 24,
|
||||
},
|
||||
Object {
|
||||
{
|
||||
"opacity": 1,
|
||||
},
|
||||
]
|
||||
}
|
||||
>
|
||||
<Text
|
||||
style={Array []}
|
||||
style={[]}
|
||||
>
|
||||
@username
|
||||
</Text>
|
||||
</Text>
|
||||
<Text
|
||||
style={
|
||||
Object {
|
||||
{
|
||||
"color": "rgba(63,67,80,0.6)",
|
||||
"fontFamily": "OpenSans",
|
||||
"fontSize": 16,
|
||||
@@ -127,7 +127,7 @@ exports[`renderSystemMessage uses renderer for Channel Header update 1`] = `
|
||||
exports[`renderSystemMessage uses renderer for Channel Purpose update 1`] = `
|
||||
<Text
|
||||
style={
|
||||
Object {
|
||||
{
|
||||
"color": "rgba(63,67,80,0.6)",
|
||||
"fontFamily": "OpenSans",
|
||||
"fontSize": 16,
|
||||
@@ -143,15 +143,15 @@ exports[`renderSystemMessage uses renderer for Channel Purpose update 1`] = `
|
||||
exports[`renderSystemMessage uses renderer for Guest added and join to channel 1`] = `
|
||||
<View
|
||||
style={
|
||||
Object {
|
||||
{
|
||||
"marginBottom": 5,
|
||||
}
|
||||
}
|
||||
>
|
||||
<View
|
||||
style={
|
||||
Array [
|
||||
Object {
|
||||
[
|
||||
{
|
||||
"alignItems": "flex-start",
|
||||
"flexDirection": "row",
|
||||
"flexWrap": "wrap",
|
||||
@@ -163,29 +163,29 @@ exports[`renderSystemMessage uses renderer for Guest added and join to channel 1
|
||||
<Text>
|
||||
<Text
|
||||
style={
|
||||
Array [
|
||||
Object {
|
||||
[
|
||||
{
|
||||
"color": "rgba(63,67,80,0.6)",
|
||||
"fontFamily": "OpenSans",
|
||||
"fontSize": 16,
|
||||
"fontWeight": "400",
|
||||
"lineHeight": 24,
|
||||
},
|
||||
Object {
|
||||
{
|
||||
"opacity": 1,
|
||||
},
|
||||
]
|
||||
}
|
||||
>
|
||||
<Text
|
||||
style={Array []}
|
||||
style={[]}
|
||||
>
|
||||
@username
|
||||
</Text>
|
||||
</Text>
|
||||
<Text
|
||||
style={
|
||||
Object {
|
||||
{
|
||||
"color": "rgba(63,67,80,0.6)",
|
||||
"fontFamily": "OpenSans",
|
||||
"fontSize": 16,
|
||||
@@ -205,15 +205,15 @@ exports[`renderSystemMessage uses renderer for Guest added and join to channel 1
|
||||
exports[`renderSystemMessage uses renderer for Guest added and join to channel 2`] = `
|
||||
<View
|
||||
style={
|
||||
Object {
|
||||
{
|
||||
"marginBottom": 5,
|
||||
}
|
||||
}
|
||||
>
|
||||
<View
|
||||
style={
|
||||
Array [
|
||||
Object {
|
||||
[
|
||||
{
|
||||
"alignItems": "flex-start",
|
||||
"flexDirection": "row",
|
||||
"flexWrap": "wrap",
|
||||
@@ -225,29 +225,29 @@ exports[`renderSystemMessage uses renderer for Guest added and join to channel 2
|
||||
<Text>
|
||||
<Text
|
||||
style={
|
||||
Array [
|
||||
Object {
|
||||
[
|
||||
{
|
||||
"color": "rgba(63,67,80,0.6)",
|
||||
"fontFamily": "OpenSans",
|
||||
"fontSize": 16,
|
||||
"fontWeight": "400",
|
||||
"lineHeight": 24,
|
||||
},
|
||||
Object {
|
||||
{
|
||||
"opacity": 1,
|
||||
},
|
||||
]
|
||||
}
|
||||
>
|
||||
<Text
|
||||
style={Array []}
|
||||
style={[]}
|
||||
>
|
||||
@other.user
|
||||
</Text>
|
||||
</Text>
|
||||
<Text
|
||||
style={
|
||||
Object {
|
||||
{
|
||||
"color": "rgba(63,67,80,0.6)",
|
||||
"fontFamily": "OpenSans",
|
||||
"fontSize": 16,
|
||||
@@ -261,22 +261,22 @@ exports[`renderSystemMessage uses renderer for Guest added and join to channel 2
|
||||
</Text>
|
||||
<Text
|
||||
style={
|
||||
Array [
|
||||
Object {
|
||||
[
|
||||
{
|
||||
"color": "rgba(63,67,80,0.6)",
|
||||
"fontFamily": "OpenSans",
|
||||
"fontSize": 16,
|
||||
"fontWeight": "400",
|
||||
"lineHeight": 24,
|
||||
},
|
||||
Object {
|
||||
{
|
||||
"opacity": 1,
|
||||
},
|
||||
]
|
||||
}
|
||||
>
|
||||
<Text
|
||||
style={Array []}
|
||||
style={[]}
|
||||
>
|
||||
@username.
|
||||
</Text>
|
||||
@@ -289,15 +289,15 @@ exports[`renderSystemMessage uses renderer for Guest added and join to channel 2
|
||||
exports[`renderSystemMessage uses renderer for OLD archived channel without a username 1`] = `
|
||||
<View
|
||||
style={
|
||||
Object {
|
||||
{
|
||||
"marginBottom": 5,
|
||||
}
|
||||
}
|
||||
>
|
||||
<View
|
||||
style={
|
||||
Array [
|
||||
Object {
|
||||
[
|
||||
{
|
||||
"alignItems": "flex-start",
|
||||
"flexDirection": "row",
|
||||
"flexWrap": "wrap",
|
||||
@@ -309,7 +309,7 @@ exports[`renderSystemMessage uses renderer for OLD archived channel without a us
|
||||
<Text>
|
||||
<Text
|
||||
style={
|
||||
Object {
|
||||
{
|
||||
"color": "rgba(63,67,80,0.6)",
|
||||
"fontFamily": "OpenSans",
|
||||
"fontSize": 16,
|
||||
@@ -329,15 +329,15 @@ exports[`renderSystemMessage uses renderer for OLD archived channel without a us
|
||||
exports[`renderSystemMessage uses renderer for archived channel 1`] = `
|
||||
<View
|
||||
style={
|
||||
Object {
|
||||
{
|
||||
"marginBottom": 5,
|
||||
}
|
||||
}
|
||||
>
|
||||
<View
|
||||
style={
|
||||
Array [
|
||||
Object {
|
||||
[
|
||||
{
|
||||
"alignItems": "flex-start",
|
||||
"flexDirection": "row",
|
||||
"flexWrap": "wrap",
|
||||
@@ -349,29 +349,29 @@ exports[`renderSystemMessage uses renderer for archived channel 1`] = `
|
||||
<Text>
|
||||
<Text
|
||||
style={
|
||||
Array [
|
||||
Object {
|
||||
[
|
||||
{
|
||||
"color": "rgba(63,67,80,0.6)",
|
||||
"fontFamily": "OpenSans",
|
||||
"fontSize": 16,
|
||||
"fontWeight": "400",
|
||||
"lineHeight": 24,
|
||||
},
|
||||
Object {
|
||||
{
|
||||
"opacity": 1,
|
||||
},
|
||||
]
|
||||
}
|
||||
>
|
||||
<Text
|
||||
style={Array []}
|
||||
style={[]}
|
||||
>
|
||||
@username
|
||||
</Text>
|
||||
</Text>
|
||||
<Text
|
||||
style={
|
||||
Object {
|
||||
{
|
||||
"color": "rgba(63,67,80,0.6)",
|
||||
"fontFamily": "OpenSans",
|
||||
"fontSize": 16,
|
||||
@@ -391,15 +391,15 @@ exports[`renderSystemMessage uses renderer for archived channel 1`] = `
|
||||
exports[`renderSystemMessage uses renderer for unarchived channel 1`] = `
|
||||
<View
|
||||
style={
|
||||
Object {
|
||||
{
|
||||
"marginBottom": 5,
|
||||
}
|
||||
}
|
||||
>
|
||||
<View
|
||||
style={
|
||||
Array [
|
||||
Object {
|
||||
[
|
||||
{
|
||||
"alignItems": "flex-start",
|
||||
"flexDirection": "row",
|
||||
"flexWrap": "wrap",
|
||||
@@ -411,29 +411,29 @@ exports[`renderSystemMessage uses renderer for unarchived channel 1`] = `
|
||||
<Text>
|
||||
<Text
|
||||
style={
|
||||
Array [
|
||||
Object {
|
||||
[
|
||||
{
|
||||
"color": "rgba(63,67,80,0.6)",
|
||||
"fontFamily": "OpenSans",
|
||||
"fontSize": 16,
|
||||
"fontWeight": "400",
|
||||
"lineHeight": 24,
|
||||
},
|
||||
Object {
|
||||
{
|
||||
"opacity": 1,
|
||||
},
|
||||
]
|
||||
}
|
||||
>
|
||||
<Text
|
||||
style={Array []}
|
||||
style={[]}
|
||||
>
|
||||
@username
|
||||
</Text>
|
||||
</Text>
|
||||
<Text
|
||||
style={
|
||||
Object {
|
||||
{
|
||||
"color": "rgba(63,67,80,0.6)",
|
||||
"fontFamily": "OpenSans",
|
||||
"fontSize": 16,
|
||||
|
||||
@@ -3,8 +3,8 @@
|
||||
exports[`ThreadOverview should match snapshot when post is not saved and 0 replies 1`] = `
|
||||
<View
|
||||
style={
|
||||
Array [
|
||||
Object {
|
||||
[
|
||||
{
|
||||
"borderBottomWidth": 1,
|
||||
"borderColor": "rgba(63,67,80,0.1)",
|
||||
"borderTopWidth": 1,
|
||||
@@ -13,7 +13,7 @@ exports[`ThreadOverview should match snapshot when post is not saved and 0 repli
|
||||
"paddingHorizontal": 16,
|
||||
"paddingVertical": 10,
|
||||
},
|
||||
Object {
|
||||
{
|
||||
"borderBottomWidth": 0,
|
||||
},
|
||||
undefined,
|
||||
@@ -23,14 +23,14 @@ exports[`ThreadOverview should match snapshot when post is not saved and 0 repli
|
||||
>
|
||||
<View
|
||||
style={
|
||||
Object {
|
||||
{
|
||||
"flex": 1,
|
||||
}
|
||||
}
|
||||
>
|
||||
<Text
|
||||
style={
|
||||
Object {
|
||||
{
|
||||
"color": "rgba(63,67,80,0.64)",
|
||||
"fontFamily": "OpenSans",
|
||||
"fontSize": 16,
|
||||
@@ -46,13 +46,15 @@ exports[`ThreadOverview should match snapshot when post is not saved and 0 repli
|
||||
</View>
|
||||
<View
|
||||
style={
|
||||
Object {
|
||||
{
|
||||
"flexDirection": "row",
|
||||
}
|
||||
}
|
||||
>
|
||||
<RNGestureHandlerButton
|
||||
collapsable={false}
|
||||
delayLongPress={600}
|
||||
enabled={true}
|
||||
exclusive={true}
|
||||
handlerTag={1}
|
||||
handlerType="NativeViewGestureHandler"
|
||||
@@ -68,7 +70,7 @@ exports[`ThreadOverview should match snapshot when post is not saved and 0 repli
|
||||
accessible={true}
|
||||
collapsable={false}
|
||||
style={
|
||||
Object {
|
||||
{
|
||||
"marginLeft": 16,
|
||||
"opacity": 1,
|
||||
}
|
||||
@@ -83,6 +85,8 @@ exports[`ThreadOverview should match snapshot when post is not saved and 0 repli
|
||||
</RNGestureHandlerButton>
|
||||
<RNGestureHandlerButton
|
||||
collapsable={false}
|
||||
delayLongPress={600}
|
||||
enabled={true}
|
||||
exclusive={true}
|
||||
handlerTag={2}
|
||||
handlerType="NativeViewGestureHandler"
|
||||
@@ -98,7 +102,7 @@ exports[`ThreadOverview should match snapshot when post is not saved and 0 repli
|
||||
accessible={true}
|
||||
collapsable={false}
|
||||
style={
|
||||
Object {
|
||||
{
|
||||
"marginLeft": 16,
|
||||
"opacity": 1,
|
||||
}
|
||||
@@ -118,8 +122,8 @@ exports[`ThreadOverview should match snapshot when post is not saved and 0 repli
|
||||
exports[`ThreadOverview should match snapshot when post is saved and has replies 1`] = `
|
||||
<View
|
||||
style={
|
||||
Array [
|
||||
Object {
|
||||
[
|
||||
{
|
||||
"borderBottomWidth": 1,
|
||||
"borderColor": "rgba(63,67,80,0.1)",
|
||||
"borderTopWidth": 1,
|
||||
@@ -135,14 +139,14 @@ exports[`ThreadOverview should match snapshot when post is saved and has replies
|
||||
>
|
||||
<View
|
||||
style={
|
||||
Object {
|
||||
{
|
||||
"flex": 1,
|
||||
}
|
||||
}
|
||||
>
|
||||
<Text
|
||||
style={
|
||||
Object {
|
||||
{
|
||||
"color": "rgba(63,67,80,0.64)",
|
||||
"fontFamily": "OpenSans",
|
||||
"fontSize": 16,
|
||||
@@ -158,13 +162,15 @@ exports[`ThreadOverview should match snapshot when post is saved and has replies
|
||||
</View>
|
||||
<View
|
||||
style={
|
||||
Object {
|
||||
{
|
||||
"flexDirection": "row",
|
||||
}
|
||||
}
|
||||
>
|
||||
<RNGestureHandlerButton
|
||||
collapsable={false}
|
||||
delayLongPress={600}
|
||||
enabled={true}
|
||||
exclusive={true}
|
||||
handlerTag={3}
|
||||
handlerType="NativeViewGestureHandler"
|
||||
@@ -180,7 +186,7 @@ exports[`ThreadOverview should match snapshot when post is saved and has replies
|
||||
accessible={true}
|
||||
collapsable={false}
|
||||
style={
|
||||
Object {
|
||||
{
|
||||
"marginLeft": 16,
|
||||
"opacity": 1,
|
||||
}
|
||||
@@ -195,6 +201,8 @@ exports[`ThreadOverview should match snapshot when post is saved and has replies
|
||||
</RNGestureHandlerButton>
|
||||
<RNGestureHandlerButton
|
||||
collapsable={false}
|
||||
delayLongPress={600}
|
||||
enabled={true}
|
||||
exclusive={true}
|
||||
handlerTag={4}
|
||||
handlerType="NativeViewGestureHandler"
|
||||
@@ -210,7 +218,7 @@ exports[`ThreadOverview should match snapshot when post is saved and has replies
|
||||
accessible={true}
|
||||
collapsable={false}
|
||||
style={
|
||||
Object {
|
||||
{
|
||||
"marginLeft": 16,
|
||||
"opacity": 1,
|
||||
}
|
||||
|
||||
@@ -90,7 +90,7 @@ const Search = forwardRef<SearchRef, SearchProps>((props: SearchProps, ref) => {
|
||||
const onClear = useCallback(() => {
|
||||
setValue('');
|
||||
props.onClear?.();
|
||||
}, []);
|
||||
}, [props.onClear]);
|
||||
|
||||
const onChangeText = useCallback((text: string) => {
|
||||
setValue(text);
|
||||
@@ -105,8 +105,8 @@ const Search = forwardRef<SearchRef, SearchProps>((props: SearchProps, ref) => {
|
||||
}), [theme]);
|
||||
|
||||
useEffect(() => {
|
||||
setValue(props.defaultValue || value || '');
|
||||
}, [props.defaultValue]);
|
||||
setValue(props.defaultValue || props.value || '');
|
||||
}, [props.defaultValue, props.value]);
|
||||
|
||||
const clearIcon = (
|
||||
<CompassIcon
|
||||
|
||||
@@ -4,7 +4,7 @@ exports[`Server Icon Server Icon Component should match snapshot 1`] = `
|
||||
<View>
|
||||
<View
|
||||
accessibilityState={
|
||||
Object {
|
||||
{
|
||||
"disabled": true,
|
||||
}
|
||||
}
|
||||
@@ -12,7 +12,7 @@ exports[`Server Icon Server Icon Component should match snapshot 1`] = `
|
||||
collapsable={false}
|
||||
focusable={false}
|
||||
hitSlop={
|
||||
Object {
|
||||
{
|
||||
"bottom": 5,
|
||||
"left": 40,
|
||||
"right": 20,
|
||||
@@ -27,7 +27,7 @@ exports[`Server Icon Server Icon Component should match snapshot 1`] = `
|
||||
onResponderTerminationRequest={[Function]}
|
||||
onStartShouldSetResponder={[Function]}
|
||||
style={
|
||||
Object {
|
||||
{
|
||||
"opacity": 1,
|
||||
}
|
||||
}
|
||||
@@ -45,7 +45,7 @@ exports[`Server Icon Server Icon Component should match snapshot with mentions 1
|
||||
<View>
|
||||
<View
|
||||
accessibilityState={
|
||||
Object {
|
||||
{
|
||||
"disabled": true,
|
||||
}
|
||||
}
|
||||
@@ -53,7 +53,7 @@ exports[`Server Icon Server Icon Component should match snapshot with mentions 1
|
||||
collapsable={false}
|
||||
focusable={false}
|
||||
hitSlop={
|
||||
Object {
|
||||
{
|
||||
"bottom": 5,
|
||||
"left": 40,
|
||||
"right": 20,
|
||||
@@ -68,7 +68,7 @@ exports[`Server Icon Server Icon Component should match snapshot with mentions 1
|
||||
onResponderTerminationRequest={[Function]}
|
||||
onStartShouldSetResponder={[Function]}
|
||||
style={
|
||||
Object {
|
||||
{
|
||||
"opacity": 1,
|
||||
}
|
||||
}
|
||||
@@ -83,7 +83,7 @@ exports[`Server Icon Server Icon Component should match snapshot with mentions 1
|
||||
collapsable={false}
|
||||
numberOfLines={1}
|
||||
style={
|
||||
Object {
|
||||
{
|
||||
"alignSelf": "flex-end",
|
||||
"backgroundColor": "#ffffff",
|
||||
"borderColor": "#14213e",
|
||||
@@ -102,8 +102,8 @@ exports[`Server Icon Server Icon Component should match snapshot with mentions 1
|
||||
"position": "absolute",
|
||||
"textAlign": "center",
|
||||
"top": -8,
|
||||
"transform": Array [
|
||||
Object {
|
||||
"transform": [
|
||||
{
|
||||
"scale": 1,
|
||||
},
|
||||
],
|
||||
@@ -121,7 +121,7 @@ exports[`Server Icon Server Icon Component should match snapshot with unreads 1`
|
||||
<View>
|
||||
<View
|
||||
accessibilityState={
|
||||
Object {
|
||||
{
|
||||
"disabled": true,
|
||||
}
|
||||
}
|
||||
@@ -129,7 +129,7 @@ exports[`Server Icon Server Icon Component should match snapshot with unreads 1`
|
||||
collapsable={false}
|
||||
focusable={false}
|
||||
hitSlop={
|
||||
Object {
|
||||
{
|
||||
"bottom": 5,
|
||||
"left": 40,
|
||||
"right": 20,
|
||||
@@ -144,7 +144,7 @@ exports[`Server Icon Server Icon Component should match snapshot with unreads 1`
|
||||
onResponderTerminationRequest={[Function]}
|
||||
onStartShouldSetResponder={[Function]}
|
||||
style={
|
||||
Object {
|
||||
{
|
||||
"opacity": 1,
|
||||
}
|
||||
}
|
||||
@@ -159,7 +159,7 @@ exports[`Server Icon Server Icon Component should match snapshot with unreads 1`
|
||||
collapsable={false}
|
||||
numberOfLines={1}
|
||||
style={
|
||||
Object {
|
||||
{
|
||||
"alignSelf": "flex-end",
|
||||
"backgroundColor": "#ffffff",
|
||||
"borderColor": "#14213e",
|
||||
@@ -178,8 +178,8 @@ exports[`Server Icon Server Icon Component should match snapshot with unreads 1`
|
||||
"position": "absolute",
|
||||
"textAlign": "center",
|
||||
"top": -5,
|
||||
"transform": Array [
|
||||
Object {
|
||||
"transform": [
|
||||
{
|
||||
"scale": 1,
|
||||
},
|
||||
],
|
||||
|
||||
@@ -5,6 +5,7 @@ import React, {useMemo} from 'react';
|
||||
import {StyleProp, Text, TextStyle, useWindowDimensions, View, ViewStyle} from 'react-native';
|
||||
import Animated, {AnimatedStyleProp} from 'react-native-reanimated';
|
||||
|
||||
import {useIsTablet} from '@app/hooks/device';
|
||||
import CompassIcon from '@components/compass_icon';
|
||||
import {useTheme} from '@context/theme';
|
||||
import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme';
|
||||
@@ -20,6 +21,9 @@ type ToastProps = {
|
||||
}
|
||||
|
||||
export const TOAST_HEIGHT = 56;
|
||||
const TOAST_MARGIN = 40;
|
||||
const WIDTH_TABLET = 484;
|
||||
const WIDTH_MOBILE = 400;
|
||||
|
||||
const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => ({
|
||||
center: {
|
||||
@@ -53,10 +57,10 @@ const Toast = ({animatedStyle, children, style, iconName, message, textStyle}: T
|
||||
const theme = useTheme();
|
||||
const styles = getStyleSheet(theme);
|
||||
const dim = useWindowDimensions();
|
||||
const isTablet = useIsTablet();
|
||||
const containerStyle = useMemo(() => {
|
||||
const totalMargin = 40;
|
||||
const width = Math.min(dim.height, dim.width, 400) - totalMargin;
|
||||
|
||||
const toast_width = isTablet ? WIDTH_TABLET : WIDTH_MOBILE;
|
||||
const width = Math.min(dim.height, dim.width, toast_width) - TOAST_MARGIN;
|
||||
return [styles.container, {width}, style];
|
||||
}, [dim, styles.container, style]);
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ exports[`UserStatus should match snapshot, away status 1`] = `
|
||||
<Icon
|
||||
name="clock"
|
||||
style={
|
||||
Object {
|
||||
{
|
||||
"color": "#ffbc1f",
|
||||
"fontSize": 32,
|
||||
}
|
||||
@@ -17,7 +17,7 @@ exports[`UserStatus should match snapshot, dnd status 1`] = `
|
||||
<Icon
|
||||
name="minus-circle"
|
||||
style={
|
||||
Object {
|
||||
{
|
||||
"color": "#d24b4e",
|
||||
"fontSize": 32,
|
||||
}
|
||||
@@ -30,7 +30,7 @@ exports[`UserStatus should match snapshot, online status 1`] = `
|
||||
<Icon
|
||||
name="check-circle"
|
||||
style={
|
||||
Object {
|
||||
{
|
||||
"color": "#3db887",
|
||||
"fontSize": 32,
|
||||
}
|
||||
@@ -43,7 +43,7 @@ exports[`UserStatus should match snapshot, should default to offline status 1`]
|
||||
<Icon
|
||||
name="circle-outline"
|
||||
style={
|
||||
Object {
|
||||
{
|
||||
"color": "rgba(184,184,184,0.64)",
|
||||
"fontSize": 32,
|
||||
}
|
||||
|
||||
@@ -149,6 +149,12 @@ export const SCREENS_WITH_TRANSPARENT_BACKGROUND = new Set<string>([
|
||||
USER_PROFILE,
|
||||
]);
|
||||
|
||||
export const OVERLAY_SCREENS = new Set<string>([
|
||||
IN_APP_NOTIFICATION,
|
||||
GALLERY,
|
||||
SNACK_BAR,
|
||||
]);
|
||||
|
||||
export const NOT_READY = [
|
||||
CHANNEL_ADD_PEOPLE,
|
||||
CHANNEL_MENTION,
|
||||
|
||||
@@ -19,8 +19,8 @@ type State = {
|
||||
serverDisplayName: string;
|
||||
};
|
||||
|
||||
export function withServerDatabase<T>(Component: ComponentType<T>): ComponentType<T> {
|
||||
return function ServerDatabaseComponent(props) {
|
||||
export function withServerDatabase<T extends JSX.IntrinsicAttributes>(Component: ComponentType<T>): ComponentType<T> {
|
||||
return function ServerDatabaseComponent(props: T) {
|
||||
const [state, setState] = useState<State | undefined>();
|
||||
|
||||
const observer = (servers: ServersModel[]) => {
|
||||
|
||||
@@ -113,7 +113,6 @@ class PushNotifications {
|
||||
const screen = Screens.IN_APP_NOTIFICATION;
|
||||
const passProps = {
|
||||
notification,
|
||||
overlay: true,
|
||||
serverName,
|
||||
serverUrl,
|
||||
};
|
||||
|
||||
@@ -2,14 +2,19 @@
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {Alert, DeviceEventEmitter, Linking} from 'react-native';
|
||||
import RNLocalize from 'react-native-localize';
|
||||
import semver from 'semver';
|
||||
|
||||
import {autoUpdateTimezone} from '@actions/remote/user';
|
||||
import LocalConfig from '@assets/config.json';
|
||||
import {Events, Sso} from '@constants';
|
||||
import DatabaseManager from '@database/manager';
|
||||
import {DEFAULT_LOCALE, getTranslations, t} from '@i18n';
|
||||
import {getServerCredentials} from '@init/credentials';
|
||||
import {getLaunchPropsFromDeepLink, relaunchApp} from '@init/launch';
|
||||
import * as analytics from '@managers/analytics';
|
||||
import {queryAllServers} from '@queries/app/servers';
|
||||
import {logError} from '@utils/log';
|
||||
|
||||
import type {jsAndNativeErrorHandler} from '@typings/global/error_handling';
|
||||
|
||||
@@ -21,6 +26,19 @@ class GlobalEventHandler {
|
||||
constructor() {
|
||||
DeviceEventEmitter.addListener(Events.SERVER_VERSION_CHANGED, this.onServerVersionChanged);
|
||||
DeviceEventEmitter.addListener(Events.CONFIG_CHANGED, this.onServerConfigChanged);
|
||||
RNLocalize.addEventListener('change', async () => {
|
||||
try {
|
||||
const {database} = DatabaseManager.getAppDatabaseAndOperator();
|
||||
const servers = await queryAllServers(database);
|
||||
for (const server of servers) {
|
||||
if (server.url && server.lastActiveAt > 0) {
|
||||
autoUpdateTimezone(server.url);
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
logError('Localize change', e);
|
||||
}
|
||||
});
|
||||
|
||||
Linking.addEventListener('url', this.onDeepLink);
|
||||
}
|
||||
|
||||
@@ -231,15 +231,15 @@ export async function newConnection(serverUrl: string, channelID: string, closeC
|
||||
|
||||
ws.on('message', ({data}: { data: string }) => {
|
||||
const msg = JSON.parse(data);
|
||||
if (msg.type === 'answer' || msg.type === 'offer') {
|
||||
if (msg.type === 'answer' || msg.type === 'candidate' || msg.type === 'offer') {
|
||||
peer?.signal(data);
|
||||
}
|
||||
});
|
||||
|
||||
const waitForPeerConnection = () => {
|
||||
const waitForReadyImpl = (callback: () => void, fail: () => void, timeout: number) => {
|
||||
const waitForReadyImpl = (callback: () => void, fail: (reason: string) => void, timeout: number) => {
|
||||
if (timeout <= 0) {
|
||||
fail();
|
||||
fail('timed out waiting for peer connection');
|
||||
return;
|
||||
}
|
||||
setTimeout(() => {
|
||||
|
||||
@@ -131,6 +131,12 @@ export const userLeftCall = (serverUrl: string, channelId: string, userId: strin
|
||||
return;
|
||||
}
|
||||
|
||||
// Was the user me?
|
||||
if (userId === callsState.myUserId) {
|
||||
myselfLeftCall();
|
||||
return;
|
||||
}
|
||||
|
||||
const nextCurrentCall = {
|
||||
...currentCall,
|
||||
participants: {...currentCall.participants},
|
||||
|
||||
@@ -251,7 +251,7 @@ export const getDefaultChannelForTeam = async (database: Database, teamId: strin
|
||||
const roles = await queryRoles(database).fetch();
|
||||
|
||||
if (roles.length) {
|
||||
canIJoinPublicChannelsInTeam = hasPermission(roles, Permissions.JOIN_PUBLIC_CHANNELS, true);
|
||||
canIJoinPublicChannelsInTeam = hasPermission(roles, Permissions.JOIN_PUBLIC_CHANNELS);
|
||||
}
|
||||
|
||||
const myChannels = await database.get<ChannelModel>(CHANNEL).query(
|
||||
@@ -605,3 +605,61 @@ export const observeChannelsByLastPostAt = (database: Database, myChannels: MyCh
|
||||
ORDER BY CASE mc.last_post_at WHEN 0 THEN c.create_at ELSE mc.last_post_at END DESC`),
|
||||
).observe();
|
||||
};
|
||||
|
||||
export const queryChannelsForAutocomplete = (database: Database, matchTerm: string, isSearch: boolean, teamId: string) => {
|
||||
const likeTerm = `%${Q.sanitizeLikeString(matchTerm)}%`;
|
||||
const clauses: Q.Clause[] = [];
|
||||
if (isSearch) {
|
||||
clauses.push(
|
||||
Q.experimentalJoinTables([CHANNEL_MEMBERSHIP]),
|
||||
Q.experimentalNestedJoin(CHANNEL_MEMBERSHIP, USER),
|
||||
);
|
||||
}
|
||||
const orConditions: Q.Condition[] = [
|
||||
Q.where('display_name', Q.like(matchTerm)),
|
||||
Q.where('name', Q.like(likeTerm)),
|
||||
];
|
||||
|
||||
if (isSearch) {
|
||||
orConditions.push(
|
||||
Q.and(
|
||||
Q.where('type', Q.oneOf([General.DM_CHANNEL, General.GM_CHANNEL])),
|
||||
Q.on(CHANNEL_MEMBERSHIP, Q.on(USER,
|
||||
Q.or(
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore: condition type error
|
||||
Q.unsafeSqlExpr(`first_name || ' ' || last_name LIKE '${likeTerm}'`),
|
||||
Q.where('nickname', Q.like(likeTerm)),
|
||||
Q.where('email', Q.like(likeTerm)),
|
||||
Q.where('username', Q.like(likeTerm)),
|
||||
),
|
||||
)),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
const teamsToSearch = [teamId];
|
||||
if (isSearch) {
|
||||
teamsToSearch.push('');
|
||||
}
|
||||
|
||||
const andConditions: Q.Condition[] = [
|
||||
Q.where('team_id', Q.oneOf(teamsToSearch)),
|
||||
];
|
||||
if (!isSearch) {
|
||||
andConditions.push(
|
||||
Q.where('type', Q.oneOf([General.OPEN_CHANNEL, General.PRIVATE_CHANNEL])),
|
||||
Q.where('delete_at', 0),
|
||||
);
|
||||
}
|
||||
|
||||
clauses.push(
|
||||
...andConditions,
|
||||
Q.or(...orConditions),
|
||||
Q.sortBy('display_name', Q.asc),
|
||||
Q.sortBy('name', Q.asc),
|
||||
Q.take(25),
|
||||
);
|
||||
|
||||
return database.get<ChannelModel>(CHANNEL).query(...clauses);
|
||||
};
|
||||
|
||||
@@ -53,7 +53,7 @@ export function observePermissionForChannel(database: Database, channel: Channel
|
||||
rolesArray.push(...mt.roles.split(' '));
|
||||
}
|
||||
return queryRolesByNames(database, rolesArray).observeWithColumns(['permissions']).pipe(
|
||||
switchMap((r) => of$(hasPermission(r, permission, defaultValue))),
|
||||
switchMap((r) => of$(hasPermission(r, permission))),
|
||||
);
|
||||
}),
|
||||
distinctUntilChanged(),
|
||||
@@ -74,7 +74,7 @@ export function observePermissionForTeam(database: Database, team: TeamModel | u
|
||||
}
|
||||
|
||||
return queryRolesByNames(database, rolesArray).observeWithColumns(['permissions']).pipe(
|
||||
switchMap((roles) => of$(hasPermission(roles, permission, defaultValue))),
|
||||
switchMap((roles) => of$(hasPermission(roles, permission))),
|
||||
);
|
||||
}),
|
||||
distinctUntilChanged(),
|
||||
|
||||
@@ -77,7 +77,6 @@ type Props = {
|
||||
closeButton: ImageResource;
|
||||
|
||||
// Properties not changing during the lifetime of the screen)
|
||||
currentUserId: string;
|
||||
currentTeamId: string;
|
||||
|
||||
// Calculated Props
|
||||
@@ -102,7 +101,6 @@ export default function BrowseChannels(props: Props) {
|
||||
canCreateChannels,
|
||||
sharedChannelsEnabled,
|
||||
closeButton,
|
||||
currentUserId,
|
||||
currentTeamId,
|
||||
canShowArchivedChannels,
|
||||
typeOfChannels,
|
||||
@@ -137,7 +135,7 @@ export default function BrowseChannels(props: Props) {
|
||||
setHeaderButtons(false);
|
||||
setAdding(true);
|
||||
|
||||
const result = await joinChannel(serverUrl, currentUserId, currentTeamId, channel.id, '', false);
|
||||
const result = await joinChannel(serverUrl, currentTeamId, channel.id, '', false);
|
||||
|
||||
if (result.error) {
|
||||
alertErrorWithFallback(
|
||||
|
||||
@@ -39,11 +39,10 @@ const enhanced = withObservables([], ({database}: WithDatabaseArgs) => {
|
||||
switchMap((values) => queryRolesByNames(database, values).observeWithColumns(['permissions'])),
|
||||
);
|
||||
|
||||
const canCreateChannels = roles.pipe(switchMap((r) => of$(hasPermission(r, Permissions.CREATE_PUBLIC_CHANNEL, false))));
|
||||
const canCreateChannels = roles.pipe(switchMap((r) => of$(hasPermission(r, Permissions.CREATE_PUBLIC_CHANNEL))));
|
||||
|
||||
return {
|
||||
canCreateChannels,
|
||||
currentUserId,
|
||||
currentTeamId,
|
||||
joinedChannels,
|
||||
sharedChannelsEnabled,
|
||||
|
||||
@@ -218,7 +218,7 @@ export default function SearchHandler(props: Props) {
|
||||
clearTimeout(searchTimeout.current);
|
||||
}
|
||||
searchTimeout.current = setTimeout(async () => {
|
||||
const results = await searchChannels(serverUrl, text);
|
||||
const results = await searchChannels(serverUrl, text, currentTeamId);
|
||||
if (results.channels) {
|
||||
setSearchResults(results.channels);
|
||||
}
|
||||
|
||||
@@ -87,11 +87,11 @@ const PrivateChannelIllustration = ({theme}: Props) => (
|
||||
/>
|
||||
<Mask
|
||||
id='a'
|
||||
|
||||
// @ts-expect-error style not intrinsic
|
||||
style={{
|
||||
maskType: 'alpha',
|
||||
}}
|
||||
|
||||
// @ts-expect-error string instead of enum
|
||||
maskUnits='userSpaceOnUse'
|
||||
x={51}
|
||||
y={63}
|
||||
@@ -132,11 +132,11 @@ const PrivateChannelIllustration = ({theme}: Props) => (
|
||||
/>
|
||||
<Mask
|
||||
id='b'
|
||||
|
||||
// @ts-expect-error style not intrinsic
|
||||
style={{
|
||||
maskType: 'alpha',
|
||||
}}
|
||||
|
||||
// @ts-expect-error string instead of enum
|
||||
maskUnits='userSpaceOnUse'
|
||||
x={41}
|
||||
y={35}
|
||||
|
||||
@@ -103,11 +103,11 @@ const PublicChannelIllustration = ({theme}: Props) => (
|
||||
/>
|
||||
<Mask
|
||||
id='a'
|
||||
|
||||
// @ts-expect-error style not intrinsic
|
||||
style={{
|
||||
maskType: 'alpha',
|
||||
}}
|
||||
|
||||
// @ts-expect-error string instead of enum
|
||||
maskUnits='userSpaceOnUse'
|
||||
x={77}
|
||||
y={31}
|
||||
|
||||
@@ -47,11 +47,11 @@ const TownSquareIllustration = ({theme}: Props) => (
|
||||
/>
|
||||
<Mask
|
||||
id='a'
|
||||
|
||||
// @ts-expect-error style not intrinsic
|
||||
style={{
|
||||
maskType: 'alpha',
|
||||
}}
|
||||
|
||||
// @ts-expect-error string instead of enum
|
||||
maskUnits='userSpaceOnUse'
|
||||
x={61}
|
||||
y={65}
|
||||
@@ -117,11 +117,11 @@ const TownSquareIllustration = ({theme}: Props) => (
|
||||
/>
|
||||
<Mask
|
||||
id='b'
|
||||
|
||||
// @ts-expect-error style not intrinsic
|
||||
style={{
|
||||
maskType: 'alpha',
|
||||
}}
|
||||
|
||||
// @ts-expect-error string instead of enum
|
||||
maskUnits='userSpaceOnUse'
|
||||
x={40}
|
||||
y={33}
|
||||
@@ -159,11 +159,11 @@ const TownSquareIllustration = ({theme}: Props) => (
|
||||
/>
|
||||
<Mask
|
||||
id='c'
|
||||
|
||||
// @ts-expect-error style not intrinsic
|
||||
style={{
|
||||
maskType: 'alpha',
|
||||
}}
|
||||
|
||||
// @ts-expect-error string instead of enum
|
||||
maskUnits='userSpaceOnUse'
|
||||
x={69}
|
||||
y={23}
|
||||
|
||||
@@ -74,12 +74,12 @@ const PublicOrPrivateChannel = ({channel, creator, roles, theme}: Props) => {
|
||||
|
||||
const canManagePeople = useMemo(() => {
|
||||
const permission = channel.type === General.OPEN_CHANNEL ? Permissions.MANAGE_PUBLIC_CHANNEL_MEMBERS : Permissions.MANAGE_PRIVATE_CHANNEL_MEMBERS;
|
||||
return hasPermission(roles, permission, false);
|
||||
return hasPermission(roles, permission);
|
||||
}, [channel.type, roles]);
|
||||
|
||||
const canSetHeader = useMemo(() => {
|
||||
const permission = channel.type === General.OPEN_CHANNEL ? Permissions.MANAGE_PUBLIC_CHANNEL_PROPERTIES : Permissions.MANAGE_PRIVATE_CHANNEL_PROPERTIES;
|
||||
return hasPermission(roles, permission, false);
|
||||
return hasPermission(roles, permission);
|
||||
}, [channel.type, roles]);
|
||||
|
||||
const createdBy = useMemo(() => {
|
||||
|
||||
@@ -60,7 +60,7 @@ const TownSquare = ({channelId, displayName, roles, theme}: Props) => {
|
||||
/>
|
||||
<IntroOptions
|
||||
channelId={channelId}
|
||||
header={hasPermission(roles, Permissions.MANAGE_PUBLIC_CHANNEL_PROPERTIES, false)}
|
||||
header={hasPermission(roles, Permissions.MANAGE_PUBLIC_CHANNEL_PROPERTIES)}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
|
||||
@@ -31,6 +31,7 @@ type ChannelProps = {
|
||||
channelId: string;
|
||||
channelType: ChannelType;
|
||||
customStatus?: UserCustomStatus;
|
||||
isCustomStatusEnabled: boolean;
|
||||
isCustomStatusExpired: boolean;
|
||||
componentId?: string;
|
||||
displayName: string;
|
||||
@@ -66,7 +67,7 @@ const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => ({
|
||||
|
||||
const ChannelHeader = ({
|
||||
channelId, channelType, componentId, customStatus, displayName,
|
||||
isCustomStatusExpired, isOwnDirectMessage, memberCount,
|
||||
isCustomStatusEnabled, isCustomStatusExpired, isOwnDirectMessage, memberCount,
|
||||
searchTerm, teamId, callsEnabledInChannel, callsFeatureRestricted,
|
||||
}: ChannelProps) => {
|
||||
const intl = useIntl();
|
||||
@@ -194,7 +195,7 @@ const ChannelHeader = ({
|
||||
} else if (customStatus && customStatus.text) {
|
||||
return (
|
||||
<View style={styles.customStatusContainer}>
|
||||
{Boolean(customStatus.emoji) &&
|
||||
{isCustomStatusEnabled && Boolean(customStatus.emoji) &&
|
||||
<CustomStatusEmoji
|
||||
customStatus={customStatus}
|
||||
emojiSize={13}
|
||||
|
||||
@@ -9,7 +9,7 @@ import {combineLatestWith, switchMap} from 'rxjs/operators';
|
||||
import {observeIsCallsFeatureRestricted} from '@calls/observers';
|
||||
import {General} from '@constants';
|
||||
import {observeChannel, observeChannelInfo} from '@queries/servers/channel';
|
||||
import {observeCurrentTeamId, observeCurrentUserId} from '@queries/servers/system';
|
||||
import {observeConfigBooleanValue, observeCurrentTeamId, observeCurrentUserId} from '@queries/servers/system';
|
||||
import {observeUser} from '@queries/servers/user';
|
||||
import {
|
||||
getUserCustomStatus,
|
||||
@@ -60,6 +60,8 @@ const enhanced = withObservables(['channelId'], ({serverUrl, channelId, database
|
||||
switchMap((dm) => of$(checkCustomStatusIsExpired(dm))),
|
||||
);
|
||||
|
||||
const isCustomStatusEnabled = observeConfigBooleanValue(database, 'EnableCustomUserStatuses');
|
||||
|
||||
const searchTerm = channel.pipe(
|
||||
combineLatestWith(dmUser),
|
||||
switchMap(([c, dm]) => {
|
||||
@@ -82,6 +84,7 @@ const enhanced = withObservables(['channelId'], ({serverUrl, channelId, database
|
||||
channelType,
|
||||
customStatus,
|
||||
displayName,
|
||||
isCustomStatusEnabled,
|
||||
isCustomStatusExpired,
|
||||
isOwnDirectMessage,
|
||||
memberCount,
|
||||
|
||||
@@ -22,6 +22,7 @@ type Props = {
|
||||
createdBy: string;
|
||||
customStatus?: UserCustomStatus;
|
||||
header?: string;
|
||||
isCustomStatusEnabled: boolean;
|
||||
}
|
||||
|
||||
const headerMetadata = {header: {width: 1, height: 1}};
|
||||
@@ -65,7 +66,7 @@ const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => ({
|
||||
},
|
||||
}));
|
||||
|
||||
const Extra = ({channelId, createdAt, createdBy, customStatus, header}: Props) => {
|
||||
const Extra = ({channelId, createdAt, createdBy, customStatus, header, isCustomStatusEnabled}: Props) => {
|
||||
const theme = useTheme();
|
||||
const styles = getStyleSheet(theme);
|
||||
const blockStyles = getMarkdownBlockStyles(theme);
|
||||
@@ -82,7 +83,7 @@ const Extra = ({channelId, createdAt, createdBy, customStatus, header}: Props) =
|
||||
|
||||
return (
|
||||
<View style={styles.container}>
|
||||
{Boolean(customStatus) &&
|
||||
{isCustomStatusEnabled && Boolean(customStatus) &&
|
||||
<View style={styles.item}>
|
||||
<FormattedText
|
||||
id='channel_info.custom_status'
|
||||
|
||||
@@ -8,6 +8,7 @@ import {combineLatestWith, switchMap} from 'rxjs/operators';
|
||||
|
||||
import {General} from '@constants';
|
||||
import {observeChannel, observeChannelInfo} from '@queries/servers/channel';
|
||||
import {observeConfigBooleanValue} from '@queries/servers/system';
|
||||
import {observeCurrentUser, observeTeammateNameDisplay, observeUser} from '@queries/servers/user';
|
||||
import {displayUsername, getUserCustomStatus, getUserIdFromChannelName, isCustomStatusExpired as checkCustomStatusIsExpired} from '@utils/user';
|
||||
|
||||
@@ -48,11 +49,14 @@ const enhanced = withObservables(['channelId'], ({channelId, database}: Props) =
|
||||
switchMap((dm) => of$(checkCustomStatusIsExpired(dm) ? undefined : getUserCustomStatus(dm))),
|
||||
);
|
||||
|
||||
const isCustomStatusEnabled = observeConfigBooleanValue(database, 'EnableCustomUserStatuses');
|
||||
|
||||
return {
|
||||
createdAt,
|
||||
createdBy,
|
||||
customStatus,
|
||||
header,
|
||||
isCustomStatusEnabled,
|
||||
};
|
||||
});
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import Clipboard from '@react-native-community/clipboard';
|
||||
import Clipboard from '@react-native-clipboard/clipboard';
|
||||
import React, {useEffect, useRef, useState} from 'react';
|
||||
import {useIntl} from 'react-intl';
|
||||
import {StyleSheet} from 'react-native';
|
||||
@@ -16,6 +16,7 @@ import {useServerUrl} from '@context/server';
|
||||
import type {GalleryAction, GalleryItemType} from '@typings/screens/gallery';
|
||||
|
||||
type Props = {
|
||||
galleryView?: boolean;
|
||||
item: GalleryItemType;
|
||||
setAction: (action: GalleryAction) => void;
|
||||
}
|
||||
@@ -29,7 +30,7 @@ const styles = StyleSheet.create({
|
||||
},
|
||||
});
|
||||
|
||||
const CopyPublicLink = ({item, setAction}: Props) => {
|
||||
const CopyPublicLink = ({item, galleryView = true, setAction}: Props) => {
|
||||
const {formatMessage} = useIntl();
|
||||
const serverUrl = useServerUrl();
|
||||
const insets = useSafeAreaInsets();
|
||||
@@ -37,11 +38,14 @@ const CopyPublicLink = ({item, setAction}: Props) => {
|
||||
const [error, setError] = useState('');
|
||||
const mounted = useRef(false);
|
||||
|
||||
const animatedStyle = useAnimatedStyle(() => ({
|
||||
position: 'absolute',
|
||||
bottom: GALLERY_FOOTER_HEIGHT + 8 + insets.bottom,
|
||||
opacity: withTiming(showToast ? 1 : 0, {duration: 300}),
|
||||
}));
|
||||
const animatedStyle = useAnimatedStyle(() => {
|
||||
const marginBottom = galleryView ? GALLERY_FOOTER_HEIGHT + 8 : 0;
|
||||
return {
|
||||
position: 'absolute',
|
||||
bottom: insets.bottom + marginBottom,
|
||||
opacity: withTiming(showToast ? 1 : 0, {duration: 300}),
|
||||
};
|
||||
});
|
||||
|
||||
const copyLink = async () => {
|
||||
try {
|
||||
@@ -87,7 +91,7 @@ const CopyPublicLink = ({item, setAction}: Props) => {
|
||||
animatedStyle={animatedStyle}
|
||||
style={error ? styles.error : styles.toast}
|
||||
message={error || formatMessage({id: 'public_link_copied', defaultMessage: 'Link copied to clipboard'})}
|
||||
iconName='check'
|
||||
iconName='link-variant'
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import CameraRoll from '@react-native-community/cameraroll';
|
||||
import {CameraRoll} from '@react-native-camera-roll/camera-roll';
|
||||
import React, {useEffect, useRef, useState} from 'react';
|
||||
import {useIntl} from 'react-intl';
|
||||
import {NativeModules, Platform, StyleSheet, Text, View} from 'react-native';
|
||||
@@ -29,6 +29,7 @@ import type {GalleryAction, GalleryItemType} from '@typings/screens/gallery';
|
||||
|
||||
type Props = {
|
||||
action: GalleryAction;
|
||||
galleryView?: boolean;
|
||||
item: GalleryItemType;
|
||||
setAction: (action: GalleryAction) => void;
|
||||
onDownloadSuccess?: (path: string) => void;
|
||||
@@ -65,7 +66,7 @@ const styles = StyleSheet.create({
|
||||
},
|
||||
});
|
||||
|
||||
const DownloadWithAction = ({action, item, onDownloadSuccess, setAction}: Props) => {
|
||||
const DownloadWithAction = ({action, item, onDownloadSuccess, setAction, galleryView = true}: Props) => {
|
||||
const intl = useIntl();
|
||||
const serverUrl = useServerUrl();
|
||||
const insets = useSafeAreaInsets();
|
||||
@@ -111,11 +112,14 @@ const DownloadWithAction = ({action, item, onDownloadSuccess, setAction}: Props)
|
||||
}
|
||||
}
|
||||
|
||||
const animatedStyle = useAnimatedStyle(() => ({
|
||||
position: 'absolute',
|
||||
bottom: GALLERY_FOOTER_HEIGHT + 8 + insets.bottom,
|
||||
opacity: withTiming(showToast ? 1 : 0, {duration: 300}),
|
||||
}));
|
||||
const animatedStyle = useAnimatedStyle(() => {
|
||||
const marginBottom = galleryView ? GALLERY_FOOTER_HEIGHT + 8 : 0;
|
||||
return {
|
||||
position: 'absolute',
|
||||
bottom: insets.bottom + marginBottom,
|
||||
opacity: withTiming(showToast ? 1 : 0, {duration: 300}),
|
||||
};
|
||||
});
|
||||
|
||||
const cancel = async () => {
|
||||
try {
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import React, {useCallback, useEffect, useState} from 'react';
|
||||
import React, {useCallback, useEffect, useMemo, useState} from 'react';
|
||||
import {DeviceEventEmitter, StyleProp, StyleSheet, View, ViewStyle} from 'react-native';
|
||||
import Animated from 'react-native-reanimated';
|
||||
import {SafeAreaView, Edge} from 'react-native-safe-area-context';
|
||||
import {SafeAreaView, Edge, useSafeAreaInsets} from 'react-native-safe-area-context';
|
||||
|
||||
import {Events} from '@constants';
|
||||
import {GALLERY_FOOTER_HEIGHT} from '@constants/gallery';
|
||||
@@ -39,11 +39,11 @@ type Props = {
|
||||
}
|
||||
|
||||
const AnimatedSafeAreaView = Animated.createAnimatedComponent(SafeAreaView);
|
||||
const edges: Edge[] = ['left', 'right', 'bottom'];
|
||||
const edges: Edge[] = ['left', 'right'];
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
alignItems: 'center',
|
||||
backgroundColor: changeOpacity('#000', 0.6),
|
||||
backgroundColor: '#000',
|
||||
borderTopColor: changeOpacity('#fff', 0.4),
|
||||
borderTopWidth: 1,
|
||||
flex: 1,
|
||||
@@ -71,6 +71,9 @@ const Footer = ({
|
||||
}: Props) => {
|
||||
const showActions = !hideActions && Boolean(item.id) && !item.id?.startsWith('uid');
|
||||
const [action, setAction] = useState<GalleryAction>('none');
|
||||
const {bottom} = useSafeAreaInsets();
|
||||
|
||||
const bottomStyle = useMemo(() => ({height: bottom, backgroundColor: '#000'}), [bottom]);
|
||||
|
||||
let overrideIconUrl;
|
||||
if (enablePostIconOverride && post?.props?.use_user_icon !== 'true' && post?.props?.override_icon_url) {
|
||||
@@ -152,6 +155,7 @@ const Footer = ({
|
||||
/>
|
||||
}
|
||||
</View>
|
||||
<View style={bottomStyle}/>
|
||||
</AnimatedSafeAreaView>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -5,7 +5,7 @@ import React, {useMemo} from 'react';
|
||||
import {StyleProp, StyleSheet, useWindowDimensions, View, ViewStyle} from 'react-native';
|
||||
import {TouchableOpacity} from 'react-native-gesture-handler';
|
||||
import Animated from 'react-native-reanimated';
|
||||
import {SafeAreaView, Edge} from 'react-native-safe-area-context';
|
||||
import {SafeAreaView, Edge, useSafeAreaInsets} from 'react-native-safe-area-context';
|
||||
|
||||
import CompassIcon from '@components/compass_icon';
|
||||
import FormattedText from '@components/formatted_text';
|
||||
@@ -23,7 +23,7 @@ type Props = {
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
alignItems: 'center',
|
||||
backgroundColor: changeOpacity('#000', 0.6),
|
||||
backgroundColor: '#000',
|
||||
borderBottomColor: changeOpacity('#fff', 0.4),
|
||||
borderBottomWidth: 1,
|
||||
flexDirection: 'row',
|
||||
@@ -39,12 +39,14 @@ const styles = StyleSheet.create({
|
||||
},
|
||||
});
|
||||
|
||||
const edges: Edge[] = ['left', 'right', 'top'];
|
||||
const edges: Edge[] = ['left', 'right'];
|
||||
const AnimatedSafeAreaView = Animated.createAnimatedComponent(SafeAreaView);
|
||||
|
||||
const Header = ({index, onClose, style, total}: Props) => {
|
||||
const {width} = useWindowDimensions();
|
||||
const height = useDefaultHeaderHeight();
|
||||
const {top} = useSafeAreaInsets();
|
||||
const topContainerStyle = useMemo(() => [{height: top, backgroundColor: '#000'}], [top]);
|
||||
const containerStyle = useMemo(() => [styles.container, {height}], [height]);
|
||||
const iconStyle = useMemo(() => [{width: height}, styles.icon], [height]);
|
||||
const titleStyle = useMemo(() => ({width: width - (height * 2)}), [height, width]);
|
||||
@@ -55,6 +57,7 @@ const Header = ({index, onClose, style, total}: Props) => {
|
||||
edges={edges}
|
||||
style={style}
|
||||
>
|
||||
<Animated.View style={topContainerStyle}/>
|
||||
<Animated.View style={containerStyle}>
|
||||
<TouchableOpacity
|
||||
onPress={onClose}
|
||||
|
||||
@@ -172,10 +172,7 @@ const VideoRenderer = ({height, index, initialIndex, item, isPageActive, onShoul
|
||||
}, [isPageActive.value, paused]);
|
||||
|
||||
return (
|
||||
<Animated.View
|
||||
onTouchStart={handleTouchStart}
|
||||
style={styles.video}
|
||||
>
|
||||
<>
|
||||
<AnimatedVideo
|
||||
ref={videoRef}
|
||||
source={source}
|
||||
@@ -189,17 +186,18 @@ const VideoRenderer = ({height, index, initialIndex, item, isPageActive, onShoul
|
||||
onFullscreenPlayerWillPresent={onFullscreenPlayerWillPresent}
|
||||
onReadyForDisplay={onReadyForDisplay}
|
||||
onEnd={onEnd}
|
||||
onTouchStart={handleTouchStart}
|
||||
/>
|
||||
{Platform.OS === 'android' && paused && videoReady &&
|
||||
<Animated.View style={styles.playContainer}>
|
||||
<CompassIcon
|
||||
color={changeOpacity('#fff', 0.8)}
|
||||
style={styles.play}
|
||||
name='play'
|
||||
onPress={onPlay}
|
||||
size={80}
|
||||
/>
|
||||
</Animated.View>
|
||||
<Animated.View style={styles.playContainer}>
|
||||
<CompassIcon
|
||||
color={changeOpacity('#fff', 0.8)}
|
||||
style={styles.play}
|
||||
name='play'
|
||||
onPress={onPlay}
|
||||
size={80}
|
||||
/>
|
||||
</Animated.View>
|
||||
}
|
||||
{downloading &&
|
||||
<DownloadWithAction
|
||||
@@ -209,7 +207,7 @@ const VideoRenderer = ({height, index, initialIndex, item, isPageActive, onShoul
|
||||
item={item}
|
||||
/>
|
||||
}
|
||||
</Animated.View>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {useIsFocused, useRoute} from '@react-navigation/native';
|
||||
import {useRoute} from '@react-navigation/native';
|
||||
import React, {useCallback, useState} from 'react';
|
||||
import {ScrollView, View} from 'react-native';
|
||||
import Animated, {useAnimatedStyle, withTiming} from 'react-native-reanimated';
|
||||
import {Edge, SafeAreaView, useSafeAreaInsets} from 'react-native-safe-area-context';
|
||||
|
||||
import FreezeScreen from '@components/freeze_screen';
|
||||
import {View as ViewConstants} from '@constants';
|
||||
import {useTheme} from '@context/theme';
|
||||
import {useIsTablet} from '@hooks/device';
|
||||
@@ -60,7 +59,6 @@ const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => {
|
||||
|
||||
const AccountScreen = ({currentUser, enableCustomUserStatuses, customStatusExpirySupported, showFullName}: AccountScreenProps) => {
|
||||
const theme = useTheme();
|
||||
const isFocused = useIsFocused();
|
||||
const [start, setStart] = useState(false);
|
||||
const route = useRoute();
|
||||
const insets = useSafeAreaInsets();
|
||||
@@ -96,46 +94,44 @@ const AccountScreen = ({currentUser, enableCustomUserStatuses, customStatusExpir
|
||||
const styles = getStyleSheet(theme);
|
||||
|
||||
return (
|
||||
<FreezeScreen freeze={!isFocused}>
|
||||
<SafeAreaView
|
||||
edges={edges}
|
||||
style={styles.container}
|
||||
testID='account.screen'
|
||||
<SafeAreaView
|
||||
edges={edges}
|
||||
style={styles.container}
|
||||
testID='account.screen'
|
||||
>
|
||||
<View style={[{height: insets.top, flexDirection: 'row'}]}>
|
||||
<View style={[styles.container, tabletSidebarStyle]}/>
|
||||
{isTablet && <View style={styles.tabletContainer}/>}
|
||||
</View>
|
||||
<Animated.View
|
||||
onLayout={onLayout}
|
||||
style={[styles.flexRow, animated]}
|
||||
>
|
||||
<View style={[{height: insets.top, flexDirection: 'row'}]}>
|
||||
<View style={[styles.container, tabletSidebarStyle]}/>
|
||||
{isTablet && <View style={styles.tabletContainer}/>}
|
||||
</View>
|
||||
<Animated.View
|
||||
onLayout={onLayout}
|
||||
style={[styles.flexRow, animated]}
|
||||
<ScrollView
|
||||
alwaysBounceVertical={false}
|
||||
style={tabletSidebarStyle}
|
||||
contentContainerStyle={styles.totalHeight}
|
||||
>
|
||||
<ScrollView
|
||||
alwaysBounceVertical={false}
|
||||
style={tabletSidebarStyle}
|
||||
contentContainerStyle={styles.totalHeight}
|
||||
>
|
||||
<AccountUserInfo
|
||||
user={currentUser}
|
||||
showFullName={showFullName}
|
||||
theme={theme}
|
||||
/>
|
||||
<AccountOptions
|
||||
enableCustomUserStatuses={enableCustomUserStatuses}
|
||||
isCustomStatusExpirySupported={customStatusExpirySupported}
|
||||
isTablet={isTablet}
|
||||
user={currentUser}
|
||||
theme={theme}
|
||||
/>
|
||||
</ScrollView>
|
||||
{isTablet &&
|
||||
<View style={[styles.tabletContainer, styles.tabletDivider]}>
|
||||
<AccountTabletView/>
|
||||
</View>
|
||||
}
|
||||
</Animated.View>
|
||||
</SafeAreaView>
|
||||
</FreezeScreen>
|
||||
<AccountUserInfo
|
||||
user={currentUser}
|
||||
showFullName={showFullName}
|
||||
theme={theme}
|
||||
/>
|
||||
<AccountOptions
|
||||
enableCustomUserStatuses={enableCustomUserStatuses}
|
||||
isCustomStatusExpirySupported={customStatusExpirySupported}
|
||||
isTablet={isTablet}
|
||||
user={currentUser}
|
||||
theme={theme}
|
||||
/>
|
||||
</ScrollView>
|
||||
{isTablet &&
|
||||
<View style={[styles.tabletContainer, styles.tabletDivider]}>
|
||||
<AccountTabletView/>
|
||||
</View>
|
||||
}
|
||||
</Animated.View>
|
||||
</SafeAreaView>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -3,15 +3,15 @@
|
||||
exports[`components/categories_list should render channels error 1`] = `
|
||||
<View
|
||||
animatedStyle={
|
||||
Object {
|
||||
"value": Object {
|
||||
{
|
||||
"value": {
|
||||
"maxWidth": "100%",
|
||||
},
|
||||
}
|
||||
}
|
||||
collapsable={false}
|
||||
style={
|
||||
Object {
|
||||
{
|
||||
"backgroundColor": "#1e325c",
|
||||
"flex": 1,
|
||||
"maxWidth": "100%",
|
||||
@@ -23,22 +23,22 @@ exports[`components/categories_list should render channels error 1`] = `
|
||||
>
|
||||
<View
|
||||
animatedStyle={
|
||||
Object {
|
||||
"value": Object {
|
||||
{
|
||||
"value": {
|
||||
"marginLeft": 0,
|
||||
},
|
||||
}
|
||||
}
|
||||
collapsable={false}
|
||||
style={
|
||||
Object {
|
||||
{
|
||||
"marginLeft": 0,
|
||||
}
|
||||
}
|
||||
>
|
||||
<View
|
||||
style={
|
||||
Object {
|
||||
{
|
||||
"alignItems": "center",
|
||||
"flexDirection": "row",
|
||||
"justifyContent": "space-between",
|
||||
@@ -57,14 +57,14 @@ exports[`components/categories_list should render channels error 1`] = `
|
||||
onResponderTerminationRequest={[Function]}
|
||||
onStartShouldSetResponder={[Function]}
|
||||
style={
|
||||
Object {
|
||||
{
|
||||
"opacity": 1,
|
||||
}
|
||||
}
|
||||
>
|
||||
<View
|
||||
style={
|
||||
Object {
|
||||
{
|
||||
"alignItems": "center",
|
||||
"flexDirection": "row",
|
||||
"justifyContent": "space-between",
|
||||
@@ -73,7 +73,7 @@ exports[`components/categories_list should render channels error 1`] = `
|
||||
>
|
||||
<Text
|
||||
style={
|
||||
Object {
|
||||
{
|
||||
"color": "#ffffff",
|
||||
"fontFamily": "Metropolis-SemiBold",
|
||||
"fontSize": 28,
|
||||
@@ -87,7 +87,7 @@ exports[`components/categories_list should render channels error 1`] = `
|
||||
</Text>
|
||||
<View
|
||||
style={
|
||||
Object {
|
||||
{
|
||||
"marginLeft": 4,
|
||||
}
|
||||
}
|
||||
@@ -96,7 +96,7 @@ exports[`components/categories_list should render channels error 1`] = `
|
||||
<Icon
|
||||
name="chevron-down"
|
||||
style={
|
||||
Object {
|
||||
{
|
||||
"color": "rgba(255,255,255,0.8)",
|
||||
"fontSize": 24,
|
||||
}
|
||||
@@ -110,7 +110,7 @@ exports[`components/categories_list should render channels error 1`] = `
|
||||
collapsable={false}
|
||||
focusable={true}
|
||||
hitSlop={
|
||||
Object {
|
||||
{
|
||||
"bottom": 30,
|
||||
"left": 20,
|
||||
"right": 20,
|
||||
@@ -125,7 +125,7 @@ exports[`components/categories_list should render channels error 1`] = `
|
||||
onResponderTerminationRequest={[Function]}
|
||||
onStartShouldSetResponder={[Function]}
|
||||
style={
|
||||
Object {
|
||||
{
|
||||
"alignItems": "center",
|
||||
"backgroundColor": "rgba(255,255,255,0.08)",
|
||||
"borderRadius": 14,
|
||||
@@ -140,7 +140,7 @@ exports[`components/categories_list should render channels error 1`] = `
|
||||
<Icon
|
||||
name="plus"
|
||||
style={
|
||||
Object {
|
||||
{
|
||||
"color": "rgba(255,255,255,0.8)",
|
||||
"fontSize": 18,
|
||||
}
|
||||
@@ -150,7 +150,7 @@ exports[`components/categories_list should render channels error 1`] = `
|
||||
</View>
|
||||
<View
|
||||
style={
|
||||
Object {
|
||||
{
|
||||
"alignItems": "center",
|
||||
"flexDirection": "row",
|
||||
"paddingRight": 60,
|
||||
@@ -161,7 +161,7 @@ exports[`components/categories_list should render channels error 1`] = `
|
||||
ellipsizeMode="tail"
|
||||
numberOfLines={1}
|
||||
style={
|
||||
Object {
|
||||
{
|
||||
"color": "rgba(255,255,255,0.64)",
|
||||
"fontFamily": "OpenSans-SemiBold",
|
||||
"fontSize": 11,
|
||||
@@ -173,11 +173,11 @@ exports[`components/categories_list should render channels error 1`] = `
|
||||
/>
|
||||
<View
|
||||
animatedStyle={
|
||||
Object {
|
||||
"value": Object {
|
||||
{
|
||||
"value": {
|
||||
"opacity": 1,
|
||||
"transform": Array [
|
||||
Object {
|
||||
"transform": [
|
||||
{
|
||||
"rotateZ": "0deg",
|
||||
},
|
||||
],
|
||||
@@ -186,7 +186,7 @@ exports[`components/categories_list should render channels error 1`] = `
|
||||
}
|
||||
collapsable={false}
|
||||
style={
|
||||
Object {
|
||||
{
|
||||
"borderBottomColor": "rgba(255,255,255,0.16)",
|
||||
"borderLeftColor": "#ffffff",
|
||||
"borderRadius": 7,
|
||||
@@ -196,8 +196,8 @@ exports[`components/categories_list should render channels error 1`] = `
|
||||
"height": 14,
|
||||
"marginLeft": 5,
|
||||
"opacity": 1,
|
||||
"transform": Array [
|
||||
Object {
|
||||
"transform": [
|
||||
{
|
||||
"rotateZ": "0deg",
|
||||
},
|
||||
],
|
||||
@@ -209,7 +209,7 @@ exports[`components/categories_list should render channels error 1`] = `
|
||||
</View>
|
||||
<View
|
||||
style={
|
||||
Object {
|
||||
{
|
||||
"alignItems": "center",
|
||||
"flex": 1,
|
||||
"justifyContent": "center",
|
||||
@@ -219,7 +219,7 @@ exports[`components/categories_list should render channels error 1`] = `
|
||||
>
|
||||
<View
|
||||
style={
|
||||
Object {
|
||||
{
|
||||
"alignItems": "center",
|
||||
"backgroundColor": "rgba(255,255,255,0.08)",
|
||||
"borderRadius": 60,
|
||||
@@ -232,7 +232,7 @@ exports[`components/categories_list should render channels error 1`] = `
|
||||
<Icon
|
||||
name="alert-circle-outline"
|
||||
style={
|
||||
Object {
|
||||
{
|
||||
"color": "rgba(255,255,255,0.48)",
|
||||
"fontSize": 72,
|
||||
"lineHeight": 72,
|
||||
@@ -242,14 +242,14 @@ exports[`components/categories_list should render channels error 1`] = `
|
||||
</View>
|
||||
<Text
|
||||
style={
|
||||
Array [
|
||||
Object {
|
||||
[
|
||||
{
|
||||
"fontFamily": "Metropolis-SemiBold",
|
||||
"fontSize": 20,
|
||||
"fontWeight": "600",
|
||||
"lineHeight": 28,
|
||||
},
|
||||
Object {
|
||||
{
|
||||
"color": "#ffffff",
|
||||
"marginTop": 20,
|
||||
"textAlign": "center",
|
||||
@@ -261,14 +261,14 @@ exports[`components/categories_list should render channels error 1`] = `
|
||||
</Text>
|
||||
<Text
|
||||
style={
|
||||
Array [
|
||||
Object {
|
||||
[
|
||||
{
|
||||
"fontFamily": "OpenSans",
|
||||
"fontSize": 16,
|
||||
"fontWeight": "400",
|
||||
"lineHeight": 24,
|
||||
},
|
||||
Object {
|
||||
{
|
||||
"color": "#ffffff",
|
||||
"marginTop": 4,
|
||||
"textAlign": "center",
|
||||
@@ -290,7 +290,7 @@ exports[`components/categories_list should render channels error 1`] = `
|
||||
onResponderTerminationRequest={[Function]}
|
||||
onStartShouldSetResponder={[Function]}
|
||||
style={
|
||||
Object {
|
||||
{
|
||||
"alignItems": "center",
|
||||
"backgroundColor": "#ffffff",
|
||||
"borderRadius": 4,
|
||||
@@ -306,8 +306,8 @@ exports[`components/categories_list should render channels error 1`] = `
|
||||
>
|
||||
<Text
|
||||
style={
|
||||
Array [
|
||||
Object {
|
||||
[
|
||||
{
|
||||
"alignItems": "center",
|
||||
"fontFamily": "OpenSans-SemiBold",
|
||||
"fontWeight": "600",
|
||||
@@ -315,12 +315,12 @@ exports[`components/categories_list should render channels error 1`] = `
|
||||
"padding": 1,
|
||||
"textAlignVertical": "center",
|
||||
},
|
||||
Object {
|
||||
{
|
||||
"fontSize": 16,
|
||||
"lineHeight": 18,
|
||||
"marginTop": 1,
|
||||
},
|
||||
Object {
|
||||
{
|
||||
"color": "#1c58d9",
|
||||
},
|
||||
]
|
||||
@@ -336,15 +336,15 @@ exports[`components/categories_list should render channels error 1`] = `
|
||||
exports[`components/categories_list should render team error 1`] = `
|
||||
<View
|
||||
animatedStyle={
|
||||
Object {
|
||||
"value": Object {
|
||||
{
|
||||
"value": {
|
||||
"maxWidth": "100%",
|
||||
},
|
||||
}
|
||||
}
|
||||
collapsable={false}
|
||||
style={
|
||||
Object {
|
||||
{
|
||||
"backgroundColor": "#1e325c",
|
||||
"flex": 1,
|
||||
"maxWidth": "100%",
|
||||
@@ -356,22 +356,22 @@ exports[`components/categories_list should render team error 1`] = `
|
||||
>
|
||||
<View
|
||||
animatedStyle={
|
||||
Object {
|
||||
"value": Object {
|
||||
{
|
||||
"value": {
|
||||
"marginLeft": 0,
|
||||
},
|
||||
}
|
||||
}
|
||||
collapsable={false}
|
||||
style={
|
||||
Object {
|
||||
{
|
||||
"marginLeft": 0,
|
||||
}
|
||||
}
|
||||
>
|
||||
<View
|
||||
style={
|
||||
Object {
|
||||
{
|
||||
"alignItems": "center",
|
||||
"flexDirection": "row",
|
||||
"height": 40,
|
||||
@@ -381,14 +381,14 @@ exports[`components/categories_list should render team error 1`] = `
|
||||
>
|
||||
<View
|
||||
style={
|
||||
Array [
|
||||
Object {
|
||||
[
|
||||
{
|
||||
"alignItems": "center",
|
||||
"flexDirection": "row",
|
||||
"height": 40,
|
||||
"justifyContent": "space-between",
|
||||
},
|
||||
Object {
|
||||
{
|
||||
"flex": 1,
|
||||
},
|
||||
]
|
||||
@@ -398,7 +398,7 @@ exports[`components/categories_list should render team error 1`] = `
|
||||
ellipsizeMode="tail"
|
||||
numberOfLines={1}
|
||||
style={
|
||||
Object {
|
||||
{
|
||||
"color": "rgba(255,255,255,0.64)",
|
||||
"fontFamily": "OpenSans-SemiBold",
|
||||
"fontSize": 14,
|
||||
@@ -421,7 +421,7 @@ exports[`components/categories_list should render team error 1`] = `
|
||||
onResponderTerminationRequest={[Function]}
|
||||
onStartShouldSetResponder={[Function]}
|
||||
style={
|
||||
Object {
|
||||
{
|
||||
"opacity": 1,
|
||||
}
|
||||
}
|
||||
@@ -429,7 +429,7 @@ exports[`components/categories_list should render team error 1`] = `
|
||||
>
|
||||
<Text
|
||||
style={
|
||||
Object {
|
||||
{
|
||||
"color": "rgba(255,255,255,0.64)",
|
||||
"fontFamily": "OpenSans-SemiBold",
|
||||
"fontSize": 14,
|
||||
@@ -446,15 +446,15 @@ exports[`components/categories_list should render team error 1`] = `
|
||||
</View>
|
||||
<View
|
||||
style={
|
||||
Object {
|
||||
{
|
||||
"flexDirection": "row",
|
||||
}
|
||||
}
|
||||
>
|
||||
<View
|
||||
animatedStyle={
|
||||
Object {
|
||||
"value": Object {
|
||||
{
|
||||
"value": {
|
||||
"marginRight": 8,
|
||||
"opacity": 1,
|
||||
"width": 40,
|
||||
@@ -463,7 +463,7 @@ exports[`components/categories_list should render team error 1`] = `
|
||||
}
|
||||
collapsable={false}
|
||||
style={
|
||||
Object {
|
||||
{
|
||||
"marginRight": 8,
|
||||
"opacity": 1,
|
||||
"width": 40,
|
||||
@@ -482,15 +482,15 @@ exports[`components/categories_list should render team error 1`] = `
|
||||
onResponderTerminationRequest={[Function]}
|
||||
onStartShouldSetResponder={[Function]}
|
||||
style={
|
||||
Object {
|
||||
{
|
||||
"opacity": 1,
|
||||
}
|
||||
}
|
||||
>
|
||||
<View
|
||||
style={
|
||||
Array [
|
||||
Object {
|
||||
[
|
||||
{
|
||||
"alignItems": "center",
|
||||
"backgroundColor": "rgba(255,255,255,0.12)",
|
||||
"borderRadius": 8,
|
||||
@@ -522,7 +522,7 @@ exports[`components/categories_list should render team error 1`] = `
|
||||
onResponderTerminationRequest={[Function]}
|
||||
onStartShouldSetResponder={[Function]}
|
||||
style={
|
||||
Object {
|
||||
{
|
||||
"backgroundColor": "rgba(255,255,255,0.12)",
|
||||
"borderRadius": 8,
|
||||
"flex": 1,
|
||||
@@ -538,7 +538,7 @@ exports[`components/categories_list should render team error 1`] = `
|
||||
<Icon
|
||||
name="magnify"
|
||||
style={
|
||||
Object {
|
||||
{
|
||||
"color": "rgba(255,255,255,0.72)",
|
||||
"fontSize": 24,
|
||||
"width": 24,
|
||||
@@ -547,7 +547,7 @@ exports[`components/categories_list should render team error 1`] = `
|
||||
/>
|
||||
<Text
|
||||
style={
|
||||
Object {
|
||||
{
|
||||
"color": "rgba(255,255,255,0.72)",
|
||||
"fontFamily": "OpenSans",
|
||||
"fontSize": 16,
|
||||
@@ -564,7 +564,7 @@ exports[`components/categories_list should render team error 1`] = `
|
||||
</View>
|
||||
<View
|
||||
style={
|
||||
Object {
|
||||
{
|
||||
"alignItems": "center",
|
||||
"flex": 1,
|
||||
"justifyContent": "center",
|
||||
@@ -574,7 +574,7 @@ exports[`components/categories_list should render team error 1`] = `
|
||||
>
|
||||
<View
|
||||
style={
|
||||
Object {
|
||||
{
|
||||
"alignItems": "center",
|
||||
"backgroundColor": "rgba(255,255,255,0.08)",
|
||||
"borderRadius": 60,
|
||||
@@ -587,7 +587,7 @@ exports[`components/categories_list should render team error 1`] = `
|
||||
<Icon
|
||||
name="alert-circle-outline"
|
||||
style={
|
||||
Object {
|
||||
{
|
||||
"color": "rgba(255,255,255,0.48)",
|
||||
"fontSize": 72,
|
||||
"lineHeight": 72,
|
||||
@@ -597,14 +597,14 @@ exports[`components/categories_list should render team error 1`] = `
|
||||
</View>
|
||||
<Text
|
||||
style={
|
||||
Array [
|
||||
Object {
|
||||
[
|
||||
{
|
||||
"fontFamily": "Metropolis-SemiBold",
|
||||
"fontSize": 20,
|
||||
"fontWeight": "600",
|
||||
"lineHeight": 28,
|
||||
},
|
||||
Object {
|
||||
{
|
||||
"color": "#ffffff",
|
||||
"marginTop": 20,
|
||||
"textAlign": "center",
|
||||
@@ -616,14 +616,14 @@ exports[`components/categories_list should render team error 1`] = `
|
||||
</Text>
|
||||
<Text
|
||||
style={
|
||||
Array [
|
||||
Object {
|
||||
[
|
||||
{
|
||||
"fontFamily": "OpenSans",
|
||||
"fontSize": 16,
|
||||
"fontWeight": "400",
|
||||
"lineHeight": 24,
|
||||
},
|
||||
Object {
|
||||
{
|
||||
"color": "#ffffff",
|
||||
"marginTop": 4,
|
||||
"textAlign": "center",
|
||||
@@ -645,7 +645,7 @@ exports[`components/categories_list should render team error 1`] = `
|
||||
onResponderTerminationRequest={[Function]}
|
||||
onStartShouldSetResponder={[Function]}
|
||||
style={
|
||||
Object {
|
||||
{
|
||||
"alignItems": "center",
|
||||
"backgroundColor": "#ffffff",
|
||||
"borderRadius": 4,
|
||||
@@ -661,8 +661,8 @@ exports[`components/categories_list should render team error 1`] = `
|
||||
>
|
||||
<Text
|
||||
style={
|
||||
Array [
|
||||
Object {
|
||||
[
|
||||
{
|
||||
"alignItems": "center",
|
||||
"fontFamily": "OpenSans-SemiBold",
|
||||
"fontWeight": "600",
|
||||
@@ -670,12 +670,12 @@ exports[`components/categories_list should render team error 1`] = `
|
||||
"padding": 1,
|
||||
"textAlignVertical": "center",
|
||||
},
|
||||
Object {
|
||||
{
|
||||
"fontSize": 16,
|
||||
"lineHeight": 18,
|
||||
"marginTop": 1,
|
||||
},
|
||||
Object {
|
||||
{
|
||||
"color": "#1c58d9",
|
||||
},
|
||||
]
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user