Merge branch 'main' into gm_to_channel

This commit is contained in:
harshil Sharma 2023-10-30 07:52:55 +05:30
commit 7c008a9726
26 changed files with 151 additions and 119 deletions

View file

@ -667,6 +667,38 @@ 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.
---
## @voximplant/react-native-foreground-service
This product contains a modified version of '@voximplant/react-native-foreground-service' by Voximplant.
A foreground service performs some operation that is noticeable to the user.
* HOMEPAGE:
* https://github.com/voximplant/react-native-foreground-service
* LICENSE: MIT License
Copyright (c) 2019 Zingaya, Inc
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.
---

View file

@ -110,8 +110,8 @@ android {
applicationId "com.mattermost.rnbeta"
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
versionCode 489
versionName "2.9.0"
versionCode 490
versionName "2.10.0"
testBuildType System.getProperty('testBuildType', 'debug')
testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner'
}

View file

@ -77,3 +77,11 @@ export const removeLastViewedChannelIdAndServer = async () => {
export const removeLastViewedThreadIdAndServer = async () => {
return storeGlobal(GLOBAL_IDENTIFIERS.LAST_VIEWED_THREAD, null, false);
};
export const storePushDisabledInServerAcknowledged = async (serverUrl: string) => {
return storeGlobal(`${GLOBAL_IDENTIFIERS.PUSH_DISABLED_ACK}${serverUrl}`, 'true', false);
};
export const removePushDisabledInServerAcknowledged = async (serverUrl: string) => {
return storeGlobal(`${GLOBAL_IDENTIFIERS.PUSH_DISABLED_ACK}${serverUrl}`, null, false);
};

View file

@ -2,7 +2,7 @@
// See LICENSE.txt for license information.
import React, {useCallback} from 'react';
import {Modal, StyleSheet, useWindowDimensions, View} from 'react-native';
import {Modal, useWindowDimensions} from 'react-native';
import HighlightItem from './item';
@ -11,11 +11,10 @@ type Props = {
itemBounds: TutorialItemBounds;
itemBorderRadius?: number;
onDismiss: () => void;
onLayout: () => void;
onShow?: () => void;
}
const TutorialHighlight = ({children, itemBounds, itemBorderRadius, onDismiss, onLayout, onShow}: Props) => {
const TutorialHighlight = ({children, itemBounds, itemBorderRadius, onDismiss, onShow}: Props) => {
const {width, height} = useWindowDimensions();
const handleShowTutorial = useCallback(() => {
@ -26,19 +25,13 @@ const TutorialHighlight = ({children, itemBounds, itemBorderRadius, onDismiss, o
return (
<Modal
visible={true}
visible={itemBounds.endX > 0}
transparent={true}
animationType='fade'
onDismiss={onDismiss}
onRequestClose={onDismiss}
testID='tutorial_highlight'
>
<View
style={StyleSheet.absoluteFill}
pointerEvents='box-none'
onLayout={onLayout}
/>
{itemBounds.endX > 0 &&
<HighlightItem
borderRadius={itemBorderRadius}
itemBounds={itemBounds}
@ -47,7 +40,6 @@ const TutorialHighlight = ({children, itemBounds, itemBorderRadius, onDismiss, o
width={width}
onLayout={handleShowTutorial}
/>
}
{children}
</Modal>
);

View file

@ -32,6 +32,7 @@ type Props = {
rightDecorator?: React.ReactNode;
onUserPress?: (user: UserProfile | UserModel) => void;
onUserLongPress?: (user: UserProfile | UserModel) => void;
onLayout?: () => void;
disabled?: boolean;
viewRef?: React.LegacyRef<View>;
padding?: number;
@ -102,6 +103,7 @@ const UserItem = ({
locale,
teammateNameDisplay,
rightDecorator,
onLayout,
onUserPress,
onUserLongPress,
disabled = false,
@ -159,6 +161,7 @@ const UserItem = ({
onPress={onPress}
onLongPress={onLongPress}
disabled={!(onUserPress || onUserLongPress)}
onLayout={onLayout}
>
<View
ref={viewRef}

View file

@ -138,6 +138,7 @@ exports[`components/channel_list_row should show no results 1`] = `
collapsable={false}
focusable={true}
onClick={[Function]}
onLayout={[Function]}
onResponderGrant={[Function]}
onResponderMove={[Function]}
onResponderRelease={[Function]}
@ -424,6 +425,7 @@ exports[`components/channel_list_row should show results and tutorial 1`] = `
collapsable={false}
focusable={true}
onClick={[Function]}
onLayout={[Function]}
onResponderGrant={[Function]}
onResponderMove={[Function]}
onResponderRelease={[Function]}
@ -551,79 +553,8 @@ exports[`components/channel_list_row should show results and tutorial 1`] = `
onRequestClose={[Function]}
testID="tutorial_highlight"
transparent={true}
visible={true}
>
<View
onLayout={[Function]}
pointerEvents="box-none"
style={
{
"bottom": 0,
"left": 0,
"position": "absolute",
"right": 0,
"top": 0,
}
}
/>
<View
pointerEvents="none"
style={
[
{
"alignItems": "center",
"bottom": 0,
"justifyContent": "center",
"left": 0,
"position": "absolute",
"right": 0,
"top": 0,
},
undefined,
]
}
testID="tutorial_swipe_left"
>
<View
style={
[
{
"alignItems": "center",
"backgroundColor": "#ffffff",
"borderRadius": 8,
"height": 161,
"padding": 16,
"width": 247,
},
{
"top": -74,
},
]
}
>
<long_press_illustration.svg />
<Text
style={
[
{
"color": "#3f4350",
"fontFamily": "Metropolis-SemiBold",
"fontSize": 16,
"fontWeight": "600",
"lineHeight": 24,
"marginTop": 8,
"paddingHorizontal": 12,
"textAlign": "center",
},
undefined,
]
}
>
Long-press on an item to view a user's profile
</Text>
</View>
</View>
</Modal>
visible={false}
/>
</View>
<View
onFocusCapture={[Function]}
@ -790,6 +721,7 @@ exports[`components/channel_list_row should show results no tutorial 1`] = `
collapsable={false}
focusable={true}
onClick={[Function]}
onLayout={[Function]}
onResponderGrant={[Function]}
onResponderMove={[Function]}
onResponderRelease={[Function]}
@ -1108,6 +1040,7 @@ exports[`components/channel_list_row should show results no tutorial 2 users 1`]
collapsable={false}
focusable={true}
onClick={[Function]}
onLayout={[Function]}
onResponderGrant={[Function]}
onResponderMove={[Function]}
onResponderRelease={[Function]}
@ -1299,6 +1232,7 @@ exports[`components/channel_list_row should show results no tutorial 2 users 1`]
collapsable={false}
focusable={true}
onClick={[Function]}
onLayout={[Function]}
onResponderGrant={[Function]}
onResponderMove={[Function]}
onResponderRelease={[Function]}

View file

@ -155,8 +155,10 @@ function UserListRow({
}, [isChannelAdmin, showManageMode, theme]);
const onLayout = useCallback(() => {
startTutorial();
}, []);
if (showTutorial) {
startTutorial();
}
}, [showTutorial]);
const icon = useMemo(() => {
if (!selectable && !selected) {
@ -190,17 +192,19 @@ function UserListRow({
viewRef={viewRef}
padding={20}
includeMargin={includeMargin}
onLayout={onLayout}
/>
{showTutorial &&
<TutorialHighlight
itemBounds={itemBounds}
onDismiss={handleDismissTutorial}
onLayout={onLayout}
>
{Boolean(itemBounds.endX) &&
<TutorialLongPress
message={formatMessage({id: 'user.tutorial.long_press', defaultMessage: "Long-press on an item to view a user's profile"})}
style={isTablet ? style.tutorialTablet : style.tutorial}
/>
}
</TutorialHighlight>
}
</>

View file

@ -82,6 +82,7 @@ export const GLOBAL_IDENTIFIERS = {
ONBOARDING: 'onboarding',
LAST_VIEWED_CHANNEL: 'lastViewedChannel',
LAST_VIEWED_THREAD: 'lastViewedThread',
PUSH_DISABLED_ACK: 'pushDisabledAck',
};
export enum OperationType {

View file

@ -120,7 +120,7 @@ export default class CategoryModel extends Model implements CategoryInterface {
toCategoryWithChannels = async (): Promise<CategoryWithChannels> => {
const categoryChannels = await this.categoryChannels.fetch();
const orderedChannelIds = categoryChannels.sort((a, b) => {
return b.sortOrder - a.sortOrder;
return a.sortOrder - b.sortOrder;
}).map((cc) => cc.channelId);
return {

View file

@ -5,7 +5,7 @@ import CookieManager, {type Cookie} from '@react-native-cookies/cookies';
import {AppState, type AppStateStatus, DeviceEventEmitter, Platform} from 'react-native';
import FastImage from 'react-native-fast-image';
import {storeOnboardingViewedValue} from '@actions/app/global';
import {removePushDisabledInServerAcknowledged, storeOnboardingViewedValue} from '@actions/app/global';
import {cancelSessionNotification, logout, scheduleSessionNotification} from '@actions/remote/session';
import {Events, Launch} from '@constants';
import DatabaseManager from '@database/manager';
@ -22,6 +22,7 @@ import {getThemeFromState} from '@screens/navigation';
import EphemeralStore from '@store/ephemeral_store';
import {deleteFileCache, deleteFileCacheByDir} from '@utils/file';
import {isMainActivity} from '@utils/helpers';
import {urlSafeBase64Encode} from '@utils/security';
import {addNewServer} from '@utils/server';
import type {LaunchType} from '@typings/launch';
@ -121,6 +122,7 @@ class SessionManager {
WebsocketManager.invalidateClient(serverUrl);
if (removeServer) {
await removePushDisabledInServerAcknowledged(urlSafeBase64Encode(serverUrl));
await DatabaseManager.destroyServerDatabase(serverUrl);
} else {
await DatabaseManager.deleteServerDatabase(serverUrl);

View file

@ -55,6 +55,22 @@ export const getDontAskForReview = async () => {
return Boolean(records?.[0]?.value);
};
export const getPushDisabledInServerAcknowledged = async (serverDomainString: string) => {
const records = await queryGlobalValue(`${GLOBAL_IDENTIFIERS.PUSH_DISABLED_ACK}${serverDomainString}`)?.fetch();
return Boolean(records?.[0]?.value);
};
export const observePushDisabledInServerAcknowledged = (serverDomainString: string) => {
const query = queryGlobalValue(`${GLOBAL_IDENTIFIERS.PUSH_DISABLED_ACK}${serverDomainString}`);
if (!query) {
return of$(false);
}
return query.observe().pipe(
switchMap((result) => (result.length ? result[0].observe() : of$(false))),
switchMap((v) => of$(Boolean(v))),
);
};
export const getFirstLaunch = async () => {
const records = await queryGlobalValue(GLOBAL_IDENTIFIERS.FIRST_LAUNCH)?.fetch();
if (!records?.[0]?.value) {

View file

@ -3,7 +3,7 @@
import React from 'react';
import {PUSH_PROXY_STATUS_VERIFIED} from '@constants/push_proxy';
import {PUSH_PROXY_RESPONSE_NOT_AVAILABLE, PUSH_PROXY_STATUS_VERIFIED} from '@constants/push_proxy';
import {renderWithIntl} from '@test/intl-test-helper';
import Header from './header';
@ -22,4 +22,18 @@ describe('components/channel_list/header', () => {
expect(toJSON()).toMatchSnapshot();
});
it('Push notifications disabled show alert icon', () => {
const wrapper = renderWithIntl(
<Header
pushProxyStatus={PUSH_PROXY_RESPONSE_NOT_AVAILABLE}
canCreateChannels={true}
canJoinChannels={true}
canInvitePeople={true}
displayName={'Test!'}
/>,
);
expect(wrapper.getByTestId('channel_list_header.push_alert')).toBeTruthy();
});
});

View file

@ -206,7 +206,7 @@ const ChannelListHeader = ({
>
{serverDisplayName}
</Text>
{(pushProxyStatus !== PUSH_PROXY_STATUS_VERIFIED) && (
{pushProxyStatus !== PUSH_PROXY_STATUS_VERIFIED && (
<TouchableWithFeedback
onPress={onPushAlertPress}
testID='channel_list_header.push_alert'

View file

@ -213,9 +213,11 @@ const ServerItem = ({
};
const onLayout = useCallback(() => {
swipeable.current?.close();
startTutorial();
}, []);
if (showTutorial) {
swipeable.current?.close();
startTutorial();
}
}, [showTutorial]);
const containerStyle = useMemo(() => {
const style: StyleProp<ViewStyle> = [styles.container];
@ -390,6 +392,7 @@ const ServerItem = ({
style={containerStyle}
ref={viewRef}
testID={serverItemTestId}
onLayout={onLayout}
>
<RectButton
onPress={onServerPressed}
@ -474,13 +477,14 @@ const ServerItem = ({
itemBounds={itemBounds}
onDismiss={handleDismissTutorial}
onShow={handleShowTutorial}
onLayout={onLayout}
itemBorderRadius={8}
>
{Boolean(itemBounds.endX) &&
<TutorialSwipeLeft
message={intl.formatMessage({id: 'server.tutorial.swipe', defaultMessage: 'Swipe left on a server to see more actions'})}
style={isTablet ? styles.tutorialTablet : styles.tutorial}
/>
}
</TutorialHighlight>
}
</>

View file

@ -180,7 +180,9 @@ const sortChannelsByName = (notifyPropsPerChannel: Record<string, Partial<Channe
export const sortChannels = (sorting: CategorySorting, channelsWithMyChannel: ChannelWithMyChannel[], notifyPropsPerChannel: Record<string, Partial<ChannelNotifyProps>>, locale: string) => {
if (sorting === 'recent') {
return channelsWithMyChannel.sort((cwmA, cwmB) => {
return cwmB.myChannel.lastPostAt - cwmA.myChannel.lastPostAt;
const a = Math.max(cwmA.myChannel.lastPostAt, cwmA.channel.createAt);
const b = Math.max(cwmB.myChannel.lastPostAt, cwmB.channel.createAt);
return b - a;
}).map((cwm) => cwm.channel);
} else if (sorting === 'manual') {
return channelsWithMyChannel.sort((cwmA, cwmB) => {

View file

@ -3,16 +3,29 @@
import {Alert} from 'react-native';
import {storePushDisabledInServerAcknowledged} from '@actions/app/global';
import {PUSH_PROXY_RESPONSE_NOT_AVAILABLE, PUSH_PROXY_RESPONSE_UNKNOWN, PUSH_PROXY_STATUS_NOT_AVAILABLE, PUSH_PROXY_STATUS_UNKNOWN, PUSH_PROXY_STATUS_VERIFIED} from '@constants/push_proxy';
import {getPushDisabledInServerAcknowledged} from '@queries/app/global';
import EphemeralStore from '@store/ephemeral_store';
import {urlSafeBase64Encode} from './security';
import type {IntlShape} from 'react-intl';
export function canReceiveNotifications(serverUrl: string, verification: string, intl: IntlShape) {
export function pushDisabledInServerAck(serverUrl: string) {
const extractedDomain = urlSafeBase64Encode(serverUrl);
return getPushDisabledInServerAcknowledged(extractedDomain);
}
export async function canReceiveNotifications(serverUrl: string, verification: string, intl: IntlShape) {
const hasAckNotification = await pushDisabledInServerAck(serverUrl);
switch (verification) {
case PUSH_PROXY_RESPONSE_NOT_AVAILABLE:
EphemeralStore.setPushProxyVerificationState(serverUrl, PUSH_PROXY_STATUS_NOT_AVAILABLE);
alertPushProxyError(intl);
if (!hasAckNotification) {
alertPushProxyError(intl, serverUrl);
}
break;
case PUSH_PROXY_RESPONSE_UNKNOWN:
EphemeralStore.setPushProxyVerificationState(serverUrl, PUSH_PROXY_STATUS_UNKNOWN);
@ -23,7 +36,14 @@ export function canReceiveNotifications(serverUrl: string, verification: string,
}
}
export function alertPushProxyError(intl: IntlShape) {
const handleAlertResponse = async (buttonIndex: number, serverUrl?: string) => {
if (buttonIndex === 0 && serverUrl) {
// User clicked "Okay" acknowledging that the push notifications are disabled on that server
await storePushDisabledInServerAcknowledged(urlSafeBase64Encode(serverUrl));
}
};
export function alertPushProxyError(intl: IntlShape, serverUrl?: string) {
Alert.alert(
intl.formatMessage({
id: 'alert.push_proxy_error.title',
@ -31,10 +51,11 @@ export function alertPushProxyError(intl: IntlShape) {
}),
intl.formatMessage({
id: 'alert.push_proxy_error.description',
defaultMessage: 'Due to the configuration for this server, notifications cannot be received in the mobile app. Contact your system admin for more information.',
defaultMessage: 'Due to the configuration of this server, notifications cannot be received in the mobile app. Contact your system admin for more information.',
}),
[{
text: intl.formatMessage({id: 'alert.push_proxy.button', defaultMessage: 'Okay'}),
onPress: () => handleAlertResponse(0, serverUrl),
}],
);
}

View file

@ -15,7 +15,7 @@
"account.your_profile": "Your Profile",
"alert.channel_deleted.description": "The channel {displayName} has been archived.",
"alert.channel_deleted.title": "Archived channel",
"alert.push_proxy_error.description": "Due to the configuration for this server, notifications cannot be received in the mobile app. Contact your system admin for more information.",
"alert.push_proxy_error.description": "Due to the configuration of this server, notifications cannot be received in the mobile app. Contact your system admin for more information.",
"alert.push_proxy_error.title": "Notifications cannot be received from this server",
"alert.push_proxy_unknown.description": "This server was unable to receive push notifications for an unknown reason. This will be attempted again next time you connect.",
"alert.push_proxy_unknown.title": "Notifications could not be received from this server",

View file

@ -686,7 +686,7 @@
"alert.push_proxy.button": "Okay",
"alert.push_proxy_unknown.title": "Notifications could not be received from this server",
"alert.push_proxy_error.title": "Notifications cannot be received from this server",
"alert.push_proxy_error.description": "Due to the configuration for this server, notifications cannot be received in the mobile app. Contact your system admin for more information.",
"alert.push_proxy_error.description": "Due to the configuration of this server, notifications cannot be received in the mobile app. Contact your system admin for more information.",
"alert.channel_deleted.title": "Archived channel",
"alert.channel_deleted.description": "The channel {displayName} has been archived.",
"account.your_profile": "Your Profile",

View file

@ -1 +0,0 @@
{}

View file

@ -1 +0,0 @@
{}

View file

@ -1929,7 +1929,7 @@
CODE_SIGN_ENTITLEMENTS = Mattermost/Mattermost.entitlements;
CODE_SIGN_IDENTITY = "iPhone Developer";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
CURRENT_PROJECT_VERSION = 489;
CURRENT_PROJECT_VERSION = 490;
DEVELOPMENT_TEAM = UQ8HT4Q2XM;
ENABLE_BITCODE = NO;
HEADER_SEARCH_PATHS = (
@ -1973,7 +1973,7 @@
CODE_SIGN_ENTITLEMENTS = Mattermost/Mattermost.entitlements;
CODE_SIGN_IDENTITY = "iPhone Developer";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
CURRENT_PROJECT_VERSION = 489;
CURRENT_PROJECT_VERSION = 490;
DEVELOPMENT_TEAM = UQ8HT4Q2XM;
ENABLE_BITCODE = NO;
HEADER_SEARCH_PATHS = (
@ -2116,7 +2116,7 @@
CODE_SIGN_IDENTITY = "iPhone Developer";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 489;
CURRENT_PROJECT_VERSION = 490;
DEBUG_INFORMATION_FORMAT = dwarf;
DEVELOPMENT_TEAM = UQ8HT4Q2XM;
GCC_C_LANGUAGE_STANDARD = gnu11;
@ -2165,7 +2165,7 @@
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
CODE_SIGN_STYLE = Automatic;
COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 489;
CURRENT_PROJECT_VERSION = 490;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
DEVELOPMENT_TEAM = UQ8HT4Q2XM;
GCC_C_LANGUAGE_STANDARD = gnu11;

View file

@ -21,7 +21,7 @@
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>2.9.0</string>
<string>2.10.0</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleURLTypes</key>
@ -37,7 +37,7 @@
</dict>
</array>
<key>CFBundleVersion</key>
<string>489</string>
<string>490</string>
<key>ITSAppUsesNonExemptEncryption</key>
<false/>
<key>LSRequiresIPhoneOS</key>

View file

@ -19,9 +19,9 @@
<key>CFBundlePackageType</key>
<string>XPC!</string>
<key>CFBundleShortVersionString</key>
<string>2.9.0</string>
<string>2.10.0</string>
<key>CFBundleVersion</key>
<string>489</string>
<string>490</string>
<key>UIAppFonts</key>
<array>
<string>OpenSans-Bold.ttf</string>

View file

@ -19,9 +19,9 @@
<key>CFBundlePackageType</key>
<string>XPC!</string>
<key>CFBundleShortVersionString</key>
<string>2.9.0</string>
<string>2.10.0</string>
<key>CFBundleVersion</key>
<string>489</string>
<string>490</string>
<key>NSExtension</key>
<dict>
<key>NSExtensionPointIdentifier</key>

5
package-lock.json generated
View file

@ -1,6 +1,6 @@
{
"name": "mattermost-mobile",
"version": "2.9.0",
"version": "2.10.0",
"lockfileVersion": 2,
"requires": true,
"packages": {
@ -28574,7 +28574,8 @@
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/@voximplant/react-native-foreground-service/-/react-native-foreground-service-3.0.2.tgz",
"integrity": "sha512-ZIyccOAXPqznA1PAVAYlKZ+GI7kXYCuYgH+gmAkhPouyJbkgrSXaCJJzQ+uBkPr4FBa/PuC/yjzK8vf6tJREQA==",
"requires": {}
"requires": {
}
},
"@webassemblyjs/ast": {
"version": "1.11.1",

View file

@ -1,6 +1,6 @@
{
"name": "mattermost-mobile",
"version": "2.9.0",
"version": "2.10.0",
"description": "Mattermost Mobile with React Native",
"repository": "git@github.com:mattermost/mattermost-mobile.git",
"author": "Mattermost, Inc.",