forked from Ivasoft/mattermost-mobile
Compare commits
19 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
70119fc026 | ||
|
|
0d9c6e0ad3 | ||
|
|
f7d8ed9e1f | ||
|
|
b8cc13d7fa | ||
|
|
317568b4c8 | ||
|
|
55f919dd27 | ||
|
|
da1b3dc71d | ||
|
|
57a9ff31bf | ||
|
|
4f86a87bdc | ||
|
|
9ab21b2f62 | ||
|
|
1934945d72 | ||
|
|
5162e6b6e7 | ||
|
|
9139a26967 | ||
|
|
56fbb3d842 | ||
|
|
56349f865f | ||
|
|
fdf593bcec | ||
|
|
8e2e016a6c | ||
|
|
4d9bc1fbed | ||
|
|
7351c7ccac |
@@ -309,13 +309,13 @@ jobs:
|
||||
- save:
|
||||
filename: "*.apk"
|
||||
|
||||
# build-android-release:
|
||||
# executor: android
|
||||
# steps:
|
||||
# - build-android
|
||||
# - persist
|
||||
# - save:
|
||||
# filename: "*.apk"
|
||||
build-android-release:
|
||||
executor: android
|
||||
steps:
|
||||
- build-android
|
||||
- persist
|
||||
- save:
|
||||
filename: "*.apk"
|
||||
|
||||
build-android-pr:
|
||||
executor: android
|
||||
@@ -326,27 +326,27 @@ jobs:
|
||||
- save:
|
||||
filename: "*.apk"
|
||||
|
||||
# build-android-unsigned:
|
||||
# executor: android
|
||||
# steps:
|
||||
# - checkout:
|
||||
# path: ~/mattermost-mobile
|
||||
# - npm-dependencies
|
||||
# - assets
|
||||
# - fastlane-dependencies:
|
||||
# for: android
|
||||
# - gradle-dependencies
|
||||
# - run:
|
||||
# name: Jetify Android libraries
|
||||
# command: ./node_modules/.bin/jetify
|
||||
# - run:
|
||||
# working_directory: fastlane
|
||||
# name: Run fastlane to build unsigned android
|
||||
# no_output_timeout: 30m
|
||||
# command: bundle exec fastlane android unsigned
|
||||
# - persist
|
||||
# - save:
|
||||
# filename: "*.apk"
|
||||
build-android-unsigned:
|
||||
executor: android
|
||||
steps:
|
||||
- checkout:
|
||||
path: ~/mattermost-mobile
|
||||
- npm-dependencies
|
||||
- assets
|
||||
- fastlane-dependencies:
|
||||
for: android
|
||||
- gradle-dependencies
|
||||
- run:
|
||||
name: Jetify Android libraries
|
||||
command: ./node_modules/.bin/jetify
|
||||
- run:
|
||||
working_directory: fastlane
|
||||
name: Run fastlane to build unsigned android
|
||||
no_output_timeout: 30m
|
||||
command: bundle exec fastlane android unsigned
|
||||
- persist
|
||||
- save:
|
||||
filename: "*.apk"
|
||||
|
||||
build-ios-beta:
|
||||
executor:
|
||||
@@ -358,13 +358,13 @@ jobs:
|
||||
- save:
|
||||
filename: "*.ipa"
|
||||
|
||||
# build-ios-release:
|
||||
# executor: ios
|
||||
# steps:
|
||||
# - build-ios
|
||||
# - persist
|
||||
# - save:
|
||||
# filename: "*.ipa"
|
||||
build-ios-release:
|
||||
executor: ios
|
||||
steps:
|
||||
- build-ios
|
||||
- persist
|
||||
- save:
|
||||
filename: "*.ipa"
|
||||
|
||||
build-ios-pr:
|
||||
executor: ios
|
||||
@@ -375,63 +375,64 @@ jobs:
|
||||
- save:
|
||||
filename: "*.ipa"
|
||||
|
||||
# build-ios-unsigned:
|
||||
# executor: ios
|
||||
# steps:
|
||||
# - checkout:
|
||||
# path: ~/mattermost-mobile
|
||||
# - npm-dependencies
|
||||
# - pods-dependencies
|
||||
# - assets
|
||||
# - fastlane-dependencies:
|
||||
# for: ios
|
||||
# - run:
|
||||
# working_directory: fastlane
|
||||
# name: Run fastlane to build unsigned iOS
|
||||
# no_output_timeout: 30m
|
||||
# command: |
|
||||
# HOMEBREW_NO_AUTO_UPDATE=1 brew install watchman
|
||||
# bundle exec fastlane ios unsigned
|
||||
# - persist_to_workspace:
|
||||
# root: ~/
|
||||
# paths:
|
||||
# - mattermost-mobile/*.ipa
|
||||
# - save:
|
||||
# filename: "*.ipa"
|
||||
build-ios-unsigned:
|
||||
executor: ios
|
||||
steps:
|
||||
- checkout:
|
||||
path: ~/mattermost-mobile
|
||||
- npm-dependencies
|
||||
- pods-dependencies
|
||||
- assets
|
||||
- fastlane-dependencies:
|
||||
for: ios
|
||||
- run:
|
||||
working_directory: fastlane
|
||||
name: Run fastlane to build unsigned iOS
|
||||
no_output_timeout: 30m
|
||||
command: |
|
||||
HOMEBREW_NO_AUTO_UPDATE=1 brew install watchman
|
||||
bundle exec fastlane ios unsigned
|
||||
- persist_to_workspace:
|
||||
root: ~/
|
||||
paths:
|
||||
- mattermost-mobile/*.ipa
|
||||
- save:
|
||||
filename: "*.ipa"
|
||||
|
||||
# build-ios-simulator:
|
||||
# executor: ios
|
||||
# steps:
|
||||
# - checkout:
|
||||
# path: ~/mattermost-mobile
|
||||
# - npm-dependencies
|
||||
# - pods-dependencies
|
||||
# - assets
|
||||
# - fastlane-dependencies:
|
||||
# for: ios
|
||||
# - run:
|
||||
# working_directory: fastlane
|
||||
# name: Run fastlane to build unsigned x86_64 iOS app for iPhone simulator
|
||||
# no_output_timeout: 30m
|
||||
# command: |
|
||||
# HOMEBREW_NO_AUTO_UPDATE=1 brew install watchman
|
||||
# bundle exec fastlane ios simulator
|
||||
# - persist_to_workspace:
|
||||
# root: ~/
|
||||
# paths:
|
||||
# - mattermost-mobile/Mattermost-simulator-x86_64.app.zip
|
||||
# - save:
|
||||
# filename: "Mattermost-simulator-x86_64.app.zip"
|
||||
build-ios-simulator:
|
||||
executor: ios
|
||||
steps:
|
||||
- checkout:
|
||||
path: ~/mattermost-mobile
|
||||
- npm-dependencies
|
||||
- pods-dependencies
|
||||
- assets
|
||||
- fastlane-dependencies:
|
||||
for: ios
|
||||
- run:
|
||||
working_directory: fastlane
|
||||
name: Run fastlane to build unsigned x86_64 iOS app for iPhone simulator
|
||||
no_output_timeout: 30m
|
||||
command: |
|
||||
HOMEBREW_NO_AUTO_UPDATE=1 brew install watchman
|
||||
bundle exec fastlane ios simulator
|
||||
- persist_to_workspace:
|
||||
root: ~/
|
||||
paths:
|
||||
- mattermost-mobile/Mattermost-simulator-x86_64.app.zip
|
||||
- save:
|
||||
filename: "Mattermost-simulator-x86_64.app.zip"
|
||||
|
||||
# deploy-android-release:
|
||||
# executor:
|
||||
# name: android
|
||||
# resource_class: medium
|
||||
# steps:
|
||||
# - deploy-to-store:
|
||||
# task: "Deploy to Google Play"
|
||||
# target: android
|
||||
# file: "*.apk"
|
||||
deploy-android-release:
|
||||
executor:
|
||||
name: android
|
||||
resource_class: medium
|
||||
steps:
|
||||
- deploy-to-store:
|
||||
task: "Deploy to Google Play"
|
||||
target: android
|
||||
file: "*.apk"
|
||||
env: "SUPPLY_TRACK=beta"
|
||||
|
||||
deploy-android-beta:
|
||||
executor:
|
||||
@@ -444,13 +445,14 @@ jobs:
|
||||
file: "*.apk"
|
||||
env: "SUPPLY_TRACK=alpha"
|
||||
|
||||
# deploy-ios-release:
|
||||
# executor: ios
|
||||
# steps:
|
||||
# - deploy-to-store:
|
||||
# task: "Deploy to TestFlight"
|
||||
# target: ios
|
||||
# file: "*.ipa"
|
||||
deploy-ios-release:
|
||||
executor: ios
|
||||
steps:
|
||||
- deploy-to-store:
|
||||
task: "Deploy to TestFlight"
|
||||
target: ios
|
||||
file: "*.ipa"
|
||||
env: ""
|
||||
|
||||
deploy-ios-beta:
|
||||
executor: ios
|
||||
@@ -461,17 +463,17 @@ jobs:
|
||||
file: "*.ipa"
|
||||
env: ""
|
||||
|
||||
# github-release:
|
||||
# executor:
|
||||
# name: android
|
||||
# resource_class: medium
|
||||
# steps:
|
||||
# - attach_workspace:
|
||||
# at: ~/
|
||||
# - run:
|
||||
# name: Create GitHub release
|
||||
# working_directory: fastlane
|
||||
# command: bundle exec fastlane github
|
||||
github-release:
|
||||
executor:
|
||||
name: android
|
||||
resource_class: medium
|
||||
steps:
|
||||
- attach_workspace:
|
||||
at: ~/
|
||||
- run:
|
||||
name: Create GitHub release
|
||||
working_directory: fastlane
|
||||
command: bundle exec fastlane github
|
||||
|
||||
workflows:
|
||||
version: 2
|
||||
@@ -483,26 +485,24 @@ workflows:
|
||||
# requires:
|
||||
# - test
|
||||
|
||||
# - build-android-release:
|
||||
# context: mattermost-mobile-android-release
|
||||
# requires:
|
||||
# - test
|
||||
# filters:
|
||||
# branches:
|
||||
# only:
|
||||
# - /^build-\d+$/
|
||||
# - /^build-android-\d+$/
|
||||
# - /^build-android-release-\d+$/
|
||||
# - deploy-android-release:
|
||||
# context: mattermost-mobile-android-release
|
||||
# requires:
|
||||
# - build-android-release
|
||||
# filters:
|
||||
# branches:
|
||||
# only:
|
||||
# - /^build-\d+$/
|
||||
# - /^build-android-\d+$/
|
||||
# - /^build-android-release-\d+$/
|
||||
- build-android-release:
|
||||
context: mattermost-mobile-android-release
|
||||
requires:
|
||||
- test
|
||||
filters:
|
||||
branches:
|
||||
only:
|
||||
- /^build-release-\d+$/
|
||||
- /^build-android-release-\d+$/
|
||||
- deploy-android-release:
|
||||
context: mattermost-mobile-android-release
|
||||
requires:
|
||||
- build-android-release
|
||||
filters:
|
||||
branches:
|
||||
only:
|
||||
- /^build-release-\d+$/
|
||||
- /^build-android-release-\d+$/
|
||||
|
||||
- build-android-beta:
|
||||
context: mattermost-mobile-android-beta
|
||||
@@ -523,26 +523,24 @@ workflows:
|
||||
- /^build-android-\d+$/
|
||||
- /^build-android-beta-\d+$/
|
||||
|
||||
# - build-ios-release:
|
||||
# context: mattermost-mobile-ios-release
|
||||
# requires:
|
||||
# - test
|
||||
# filters:
|
||||
# branches:
|
||||
# only:
|
||||
# - /^build-\d+$/
|
||||
# - /^build-ios-\d+$/
|
||||
# - /^build-ios-release-\d+$/
|
||||
# - deploy-ios-release:
|
||||
# context: mattermost-mobile-ios-release
|
||||
# requires:
|
||||
# - build-ios-release
|
||||
# filters:
|
||||
# branches:
|
||||
# only:
|
||||
# - /^build-\d+$/
|
||||
# - /^build-ios-\d+$/
|
||||
# - /^build-ios-release-\d+$/
|
||||
- build-ios-release:
|
||||
context: mattermost-mobile-ios-release
|
||||
requires:
|
||||
- test
|
||||
filters:
|
||||
branches:
|
||||
only:
|
||||
- /^build-release-\d+$/
|
||||
- /^build-ios-release-\d+$/
|
||||
- deploy-ios-release:
|
||||
context: mattermost-mobile-ios-release
|
||||
requires:
|
||||
- build-ios-release
|
||||
filters:
|
||||
branches:
|
||||
only:
|
||||
- /^build-release-\d+$/
|
||||
- /^build-ios-release-\d+$/
|
||||
|
||||
- build-ios-beta:
|
||||
context: mattermost-mobile-ios-beta
|
||||
@@ -578,43 +576,41 @@ workflows:
|
||||
branches:
|
||||
only: /^(build|ios)-pr-.*/
|
||||
|
||||
# - build-android-unsigned:
|
||||
# context: mattermost-mobile-unsigned
|
||||
# requires:
|
||||
# - test
|
||||
# filters:
|
||||
# tags:
|
||||
# only: /^v(\d+\.)(\d+\.)(\d+)(.*)?$/
|
||||
# branches:
|
||||
# only: unsigned
|
||||
# - build-ios-unsigned:
|
||||
# context: mattermost-mobile-unsigned
|
||||
# requires:
|
||||
# - test
|
||||
# filters:
|
||||
# tags:
|
||||
# only: /^v(\d+\.)(\d+\.)(\d+)(.*)?$/
|
||||
# branches:
|
||||
# only: unsigned
|
||||
# - build-ios-simulator:
|
||||
# context: mattermost-mobile-unsigned
|
||||
# requires:
|
||||
# - test
|
||||
# filters:
|
||||
# branches:
|
||||
# only:
|
||||
# - /^build-\d+$/
|
||||
# - /^build-ios-\d+$/
|
||||
# - /^build-ios-beta-\d+$/
|
||||
# - /^build-ios-sim-\d+$/
|
||||
- build-android-unsigned:
|
||||
context: mattermost-mobile-unsigned
|
||||
requires:
|
||||
- test
|
||||
filters:
|
||||
tags:
|
||||
only: /^v(\d+\.)(\d+\.)(\d+)(.*)?$/
|
||||
branches:
|
||||
only: unsigned
|
||||
- build-ios-unsigned:
|
||||
context: mattermost-mobile-unsigned
|
||||
requires:
|
||||
- test
|
||||
filters:
|
||||
tags:
|
||||
only: /^v(\d+\.)(\d+\.)(\d+)(.*)?$/
|
||||
branches:
|
||||
only: unsigned
|
||||
- build-ios-simulator:
|
||||
context: mattermost-mobile-unsigned
|
||||
requires:
|
||||
- test
|
||||
filters:
|
||||
branches:
|
||||
only:
|
||||
- /^build-\d+$/
|
||||
- /^build-ios-sim-\d+$/
|
||||
|
||||
# - github-release:
|
||||
# context: mattermost-mobile-unsigned
|
||||
# requires:
|
||||
# - build-android-unsigned
|
||||
# - build-ios-unsigned
|
||||
# filters:
|
||||
# tags:
|
||||
# only: /^v(\d+\.)(\d+\.)(\d+)(.*)?$/
|
||||
# branches:
|
||||
# only: unsigned
|
||||
- github-release:
|
||||
context: mattermost-mobile-unsigned
|
||||
requires:
|
||||
- build-android-unsigned
|
||||
- build-ios-unsigned
|
||||
filters:
|
||||
tags:
|
||||
only: /^v(\d+\.)(\d+\.)(\d+)(.*)?$/
|
||||
branches:
|
||||
only: unsigned
|
||||
|
||||
@@ -145,7 +145,7 @@ android {
|
||||
applicationId "com.mattermost.rnbeta"
|
||||
minSdkVersion rootProject.ext.minSdkVersion
|
||||
targetSdkVersion rootProject.ext.targetSdkVersion
|
||||
versionCode 448
|
||||
versionCode 452
|
||||
versionName "2.0.0"
|
||||
testBuildType System.getProperty('testBuildType', 'debug')
|
||||
testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner'
|
||||
|
||||
@@ -8,6 +8,7 @@ import {prepareCommonSystemValues, getCurrentTeamId, getWebSocketLastDisconnecte
|
||||
import {getCurrentUser} from '@queries/servers/user';
|
||||
import {setTeamLoading} from '@store/team_load_store';
|
||||
import {deleteV1Data} from '@utils/file';
|
||||
import {isTablet} from '@utils/helpers';
|
||||
import {logInfo} from '@utils/log';
|
||||
|
||||
import {handleEntryAfterLoadNavigation, registerDeviceToken, syncOtherServers, verifyPushProxy} from './common';
|
||||
@@ -29,7 +30,7 @@ export async function appEntry(serverUrl: string, since = 0, isUpgrade = false)
|
||||
// clear lastUnreadChannelId
|
||||
const removeLastUnreadChannelId = await prepareCommonSystemValues(operator, {lastUnreadChannelId: ''});
|
||||
if (removeLastUnreadChannelId) {
|
||||
operator.batchRecords(removeLastUnreadChannelId);
|
||||
await operator.batchRecords(removeLastUnreadChannelId);
|
||||
}
|
||||
|
||||
const {database} = operator;
|
||||
@@ -47,7 +48,12 @@ export async function appEntry(serverUrl: string, since = 0, isUpgrade = false)
|
||||
|
||||
const {models, initialTeamId, initialChannelId, prefData, teamData, chData, meData} = entryData;
|
||||
if (isUpgrade && meData?.user) {
|
||||
const me = await prepareCommonSystemValues(operator, {currentUserId: meData.user.id});
|
||||
const isTabletDevice = await isTablet();
|
||||
const me = await prepareCommonSystemValues(operator, {
|
||||
currentUserId: meData.user.id,
|
||||
currentTeamId: initialTeamId,
|
||||
currentChannelId: isTabletDevice ? initialChannelId : undefined,
|
||||
});
|
||||
if (me?.length) {
|
||||
await operator.batchRecords(me);
|
||||
}
|
||||
@@ -84,8 +90,8 @@ export async function upgradeEntry(serverUrl: string) {
|
||||
const error = configAndLicense.error || entryData.error;
|
||||
|
||||
if (!error) {
|
||||
DatabaseManager.updateServerIdentifier(serverUrl, configAndLicense.config!.DiagnosticId);
|
||||
DatabaseManager.setActiveServerDatabase(serverUrl);
|
||||
await DatabaseManager.updateServerIdentifier(serverUrl, configAndLicense.config!.DiagnosticId);
|
||||
await DatabaseManager.setActiveServerDatabase(serverUrl);
|
||||
deleteV1Data();
|
||||
}
|
||||
|
||||
|
||||
@@ -544,9 +544,15 @@ export const fetchPostAuthors = async (serverUrl: string, posts: Post[], fetchOn
|
||||
}
|
||||
|
||||
if (promises.length) {
|
||||
const result = await Promise.all(promises);
|
||||
const authors = result.flat();
|
||||
const authorsResult = await Promise.allSettled(promises);
|
||||
const result = authorsResult.reduce<UserProfile[][]>((acc, item) => {
|
||||
if (item.status === 'fulfilled') {
|
||||
acc.push(item.value);
|
||||
}
|
||||
return acc;
|
||||
}, []);
|
||||
|
||||
const authors = result.flat();
|
||||
if (!fetchOnly && authors.length) {
|
||||
await operator.handleUsers({
|
||||
users: authors,
|
||||
|
||||
@@ -164,8 +164,13 @@ export async function handleNewPostEvent(serverUrl: string, msg: WebSocketMessag
|
||||
}
|
||||
}
|
||||
|
||||
let actionType: string = ActionType.POSTS.RECEIVED_NEW;
|
||||
if (isCRTEnabled && post.root_id) {
|
||||
actionType = ActionType.POSTS.RECEIVED_IN_THREAD;
|
||||
}
|
||||
|
||||
const postModels = await operator.handlePosts({
|
||||
actionType: ActionType.POSTS.RECEIVED_NEW,
|
||||
actionType,
|
||||
order: [post.id],
|
||||
posts: [post],
|
||||
prepareRecordsOnly: true,
|
||||
@@ -203,8 +208,14 @@ export async function handlePostEdited(serverUrl: string, msg: WebSocketMessage)
|
||||
models.push(...authorsModels);
|
||||
}
|
||||
|
||||
let actionType: string = ActionType.POSTS.RECEIVED_NEW;
|
||||
const isCRTEnabled = await getIsCRTEnabled(operator.database);
|
||||
if (isCRTEnabled && post.root_id) {
|
||||
actionType = ActionType.POSTS.RECEIVED_IN_THREAD;
|
||||
}
|
||||
|
||||
const postModels = await operator.handlePosts({
|
||||
actionType: ActionType.POSTS.RECEIVED_NEW,
|
||||
actionType,
|
||||
order: [post.id],
|
||||
posts: [post],
|
||||
prepareRecordsOnly: true,
|
||||
|
||||
@@ -2,12 +2,22 @@
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {markTeamThreadsAsRead, processReceivedThreads, updateThread} from '@actions/local/thread';
|
||||
import {getCurrentTeamId} from '@app/queries/servers/system';
|
||||
import DatabaseManager from '@database/manager';
|
||||
import EphemeralStore from '@store/ephemeral_store';
|
||||
|
||||
export async function handleThreadUpdatedEvent(serverUrl: string, msg: WebSocketMessage): Promise<void> {
|
||||
try {
|
||||
const database = DatabaseManager.serverDatabases[serverUrl]?.database;
|
||||
if (!database) {
|
||||
return;
|
||||
}
|
||||
const thread: Thread = JSON.parse(msg.data.thread);
|
||||
const teamId = msg.broadcast.team_id;
|
||||
let teamId = msg.broadcast.team_id;
|
||||
|
||||
if (!teamId) {
|
||||
teamId = await getCurrentTeamId(database);
|
||||
}
|
||||
|
||||
// Mark it as following
|
||||
thread.is_following = true;
|
||||
|
||||
@@ -33,6 +33,7 @@ export default class WebSocketClient {
|
||||
private firstConnectCallback?: () => void;
|
||||
private missedEventsCallback?: () => void;
|
||||
private reconnectCallback?: () => void;
|
||||
private reliableReconnectCallback?: () => void;
|
||||
private errorCallback?: Function;
|
||||
private closeCallback?: (connectFailCount: number, lastDisconnect: number) => void;
|
||||
private connectingCallback?: () => void;
|
||||
@@ -148,8 +149,11 @@ export default class WebSocketClient {
|
||||
logInfo('websocket re-established connection to', this.url);
|
||||
if (!reliableWebSockets && this.reconnectCallback) {
|
||||
this.reconnectCallback();
|
||||
} else if (reliableWebSockets && this.serverSequence && this.missedEventsCallback) {
|
||||
this.missedEventsCallback();
|
||||
} else if (reliableWebSockets) {
|
||||
this.reliableReconnectCallback?.();
|
||||
if (this.serverSequence && this.missedEventsCallback) {
|
||||
this.missedEventsCallback();
|
||||
}
|
||||
}
|
||||
} else if (this.firstConnectCallback) {
|
||||
logInfo('websocket connected to', this.url);
|
||||
@@ -295,6 +299,10 @@ export default class WebSocketClient {
|
||||
this.reconnectCallback = callback;
|
||||
}
|
||||
|
||||
public setReliableReconnectCallback(callback: () => void) {
|
||||
this.reliableReconnectCallback = callback;
|
||||
}
|
||||
|
||||
public setErrorCallback(callback: Function) {
|
||||
this.errorCallback = callback;
|
||||
}
|
||||
|
||||
@@ -106,6 +106,7 @@ const ConnectionBanner = ({
|
||||
}
|
||||
return () => {
|
||||
clearTimeoutRef(openTimeout);
|
||||
clearTimeoutRef(closeTimeout);
|
||||
};
|
||||
}, []);
|
||||
|
||||
@@ -125,7 +126,7 @@ const ConnectionBanner = ({
|
||||
}
|
||||
}, [isConnected]);
|
||||
|
||||
useEffect(() => {
|
||||
useDidUpdate(() => {
|
||||
if (appState === 'active') {
|
||||
if (!isConnected && !visible) {
|
||||
if (!openTimeout.current) {
|
||||
@@ -138,10 +139,11 @@ const ConnectionBanner = ({
|
||||
}
|
||||
}
|
||||
} else {
|
||||
setVisible(false);
|
||||
clearTimeoutRef(openTimeout);
|
||||
clearTimeoutRef(closeTimeout);
|
||||
}
|
||||
}, [appState]);
|
||||
}, [appState === 'active']);
|
||||
|
||||
useEffect(() => {
|
||||
height.value = withTiming(visible ? ANNOUNCEMENT_BAR_HEIGHT : 0, {
|
||||
@@ -149,12 +151,6 @@ const ConnectionBanner = ({
|
||||
});
|
||||
}, [visible]);
|
||||
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
clearTimeoutRef(closeTimeout);
|
||||
};
|
||||
});
|
||||
|
||||
const bannerStyle = useAnimatedStyle(() => ({
|
||||
height: height.value,
|
||||
}));
|
||||
|
||||
@@ -85,7 +85,7 @@ const MarkdownCodeBlock = ({language = '', content, textStyle}: MarkdownCodeBloc
|
||||
const screen = Screens.CODE;
|
||||
const passProps = {
|
||||
code: content,
|
||||
language,
|
||||
language: getHighlightLanguageFromNameOrAlias(language),
|
||||
textStyle,
|
||||
};
|
||||
|
||||
|
||||
@@ -142,20 +142,23 @@ export const MODAL_SCREENS_WITHOUT_BACK = new Set<string>([
|
||||
EDIT_POST,
|
||||
EDIT_PROFILE,
|
||||
EDIT_SERVER,
|
||||
EMOJI_PICKER,
|
||||
FIND_CHANNELS,
|
||||
GALLERY,
|
||||
PERMALINK,
|
||||
REACTIONS,
|
||||
]);
|
||||
|
||||
export const SCREENS_WITH_TRANSPARENT_BACKGROUND = new Set<string>([
|
||||
PERMALINK,
|
||||
REVIEW_APP,
|
||||
SNACK_BAR,
|
||||
]);
|
||||
|
||||
export const SCREENS_AS_BOTTOM_SHEET = new Set<string>([
|
||||
BOTTOM_SHEET,
|
||||
EMOJI_PICKER,
|
||||
POST_OPTIONS,
|
||||
THREAD_OPTIONS,
|
||||
PERMALINK,
|
||||
REACTIONS,
|
||||
SNACK_BAR,
|
||||
USER_PROFILE,
|
||||
]);
|
||||
|
||||
|
||||
@@ -22,6 +22,7 @@ import AppDataOperator from '@database/operator/app_data_operator';
|
||||
import ServerDataOperator from '@database/operator/server_data_operator';
|
||||
import {schema as appSchema} from '@database/schema/app';
|
||||
import {serverSchema} from '@database/schema/server';
|
||||
import {beforeUpgrade} from '@helpers/database/upgrade';
|
||||
import {getActiveServer, getServer, getServerByIdentifier} from '@queries/app/servers';
|
||||
import {querySystemValue} from '@queries/servers/system';
|
||||
import {deleteLegacyFileCache} from '@utils/file';
|
||||
@@ -63,14 +64,17 @@ class DatabaseManager {
|
||||
*/
|
||||
public init = async (serverUrls: string[]): Promise<void> => {
|
||||
await this.createAppDatabase();
|
||||
const buildNumber = DeviceInfo.getBuildNumber();
|
||||
const versionNumber = DeviceInfo.getVersion();
|
||||
await beforeUpgrade.call(this, serverUrls, versionNumber, buildNumber);
|
||||
for await (const serverUrl of serverUrls) {
|
||||
await this.initServerDatabase(serverUrl);
|
||||
}
|
||||
this.appDatabase?.operator.handleInfo({
|
||||
info: [{
|
||||
build_number: DeviceInfo.getBuildNumber(),
|
||||
build_number: buildNumber,
|
||||
created_at: Date.now(),
|
||||
version_number: DeviceInfo.getVersion(),
|
||||
version_number: versionNumber,
|
||||
}],
|
||||
prepareRecordsOnly: false,
|
||||
});
|
||||
@@ -428,7 +432,7 @@ class DatabaseManager {
|
||||
*/
|
||||
private deleteServerDatabaseFiles = async (serverUrl: string): Promise<void> => {
|
||||
const databaseName = urlSafeBase64Encode(serverUrl);
|
||||
this.deleteServerDatabaseFilesByName(databaseName);
|
||||
return this.deleteServerDatabaseFilesByName(databaseName);
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -439,7 +443,7 @@ class DatabaseManager {
|
||||
private deleteServerDatabaseFilesByName = async (databaseName: string): Promise<void> => {
|
||||
if (Platform.OS === 'ios') {
|
||||
// On iOS, we'll delete the *.db file under the shared app-group/databases folder
|
||||
deleteIOSDatabase({databaseName});
|
||||
await deleteIOSDatabase({databaseName});
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -449,14 +453,15 @@ class DatabaseManager {
|
||||
const databaseShm = `${androidFilesDir}${databaseName}.db-shm`;
|
||||
const databaseWal = `${androidFilesDir}${databaseName}.db-wal`;
|
||||
|
||||
FileSystem.unlink(databaseFile).catch(emptyFunction);
|
||||
FileSystem.unlink(databaseShm).catch(emptyFunction);
|
||||
FileSystem.unlink(databaseWal).catch(emptyFunction);
|
||||
await FileSystem.unlink(databaseFile).catch(emptyFunction);
|
||||
await FileSystem.unlink(databaseShm).catch(emptyFunction);
|
||||
await FileSystem.unlink(databaseWal).catch(emptyFunction);
|
||||
};
|
||||
|
||||
/**
|
||||
* deleteServerDatabaseFilesByName: Removes the *.db file from the App-Group directory for iOS or the files directory for Android, given the database name
|
||||
* renameDatabase: Renames the *.db file from the App-Group directory for iOS or the files directory for Android
|
||||
* @param {string} databaseName
|
||||
* @param {string} newDBName
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
private renameDatabase = async (databaseName: string, newDBName: string): Promise<void> => {
|
||||
|
||||
@@ -4,105 +4,6 @@
|
||||
// NOTE : To implement migration, please follow this document
|
||||
// https://nozbe.github.io/WatermelonDB/Advanced/Migrations.html
|
||||
|
||||
import {schemaMigrations, addColumns, createTable} from '@nozbe/watermelondb/Schema/migrations';
|
||||
import {schemaMigrations} from '@nozbe/watermelondb/Schema/migrations';
|
||||
|
||||
import {MM_TABLES} from '@constants/database';
|
||||
import {tableSchemaSpec as configSpec} from '@database/schema/server/table_schemas/config';
|
||||
import {tableSchemaSpec as teamThreadsSyncSpec} from '@database/schema/server/table_schemas/team_threads_sync';
|
||||
import {tableSchemaSpec as threadSpec} from '@database/schema/server/table_schemas/thread';
|
||||
import {tableSchemaSpec as threadInTeamSpec} from '@database/schema/server/table_schemas/thread_in_team';
|
||||
import {tableSchemaSpec as threadParticipantSpec} from '@database/schema/server/table_schemas/thread_participant';
|
||||
|
||||
const {SERVER: {
|
||||
GROUP,
|
||||
MY_CHANNEL,
|
||||
TEAM,
|
||||
THREAD,
|
||||
THREAD_PARTICIPANT,
|
||||
THREADS_IN_TEAM,
|
||||
USER,
|
||||
}} = MM_TABLES;
|
||||
|
||||
export default schemaMigrations({migrations: [
|
||||
{
|
||||
toVersion: 7,
|
||||
steps: [
|
||||
|
||||
// Along with adding the new table - TeamThreadsSync,
|
||||
// We need to clear the data in thread related tables (DROP & CREATE) to fetch the fresh data from the server
|
||||
createTable({
|
||||
...teamThreadsSyncSpec,
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-expect-error
|
||||
unsafeSql: (baseSql) => {
|
||||
return `
|
||||
${baseSql}
|
||||
DROP TABLE ${THREAD};
|
||||
DROP TABLE ${THREADS_IN_TEAM};
|
||||
DROP TABLE ${THREAD_PARTICIPANT};
|
||||
`;
|
||||
},
|
||||
}),
|
||||
createTable(threadSpec),
|
||||
createTable(threadInTeamSpec),
|
||||
createTable(threadParticipantSpec),
|
||||
],
|
||||
},
|
||||
{
|
||||
toVersion: 6,
|
||||
steps: [
|
||||
addColumns({
|
||||
table: USER,
|
||||
columns: [
|
||||
{name: 'terms_of_service_id', type: 'string'},
|
||||
{name: 'terms_of_service_create_at', type: 'number'},
|
||||
],
|
||||
}),
|
||||
],
|
||||
},
|
||||
{
|
||||
toVersion: 5,
|
||||
steps: [
|
||||
createTable(configSpec),
|
||||
],
|
||||
},
|
||||
{
|
||||
toVersion: 4,
|
||||
steps: [
|
||||
addColumns({
|
||||
table: TEAM,
|
||||
columns: [
|
||||
{name: 'invite_id', type: 'string'},
|
||||
],
|
||||
}),
|
||||
],
|
||||
},
|
||||
{
|
||||
toVersion: 3,
|
||||
steps: [
|
||||
addColumns({
|
||||
table: GROUP,
|
||||
columns: [
|
||||
{name: 'member_count', type: 'number'},
|
||||
],
|
||||
}),
|
||||
],
|
||||
},
|
||||
{
|
||||
toVersion: 2,
|
||||
steps: [
|
||||
addColumns({
|
||||
table: MY_CHANNEL,
|
||||
columns: [
|
||||
{name: 'last_fetched_at', type: 'number', isIndexed: true},
|
||||
],
|
||||
}),
|
||||
addColumns({
|
||||
table: THREAD,
|
||||
columns: [
|
||||
{name: 'last_fetched_at', type: 'number', isIndexed: true},
|
||||
],
|
||||
}),
|
||||
],
|
||||
},
|
||||
]});
|
||||
export default schemaMigrations({migrations: []});
|
||||
|
||||
@@ -39,7 +39,7 @@ import {
|
||||
} from './table_schemas';
|
||||
|
||||
export const serverSchema: AppSchema = appSchema({
|
||||
version: 7,
|
||||
version: 1,
|
||||
tables: [
|
||||
CategorySchema,
|
||||
CategoryChannelSchema,
|
||||
|
||||
@@ -45,7 +45,7 @@ const {
|
||||
describe('*** Test schema for SERVER database ***', () => {
|
||||
it('=> The SERVER SCHEMA should strictly match', () => {
|
||||
expect(serverSchema).toEqual({
|
||||
version: 7,
|
||||
version: 1,
|
||||
unsafeSql: undefined,
|
||||
tables: {
|
||||
[CATEGORY]: {
|
||||
|
||||
30
app/helpers/database/upgrade.ts
Normal file
30
app/helpers/database/upgrade.ts
Normal file
@@ -0,0 +1,30 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {getLastInstalledVersion} from '@queries/app/info';
|
||||
import {logError, logInfo} from '@utils/log';
|
||||
|
||||
import type {DatabaseManager} from '@typings/database/manager';
|
||||
import type InfoModel from '@typings/database/models/app/info';
|
||||
|
||||
export async function beforeUpgrade(serverUrls: string[], versionNumber: string, buildNumber: string) {
|
||||
const info = await getLastInstalledVersion();
|
||||
const manager: DatabaseManager | undefined = this.serverDatabases ? this : undefined;
|
||||
if (manager && serverUrls.length && info && (versionNumber !== info.versionNumber || buildNumber !== info.buildNumber)) {
|
||||
await beforeUpgradeTo450(manager, serverUrls, info);
|
||||
}
|
||||
}
|
||||
|
||||
async function beforeUpgradeTo450(manager: DatabaseManager, serverUrls: string[], info: InfoModel) {
|
||||
try {
|
||||
const buildNumber = parseInt(info.buildNumber, 10);
|
||||
if (info.versionNumber === '2.0.0' && buildNumber < 450) {
|
||||
for await (const serverUrl of serverUrls) {
|
||||
logInfo('Remove database before upgrading for', serverUrl);
|
||||
await manager.deleteServerDatabaseFiles(serverUrl);
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
logError('Error running the upgrade before build 450', e);
|
||||
}
|
||||
}
|
||||
@@ -47,11 +47,11 @@ export async function initialize() {
|
||||
|
||||
export async function start() {
|
||||
await initialize();
|
||||
await WebsocketManager.init(serverCredentials);
|
||||
|
||||
PushNotifications.init(serverCredentials.length > 0);
|
||||
|
||||
registerNavigationListeners();
|
||||
registerScreens();
|
||||
initialLaunch();
|
||||
await initialLaunch();
|
||||
WebsocketManager.init(serverCredentials);
|
||||
}
|
||||
|
||||
@@ -27,7 +27,7 @@ const initialNotificationTypes = [PushNotification.NOTIFICATION_TYPE.MESSAGE, Pu
|
||||
export const initialLaunch = async () => {
|
||||
const deepLinkUrl = await Linking.getInitialURL();
|
||||
if (deepLinkUrl) {
|
||||
launchAppFromDeepLink(deepLinkUrl, true);
|
||||
await launchAppFromDeepLink(deepLinkUrl, true);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -43,21 +43,21 @@ export const initialLaunch = async () => {
|
||||
tapped = delivered.find((d) => (d as unknown as NotificationData).ack_id === notification?.payload.ack_id) == null;
|
||||
}
|
||||
if (initialNotificationTypes.includes(notification?.payload?.type) && tapped) {
|
||||
launchAppFromNotification(convertToNotificationData(notification!), true);
|
||||
await launchAppFromNotification(convertToNotificationData(notification!), true);
|
||||
return;
|
||||
}
|
||||
|
||||
launchApp({launchType: Launch.Normal, coldStart: true});
|
||||
await launchApp({launchType: Launch.Normal, coldStart: true});
|
||||
};
|
||||
|
||||
const launchAppFromDeepLink = (deepLinkUrl: string, coldStart = false) => {
|
||||
const launchAppFromDeepLink = async (deepLinkUrl: string, coldStart = false) => {
|
||||
const props = getLaunchPropsFromDeepLink(deepLinkUrl, coldStart);
|
||||
launchApp(props);
|
||||
return launchApp(props);
|
||||
};
|
||||
|
||||
const launchAppFromNotification = async (notification: NotificationWithData, coldStart = false) => {
|
||||
const props = await getLaunchPropsFromNotification(notification, coldStart);
|
||||
launchApp(props);
|
||||
return launchApp(props);
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -28,7 +28,9 @@ export class Analytics {
|
||||
};
|
||||
|
||||
async init(config: ClientConfig) {
|
||||
this.analytics = require('@rudderstack/rudder-sdk-react-native').default;
|
||||
if (LocalConfig.RudderApiKey) {
|
||||
this.analytics = require('@rudderstack/rudder-sdk-react-native').default;
|
||||
}
|
||||
|
||||
if (this.analytics) {
|
||||
const {height, width} = Dimensions.get('window');
|
||||
@@ -123,10 +125,18 @@ export class Analytics {
|
||||
}
|
||||
|
||||
trackAPI(event: string, props?: any) {
|
||||
if (!this.analytics) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.trackEvent('api', event, props);
|
||||
}
|
||||
|
||||
trackCommand(event: string, command: string, errorMessage?: string) {
|
||||
if (!this.analytics) {
|
||||
return;
|
||||
}
|
||||
|
||||
const sanitizedCommand = this.sanitizeCommand(command);
|
||||
let props: any;
|
||||
if (errorMessage) {
|
||||
@@ -139,6 +149,9 @@ export class Analytics {
|
||||
}
|
||||
|
||||
trackAction(event: string, props?: any) {
|
||||
if (!this.analytics) {
|
||||
return;
|
||||
}
|
||||
this.trackEvent('action', event, props);
|
||||
}
|
||||
|
||||
|
||||
@@ -78,8 +78,9 @@ class SessionManager {
|
||||
};
|
||||
|
||||
private clearCookiesForServer = async (serverUrl: string) => {
|
||||
this.clearCookies(serverUrl, false);
|
||||
if (Platform.OS === 'ios') {
|
||||
this.clearCookies(serverUrl, false);
|
||||
|
||||
// Also delete any cookies that were set by react-native-webview
|
||||
this.clearCookies(serverUrl, true);
|
||||
} else if (Platform.OS === 'android') {
|
||||
|
||||
@@ -30,12 +30,12 @@ class WebsocketManager {
|
||||
private connectionTimerIDs: Record<string, DebouncedFunc<() => void>> = {};
|
||||
private isBackgroundTimerRunning = false;
|
||||
private netConnected = false;
|
||||
private previousAppState: AppStateStatus;
|
||||
private previousActiveState: boolean;
|
||||
private statusUpdatesIntervalIDs: Record<string, NodeJS.Timer> = {};
|
||||
private backgroundIntervalId: number | undefined;
|
||||
|
||||
constructor() {
|
||||
this.previousAppState = AppState.currentState;
|
||||
this.previousActiveState = AppState.currentState === 'active';
|
||||
}
|
||||
|
||||
public init = async (serverCredentials: ServerCredential[]) => {
|
||||
@@ -81,6 +81,7 @@ class WebsocketManager {
|
||||
|
||||
//client.setMissedEventsCallback(() => {}) Nothing to do on missedEvents callback
|
||||
client.setReconnectCallback(() => this.onReconnect(serverUrl));
|
||||
client.setReliableReconnectCallback(() => this.onReliableReconnect(serverUrl));
|
||||
client.setCloseCallback((connectFailCount: number, lastDisconnect: number) => this.onWebsocketClose(serverUrl, connectFailCount, lastDisconnect));
|
||||
|
||||
if (this.netConnected && ['unknown', 'active'].includes(AppState.currentState)) {
|
||||
@@ -153,23 +154,27 @@ class WebsocketManager {
|
||||
|
||||
private onFirstConnect = (serverUrl: string) => {
|
||||
this.startPeriodicStatusUpdates(serverUrl);
|
||||
handleFirstConnect(serverUrl);
|
||||
this.getConnectedSubject(serverUrl).next('connected');
|
||||
handleFirstConnect(serverUrl);
|
||||
};
|
||||
|
||||
private onReconnect = async (serverUrl: string) => {
|
||||
this.startPeriodicStatusUpdates(serverUrl);
|
||||
this.getConnectedSubject(serverUrl).next('connected');
|
||||
await handleReconnect(serverUrl);
|
||||
};
|
||||
|
||||
private onReliableReconnect = async (serverUrl: string) => {
|
||||
this.getConnectedSubject(serverUrl).next('connected');
|
||||
};
|
||||
|
||||
private onWebsocketClose = async (serverUrl: string, connectFailCount: number, lastDisconnect: number) => {
|
||||
this.getConnectedSubject(serverUrl).next('not_connected');
|
||||
if (connectFailCount <= 1) { // First fail
|
||||
await setCurrentUserStatusOffline(serverUrl);
|
||||
await handleClose(serverUrl, lastDisconnect);
|
||||
|
||||
this.stopPeriodicStatusUpdates(serverUrl);
|
||||
this.getConnectedSubject(serverUrl).next('not_connected');
|
||||
}
|
||||
};
|
||||
|
||||
@@ -205,14 +210,15 @@ class WebsocketManager {
|
||||
}
|
||||
|
||||
private onAppStateChange = async (appState: AppStateStatus) => {
|
||||
if (appState === this.previousAppState) {
|
||||
const isActive = appState === 'active';
|
||||
if (isActive === this.previousActiveState) {
|
||||
return;
|
||||
}
|
||||
|
||||
const isMain = isMainActivity();
|
||||
|
||||
this.cancelAllConnections();
|
||||
if (appState !== 'active' && !this.isBackgroundTimerRunning) {
|
||||
if (!isActive && !this.isBackgroundTimerRunning) {
|
||||
this.isBackgroundTimerRunning = true;
|
||||
this.cancelAllConnections();
|
||||
this.backgroundIntervalId = BackgroundTimer.setInterval(() => {
|
||||
@@ -221,22 +227,22 @@ class WebsocketManager {
|
||||
this.isBackgroundTimerRunning = false;
|
||||
}, WAIT_TO_CLOSE);
|
||||
|
||||
this.previousAppState = appState;
|
||||
this.previousActiveState = isActive;
|
||||
return;
|
||||
}
|
||||
|
||||
if (appState === 'active' && this.netConnected && isMain) { // Reopen the websockets only if there is connection
|
||||
if (isActive && this.netConnected && isMain) { // Reopen the websockets only if there is connection
|
||||
if (this.backgroundIntervalId) {
|
||||
BackgroundTimer.clearInterval(this.backgroundIntervalId);
|
||||
}
|
||||
this.isBackgroundTimerRunning = false;
|
||||
this.openAll();
|
||||
this.previousAppState = appState;
|
||||
this.previousActiveState = isActive;
|
||||
return;
|
||||
}
|
||||
|
||||
if (isMain) {
|
||||
this.previousAppState = appState;
|
||||
this.previousActiveState = isActive;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -248,7 +254,7 @@ class WebsocketManager {
|
||||
|
||||
this.netConnected = newState;
|
||||
|
||||
if (this.netConnected && this.previousAppState === 'active') { // Reopen the websockets only if the app is active
|
||||
if (this.netConnected && this.previousActiveState) { // Reopen the websockets only if the app is active
|
||||
this.openAll();
|
||||
return;
|
||||
}
|
||||
|
||||
24
app/queries/app/info.ts
Normal file
24
app/queries/app/info.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {Q} from '@nozbe/watermelondb';
|
||||
|
||||
import {MM_TABLES} from '@constants/database';
|
||||
import DatabaseManager from '@database/manager';
|
||||
|
||||
import type InfoModel from '@typings/database/models/app/info';
|
||||
|
||||
const {APP: {INFO}} = MM_TABLES;
|
||||
|
||||
export const getLastInstalledVersion = async () => {
|
||||
try {
|
||||
const {database} = DatabaseManager.getAppDatabaseAndOperator();
|
||||
const infos = await database.get<InfoModel>(INFO).query(
|
||||
Q.sortBy('created_at', Q.desc),
|
||||
Q.take(1),
|
||||
).fetch();
|
||||
return infos[0];
|
||||
} catch {
|
||||
return undefined;
|
||||
}
|
||||
};
|
||||
@@ -2,7 +2,7 @@
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import React, {Dispatch, RefObject, SetStateAction, useCallback} from 'react';
|
||||
import {StyleSheet} from 'react-native';
|
||||
import {Platform, StyleSheet} from 'react-native';
|
||||
|
||||
import OptionItem from '@components/option_item';
|
||||
import {SearchRef} from '@components/search';
|
||||
@@ -33,6 +33,12 @@ const Modifier = ({item, searchRef, searchValue, setSearchValue}: Props) => {
|
||||
addModifierTerm(item.term);
|
||||
}, [item.term, searchValue]);
|
||||
|
||||
const setNativeCursorPositionProp = (position?: number) => {
|
||||
setTimeout(() => {
|
||||
searchRef.current?.setNativeProps({selection: {start: position, end: position}});
|
||||
}, 50);
|
||||
};
|
||||
|
||||
const addModifierTerm = preventDoubleTap((modifierTerm) => {
|
||||
let newValue = '';
|
||||
if (!searchValue) {
|
||||
@@ -46,9 +52,16 @@ const Modifier = ({item, searchRef, searchValue, setSearchValue}: Props) => {
|
||||
setSearchValue(newValue);
|
||||
if (item.cursorPosition) {
|
||||
const position = newValue.length + item.cursorPosition;
|
||||
setTimeout(() => {
|
||||
searchRef.current?.setNativeProps({selection: {start: position, end: position}});
|
||||
}, 50);
|
||||
setNativeCursorPositionProp(position);
|
||||
|
||||
if (Platform.OS === 'android') {
|
||||
// on Android the selection set by setNativeProps is permanent thus the caret returns to the same
|
||||
// position after we stop typing for a few ms. By setting the position to undefined,
|
||||
// then the caret remains in place.
|
||||
setTimeout(() => {
|
||||
setNativeCursorPositionProp(undefined);
|
||||
}, 50);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -219,11 +219,7 @@ Appearance.addChangeListener(() => {
|
||||
});
|
||||
|
||||
export function getThemeFromState(): Theme {
|
||||
if (EphemeralStore.theme) {
|
||||
return EphemeralStore.theme;
|
||||
}
|
||||
|
||||
return getDefaultThemeByAppearance();
|
||||
return EphemeralStore.theme || getDefaultThemeByAppearance();
|
||||
}
|
||||
|
||||
// This is a temporary helper function to avoid
|
||||
@@ -479,12 +475,17 @@ export function goToScreen(name: string, title: string, passProps = {}, options
|
||||
});
|
||||
}
|
||||
|
||||
export function popTopScreen(screenId?: string) {
|
||||
if (screenId) {
|
||||
Navigation.pop(screenId);
|
||||
} else {
|
||||
const componentId = NavigationStore.getVisibleScreen();
|
||||
Navigation.pop(componentId);
|
||||
export async function popTopScreen(screenId?: string) {
|
||||
try {
|
||||
if (screenId) {
|
||||
await Navigation.pop(screenId);
|
||||
} else {
|
||||
const componentId = NavigationStore.getVisibleScreen();
|
||||
await Navigation.pop(componentId);
|
||||
}
|
||||
} catch (error) {
|
||||
// RNN returns a promise rejection if there are no screens
|
||||
// atop the root screen to pop. We'll do nothing in this case.
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -36,12 +36,12 @@ export const getIOSAppGroupDetails = (): IOSAppGroupDetails => {
|
||||
* e.g :
|
||||
* MattermostManaged.deleteDatabaseDirectory(databaseName, shouldRemoveDirectory, (error: any, success: any) => { });
|
||||
*/
|
||||
export const deleteIOSDatabase = ({
|
||||
export const deleteIOSDatabase = async ({
|
||||
databaseName = undefined,
|
||||
shouldRemoveDirectory = false,
|
||||
}: IOSDeleteDatabase) => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
MattermostManaged.deleteDatabaseDirectory(databaseName, shouldRemoveDirectory, () => null);
|
||||
return MattermostManaged.deleteDatabaseDirectory(databaseName, shouldRemoveDirectory, () => null);
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {Database} from '@nozbe/watermelondb';
|
||||
import Sentry from '@sentry/react-native';
|
||||
import {Breadcrumb, Event} from '@sentry/types';
|
||||
import {Platform} from 'react-native';
|
||||
import {Navigation} from 'react-native-navigation';
|
||||
@@ -19,11 +18,16 @@ import {logError, logWarning} from './log';
|
||||
export const BREADCRUMB_UNCAUGHT_APP_ERROR = 'uncaught-app-error';
|
||||
export const BREADCRUMB_UNCAUGHT_NON_ERROR = 'uncaught-non-error';
|
||||
|
||||
let Sentry: any;
|
||||
export function initializeSentry() {
|
||||
if (!Config.SentryEnabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!Sentry) {
|
||||
Sentry = require('@sentry/react-native');
|
||||
}
|
||||
|
||||
const dsn = getDsn();
|
||||
|
||||
if (!dsn) {
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import deepEqual from 'deep-equal';
|
||||
import merge from 'deepmerge';
|
||||
import {StatusBar, StyleSheet} from 'react-native';
|
||||
import tinyColor from 'tinycolor2';
|
||||
|
||||
import {Preferences} from '@constants';
|
||||
import {MODAL_SCREENS_WITHOUT_BACK, SCREENS_WITH_TRANSPARENT_BACKGROUND} from '@constants/screens';
|
||||
import {MODAL_SCREENS_WITHOUT_BACK, SCREENS_AS_BOTTOM_SHEET, SCREENS_WITH_TRANSPARENT_BACKGROUND} from '@constants/screens';
|
||||
import EphemeralStore from '@store/ephemeral_store';
|
||||
import NavigationStore from '@store/navigation_store';
|
||||
import {NamedStyles} from '@typings/global/styles';
|
||||
@@ -99,13 +100,25 @@ export function setNavigatorStyles(componentId: string, theme: Theme, additional
|
||||
},
|
||||
};
|
||||
|
||||
if (!SCREENS_WITH_TRANSPARENT_BACKGROUND.has(componentId)) {
|
||||
if (SCREENS_AS_BOTTOM_SHEET.has(componentId)) {
|
||||
options.topBar = {
|
||||
leftButtonColor: changeOpacity(theme.centerChannelColor, 0.56),
|
||||
background: {
|
||||
color: theme.centerChannelBg,
|
||||
},
|
||||
title: {
|
||||
color: theme.centerChannelColor,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
if (!SCREENS_WITH_TRANSPARENT_BACKGROUND.has(componentId) && !SCREENS_AS_BOTTOM_SHEET.has(componentId)) {
|
||||
options.layout = {
|
||||
componentBackgroundColor: theme.centerChannelBg,
|
||||
};
|
||||
}
|
||||
|
||||
if (!MODAL_SCREENS_WITHOUT_BACK.has(componentId) && options.topBar) {
|
||||
if (!MODAL_SCREENS_WITHOUT_BACK.has(componentId) && !SCREENS_AS_BOTTOM_SHEET.has(componentId) && options.topBar) {
|
||||
options.topBar.backButton = {
|
||||
color: theme.sidebarHeaderTextColor,
|
||||
};
|
||||
@@ -263,7 +276,8 @@ export function setThemeDefaults(theme: ExtendedTheme): Theme {
|
||||
}
|
||||
|
||||
export const updateThemeIfNeeded = (theme: Theme, force = false) => {
|
||||
if (theme !== EphemeralStore.theme || force) {
|
||||
const storedTheme = EphemeralStore.theme;
|
||||
if (!deepEqual(theme, storedTheme) || force) {
|
||||
EphemeralStore.theme = theme;
|
||||
requestAnimationFrame(() => {
|
||||
setNavigationStackStyles(theme);
|
||||
|
||||
@@ -563,7 +563,6 @@
|
||||
"mobile.post.delete_title": "Delete Post",
|
||||
"mobile.post.failed_delete": "Delete Message",
|
||||
"mobile.post.failed_retry": "Try Again",
|
||||
"mobile.posts_view.moreMsg": "More New Messages Above",
|
||||
"mobile.privacy_link": "Privacy Policy",
|
||||
"mobile.push_notification_reply.button": "Send",
|
||||
"mobile.push_notification_reply.placeholder": "Write a reply...",
|
||||
|
||||
@@ -552,5 +552,7 @@
|
||||
"channel_info.edit_header": "제목글 편집",
|
||||
"channel_info.custom_status": "사용자 정의 상태:",
|
||||
"channel_info.copy_link": "링크 복사",
|
||||
"general_settings.display": "화면"
|
||||
"general_settings.display": "화면",
|
||||
"intro.group_message": "여기에서 이 그룹과의 대화가 시작됩니다. 여기에 공유된 메시지와 파일은 다른 사람에게는 보이지 않습니다.",
|
||||
"intro.direct_message": "이곳에서 {teammate}님과의 대화가 시작됩니다. 여기에 공유된 메시지와 파일은 다른 사람에게는 보이지 않습니다."
|
||||
}
|
||||
|
||||
@@ -280,7 +280,7 @@
|
||||
"mobile.reset_status.title_ooo": "\"Ofis dışında\" devre dışı bırakılsın mı?",
|
||||
"mobile.routes.code": "{language} kodu",
|
||||
"mobile.routes.code.noLanguage": "Kod",
|
||||
"mobile.routes.custom_status": "Bir durum ayarlayın",
|
||||
"mobile.routes.custom_status": "Bir özel durum ayarlayın",
|
||||
"mobile.routes.table": "Tablo",
|
||||
"mobile.routes.user_profile": "Profil",
|
||||
"mobile.search.jump": "Son iletilere git",
|
||||
@@ -467,7 +467,7 @@
|
||||
"display_settings.clock.military": "24 saat",
|
||||
"custom_status.suggestions.title": "Öneriler",
|
||||
"custom_status.suggestions.recent_title": "Son kullanılanlar",
|
||||
"custom_status.set_status": "Bir durum ayarlayın",
|
||||
"custom_status.set_status": "Bir özel durum ayarlayın",
|
||||
"create_post.thread_reply": "Bu konuyu yanıtla...",
|
||||
"create_direct_message.title": "Doğrudan ileti oluştur",
|
||||
"channel_modal.purposeEx": "Hataların ve geliştirmelerin bildirileceği bir kanal",
|
||||
@@ -644,7 +644,7 @@
|
||||
"server_upgrade.dismiss": "Yok say",
|
||||
"server_upgrade.alert_description": "{serverDisplayName} sunucunuz desteklenmeyen bir sunucu sürümü kullanıyor. Kullanıcılar uyumluluk sorunlarından kaynaklanan çökmeler ya da uygulamanın temel işlevselliğini bozan ciddi sorunlar ile karşılaşacak. Sunucu sürümünün {supportedServerVersion} ya da üzerindeki bir sürüme güncellenmesi gerekli.",
|
||||
"your.servers": "Sunucularınız",
|
||||
"video.failed_description": "Görüntü oynatılmaya çalışılırken bir sorun çıktı.\n",
|
||||
"video.failed_description": "Görüntü oynatılmaya çalışılırken bir sorun çıktı.",
|
||||
"video.download": "Görüntüyü indir",
|
||||
"user.tutorial.long_press": "Bir kullanıcının profilini görüntülemek için bir ögeye uzun dokunun",
|
||||
"user.settings.notifications.email_threads.description": "İzlediğim konulara yazılan tüm yanıtlar ile ilgili bildirim gönderilsin",
|
||||
@@ -838,12 +838,44 @@
|
||||
"connection_banner.connected": "Bağlantı yeniden kuruldu",
|
||||
"mobile.calls_host": "sunucu",
|
||||
"mobile.calls_dismiss": "Yok say",
|
||||
"mobile.calls_call_thread": "Kanalı aç",
|
||||
"mobile.calls_call_thread": "Çağrı konusu",
|
||||
"mobile.announcement_banner.title": "Duyuru",
|
||||
"integration_selector.multiselect.submit": "Bitti",
|
||||
"extension.no_memberships.description": "İçerik paylaşabilmek için bir Mattermost sunucusundaki bir takımın üyesi olmalısınız.",
|
||||
"general_settings.report_problem": "Sorun bildirin",
|
||||
"extension.no_servers.title": "Herhangi bir sunucu ile bağlantı kurulmamış",
|
||||
"extension.no_servers.description": "İçerik paylaşabilmek için bir Mattermost sunucusundaki oturum açmış olmalısınız.",
|
||||
"extension.no_memberships.title": "Henüz herhangi bir takımın üyesi değil"
|
||||
"extension.no_memberships.title": "Henüz herhangi bir takımın üyesi değil",
|
||||
"onboarding.integrations": "Sevdiğiniz araçlarla bütünleştirin",
|
||||
"notification_settings.mentions.keywords_mention": "Anmaları tetikleyecek sözcükler",
|
||||
"notification_settings.auto_responder.message": "İleti",
|
||||
"mobile.open_dm.error": "{displayName} ile doğrudan ileti açılamadı. Lütfen bağlantınızı denetleyip yeniden deneyin.",
|
||||
"mobile.onboarding.sign_in_to_get_started": "Başlamak için oturum açın",
|
||||
"mobile.onboarding.sign_in": "Oturum aç",
|
||||
"mobile.onboarding.next": "Sonraki",
|
||||
"mobile.integration_selector.loading_options": "Ayarlar yükleniyor...",
|
||||
"mobile.integration_selector.loading_channels": "Kanallar yükleniyor...",
|
||||
"mobile.custom_list.no_results": "Herhangi bir sonuç bulunamadı",
|
||||
"mobile.create_direct_message.max_limit_reached": "Grup iletileri {maxCount} üye ile sınırlıdır",
|
||||
"mobile.camera_type.title": "Kamera ayarları",
|
||||
"mobile.calls_stop_recording": "Kaydı durdur",
|
||||
"mobile.calls_request_title": "Çağrılar şu anda etkinleştirilmemiş",
|
||||
"mobile.calls_request_message": "Çağrılar şu anda deneme kipinde çalışıyor ve bunları yalnızca sistem yöneticileri başlatabilir. Yardım almak için doğrudan sistem yöneticinizle görüşün",
|
||||
"mobile.calls_record": "Kaydet",
|
||||
"mobile.calls_rec": "kayıt",
|
||||
"mobile.calls_react": "Tepki ver",
|
||||
"mobile.calls_host_rec_title": "Kaydediyorsunuz",
|
||||
"mobile.calls_participant_rec_title": "Kaydediliyor",
|
||||
"mobile.calls_participant_rec": "Toplantı sahibi bu toplantıyı kaydetmeye başladı. Toplantıda kalarak kayıt alınmasına onay vermiş olursunuz.",
|
||||
"mobile.calls_participant_limit_title_GA": "Bu çağrının kapasitesi doldu",
|
||||
"mobile.calls_open_channel": "Kanalı aç",
|
||||
"mobile.calls_okay": "Tamam",
|
||||
"mobile.calls_limit_msg_GA": "{maxParticipants} üzerinde katılımcılı grup aramaları yapabilmek için Cloud Professional ya da Cloud Enterprise tarifesine yükseltin.",
|
||||
"mobile.calls_host_rec_stopped_title": "Kayıt durduruldu. İşleniyor...",
|
||||
"mobile.calls_host_rec_stopped": "İşlenmesi tamamlandığında, kaydı bu görüşmenin sohbet konusunda bulabilirsiniz.",
|
||||
"mobile.calls_host_rec": "Bu toplantıyı kaydediyorsunuz. Bu toplantının kaydedildiğinin herkese bildirilmesi değerlendirin.",
|
||||
"join_team.error.title": "Bir takıma katılınırken sorun çıktı",
|
||||
"join_team.error.message": "Takıma katılınırken bir sorun çıktı",
|
||||
"join_team.error.group_error": "Bu takıma katılmak için ilişkilendirilmiş bir grubun üyesi olmalısınız.",
|
||||
"connection_banner.connecting": "Bağlantı kuruluyor..."
|
||||
}
|
||||
|
||||
@@ -1116,7 +1116,7 @@
|
||||
CODE_SIGN_ENTITLEMENTS = Mattermost/Mattermost.entitlements;
|
||||
CODE_SIGN_IDENTITY = "iPhone Developer";
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||
CURRENT_PROJECT_VERSION = 448;
|
||||
CURRENT_PROJECT_VERSION = 452;
|
||||
DEVELOPMENT_TEAM = UQ8HT4Q2XM;
|
||||
ENABLE_BITCODE = NO;
|
||||
HEADER_SEARCH_PATHS = (
|
||||
@@ -1160,7 +1160,7 @@
|
||||
CODE_SIGN_ENTITLEMENTS = Mattermost/Mattermost.entitlements;
|
||||
CODE_SIGN_IDENTITY = "iPhone Developer";
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||
CURRENT_PROJECT_VERSION = 448;
|
||||
CURRENT_PROJECT_VERSION = 452;
|
||||
DEVELOPMENT_TEAM = UQ8HT4Q2XM;
|
||||
ENABLE_BITCODE = NO;
|
||||
HEADER_SEARCH_PATHS = (
|
||||
@@ -1303,7 +1303,7 @@
|
||||
CODE_SIGN_IDENTITY = "iPhone Developer";
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 448;
|
||||
CURRENT_PROJECT_VERSION = 452;
|
||||
DEBUG_INFORMATION_FORMAT = dwarf;
|
||||
DEVELOPMENT_TEAM = UQ8HT4Q2XM;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu11;
|
||||
@@ -1354,7 +1354,7 @@
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
COPY_PHASE_STRIP = NO;
|
||||
CURRENT_PROJECT_VERSION = 448;
|
||||
CURRENT_PROJECT_VERSION = 452;
|
||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||
DEVELOPMENT_TEAM = UQ8HT4Q2XM;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu11;
|
||||
|
||||
@@ -37,7 +37,7 @@
|
||||
</dict>
|
||||
</array>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>448</string>
|
||||
<string>452</string>
|
||||
<key>ITSAppUsesNonExemptEncryption</key>
|
||||
<false/>
|
||||
<key>LSRequiresIPhoneOS</key>
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>2.0.0</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>448</string>
|
||||
<string>452</string>
|
||||
<key>UIAppFonts</key>
|
||||
<array>
|
||||
<string>OpenSans-Bold.ttf</string>
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>2.0.0</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>448</string>
|
||||
<string>452</string>
|
||||
<key>NSExtension</key>
|
||||
<dict>
|
||||
<key>NSExtensionPointIdentifier</key>
|
||||
|
||||
@@ -157,7 +157,7 @@ class NotificationService: UNNotificationServiceExtension {
|
||||
os_log(
|
||||
OSLogType.default,
|
||||
"Mattermost Notifications: notification receipt failed with status %{public}@. Will call sendMessageIntent",
|
||||
httpResponse.statusCode
|
||||
String(describing: httpResponse.statusCode)
|
||||
)
|
||||
self.sendMessageIntent(notification: self.bestAttemptContent!)
|
||||
return
|
||||
@@ -172,7 +172,7 @@ class NotificationService: UNNotificationServiceExtension {
|
||||
os_log(
|
||||
OSLogType.default,
|
||||
"Mattermost Notifications: receipt retrieval failed. Retry %{public}@",
|
||||
self.retryIndex
|
||||
String(describing: self.retryIndex)
|
||||
)
|
||||
self.fetchReceipt(ackNotification)
|
||||
})
|
||||
|
||||
45
types/database/manager.ts
Normal file
45
types/database/manager.ts
Normal file
@@ -0,0 +1,45 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import type {Database} from '@nozbe/watermelondb';
|
||||
import type {AppDatabase, ServerDatabase, ServerDatabases} from '@typings/database/database';
|
||||
|
||||
export type DatabaseManager = {
|
||||
serverDatabases: ServerDatabases;
|
||||
|
||||
updateServerIdentifier: (serverUrl: string, identifier: string, displayName?: string) => Promise<void>;
|
||||
|
||||
updateServerDisplayName: (serverUrl: string, displayName: string) => Promise<void>;
|
||||
|
||||
isServerPresent: (serverUrl: string) => Promise<boolean>;
|
||||
|
||||
getActiveServerUrl: () => Promise<string|undefined>;
|
||||
|
||||
getActiveServerDisplayName: () => Promise<string|undefined>;
|
||||
|
||||
getServerUrlFromIdentifier: (identifier: string) => Promise<string|undefined>;
|
||||
|
||||
getActiveServerDatabase: () => Promise<Database|undefined>;
|
||||
|
||||
getAppDatabaseAndOperator: () => AppDatabase|undefined;
|
||||
|
||||
getServerDatabaseAndOperator: (serverUrl: string) => ServerDatabase | undefined;
|
||||
|
||||
setActiveServerDatabase: (serverUrl: string) => Promise<void>;
|
||||
|
||||
deleteServerDatabase: (serverUrl: string) => Promise<void>;
|
||||
|
||||
destroyServerDatabase: (serverUrl: string) => Promise<void>;
|
||||
|
||||
deleteServerDatabaseFiles: (serverUrl: string) => Promise<void>;
|
||||
|
||||
deleteServerDatabaseFilesByName: (databaseName: string) => Promise<void>;
|
||||
|
||||
renameDatabase: (databaseName: string, newDBName: string) => Promise<void>;
|
||||
|
||||
factoryReset: (shouldRemoveDirectory: boolean) => Promise<boolean>;
|
||||
|
||||
getDatabaseFilePath: (dbName: string) => string;
|
||||
|
||||
searchUrl: (toFind: string) => string | undefined;
|
||||
}
|
||||
Reference in New Issue
Block a user