Compare commits

..

15 Commits

Author SHA1 Message Date
Mattermost Build
2ba6b1d641 Bump app build number to 359 (#5458) (#5459)
(cherry picked from commit 362006db29)

Co-authored-by: Elias Nahum <nahumhbl@gmail.com>
2021-06-14 16:30:47 -04:00
Mattermost Build
286f05c3be fix: connect websocket when the component mounts (#5456) (#5457)
(cherry picked from commit 0e81e0e2a8)

Co-authored-by: Elias Nahum <nahumhbl@gmail.com>
2021-06-14 16:01:54 -04:00
Mattermost Build
e1329d41a3 Bump app build number to 358 (#5450) (#5451)
(cherry picked from commit d871029b9d)

Co-authored-by: Elias Nahum <nahumhbl@gmail.com>
2021-06-11 19:04:41 -04:00
Mattermost Build
bc39b38bf4 fix: reset iOS scrollView when switching channels (#5447) (#5448)
* fix: reset iOS scrollView when switching channels

* cancel animation frame after resetting the scrollview

* add useResetNativeScrollView hook

(cherry picked from commit b2d233b5ed)

Co-authored-by: Elias Nahum <nahumhbl@gmail.com>
2021-06-11 18:44:49 -04:00
Mattermost Build
9173752390 1.44 fixes (#5444) (#5446)
* fix: get status as soon as appstate is in the foreground

* re-render post list when theme changes

* remove invalid userIds from get status request

(cherry picked from commit 6335932883)

Co-authored-by: Elias Nahum <nahumhbl@gmail.com>
2021-06-11 14:46:26 -04:00
Mattermost Build
033241691c MM-36269 fix post reaction handle press (#5438) (#5442)
(cherry picked from commit c7ef19d36e)

Co-authored-by: Elias Nahum <nahumhbl@gmail.com>
2021-06-09 15:57:10 -04:00
Mattermost Build
3a4c9e75bf Fixed the warning about isCustomStatusEnabled prop in ChannelInfo (#5436) (#5439) 2021-06-08 13:58:13 -04:00
Mattermost Build
840fda2051 MM-36256 avoid path traversal for Android image picker (#5432) (#5437)
(cherry picked from commit 0a4dafa127)

Co-authored-by: Elias Nahum <nahumhbl@gmail.com>
2021-06-08 09:24:33 -04:00
Mattermost Build
afa18fb5d9 set npm version as part of the npm-dependencies CI task (#5427) (#5428)
* set npm version as part of the npm-dependencies CI task

* Set branch name to only build ios-simulator

(cherry picked from commit 37c74ecef0)

Co-authored-by: Elias Nahum <nahumhbl@gmail.com>
2021-06-05 11:47:14 -04:00
Mattermost Build
84de817451 Fix Fastlane Webhook (#5424) (#5425)
(cherry picked from commit e2bb6497df)

Co-authored-by: Elias Nahum <nahumhbl@gmail.com>
2021-06-04 11:07:08 -04:00
Mattermost Build
93777aefe6 Force CI to use npm 6.14.11 (#5414) (#5423)
(cherry picked from commit 339a4b0554)

Co-authored-by: Elias Nahum <nahumhbl@gmail.com>
2021-06-04 10:46:39 -04:00
Mattermost Build
825d8fcad3 Version number (#5421) (#5422)
* Bump app build number to  357

* Bump app version number to  1.44.0

(cherry picked from commit e87bc86f44)

Co-authored-by: Elias Nahum <nahumhbl@gmail.com>
2021-06-04 10:05:18 -04:00
Mattermost Build
abc2f30ef3 MM-34586 Custom status feature (#5220) (#5420)
(cherry picked from commit e442275c6f)

Co-authored-by: Chetanya Kandhari <chetanya.kandhari@brightscout.com>
2021-06-04 09:02:35 -04:00
Mattermost Build
0d83ac4c93 Post List & post components refactored (#5409) (#5417) 2021-06-03 14:59:42 -04:00
Mattermost Build
cf88a2ae8f Update NOTICE.txt (#5415) (#5416)
(cherry picked from commit 2e34ee4a80)

Co-authored-by: Amy Blais <29708087+amyblais@users.noreply.github.com>
2021-06-03 13:31:46 -04:00
51 changed files with 757 additions and 333 deletions

View File

@@ -132,7 +132,7 @@ android {
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
missingDimensionStrategy "RNNotifications.reactNativeVersion", "reactNative60"
versionCode 357
versionCode 359
versionName "1.44.0"
multiDexEnabled = true
testBuildType System.getProperty('testBuildType', 'debug')

View File

@@ -3,11 +3,9 @@ package com.mattermost.share;
import android.content.Context;
import android.database.Cursor;
import android.net.Uri;
import android.os.Build;
import android.provider.DocumentsContract;
import android.provider.MediaStore;
import android.provider.OpenableColumns;
import android.content.ContentUris;
import android.content.ContentResolver;
import android.os.Environment;
import android.webkit.MimeTypeMap;
@@ -15,18 +13,18 @@ import android.util.Log;
import android.text.TextUtils;
import android.os.ParcelFileDescriptor;
import java.io.*;
import java.nio.channels.FileChannel;
import java.util.Objects;
// Class based on the steveevers DocumentHelper https://gist.github.com/steveevers/a5af24c226f44bb8fdc3
public class RealPathUtil {
public static String getRealPathFromURI(final Context context, final Uri uri) {
final boolean isKitKatOrNewer = Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT;
// DocumentProvider
if (isKitKatOrNewer && DocumentsContract.isDocumentUri(context, uri)) {
if (DocumentsContract.isDocumentUri(context, uri)) {
// ExternalStorageProvider
if (isExternalStorageDocument(uri)) {
final String docId = DocumentsContract.getDocumentId(uri);
@@ -111,6 +109,7 @@ public class RealPathUtil {
int nameIndex = returnCursor.getColumnIndex(OpenableColumns.DISPLAY_NAME);
returnCursor.moveToFirst();
fileName = sanitizeFilename(returnCursor.getString(nameIndex));
returnCursor.close();
} catch (Exception e) {
// just continue to get the filename with the last segment of the path
@@ -118,7 +117,7 @@ public class RealPathUtil {
try {
if (TextUtils.isEmpty(fileName)) {
fileName = sanitizeFilename(uri.getLastPathSegment().toString().trim());
fileName = sanitizeFilename(uri.getLastPathSegment().trim());
}
@@ -127,7 +126,6 @@ public class RealPathUtil {
cacheDir.mkdirs();
}
String mimeType = getMimeType(uri.getPath());
tmpFile = new File(cacheDir, fileName);
tmpFile.createNewFile();
@@ -234,7 +232,7 @@ public class RealPathUtil {
private static void deleteRecursive(File fileOrDirectory) {
if (fileOrDirectory.isDirectory())
for (File child : fileOrDirectory.listFiles())
for (File child : Objects.requireNonNull(fileOrDirectory.listFiles()))
deleteRecursive(child);
fileOrDirectory.delete();

View File

@@ -24,7 +24,7 @@ type Props = {
initWebSocket: (additionalOptions: {forceConnection: boolean}) => void;
markChannelViewedAndReadOnReconnect: (channelId: string) => void;
setCurrentUserStatusOffline: () => void;
startPeriodicStatusUpdates: () => void;
startPeriodicStatusUpdates: (forceStatusUpdate: boolean) => void;
status: string;
stopPeriodicStatusUpdates: () => void;
}
@@ -111,10 +111,7 @@ const NetworkIndicator = ({
};
const handleConnectionChange = ({hasInternet}: ConnectionChangedEvent) => {
if (firstRun.current) {
firstRun.current = false;
handleWebSocket(true);
} else {
if (!firstRun.current) {
if (!hasInternet) {
setConnected(false);
}
@@ -142,7 +139,7 @@ const NetworkIndicator = ({
const handleWebSocket = (connect: boolean) => {
if (connect) {
initWebSocket({forceConnection: true});
startPeriodicStatusUpdates();
startPeriodicStatusUpdates(true);
} else {
closeWebSocket(true);
stopPeriodicStatusUpdates();
@@ -150,6 +147,11 @@ const NetworkIndicator = ({
}
};
useEffect(() => {
handleWebSocket(true);
firstRun.current = false;
}, []);
useEffect(() => {
const networkListener = networkConnectionListener(handleConnectionChange);
return () => networkListener.removeEventListener();
@@ -168,7 +170,6 @@ const NetworkIndicator = ({
useEffect(() => {
const handleAppStateChange = stateChange(async (appState: AppStateStatus) => {
const active = appState === 'active';
handleWebSocket(active);
if (active) {

View File

@@ -51,7 +51,10 @@ export default class PostDraft extends PureComponent {
updateNativeScrollView = (scrollViewNativeID) => {
if (this.keyboardTracker?.current) {
this.keyboardTracker.current.resetScrollView(scrollViewNativeID);
const resetScrollView = requestAnimationFrame(() => {
this.keyboardTracker.current.resetScrollView(scrollViewNativeID);
cancelAnimationFrame(resetScrollView);
});
}
};

View File

@@ -48,7 +48,7 @@ const Reaction = ({count, emojiName, highlight, onPress, onLongPress, theme}: Re
const handlePress = useCallback(() => {
onPress(emojiName, highlight);
}, []);
}, [highlight]);
return (
<TouchableWithFeedback

View File

@@ -6,6 +6,7 @@ import {injectIntl, intlShape} from 'react-intl';
import {DeviceEventEmitter, FlatList, Platform, StyleSheet, ViewToken} from 'react-native';
import {DeepLinkTypes, NavigationTypes} from '@constants';
import {useResetNativeScrollView} from '@hooks';
import {Posts} from '@mm-redux/constants';
import EventEmitter from '@mm-redux/utils/event_emitter';
import {getDateForDateLine, isCombinedUserActivityPost, isDateLine, isStartOfNewMessages} from '@mm-redux/utils/post_list';
@@ -217,7 +218,7 @@ const PostList = ({
{...postProps}
/>
);
}, [postIds]);
}, [postIds, theme]);
const scrollToIndex = useCallback((index: number, animated = true) => {
flatListRef.current?.scrollToIndex({
@@ -228,6 +229,8 @@ const PostList = ({
});
}, []);
useResetNativeScrollView(scrollViewNativeID, postIds);
useEffect(() => {
const scrollToBottom = (screen: string) => {
if (screen === location) {

View File

@@ -2,3 +2,6 @@
// See LICENSE.txt for license information.
export {default as useDidUpdate} from './did_update';
export {usePermanentSidebar, useSplitView} from './permanent_sidebar';
export {useResetNativeScrollView} from './reset_native_scrollview';
export {useShowMoreAnimatedStyle} from './show_more';

View File

@@ -0,0 +1,24 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {useEffect, useRef} from 'react';
import {UPDATE_NATIVE_SCROLLVIEW} from '@constants/post_draft';
import EventEmitter from '@mm-redux/utils/event_emitter';
export const useResetNativeScrollView = (scrollViewNativeID: string | undefined, postIds: string[]) => {
const prevPostCount = useRef(postIds.length);
useEffect(() => {
if (scrollViewNativeID) {
EventEmitter.emit(UPDATE_NATIVE_SCROLLVIEW, scrollViewNativeID);
}
}, [scrollViewNativeID]);
useEffect(() => {
if (!prevPostCount.current && postIds.length) {
EventEmitter.emit(UPDATE_NATIVE_SCROLLVIEW, scrollViewNativeID);
}
prevPostCount.current = postIds.length;
}, [postIds]);
};

View File

@@ -30,11 +30,6 @@ function loadTranslation(locale) {
localeData = require('react-intl/locale-data/de');
momentData = require('moment/locale/de');
break;
case 'en-AU':
translations = require('@assets/i18n/en_AU.json');
localeData = require('react-intl/locale-data/en');
momentData = require('moment/locale/en-au');
break;
case 'es':
translations = require('@assets/i18n/es.json');
localeData = require('react-intl/locale-data/es');

View File

@@ -7,7 +7,6 @@ const languages: Record<string, string> = keyMirror({
bg: null,
de: null,
en: null,
'en-AU': null,
es: null,
fr: null,
hu: null,

View File

@@ -76,7 +76,9 @@ const launchApp = (credentials) => {
resetToSelectServer(emmProvider.allowOtherServers);
}
telemetry.startSinceLaunch(credentials ? 'Launch on Channel Screen' : 'Launch on Server Screen');
if (!EphemeralStore.appStarted) {
telemetry.startSinceLaunch(credentials ? 'Launch on Channel Screen' : 'Launch on Server Screen');
}
});
EphemeralStore.appStarted = true;

View File

@@ -802,29 +802,32 @@ export function searchProfiles(term: string, options: any = {}): ActionFunc {
}
let statusIntervalId: NodeJS.Timeout|null;
export function startPeriodicStatusUpdates(): ActionFunc {
export function startPeriodicStatusUpdates(forceStatusUpdate = false): ActionFunc {
return async (dispatch: DispatchFunc, getState: GetStateFunc) => {
if (statusIntervalId) {
clearInterval(statusIntervalId);
}
statusIntervalId = setInterval(
() => {
const {statuses} = getState().entities.users;
const getStatusForUsers = () => {
const {statuses} = getState().entities.users;
if (!statuses) {
return;
}
if (!statuses) {
return;
}
const userIds = Object.keys(statuses);
if (!userIds.length) {
return;
}
const userIds = Object.keys(statuses).filter((u) => u);
if (!userIds.length) {
return;
}
dispatch(getStatusesByIds(userIds));
},
General.STATUS_INTERVAL,
);
dispatch(getStatusesByIds(userIds));
};
statusIntervalId = setInterval(getStatusForUsers, General.STATUS_INTERVAL);
if (forceStatusUpdate) {
getStatusForUsers();
}
return {data: true};
};

View File

@@ -61,10 +61,7 @@ export default class ChannelIOS extends ChannelBase {
renderDraftArea = true;
component = (
<>
<ChannelPostList
updateNativeScrollView={this.updateNativeScrollView}
registerTypingAnimation={this.registerTypingAnimation}
/>
<ChannelPostList registerTypingAnimation={this.registerTypingAnimation}/>
</>
);
}

View File

@@ -10,7 +10,7 @@ import {General} from '@mm-redux/constants';
import EventEmitter from '@mm-redux/utils/event_emitter';
import {showModal, showModalOverCurrentContext} from '@actions/navigation';
import {UPDATE_NATIVE_SCROLLVIEW, TYPING_VISIBLE} from '@constants/post_draft';
import {TYPING_VISIBLE} from '@constants/post_draft';
import CompassIcon from '@components/compass_icon';
import PushNotifications from '@init/push_notifications';
import EphemeralStore from '@store/ephemeral_store';
@@ -127,7 +127,6 @@ export default class ChannelBase extends PureComponent {
requestAnimationFrame(() => {
this.props.actions.getChannelStats(this.props.currentChannelId);
this.updateNativeScrollView();
});
}
}
@@ -297,10 +296,6 @@ export default class ChannelBase extends PureComponent {
});
};
updateNativeScrollView = () => {
EventEmitter.emit(UPDATE_NATIVE_SCROLLVIEW, this.props.currentChannelId);
};
render() {
// Overriden in channel.android.js and channel.ios.js
// but defined here for channel_base.test.js

View File

@@ -34,7 +34,6 @@ export default class ChannelPostList extends PureComponent {
postIds: PropTypes.array,
refreshing: PropTypes.bool.isRequired,
theme: PropTypes.object.isRequired,
updateNativeScrollView: PropTypes.func,
registerTypingAnimation: PropTypes.func.isRequired,
};
@@ -62,11 +61,6 @@ export default class ChannelPostList extends PureComponent {
if (this.props.channelId !== prevProps.channelId) {
this.isLoadingMoreTop = false;
}
if (!prevProps.postIds?.length && this.props.postIds?.length > 0 && this.props.updateNativeScrollView) {
// This is needed to re-bind the scrollview natively when getting the first posts
this.props.updateNativeScrollView();
}
}
componentWillUnmount() {

View File

@@ -32,7 +32,7 @@ function makeMapStateToProps() {
let teammateId;
let isTeammateGuest = false;
let customStatusEnabled;
let customStatusEnabled = false;
let customStatus;
const isDirectMessage = currentChannel.type === General.DM_CHANNEL;

View File

@@ -171,6 +171,7 @@ export default class PinnedPosts extends PureComponent {
ref={this.setListRef}
contentContainerStyle={style.sectionList}
data={postIds}
extraData={theme}
keyExtractor={this.keyExtractor}
keyboardShouldPersistTaps='always'
keyboardDismissMode='interactive'

View File

@@ -171,6 +171,7 @@ export default class RecentMentions extends PureComponent {
ref={this.setListRef}
contentContainerStyle={style.sectionList}
data={postIds}
extraData={theme}
keyExtractor={this.keyExtractor}
keyboardShouldPersistTaps='always'
keyboardDismissMode='interactive'

View File

@@ -173,6 +173,7 @@ export default class SavedPosts extends PureComponent {
ref={this.setListRef}
contentContainerStyle={style.sectionList}
data={postIds}
extraData={theme}
keyExtractor={this.keyExtractor}
keyboardShouldPersistTaps='always'
keyboardDismissMode='interactive'

View File

@@ -19,12 +19,10 @@ export const PERF_MARKERS = {
class Telemetry {
metrics: PerfMetric[];
currentMetrics: Record<string, PerfMetric>;
started: boolean;
constructor() {
this.metrics = [];
this.currentMetrics = {};
this.started = false;
}
getMetrics = () => {
@@ -64,17 +62,14 @@ class Telemetry {
}
startSinceLaunch(extra = '') {
if (!this.started) {
this.started = true;
getTimeSinceStartup().then((endTime) => {
this.metrics.push({
extra,
name: PERF_MARKERS.START_SINCE_LAUNCH,
startTime: 0,
endTime,
});
getTimeSinceStartup().then((endTime) => {
this.metrics.push({
extra,
name: PERF_MARKERS.START_SINCE_LAUNCH,
startTime: 0,
endTime,
});
}
});
}
remove(names = []) {

View File

@@ -322,6 +322,7 @@
"mobile.loading_options": "Зареждане на опции ...",
"mobile.loading_posts": "Зареждане на съобщения ...",
"mobile.login_options.choose_title": "Изберете вашия метод за вход",
"mobile.long_post_title": "{channelName} - Публикуване",
"mobile.mailTo.error.text": "Неуспешно отваряне клиент за ел. поща.",
"mobile.mailTo.error.title": "Грешка",
"mobile.managed.blocked_by": "Блокиран от {vendor}",
@@ -607,6 +608,8 @@
"post_info.del": "Изтрий",
"post_info.edit": "Редактирай",
"post_info.guest": "ГОСТ",
"post_info.message.show_less": "Показване на по-малко",
"post_info.message.show_more": "Покажи повече",
"post_info.system": "Система",
"post_message_view.edited": "(редактирано)",
"posts_view.newMsg": "Нови съобщения",

View File

@@ -288,6 +288,7 @@
"mobile.loading_options": "Lade Optionen...",
"mobile.loading_posts": "Lade Nachrichten...",
"mobile.login_options.choose_title": "Wählen Sie ihre Anmeldemethode",
"mobile.long_post_title": "{channelName} - Nachricht",
"mobile.mailTo.error.title": "Fehler",
"mobile.managed.blocked_by": "Blockiert durch {vendor}",
"mobile.managed.exit": "Beenden",
@@ -539,6 +540,8 @@
"post_info.del": "Löschen",
"post_info.edit": "Bearbeiten",
"post_info.guest": "GAST",
"post_info.message.show_less": "Weniger anzeigen",
"post_info.message.show_more": "Mehr anzeigen",
"post_info.system": "System",
"post_message_view.edited": "(bearbeitet)",
"posts_view.newMsg": "Neue Nachrichten",

View File

@@ -125,15 +125,6 @@
"create_comment.addComment": "Add a comment...",
"create_post.deactivated": "You are viewing an archived channel with a deactivated user.",
"create_post.write": "Write to {channelDisplayName}",
"custom_status.failure_message": "Failed to update status. Try again",
"custom_status.set_status": "Set a status",
"custom_status.suggestions.in_a_meeting": "In a meeting",
"custom_status.suggestions.on_a_vacation": "Away on holiday",
"custom_status.suggestions.out_for_lunch": "Out for lunch",
"custom_status.suggestions.out_sick": "Out sick",
"custom_status.suggestions.recent_title": "RECENT",
"custom_status.suggestions.title": "SUGGESTIONS",
"custom_status.suggestions.working_from_home": "Working from home",
"date_separator.today": "Today",
"date_separator.yesterday": "Yesterday",
"edit_post.editPost": "Edit the post...",
@@ -261,8 +252,6 @@
"mobile.create_channel.public": "New Public Channel",
"mobile.create_post.read_only": "This channel is read-only",
"mobile.custom_list.no_results": "No Results",
"mobile.custom_status.choose_emoji": "Choose an emoji",
"mobile.custom_status.modal_confirm": "Done",
"mobile.display_settings.sidebar": "Sidebar",
"mobile.display_settings.theme": "Theme",
"mobile.document_preview.failed_description": "An error occurred while opening the document. Please make sure you have a {fileType} viewer installed and try again.\n",
@@ -333,6 +322,7 @@
"mobile.loading_options": "Loading Options...",
"mobile.loading_posts": "Loading messages...",
"mobile.login_options.choose_title": "Choose your login method",
"mobile.long_post_title": "{channelName} - Post",
"mobile.mailTo.error.text": "Unable to open an email client.",
"mobile.mailTo.error.title": "Error",
"mobile.managed.blocked_by": "Blocked by {vendor}",
@@ -469,7 +459,7 @@
"mobile.reset_status.alert_cancel": "Cancel",
"mobile.reset_status.alert_ok": "Ok",
"mobile.reset_status.title_ooo": "Disable 'Out Of Office'?",
"mobile.retry_message": "Fetching messages failed. Tap here to try again.",
"mobile.retry_message": "Refreshing messages failed. Pull up to try again.",
"mobile.routes.channelInfo": "Info",
"mobile.routes.channelInfo.createdBy": "Created by {creator} on ",
"mobile.routes.channelInfo.delete_channel": "Archive Channel",
@@ -481,7 +471,6 @@
"mobile.routes.channel_members.action_message_confirm": "Are you sure you want to remove the selected members from the channel?",
"mobile.routes.code": "{language} Code",
"mobile.routes.code.noLanguage": "Code",
"mobile.routes.custom_status": "Set a Status",
"mobile.routes.edit_profile": "Edit Profile",
"mobile.routes.login": "Login",
"mobile.routes.loginOptions": "Login Chooser",
@@ -619,6 +608,8 @@
"post_info.del": "Delete",
"post_info.edit": "Edit",
"post_info.guest": "GUEST",
"post_info.message.show_less": "Show less",
"post_info.message.show_more": "Show more",
"post_info.system": "System",
"post_message_view.edited": "(edited)",
"posts_view.newMsg": "New Messages",
@@ -677,7 +668,6 @@
"user.settings.general.lastName": "Last Name",
"user.settings.general.nickname": "Nickname",
"user.settings.general.position": "Position",
"user.settings.general.status": "Status",
"user.settings.general.username": "Username",
"user.settings.modal.display": "Display",
"user.settings.modal.notifications": "Notifications",

View File

@@ -322,6 +322,7 @@
"mobile.loading_options": "Cargando Opciones...",
"mobile.loading_posts": "Cargando Mensajes...",
"mobile.login_options.choose_title": "Selecciona un método para iniciar sesión",
"mobile.long_post_title": "{channelName} - Mensaje",
"mobile.mailTo.error.text": "No se pudo abrir la app de correos electrónicos.",
"mobile.mailTo.error.title": "Error",
"mobile.managed.blocked_by": "Bloqueado por {vendor}",
@@ -607,6 +608,8 @@
"post_info.del": "Eliminar",
"post_info.edit": "Editar",
"post_info.guest": "HUÉSPEDES",
"post_info.message.show_less": "Ver menos",
"post_info.message.show_more": "Ver más",
"post_info.system": "Sistema",
"post_message_view.edited": "(editado)",
"posts_view.newMsg": "Nuevos Mensajes",

View File

@@ -310,6 +310,7 @@
"mobile.loading_options": "Chargement des paramètres...",
"mobile.loading_posts": "Chargement des messages...",
"mobile.login_options.choose_title": "Spécifiez votre méthode de connexion",
"mobile.long_post_title": "{channelName} - Publication",
"mobile.mailTo.error.text": "Impossible d'ouvrir un client de messagerie.",
"mobile.mailTo.error.title": "Erreur",
"mobile.managed.blocked_by": "Bloqué par {vendor}",
@@ -591,6 +592,8 @@
"post_info.del": "Supprimer",
"post_info.edit": "Modifier",
"post_info.guest": "INVITÉ",
"post_info.message.show_less": "Afficher moins",
"post_info.message.show_more": "Afficher plus",
"post_info.system": "Système",
"post_message_view.edited": "(édité)",
"posts_view.newMsg": "Nouveaux messages",

View File

@@ -322,6 +322,7 @@
"mobile.loading_options": "Beállítások betöltése...",
"mobile.loading_posts": "Üzenetek betöltése...",
"mobile.login_options.choose_title": "Válassza ki a bejelentkezési módját",
"mobile.long_post_title": "{channelName} - Küldés",
"mobile.mailTo.error.text": "Nem lehet megnyitni az email kliens-t.",
"mobile.mailTo.error.title": "Hiba",
"mobile.managed.blocked_by": "Letiltotta: {vendor}",
@@ -607,6 +608,8 @@
"post_info.del": "Törlés",
"post_info.edit": "Szerkesztés",
"post_info.guest": "VENDÉG",
"post_info.message.show_less": "Kevesebb",
"post_info.message.show_more": "Több",
"post_info.system": "Rendszer",
"post_message_view.edited": "(szerkesztett)",
"posts_view.newMsg": "Új üzenetek",

View File

@@ -273,6 +273,7 @@
"mobile.loading_options": "Carimento Opzioni...",
"mobile.loading_posts": "Caricamento messaggi...",
"mobile.login_options.choose_title": "Seleziona il tuo metodo di login",
"mobile.long_post_title": "{channelName} - Pubblicazione",
"mobile.managed.blocked_by": "Bloccati da {vendor}",
"mobile.managed.exit": "Esci",
"mobile.managed.jailbreak": "Dispositivi con jailbreak considerati non sicuri da {vendor}, per favore uscire dall'app.",
@@ -532,6 +533,8 @@
"post_info.del": "Cancella",
"post_info.edit": "Modifica",
"post_info.guest": "OSPITE",
"post_info.message.show_less": "Mostra di meno",
"post_info.message.show_more": "Mostra di più",
"post_info.system": "Sistema",
"post_message_view.edited": "(modificato)",
"posts_view.newMsg": "Nuovo Messaggio",

View File

@@ -125,15 +125,6 @@
"create_comment.addComment": "コメントを追加する...",
"create_post.deactivated": "無効化されたユーザーのいるアーカイブされたチャンネルを見ています。",
"create_post.write": "{channelDisplayName}へ投稿する",
"custom_status.failure_message": "ステータスを更新できませんでした。再度試してみてください",
"custom_status.set_status": "ステータスを設定する",
"custom_status.suggestions.in_a_meeting": "ミーティング中",
"custom_status.suggestions.on_a_vacation": "休暇中",
"custom_status.suggestions.out_for_lunch": "ランチ中",
"custom_status.suggestions.out_sick": "病欠",
"custom_status.suggestions.recent_title": "最新",
"custom_status.suggestions.title": "おすすめ",
"custom_status.suggestions.working_from_home": "自宅での作業",
"date_separator.today": "今日",
"date_separator.yesterday": "昨日",
"edit_post.editPost": "投稿を編集する...",
@@ -261,8 +252,6 @@
"mobile.create_channel.public": "新しい公開チャンネル",
"mobile.create_post.read_only": "このチャンネルは読み取り専用です",
"mobile.custom_list.no_results": "該当するものはありません",
"mobile.custom_status.choose_emoji": "絵文字を選択する",
"mobile.custom_status.modal_confirm": "完了",
"mobile.display_settings.sidebar": "サイドバー",
"mobile.display_settings.theme": "テーマ",
"mobile.document_preview.failed_description": "文書を開く際にエラーが発生しました。{fileType}ビューワーがインストールされていることを確認し、再度実行してください。\n",
@@ -333,6 +322,7 @@
"mobile.loading_options": "オプションを読み込んでいます...",
"mobile.loading_posts": "メッセージをロードしています...",
"mobile.login_options.choose_title": "ログイン方法を選択してください",
"mobile.long_post_title": "{channelName} - 投稿",
"mobile.mailTo.error.text": "電子メールクライアントを開けませんでした。",
"mobile.mailTo.error.title": "エラー",
"mobile.managed.blocked_by": "{vendor}によりブロックされました",
@@ -469,7 +459,7 @@
"mobile.reset_status.alert_cancel": "キャンセル",
"mobile.reset_status.alert_ok": "了解",
"mobile.reset_status.title_ooo": "外出中を無効化しますか?",
"mobile.retry_message": "メッセージを取得できませんでした。再度更新するにはここをタップしてください。",
"mobile.retry_message": "メッセージを更新できませんでした。再度更新するには画面を引き上げてください。",
"mobile.routes.channelInfo": "情報",
"mobile.routes.channelInfo.createdBy": "{creator} によって作成 ",
"mobile.routes.channelInfo.delete_channel": "チャンネルをアーカイブする",
@@ -481,7 +471,6 @@
"mobile.routes.channel_members.action_message_confirm": "本当に選択したメンバーをチャンネルから削除しますか?",
"mobile.routes.code": "{language} コード",
"mobile.routes.code.noLanguage": "コード",
"mobile.routes.custom_status": "ステータスを設定する",
"mobile.routes.edit_profile": "プロフィールを編集する",
"mobile.routes.login": "ログイン",
"mobile.routes.loginOptions": "ログイン選択",
@@ -496,7 +485,6 @@
"mobile.routes.user_profile": "プロフィール",
"mobile.routes.user_profile.edit": "編集する",
"mobile.routes.user_profile.local_time": "ローカルタイム",
"mobile.routes.user_profile.organization": "ORGANIZATION",
"mobile.routes.user_profile.send_message": "メッセージを送信",
"mobile.search.after_modifier_description": "指定された日付以降の投稿を探す",
"mobile.search.before_modifier_description": "指定された日付以前の投稿を探す",
@@ -579,10 +567,8 @@
"more_channels.dropdownTitle": "表示する",
"more_channels.noMore": "参加できるチャンネルがありません",
"more_channels.publicChannels": "公開チャンネル",
"more_channels.sharedChannels": "共有チャンネル",
"more_channels.showArchivedChannels": "表示: アーカイブチャンネル",
"more_channels.showPublicChannels": "表示: 公開チャンネル",
"more_channels.showSharedChannels": "表示: 共有チャンネル",
"more_channels.title": "他のチャンネル",
"msg_typing.areTyping": "{users}と{last}が入力しています...",
"msg_typing.isTyping": "{user}が入力しています...",
@@ -619,6 +605,8 @@
"post_info.del": "削除",
"post_info.edit": "編集する",
"post_info.guest": "ゲスト",
"post_info.message.show_less": "表示を少なくする",
"post_info.message.show_more": "さらに表示",
"post_info.system": "システム",
"post_message_view.edited": "(編集済)",
"posts_view.newMsg": "新しいメッセージ",
@@ -677,7 +665,6 @@
"user.settings.general.lastName": "苗字(ラストネーム)",
"user.settings.general.nickname": "ニックネーム",
"user.settings.general.position": "役職",
"user.settings.general.status": "ステータス",
"user.settings.general.username": "ユーザー名",
"user.settings.modal.display": "表示",
"user.settings.modal.notifications": "通知",

View File

@@ -286,6 +286,7 @@
"mobile.loading_options": "Loading Options...",
"mobile.loading_posts": "메시지 불러오는 중...",
"mobile.login_options.choose_title": "Choose your login method",
"mobile.long_post_title": "{channelName} - Post",
"mobile.mailTo.error.title": "에러",
"mobile.managed.blocked_by": "Blocked by {vendor}",
"mobile.managed.exit": "종료",
@@ -556,6 +557,8 @@
"post_info.del": "삭제",
"post_info.edit": "편집",
"post_info.guest": "게스트",
"post_info.message.show_less": "감추기",
"post_info.message.show_more": "더보기",
"post_info.system": "시스템",
"post_message_view.edited": "(수정됨)",
"posts_view.newMsg": "새로운 메시지",

View File

@@ -125,15 +125,6 @@
"create_comment.addComment": "Voeg commentaar toe...",
"create_post.deactivated": "Je bekijkt een gearchiveerd kanaal met een gedeactiveerde gebruiker.",
"create_post.write": "Schrijven naar {channelDisplayName}",
"custom_status.failure_message": "Het bijwerken van de status is mislukt. Probeer opnieuw",
"custom_status.set_status": "Een status instellen",
"custom_status.suggestions.in_a_meeting": "In vergadering",
"custom_status.suggestions.on_a_vacation": "Op vakantie",
"custom_status.suggestions.out_for_lunch": "Lunch",
"custom_status.suggestions.out_sick": "Ziek",
"custom_status.suggestions.recent_title": "RECENT",
"custom_status.suggestions.title": "SUGGESTIES",
"custom_status.suggestions.working_from_home": "Thuiswerk",
"date_separator.today": "Vandaag",
"date_separator.yesterday": "Gisteren",
"edit_post.editPost": "Bewerk het bericht...",
@@ -261,8 +252,6 @@
"mobile.create_channel.public": "Nieuw Publiek kanaal",
"mobile.create_post.read_only": "Dit kanaal is alleen-lezen",
"mobile.custom_list.no_results": "Geen resultaten",
"mobile.custom_status.choose_emoji": "Kies een emoticon",
"mobile.custom_status.modal_confirm": "Klaar",
"mobile.display_settings.sidebar": "Zijbalk",
"mobile.display_settings.theme": "Thema",
"mobile.document_preview.failed_description": "Er is een fout opgetreden tijdens het openen van het document. Zorg ervoor dat je een {fileType} - viewer geïnstalleerd en probeer het opnieuw. \n",
@@ -333,6 +322,7 @@
"mobile.loading_options": "Opties aan het laden...",
"mobile.loading_posts": "Berichten aan het laden...",
"mobile.login_options.choose_title": "Kies je loginmethode",
"mobile.long_post_title": "{channelName} - Bericht",
"mobile.mailTo.error.text": "Kan het e-mailprogramma niet openen.",
"mobile.mailTo.error.title": "Fout",
"mobile.managed.blocked_by": "Geblokkeerd door {vendor}",
@@ -469,7 +459,7 @@
"mobile.reset_status.alert_cancel": "Annuleren",
"mobile.reset_status.alert_ok": "Ok",
"mobile.reset_status.title_ooo": "\"Out of Office\" Uitschakelen?",
"mobile.retry_message": "Het ophalen van berichten is mislukt. Tik hier om het opnieuw te proberen.",
"mobile.retry_message": "Het vernieuwen van berichten is mislukt. Probeer het nog eens.",
"mobile.routes.channelInfo": "Info",
"mobile.routes.channelInfo.createdBy": "Gemaakt door {creator} op ",
"mobile.routes.channelInfo.delete_channel": "Kanaal Archiveren",
@@ -481,7 +471,6 @@
"mobile.routes.channel_members.action_message_confirm": "Weet je zeker dat je de geselecteerde leden uit het kanaal wilt verwijderen?",
"mobile.routes.code": "{language} Code",
"mobile.routes.code.noLanguage": "Code",
"mobile.routes.custom_status": "Een status instellen",
"mobile.routes.edit_profile": "Bewerk Profiel",
"mobile.routes.login": "Aanmelden",
"mobile.routes.loginOptions": "Aanmeldkiezer",
@@ -619,6 +608,8 @@
"post_info.del": "Verwijderen",
"post_info.edit": "Bewerken",
"post_info.guest": "GAST",
"post_info.message.show_less": "Toon Minder",
"post_info.message.show_more": "Toon Meer",
"post_info.system": "Systeem",
"post_message_view.edited": "(bewerkt)",
"posts_view.newMsg": "Nieuwe Berichten",
@@ -677,7 +668,6 @@
"user.settings.general.lastName": "Achternaam",
"user.settings.general.nickname": "Roepnaam",
"user.settings.general.position": "Positie",
"user.settings.general.status": "Status",
"user.settings.general.username": "Gebruikersnaam",
"user.settings.modal.display": "Weergave",
"user.settings.modal.notifications": "Meldingen",

View File

@@ -260,6 +260,7 @@
"mobile.loading_options": "Wczytuję Opcje...",
"mobile.loading_posts": "Wczytuję Wiadomości...",
"mobile.login_options.choose_title": "Wybierz metodę logowania",
"mobile.long_post_title": "{channelName} - Wiadomość",
"mobile.managed.blocked_by": "Zablokowany przez {vendor}",
"mobile.managed.exit": "Wyjście",
"mobile.managed.jailbreak": "Urządzenia z Jailbreakiem nie są zaufane przez {vendor}, proszę wyjść z aplikacji.",
@@ -502,6 +503,8 @@
"post_info.del": "Usuń",
"post_info.edit": "Edytuj",
"post_info.guest": "GOŚĆ",
"post_info.message.show_less": "Pokaż Mniej",
"post_info.message.show_more": "Pokaż Więcej",
"post_info.system": "System",
"post_message_view.edited": "(edytowany)",
"posts_view.newMsg": "Nowe Wiadomości",

View File

@@ -322,6 +322,7 @@
"mobile.loading_options": "Carregando Opções...",
"mobile.loading_posts": "Carregando Mensagens...",
"mobile.login_options.choose_title": "Escolha seu método de login",
"mobile.long_post_title": "{channelName} - Post",
"mobile.mailTo.error.text": "Não foi possível abrir um cliente de e-mail.",
"mobile.mailTo.error.title": "Erro",
"mobile.managed.blocked_by": "Bloqueado por {vendor}",
@@ -604,6 +605,8 @@
"post_info.del": "Deletar",
"post_info.edit": "Editar",
"post_info.guest": "CONVIDADO",
"post_info.message.show_less": "Mostrar menos",
"post_info.message.show_more": "Mostrar mais",
"post_info.system": "Sistema",
"post_message_view.edited": "(editado)",
"posts_view.newMsg": "Novas Mensagens",

View File

@@ -322,6 +322,7 @@
"mobile.loading_options": "Încărcare Opțiuni...",
"mobile.loading_posts": "Se încarcă mesajele ...",
"mobile.login_options.choose_title": "Alegeți metoda dvs. de conectare",
"mobile.long_post_title": "{channelName} - Postați",
"mobile.mailTo.error.text": "Nu se poate deschide un client de e-mail.",
"mobile.mailTo.error.title": "Eroare",
"mobile.managed.blocked_by": "Blocat de {vendor}",
@@ -604,6 +605,8 @@
"post_info.del": "Șterge",
"post_info.edit": "Editați",
"post_info.guest": "OASPETE",
"post_info.message.show_less": "Afișați mai puține",
"post_info.message.show_more": "Detalii",
"post_info.system": "Sistem",
"post_message_view.edited": "(editat)",
"posts_view.newMsg": "Mesaje noi",

View File

@@ -322,6 +322,7 @@
"mobile.loading_options": "Загрузка опций...",
"mobile.loading_posts": "Загрузка сообщений...",
"mobile.login_options.choose_title": "Выберите метод входа",
"mobile.long_post_title": "{channelName} - Сообщение",
"mobile.mailTo.error.text": "Невозможно открыть почтовый клиент.",
"mobile.mailTo.error.title": "Ошибка",
"mobile.managed.blocked_by": "Заблокирован {vendor}",
@@ -604,6 +605,8 @@
"post_info.del": "Удалить",
"post_info.edit": "Изменить",
"post_info.guest": "ГОСТЬ",
"post_info.message.show_less": "Показать меньше",
"post_info.message.show_more": "Показать больше",
"post_info.system": "Система",
"post_message_view.edited": "(отредактировано)",
"posts_view.newMsg": "Новые сообщения",

View File

@@ -125,15 +125,6 @@
"create_comment.addComment": "Lägg till en kommentar...",
"create_post.deactivated": "Du visar en arkiverad kanal med ett avstängt användarkonto.",
"create_post.write": "Skriv i kanalen {channelDisplayName}",
"custom_status.failure_message": "Statusuppdateringen misslyckades. Försök igen",
"custom_status.set_status": "Ange status",
"custom_status.suggestions.in_a_meeting": "I möte",
"custom_status.suggestions.on_a_vacation": "På ledighet",
"custom_status.suggestions.out_for_lunch": "På lunch",
"custom_status.suggestions.out_sick": "Sjuk",
"custom_status.suggestions.recent_title": "NYLIGEN",
"custom_status.suggestions.title": "FÖRSLAG",
"custom_status.suggestions.working_from_home": "Arbetar hemifrån",
"date_separator.today": "Idag",
"date_separator.yesterday": "Igår",
"edit_post.editPost": "Redigera meddelandet...",
@@ -261,8 +252,6 @@
"mobile.create_channel.public": "Publika kanaler",
"mobile.create_post.read_only": "Kanalen är read-only",
"mobile.custom_list.no_results": "Inga resultat",
"mobile.custom_status.choose_emoji": "Välj en emoji",
"mobile.custom_status.modal_confirm": "Klar",
"mobile.display_settings.sidebar": "Sidofält",
"mobile.display_settings.theme": "Tema",
"mobile.document_preview.failed_description": "Ett fel inträffade när dokumentet skulle öppnas. Kontrollera att du har {fileType}-visare installerad och försök igen.\n",
@@ -333,6 +322,7 @@
"mobile.loading_options": "Hämtar alternativ...",
"mobile.loading_posts": "Hämtar meddelanden...",
"mobile.login_options.choose_title": "Välj inloggningsmetod",
"mobile.long_post_title": "{channelName} - Post",
"mobile.mailTo.error.text": "Kunde inte öppna en mejlklient.",
"mobile.mailTo.error.title": "Fel",
"mobile.managed.blocked_by": "Blockerad av {vendor}",
@@ -469,7 +459,7 @@
"mobile.reset_status.alert_cancel": "Avbryt",
"mobile.reset_status.alert_ok": "Ok",
"mobile.reset_status.title_ooo": "Inaktivera \"Ej på kontoret\"?",
"mobile.retry_message": "Hämta meddelanden misslyckades. Tryck här för att försöka igen.",
"mobile.retry_message": "Misslyckades att ladda om meddelanden. Dra uppåt för att försöka igen.",
"mobile.routes.channelInfo": "Info",
"mobile.routes.channelInfo.createdBy": "Skapad av {creator} den ",
"mobile.routes.channelInfo.delete_channel": "Arkivera kanaler",
@@ -481,7 +471,6 @@
"mobile.routes.channel_members.action_message_confirm": "Är du säker på att du vill ta bort de valda medlemmarna från kanalen?",
"mobile.routes.code": "{language} kod",
"mobile.routes.code.noLanguage": "Kod",
"mobile.routes.custom_status": "Ange status",
"mobile.routes.edit_profile": "Editera profil",
"mobile.routes.login": "Logga in",
"mobile.routes.loginOptions": "Välj inloggningsmetod",
@@ -619,6 +608,8 @@
"post_info.del": "Ta bort",
"post_info.edit": "Redigera",
"post_info.guest": "GÄST",
"post_info.message.show_less": "Visa mindre",
"post_info.message.show_more": "Visa mer",
"post_info.system": "System",
"post_message_view.edited": "(redigerad)",
"posts_view.newMsg": "Nya meddelanden",
@@ -677,7 +668,6 @@
"user.settings.general.lastName": "Efternamn",
"user.settings.general.nickname": "Alias",
"user.settings.general.position": "Position",
"user.settings.general.status": "Status",
"user.settings.general.username": "Användarnamn",
"user.settings.modal.display": "Utseende",
"user.settings.modal.notifications": "Meddelanden",

View File

@@ -125,15 +125,6 @@
"create_comment.addComment": "Yorum yazın...",
"create_post.deactivated": "Devre dışı bırakılmış bir kullanıcı ile arşivlenmiş bir kanala bakıyorsunuz.",
"create_post.write": "{channelDisplayName} kanalına yazın",
"custom_status.failure_message": "Durum güncellenemedi. Yeniden deneyin",
"custom_status.set_status": "Bir durum ayarlayın",
"custom_status.suggestions.in_a_meeting": "Toplantıda",
"custom_status.suggestions.on_a_vacation": "Tatilde",
"custom_status.suggestions.out_for_lunch": "Öğle yemeğinde",
"custom_status.suggestions.out_sick": "Hasta",
"custom_status.suggestions.recent_title": "SON KULLANILANLAR",
"custom_status.suggestions.title": "ÖNERİLER",
"custom_status.suggestions.working_from_home": "Evden çalışıyor",
"date_separator.today": "Bugün",
"date_separator.yesterday": "Dün",
"edit_post.editPost": "İletiyi düzenle...",
@@ -261,8 +252,6 @@
"mobile.create_channel.public": "Yeni Herkese Açık Kanal",
"mobile.create_post.read_only": "Bu kanal salt okunur",
"mobile.custom_list.no_results": "Herhangi bir sonuç bulunamadı",
"mobile.custom_status.choose_emoji": "Bir emoji seçin",
"mobile.custom_status.modal_confirm": "Tamam",
"mobile.display_settings.sidebar": "Yan Çubuk",
"mobile.display_settings.theme": "Tema",
"mobile.document_preview.failed_description": "Belge açılırken bir sorun çıktı. Lütfen {fileType} türündeki dosyalar için bir görüntüleyicinin kurulmuş olduğundan emin olun ve yeniden deneyin.\n",
@@ -333,6 +322,7 @@
"mobile.loading_options": "Ayarlar Yükleniyor...",
"mobile.loading_posts": "İletiler yükleniyor...",
"mobile.login_options.choose_title": "Oturum açma yönteminizi seçin",
"mobile.long_post_title": "{channelName} - İleti",
"mobile.mailTo.error.text": "Bir e-posta istemcisi açılamadı.",
"mobile.mailTo.error.title": "Hata",
"mobile.managed.blocked_by": "{vendor} tarafından engellenmiş",
@@ -469,7 +459,7 @@
"mobile.reset_status.alert_cancel": "İptal",
"mobile.reset_status.alert_ok": "Tamam",
"mobile.reset_status.title_ooo": "\"Ofis Dışında\" devre dışı bırakılsın mı?",
"mobile.retry_message": "İletiler alınamadı. Yeniden denemek için buraya dokunun.",
"mobile.retry_message": "İletiler yenilenemedi. Yeniden denemek için yukarı çekin.",
"mobile.routes.channelInfo": "Bilgiler",
"mobile.routes.channelInfo.createdBy": "{creator} tarafından şu zamanda oluşturuldu ",
"mobile.routes.channelInfo.delete_channel": "Kanalı Arşivle",
@@ -481,7 +471,6 @@
"mobile.routes.channel_members.action_message_confirm": "Seçilmiş üyeleri kanaldan çıkarmak istediğinize emin misiniz?",
"mobile.routes.code": "{language} Kodu",
"mobile.routes.code.noLanguage": "Kod",
"mobile.routes.custom_status": "Bir durum ayarlayın",
"mobile.routes.edit_profile": "Profili Düzenle",
"mobile.routes.login": "Oturum Aç",
"mobile.routes.loginOptions": "Oturum Açma Seçici",
@@ -619,6 +608,8 @@
"post_info.del": "Sil",
"post_info.edit": "Düzenle",
"post_info.guest": "KONUK",
"post_info.message.show_less": "Daha az ayrıntı",
"post_info.message.show_more": "Daha çok ayrıntı",
"post_info.system": "Sistem",
"post_message_view.edited": "(düzenlendi)",
"posts_view.newMsg": "Yeni İletiler",
@@ -677,7 +668,6 @@
"user.settings.general.lastName": "Soyad",
"user.settings.general.nickname": "Takma Ad",
"user.settings.general.position": "Unvan",
"user.settings.general.status": "Durum",
"user.settings.general.username": "Kullanıcı Adı",
"user.settings.modal.display": "Görünüm",
"user.settings.modal.notifications": "Bildirimler",

View File

@@ -278,6 +278,7 @@
"mobile.loading_options": "Параметри завантаження...",
"mobile.loading_posts": "Завантаження повідомлень...",
"mobile.login_options.choose_title": "Виберіть спосіб входу",
"mobile.long_post_title": "{channelName} - повідомлення",
"mobile.managed.blocked_by": "Заблоковано {vendor}",
"mobile.managed.exit": "Вхід",
"mobile.managed.jailbreak": "Пристрої з Jailbroken не є довіреними {vendor}, вийдіть з програми.",
@@ -534,6 +535,8 @@
"post_info.del": "Видалити",
"post_info.edit": "Редагувати",
"post_info.guest": "GUEST",
"post_info.message.show_less": "Показати менше",
"post_info.message.show_more": "Показати більше",
"post_info.system": "Система",
"post_message_view.edited": "(змінено)",
"posts_view.newMsg": "Нові повідомлення",

View File

@@ -322,6 +322,7 @@
"mobile.loading_options": "加载选项中...",
"mobile.loading_posts": "正在加载消息...",
"mobile.login_options.choose_title": "选择您的登入方式",
"mobile.long_post_title": "{channelName} - 消息",
"mobile.mailTo.error.text": "无法打开邮件客户端。",
"mobile.mailTo.error.title": "错误",
"mobile.managed.blocked_by": "被 {vendor} 封锁",
@@ -604,6 +605,8 @@
"post_info.del": "删除",
"post_info.edit": "编辑",
"post_info.guest": "游客",
"post_info.message.show_less": "显示更少",
"post_info.message.show_more": "显示更多",
"post_info.system": "系统",
"post_message_view.edited": "(已编辑)",
"posts_view.newMsg": "新消息",

View File

@@ -271,6 +271,7 @@
"mobile.loading_options": "讀取選項...",
"mobile.loading_posts": "正在載入訊息...",
"mobile.login_options.choose_title": "選擇登入方式",
"mobile.long_post_title": "{channelName} - 訊息",
"mobile.managed.blocked_by": "被 {vendor} 阻擋",
"mobile.managed.exit": "離開",
"mobile.managed.jailbreak": "{vendor} 不信任越獄後的裝置,請關閉應用程式。",
@@ -533,6 +534,8 @@
"post_info.del": "刪除",
"post_info.edit": "編輯",
"post_info.guest": "訪客",
"post_info.message.show_less": "顯示較少內容",
"post_info.message.show_more": "顯示更多",
"post_info.system": "系統",
"post_message_view.edited": "(被編輯過)",
"posts_view.newMsg": "新訊息",

View File

@@ -15,6 +15,7 @@ import {
} from '@support/ui/component';
import {
LoginScreen,
LongPostScreen,
SelectServerScreen,
ThreadScreen,
} from '@support/ui/screen';
@@ -76,6 +77,14 @@ class ChannelScreen {
return this.postList.getNewMessagesDivider();
}
getLongPostItem = (postId, text, postProfileOptions = {}) => {
return LongPostScreen.getPost(postId, text, postProfileOptions);
}
getLongPostMessage = () => {
return LongPostScreen.getPostMessage();
}
getPostListPostItem = (postId, text, postProfileOptions = {}) => {
return this.postList.getPost(postId, text, postProfileOptions);
}

View File

@@ -18,6 +18,7 @@ import EditPostScreen from './edit_post';
import EditProfileScreen from './edit_profile';
import GeneralSettingsScreen from './general_settings';
import LoginScreen from './login';
import LongPostScreen from './long_post';
import MoreChannelsScreen from './more_channels';
import MoreDirectMessagesScreen from './more_direct_messages';
import NotificationScreen from './notification';
@@ -54,6 +55,7 @@ export {
EditProfileScreen,
GeneralSettingsScreen,
LoginScreen,
LongPostScreen,
MoreChannelsScreen,
MoreDirectMessagesScreen,
NotificationScreen,

View File

@@ -0,0 +1,60 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {Post} from '@support/ui/component';
class LongPostScreen {
testID = {
longPostItem: 'long_post.post',
closeLongPostButton: 'close.long_post.button',
}
closeLongPostButton = element(by.id(this.testID.closeLongPostButton));
getPost = (postId, postMessage, postProfileOptions = {}) => {
const {
postItem,
postItemBlockQuote,
postItemEmoji,
postItemHeaderDateTime,
postItemHeaderDisplayName,
postItemHeaderGuestTag,
postItemHeaderReply,
postItemImage,
postItemMessage,
postItemProfilePicture,
postItemProfilePictureUserStatus,
postItemShowLessButton,
postItemShowMoreButton,
postItemTable,
postItemTableExpandButton,
postItemThematicBreak,
} = Post.getPost(this.testID.longPostItem, postId, postMessage, postProfileOptions);
return {
longPostItem: postItem,
longPostItemBlockQuote: postItemBlockQuote,
longPostItemEmoji: postItemEmoji,
longPostItemHeaderDateTime: postItemHeaderDateTime,
longPostItemHeaderDisplayName: postItemHeaderDisplayName,
longPostItemHeaderGuestTag: postItemHeaderGuestTag,
longPostItemHeaderReply: postItemHeaderReply,
longPostItemImage: postItemImage,
longPostItemMessage: postItemMessage,
longPostItemProfilePicture: postItemProfilePicture,
longPostItemProfilePictureUserStatus: postItemProfilePictureUserStatus,
longPostItemShowLessButton: postItemShowLessButton,
longPostItemShowMoreButton: postItemShowMoreButton,
longPostItemTable: postItemTable,
longPostItemTableExpandButton: postItemTableExpandButton,
longPostItemThematicBreak: postItemThematicBreak,
};
}
getPostMessage = () => {
return Post.getPostMessage(this.testID.longPostItem);
}
}
const longPostScreen = new LongPostScreen();
export default longPostScreen;

View File

@@ -2,6 +2,7 @@
// See LICENSE.txt for license information.
import {PostList} from '@support/ui/component';
import {LongPostScreen} from '@support/ui/screen';
class PermalinkScreen {
testID = {
@@ -17,6 +18,14 @@ class PermalinkScreen {
postList = new PostList(this.testID.permalinkScreenPrefix);
getLongPostItem = (postId, text, postProfileOptions = {}) => {
return LongPostScreen.getPost(postId, text, postProfileOptions);
}
getLongPostMessage = () => {
return LongPostScreen.getPostMessage();
}
getPostListPostItem = (postId, text, postProfileOptions = {}) => {
return this.postList.getPost(postId, text, postProfileOptions);
}

View File

@@ -11,6 +11,7 @@ import {
PostOptions,
SendButton,
} from '@support/ui/component';
import {LongPostScreen} from '@support/ui/screen';
import {timeouts, wait} from '@support/utils';
class ThreadScreen {
@@ -45,6 +46,14 @@ class ThreadScreen {
postList = new PostList(this.testID.threadScreenPrefix);
getLongPostItem = (postId, text, postProfileOptions = {}) => {
return LongPostScreen.getPost(postId, text, postProfileOptions);
}
getLongPostMessage = () => {
return LongPostScreen.getPostMessage();
}
getPostListPostItem = (postId, text, postProfileOptions = {}) => {
return this.postList.getPost(postId, text, postProfileOptions);
}

View File

@@ -9,7 +9,10 @@
import moment from 'moment-timezone';
import {ChannelScreen} from '@support/ui/screen';
import {
ChannelScreen,
LongPostScreen,
} from '@support/ui/screen';
import {
Channel,
Post,
@@ -20,6 +23,7 @@ import {isAndroid} from '@support/utils';
describe('Message Posting', () => {
const longMessage = 'The quick brown fox jumps over the lazy dog.'.repeat(30);
const {
getLongPostItem,
getPostListPostItem,
goToChannel,
postMessage,
@@ -58,21 +62,15 @@ describe('Message Posting', () => {
it('MM-T3229 should be able to open long post via show more', async () => {
// # Open long post via show more
const {post} = await Post.apiGetLastPostInChannel(townSquareChannel.id);
const {
postListPostItem,
postListPostItemShowLessButton,
postListPostItemShowMoreButton,
} = await getPostListPostItem(post.id, longMessage);
const {postListPostItemShowMoreButton} = await getPostListPostItem(post.id, longMessage);
await postListPostItemShowMoreButton.tap();
// * Verify long post is displayed
await expect(postListPostItem).toExist();
const {longPostItem} = getLongPostItem(post.id, longMessage);
await expect(longPostItem).toExist();
// # Close long post
await postListPostItemShowLessButton.tap();
// * Verify show more button is displayed again
await expect(postListPostItemShowMoreButton).toExist();
// # Close long post screen
await LongPostScreen.closeLongPostButton.tap();
});
it('MM-T3269 should be able to post a markdown image', async () => {
@@ -125,21 +123,19 @@ describe('Message Posting', () => {
createAt: lastMessageDate.getTime(),
});
// # Switch channels
await goToChannel(townSquareChannel.display_name);
await goToChannel(testChannel.display_name);
// Detox is having trouble scrolling
if (isAndroid()) {
return;
}
// * Verify last message is posted
await goToChannel(townSquareChannel.display_name);
await goToChannel(testChannel.display_name);
const {postListPostItem: lastPostItem} = await getPostListPostItem(lastPost.post.id, lastMessage);
await waitFor(lastPostItem).toBeVisible().whileElement(by.id(ChannelScreen.testID.channelPostList)).scroll(1000, 'down');
// * Verify user can scroll up multiple times until first matching post is seen
const {postListPostItem: firstPostItem} = await getPostListPostItem(firstPost.post.id, firstMessage);
await waitFor(firstPostItem).toBeVisible().whileElement(by.id(ChannelScreen.testID.channelPostList)).scroll(2000, 'up');
await waitFor(firstPostItem).toBeVisible().whileElement(by.id(ChannelScreen.testID.channelPostList)).scroll(1000, 'up');
});
});

View File

@@ -7,8 +7,8 @@ GEM
artifactory (3.0.15)
atomos (0.1.3)
aws-eventstream (1.1.1)
aws-partitions (1.465.0)
aws-sdk-core (3.114.1)
aws-partitions (1.467.0)
aws-sdk-core (3.114.2)
aws-eventstream (~> 1, >= 1.0.2)
aws-partitions (~> 1, >= 1.239.0)
aws-sigv4 (~> 1.1)
@@ -16,7 +16,7 @@ GEM
aws-sdk-kms (1.43.0)
aws-sdk-core (~> 3, >= 3.112.0)
aws-sigv4 (~> 1.1)
aws-sdk-s3 (1.96.0)
aws-sdk-s3 (1.96.1)
aws-sdk-core (~> 3, >= 3.112.0)
aws-sdk-kms (~> 1)
aws-sigv4 (~> 1.1)
@@ -55,7 +55,7 @@ GEM
faraday_middleware (1.0.0)
faraday (~> 1.0)
fastimage (2.2.4)
fastlane (2.184.1)
fastlane (2.185.0)
CFPropertyList (>= 2.3, < 4.0.0)
addressable (>= 2.3, < 3.0.0)
artifactory (~> 3.0)
@@ -139,7 +139,7 @@ GEM
os (>= 0.9, < 2.0)
signet (~> 0.14)
highline (2.0.3)
http-cookie (1.0.3)
http-cookie (1.0.4)
domain_name (~> 0.5)
httpclient (2.8.3)
jmespath (1.4.0)

View File

@@ -912,7 +912,7 @@
CODE_SIGN_ENTITLEMENTS = Mattermost/Mattermost.entitlements;
CODE_SIGN_IDENTITY = "iPhone Developer";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
CURRENT_PROJECT_VERSION = 357;
CURRENT_PROJECT_VERSION = 359;
DEAD_CODE_STRIPPING = NO;
DEVELOPMENT_TEAM = UQ8HT4Q2XM;
ENABLE_BITCODE = NO;
@@ -954,7 +954,7 @@
CODE_SIGN_ENTITLEMENTS = Mattermost/Mattermost.entitlements;
CODE_SIGN_IDENTITY = "iPhone Developer";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
CURRENT_PROJECT_VERSION = 357;
CURRENT_PROJECT_VERSION = 359;
DEAD_CODE_STRIPPING = NO;
DEVELOPMENT_TEAM = UQ8HT4Q2XM;
ENABLE_BITCODE = NO;

View File

@@ -37,7 +37,7 @@
</dict>
</array>
<key>CFBundleVersion</key>
<string>357</string>
<string>359</string>
<key>ITSAppUsesNonExemptEncryption</key>
<false/>
<key>LSRequiresIPhoneOS</key>

View File

@@ -21,7 +21,7 @@
<key>CFBundleShortVersionString</key>
<string>1.44.0</string>
<key>CFBundleVersion</key>
<string>357</string>
<string>359</string>
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsArbitraryLoads</key>

View File

@@ -21,7 +21,7 @@
<key>CFBundleShortVersionString</key>
<string>1.44.0</string>
<key>CFBundleVersion</key>
<string>357</string>
<string>359</string>
<key>NSExtension</key>
<dict>
<key>NSExtensionPointIdentifier</key>

View File

@@ -1,5 +1,5 @@
diff --git a/node_modules/react-native-image-picker/android/src/main/java/com/imagepicker/ImagePickerModule.java b/node_modules/react-native-image-picker/android/src/main/java/com/imagepicker/ImagePickerModule.java
index 48fb5c1..3872d91 100644
index 48fb5c1..5ca1e20 100644
--- a/node_modules/react-native-image-picker/android/src/main/java/com/imagepicker/ImagePickerModule.java
+++ b/node_modules/react-native-image-picker/android/src/main/java/com/imagepicker/ImagePickerModule.java
@@ -3,6 +3,7 @@ package com.imagepicker;
@@ -10,15 +10,7 @@ index 48fb5c1..3872d91 100644
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
@@ -49,6 +50,7 @@ import java.io.InputStream;
import java.io.OutputStream;
import java.lang.ref.WeakReference;
import java.util.List;
+import java.util.ArrayList;
import com.facebook.react.modules.core.PermissionListener;
import com.facebook.react.modules.core.PermissionAwareActivity;
@@ -69,6 +71,7 @@ public class ImagePickerModule extends ReactContextBaseJavaModule
@@ -69,6 +70,7 @@ public class ImagePickerModule extends ReactContextBaseJavaModule
public static final int REQUEST_LAUNCH_IMAGE_LIBRARY = 13002;
public static final int REQUEST_LAUNCH_VIDEO_LIBRARY = 13003;
public static final int REQUEST_LAUNCH_VIDEO_CAPTURE = 13004;
@@ -26,11 +18,14 @@ index 48fb5c1..3872d91 100644
public static final int REQUEST_PERMISSIONS_FOR_CAMERA = 14001;
public static final int REQUEST_PERMISSIONS_FOR_LIBRARY = 14002;
@@ -266,26 +269,24 @@ public class ImagePickerModule extends ReactContextBaseJavaModule
@@ -265,27 +267,22 @@ public class ImagePickerModule extends ReactContextBaseJavaModule
{
cameraIntent.putExtra(MediaStore.EXTRA_DURATION_LIMIT, videoDurationLimit);
}
}
+ else if (pickBoth) {
- }
- else
- {
+ } else if (pickBoth) {
+ Intent takePictureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
+ this.setImageCaptureUri(takePictureIntent);
+ Intent takeVideoIntent = new Intent(MediaStore.ACTION_VIDEO_CAPTURE);
@@ -41,9 +36,7 @@ index 48fb5c1..3872d91 100644
+ cameraIntent.putExtra(Intent.EXTRA_TITLE, "Choose an action");
+ cameraIntent.putExtra(Intent.EXTRA_INITIAL_INTENTS, intentArray);
+ requestCode = REQUEST_LAUNCH_MIXED_CAPTURE;
+ }
else
{
+ } else {
requestCode = REQUEST_LAUNCH_IMAGE_CAPTURE;
cameraIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
@@ -66,23 +59,24 @@ index 48fb5c1..3872d91 100644
}
if (cameraIntent.resolveActivity(reactContext.getPackageManager()) == null)
@@ -350,16 +351,19 @@ public class ImagePickerModule extends ReactContextBaseJavaModule
@@ -349,17 +346,18 @@ public class ImagePickerModule extends ReactContextBaseJavaModule
requestCode = REQUEST_LAUNCH_VIDEO_LIBRARY;
libraryIntent = new Intent(Intent.ACTION_PICK);
libraryIntent.setType("video/*");
}
+ else if (pickBoth) {
- }
- else
- {
+ } else if (pickBoth) {
+ libraryIntent = new Intent(Intent.ACTION_GET_CONTENT);
+ libraryIntent.addCategory(Intent.CATEGORY_OPENABLE);
+ libraryIntent.setType("image/*");
+ libraryIntent.putExtra(Intent.EXTRA_MIME_TYPES, new String[] {"image/*", "video/*"});
+ requestCode = REQUEST_LAUNCH_MIXED_CAPTURE;
+ }
else
{
+ } else {
requestCode = REQUEST_LAUNCH_IMAGE_LIBRARY;
libraryIntent = new Intent(Intent.ACTION_PICK,
MediaStore.Images.Media.EXTERNAL_CONTENT_URI);
-
- if (pickBoth)
- {
- libraryIntent.setType("image/* video/*");
@@ -91,7 +85,7 @@ index 48fb5c1..3872d91 100644
}
if (libraryIntent.resolveActivity(reactContext.getPackageManager()) == null)
@@ -385,75 +389,47 @@ public class ImagePickerModule extends ReactContextBaseJavaModule
@@ -385,75 +383,42 @@ public class ImagePickerModule extends ReactContextBaseJavaModule
}
}
@@ -113,29 +107,10 @@ index 48fb5c1..3872d91 100644
- callback = null;
- return;
- }
+ protected String getMimeType(Activity activity, Uri uri) {
+ String mimeType = null;
+ if (uri.getScheme().equals(ContentResolver.SCHEME_CONTENT)) {
+ ContentResolver cr = activity.getApplicationContext().getContentResolver();
+ mimeType = cr.getType(uri);
+ } else {
+ String fileExtension = MimeTypeMap.getFileExtensionFromUrl(uri
+ .toString());
+ mimeType = MimeTypeMap.getSingleton().getMimeTypeFromExtension(
+ fileExtension.toLowerCase());
+ }
+ return mimeType;
+ }
-
- Uri uri = null;
- switch (requestCode)
+ protected void extractImageFromResult(Activity activity, Uri uri, int requestCode) {
+ String realPath = getRealPathFromURI(uri);
+ String mime = getMimeType(activity, uri);
+ final boolean isUrl = !TextUtils.isEmpty(realPath) &&
+ Patterns.WEB_URL.matcher(realPath).matches();
+ if (realPath == null || isUrl)
{
- {
- case REQUEST_LAUNCH_IMAGE_CAPTURE:
- uri = cameraCaptureURI;
- break;
@@ -172,20 +147,35 @@ index 48fb5c1..3872d91 100644
- responseHelper.invokeResponse(callback);
- callback = null;
- return;
-
+ protected String getMimeType(Activity activity, Uri uri) {
+ String mimeType = null;
+ if (uri.getScheme().equals(ContentResolver.SCHEME_CONTENT)) {
+ ContentResolver cr = activity.getApplicationContext().getContentResolver();
+ mimeType = cr.getType(uri);
+ } else {
+ String fileExtension = MimeTypeMap.getFileExtensionFromUrl(uri.toString());
+ mimeType = MimeTypeMap.getSingleton().getMimeTypeFromExtension(
+ fileExtension.toLowerCase());
+ }
+ return mimeType;
+ }
- case REQUEST_LAUNCH_VIDEO_CAPTURE:
- final String path = getRealPathFromURI(data.getData());
- responseHelper.putString("uri", data.getData().toString());
- responseHelper.putString("path", path);
- fileScan(reactContext, path);
+ try
+ {
+ protected void extractImageFromResult(Activity activity, Uri uri, int requestCode) {
+ String realPath = getRealPathFromURI(uri);
+ String mime = getMimeType(activity, uri);
+ final boolean isUrl = !TextUtils.isEmpty(realPath) &&
+ Patterns.WEB_URL.matcher(realPath).matches();
+ if (isUrl) {
+ try {
+ File file = createFileFromURI(uri);
+ realPath = file.getAbsolutePath();
+ uri = Uri.fromFile(file);
+ }
+ catch (Exception e)
+ {
+ } catch (Exception e) {
+ // image not in cache
+ responseHelper.putString("error", "Could not read photo");
+ responseHelper.putString("uri", uri.toString());
@@ -201,7 +191,7 @@ index 48fb5c1..3872d91 100644
final ReadExifResult result = readExifInterface(responseHelper, imageConfig);
if (result.error != null)
@@ -461,6 +437,7 @@ public class ImagePickerModule extends ReactContextBaseJavaModule
@@ -461,6 +426,7 @@ public class ImagePickerModule extends ReactContextBaseJavaModule
removeUselessFiles(requestCode, imageConfig);
responseHelper.invokeError(callback, result.error.getMessage());
callback = null;
@@ -209,7 +199,7 @@ index 48fb5c1..3872d91 100644
return;
}
@@ -472,7 +449,7 @@ public class ImagePickerModule extends ReactContextBaseJavaModule
@@ -472,7 +438,7 @@ public class ImagePickerModule extends ReactContextBaseJavaModule
updatedResultResponse(uri, imageConfig.original.getAbsolutePath());
// don't create a new file if contraint are respected
@@ -218,7 +208,7 @@ index 48fb5c1..3872d91 100644
{
responseHelper.putInt("width", initialWidth);
responseHelper.putInt("height", initialHeight);
@@ -481,6 +458,14 @@ public class ImagePickerModule extends ReactContextBaseJavaModule
@@ -481,6 +447,13 @@ public class ImagePickerModule extends ReactContextBaseJavaModule
else
{
imageConfig = getResizedImage(reactContext, this.options, imageConfig, initialWidth, initialHeight, requestCode);
@@ -229,11 +219,10 @@ index 48fb5c1..3872d91 100644
+ this.options = null;
+ return;
+ }
+
if (imageConfig.resized == null)
{
removeUselessFiles(requestCode, imageConfig);
@@ -523,6 +508,64 @@ public class ImagePickerModule extends ReactContextBaseJavaModule
@@ -523,6 +496,61 @@ public class ImagePickerModule extends ReactContextBaseJavaModule
this.options = null;
}
@@ -269,15 +258,12 @@ index 48fb5c1..3872d91 100644
+ case REQUEST_LAUNCH_IMAGE_CAPTURE:
+ extractImageFromResult(activity, cameraCaptureURI, requestCode);
+ break;
+
+ case REQUEST_LAUNCH_IMAGE_LIBRARY:
+ extractImageFromResult(activity, data.getData(), requestCode);
+ break;
+
+ case REQUEST_LAUNCH_VIDEO_LIBRARY:
+ extractVideoFromResult(data.getData());
+ break;
+
+ case REQUEST_LAUNCH_MIXED_CAPTURE:
+ case REQUEST_LAUNCH_VIDEO_CAPTURE:
+ if (data == null || data.getData() == null) {
@@ -298,7 +284,7 @@ index 48fb5c1..3872d91 100644
public void invokeCustomButton(@NonNull final String action)
{
responseHelper.invokeCustomButton(this.callback, action);
@@ -551,7 +594,8 @@ public class ImagePickerModule extends ReactContextBaseJavaModule
@@ -551,7 +579,8 @@ public class ImagePickerModule extends ReactContextBaseJavaModule
{
return callback == null || (cameraCaptureURI == null && requestCode == REQUEST_LAUNCH_IMAGE_CAPTURE)
|| (requestCode != REQUEST_LAUNCH_IMAGE_CAPTURE && requestCode != REQUEST_LAUNCH_IMAGE_LIBRARY
@@ -308,7 +294,7 @@ index 48fb5c1..3872d91 100644
}
private void updatedResultResponse(@Nullable final Uri uri,
@@ -571,22 +615,23 @@ public class ImagePickerModule extends ReactContextBaseJavaModule
@@ -571,22 +600,24 @@ public class ImagePickerModule extends ReactContextBaseJavaModule
@NonNull final Callback callback,
@NonNull final int requestCode)
{
@@ -318,21 +304,21 @@ index 48fb5c1..3872d91 100644
- .checkSelfPermission(activity, Manifest.permission.CAMERA);
-
- boolean permissionsGranted = false;
-
+ int selfCheckResult = 0;
switch (requestCode) {
case REQUEST_PERMISSIONS_FOR_LIBRARY:
- permissionsGranted = writePermission == PackageManager.PERMISSION_GRANTED;
+ selfCheckResult = ActivityCompat
+ .checkSelfPermission(activity, Manifest.permission.WRITE_EXTERNAL_STORAGE);
+ .checkSelfPermission(activity, Manifest.permission.WRITE_EXTERNAL_STORAGE);
break;
case REQUEST_PERMISSIONS_FOR_CAMERA:
- permissionsGranted = cameraPermission == PackageManager.PERMISSION_GRANTED && writePermission == PackageManager.PERMISSION_GRANTED;
+ selfCheckResult = ActivityCompat
+ .checkSelfPermission(activity, Manifest.permission.CAMERA);
+ .checkSelfPermission(activity, Manifest.permission.CAMERA);
+ if (selfCheckResult == PackageManager.PERMISSION_GRANTED) {
+ selfCheckResult = ActivityCompat
+ .checkSelfPermission(activity, Manifest.permission.WRITE_EXTERNAL_STORAGE);
+ .checkSelfPermission(activity, Manifest.permission.WRITE_EXTERNAL_STORAGE);
+ }
break;
}
@@ -341,42 +327,52 @@ index 48fb5c1..3872d91 100644
if (!permissionsGranted)
{
final Boolean dontAskAgain = ActivityCompat.shouldShowRequestPermissionRationale(activity, Manifest.permission.WRITE_EXTERNAL_STORAGE) && ActivityCompat.shouldShowRequestPermissionRationale(activity, Manifest.permission.CAMERA);
@@ -787,4 +832,22 @@ public class ImagePickerModule extends ReactContextBaseJavaModule
@@ -787,4 +818,21 @@ public class ImagePickerModule extends ReactContextBaseJavaModule
videoDurationLimit = options.getInt("durationLimit");
}
}
+
+ private void setImageCaptureUri(Intent cameraIntent) {
+ final File original = createNewFile(reactContext, this.options, false);
+ imageConfig = imageConfig.withOriginalFile(original);
+ imageConfig = imageConfig.withOriginalFile(original);
+
+ if (imageConfig.original != null) {
+ cameraCaptureURI = RealPathUtil.compatUriFromFile(reactContext, imageConfig.original);
+ }else {
+ responseHelper.invokeError(callback, "Couldn't get file path for photo");
+ return;
+ }
+ if (cameraCaptureURI == null)
+ {
+ responseHelper.invokeError(callback, "Couldn't get file path for photo");
+ return;
+ }
+ cameraIntent.putExtra(MediaStore.EXTRA_OUTPUT, cameraCaptureURI);
+ if (imageConfig.original != null) {
+ cameraCaptureURI = RealPathUtil.compatUriFromFile(reactContext, imageConfig.original);
+ } else {
+ responseHelper.invokeError(callback, "Couldn't get file path for photo");
+ return;
+ }
+ if (cameraCaptureURI == null) {
+ responseHelper.invokeError(callback, "Couldn't get file path for photo");
+ return;
+ }
+ cameraIntent.putExtra(MediaStore.EXTRA_OUTPUT, cameraCaptureURI);
+ }
}
diff --git a/node_modules/react-native-image-picker/android/src/main/java/com/imagepicker/utils/RealPathUtil.java b/node_modules/react-native-image-picker/android/src/main/java/com/imagepicker/utils/RealPathUtil.java
index cc90dce..c1cc74d 100644
index cc90dce..72ddc92 100644
--- a/node_modules/react-native-image-picker/android/src/main/java/com/imagepicker/utils/RealPathUtil.java
+++ b/node_modules/react-native-image-picker/android/src/main/java/com/imagepicker/utils/RealPathUtil.java
@@ -7,16 +7,22 @@ import android.net.Uri;
import android.os.Build;
@@ -1,201 +1,270 @@
package com.imagepicker.utils;
-import android.annotation.SuppressLint;
import android.content.Context;
import android.database.Cursor;
import android.net.Uri;
-import android.os.Build;
import android.provider.DocumentsContract;
import android.provider.MediaStore;
-import android.content.ContentUris;
+import android.provider.OpenableColumns;
import android.content.ContentUris;
+import android.content.ContentResolver;
import android.os.Environment;
+import android.os.ParcelFileDescriptor;
+import android.webkit.MimeTypeMap;
+import android.util.Log;
+import android.text.TextUtils;
+
+import android.os.ParcelFileDescriptor;
+
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.content.FileProvider;
@@ -384,93 +380,432 @@ index cc90dce..c1cc74d 100644
-import java.io.File;
+import java.io.*;
+import java.nio.channels.FileChannel;
+import java.util.Objects;
+
+// Class based on the steveevers DocumentHelper https://gist.github.com/steveevers/a5af24c226f44bb8fdc3
public class RealPathUtil {
+ public static final String CACHE_DIR_NAME = "mmShare";
+ public static final String CACHE_DIR_NAME = "mmShare";
+
public static @Nullable Uri compatUriFromFile(@NonNull final Context context,
@NonNull final File file) {
Uri result = null;
@@ -58,12 +64,7 @@ public class RealPathUtil {
}
// DownloadsProvider
else if (isDownloadsDocument(uri)) {
+ public static @Nullable
+ Uri compatUriFromFile(@NonNull final Context context,
+ @NonNull final File file) {
+ Uri result = null;
+ final String packageName = context.getApplicationContext().getPackageName();
+ final String authority = packageName + ".provider";
+ try {
+ result = FileProvider.getUriForFile(context, authority, file);
+ }
+ catch(IllegalArgumentException e) {
+ e.printStackTrace();
+ }
+ return result;
+ }
+
+ public static String getRealPathFromURI(final Context context, final Uri uri) {
+
+ // DocumentProvider
+ if (DocumentsContract.isDocumentUri(context, uri)) {
+ // ExternalStorageProvider
+ if (isExternalStorageDocument(uri)) {
+ final String docId = DocumentsContract.getDocumentId(uri);
+ final String[] split = docId.split(":");
+ final String type = split[0];
+
+ if ("primary".equalsIgnoreCase(type)) {
+ return Environment.getExternalStorageDirectory() + "/" + split[1];
+ }
+ } else if (isDownloadsDocument(uri)) {
+ // DownloadsProvider
+
+ final String id = DocumentsContract.getDocumentId(uri);
+ if (!TextUtils.isEmpty(id)) {
+ if (id.startsWith("raw:")) {
+ return id.replaceFirst("raw:", "");
+ }
+ try {
+ return getPathFromSavingTempFile(context, uri);
+ } catch (NumberFormatException e) {
+ Log.e("ReactNative", "DownloadsProvider unexpected uri " + uri.toString());
+ return null;
+ }
+ }
+ } else if (isMediaDocument(uri)) {
+ // MediaProvider
+
+ final String docId = DocumentsContract.getDocumentId(uri);
+ final String[] split = docId.split(":");
+ final String type = split[0];
+
+ Uri contentUri = null;
+ if ("image".equals(type)) {
+ contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
+ } else if ("video".equals(type)) {
+ contentUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI;
+ } else if ("audio".equals(type)) {
+ contentUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;
+ }
+
+ final String selection = "_id=?";
+ final String[] selectionArgs = new String[] {
+ split[1]
+ };
+
+ if (contentUri != null) {
+ return getDataColumn(context, contentUri, selection, selectionArgs);
+ } else {
+ return getPathFromSavingTempFile(context, uri);
+ }
+ }
+ }
+
+ if ("content".equalsIgnoreCase(uri.getScheme())) {
+ // MediaStore (and general)
+
+ if (isGooglePhotosUri(uri)) {
+ return uri.getLastPathSegment();
+ }
+
+ // Try save to tmp file, and return tmp file path
+ return getPathFromSavingTempFile(context, uri);
+ } else if ("file".equalsIgnoreCase(uri.getScheme())) {
+ return uri.getPath();
+ }
+
+ return null;
+ }
+
+ public static String getPathFromSavingTempFile(Context context, final Uri uri) {
+ File tmpFile;
+ String fileName = null;
+
+ if (uri == null || uri.isRelative()) {
+ return null;
+ }
+
+ // Try and get the filename from the Uri
+ try {
+ Cursor returnCursor =
+ context.getContentResolver().query(uri, null, null, null, null);
+ int nameIndex = returnCursor.getColumnIndex(OpenableColumns.DISPLAY_NAME);
+ returnCursor.moveToFirst();
+ fileName = sanitizeFilename(returnCursor.getString(nameIndex));
+ returnCursor.close();
+
+ } catch (Exception e) {
+ // just continue to get the filename with the last segment of the path
+ }
+
+ try {
+ if (TextUtils.isEmpty(fileName)) {
+ fileName = sanitizeFilename(uri.getLastPathSegment().trim());
+ }
+
+
+ File cacheDir = new File(context.getCacheDir(), CACHE_DIR_NAME);
+ if (!cacheDir.exists()) {
+ cacheDir.mkdirs();
+ }
+
+ tmpFile = new File(cacheDir, fileName);
+ tmpFile.createNewFile();
+
+ ParcelFileDescriptor pfd = context.getContentResolver().openFileDescriptor(uri, "r");
+
+ FileChannel src = new FileInputStream(pfd.getFileDescriptor()).getChannel();
+ FileChannel dst = new FileOutputStream(tmpFile).getChannel();
+ dst.transferFrom(src, 0, src.size());
+ src.close();
+ dst.close();
+ } catch (IOException ex) {
+ return null;
+ }
+ return tmpFile.getAbsolutePath();
+ }
+
+ public static String getDataColumn(Context context, Uri uri, String selection,
+ String[] selectionArgs) {
+
+ Cursor cursor = null;
+ final String column = "_data";
+ final String[] projection = {
+ column
+ };
+
+ try {
+ cursor = context.getContentResolver().query(uri, projection, selection, selectionArgs,
+ null);
+ if (cursor != null && cursor.moveToFirst()) {
+ final int index = cursor.getColumnIndexOrThrow(column);
+ return cursor.getString(index);
+ }
+ } finally {
+ if (cursor != null)
+ cursor.close();
+ }
+ return null;
+ }
+
+
+ public static boolean isExternalStorageDocument(Uri uri) {
+ return "com.android.externalstorage.documents".equals(uri.getAuthority());
+ }
+
+ public static boolean isDownloadsDocument(Uri uri) {
+ return "com.android.providers.downloads.documents".equals(uri.getAuthority());
+ }
+
+ public static boolean isMediaDocument(Uri uri) {
+ return "com.android.providers.media.documents".equals(uri.getAuthority());
+ }
+
+ public static boolean isGooglePhotosUri(Uri uri) {
+ return "com.google.android.apps.photos.content".equals(uri.getAuthority());
+ }
+
+ public static String getExtension(String uri) {
+ if (uri == null) {
+ return null;
+ }
+
+ int dot = uri.lastIndexOf(".");
+ if (dot >= 0) {
+ return uri.substring(dot);
+ } else {
+ // No extension.
+ return "";
+ }
+ }
+
+ public static String getMimeType(File file) {
+
+ String extension = getExtension(file.getName());
+
+ if (extension.length() > 0)
+ return MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension.substring(1));
+
+ return "application/octet-stream";
+ }
+
+ public static String getMimeType(String filePath) {
+ File file = new File(filePath);
+ return getMimeType(file);
+ }
+
+ public static String getMimeTypeFromUri(final Context context, final Uri uri) {
+ try {
+ ContentResolver cR = context.getContentResolver();
+ return cR.getType(uri);
+ } catch (Exception e) {
+ return "application/octet-stream";
+ }
+ }
+
+ public static void deleteTempFiles(final File dir) {
+ try {
+ if (dir.isDirectory()) {
+ deleteRecursive(dir);
+ }
+ } catch (Exception e) {
+ // do nothing
+ }
+ }
+
+ private static void deleteRecursive(File fileOrDirectory) {
+ if (fileOrDirectory.isDirectory())
+ for (File child : Objects.requireNonNull(fileOrDirectory.listFiles()))
+ deleteRecursive(child);
+
+ fileOrDirectory.delete();
+ }
+
+ private static String sanitizeFilename(String filename) {
+ if (filename == null) {
+ return null;
+ }
- public static @Nullable Uri compatUriFromFile(@NonNull final Context context,
- @NonNull final File file) {
- Uri result = null;
- if (Build.VERSION.SDK_INT < 21) {
- result = Uri.fromFile(file);
- }
- else {
- final String packageName = context.getApplicationContext().getPackageName();
- final String authority = new StringBuilder(packageName).append(".provider").toString();
- try {
- result = FileProvider.getUriForFile(context, authority, file);
- }
- catch(IllegalArgumentException e) {
- e.printStackTrace();
- }
- }
- return result;
- }
-
- @SuppressLint("NewApi")
- public static @Nullable String getRealPathFromURI(@NonNull final Context context,
- @NonNull final Uri uri) {
-
- final boolean isKitKat = Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT;
-
- // DocumentProvider
- if (isKitKat && DocumentsContract.isDocumentUri(context, uri)) {
- // ExternalStorageProvider
- if (isExternalStorageDocument(uri)) {
- final String docId = DocumentsContract.getDocumentId(uri);
- final String[] split = docId.split(":");
- final String type = split[0];
-
- if ("primary".equalsIgnoreCase(type)) {
- return Environment.getExternalStorageDirectory() + "/" + split[1];
- }
-
- // TODO handle non-primary volumes
- }
- // DownloadsProvider
- else if (isDownloadsDocument(uri)) {
-
- final String id = DocumentsContract.getDocumentId(uri);
- final Uri contentUri = ContentUris.withAppendedId(
- Uri.parse("content://downloads/public_downloads"), Long.valueOf(id));
-
- return getDataColumn(context, contentUri, null, null);
+ return getPathFromSavingTempFile(context, uri);
}
// MediaProvider
else if (isMediaDocument(uri)) {
@@ -89,7 +90,7 @@ public class RealPathUtil {
}
}
// MediaStore (and general)
- }
- // MediaProvider
- else if (isMediaDocument(uri)) {
- final String docId = DocumentsContract.getDocumentId(uri);
- final String[] split = docId.split(":");
- final String type = split[0];
-
- Uri contentUri = null;
- if ("image".equals(type)) {
- contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
- } else if ("video".equals(type)) {
- contentUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI;
- } else if ("audio".equals(type)) {
- contentUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;
- }
-
- final String selection = "_id=?";
- final String[] selectionArgs = new String[] {
- split[1]
- };
-
- return getDataColumn(context, contentUri, selection, selectionArgs);
- }
- }
- // MediaStore (and general)
- else if ("content".equalsIgnoreCase(uri.getScheme())) {
+ if ("content".equalsIgnoreCase(uri.getScheme())) {
// Return the remote address
if (isGooglePhotosUri(uri))
@@ -98,7 +99,7 @@ public class RealPathUtil {
if (isFileProviderUri(context, uri))
return getFileProviderPath(context, uri);
-
- // Return the remote address
- if (isGooglePhotosUri(uri))
- return uri.getLastPathSegment();
-
- if (isFileProviderUri(context, uri))
- return getFileProviderPath(context, uri);
-
- return getDataColumn(context, uri, null, null);
+ return getPathFromSavingTempFile(context, uri);
}
// File
else if ("file".equalsIgnoreCase(uri.getScheme())) {
@@ -108,6 +109,49 @@ public class RealPathUtil {
return null;
}
+
+ public static String getPathFromSavingTempFile(Context context, final Uri uri) {
+ File tmpFile;
+ String fileName = null;
+
+ // Try and get the filename from the Uri
+ try {
+ Cursor returnCursor =
+ context.getContentResolver().query(uri, null, null, null, null);
+ int nameIndex = returnCursor.getColumnIndex(OpenableColumns.DISPLAY_NAME);
+ returnCursor.moveToFirst();
+ fileName = returnCursor.getString(nameIndex);
+ } catch (Exception e) {
+ // just continue to get the filename with the last segment of the path
+ }
+
+ try {
+ if (fileName == null) {
+ fileName = uri.getLastPathSegment().toString().trim();
+ }
+
+
+ File cacheDir = new File(context.getCacheDir(), CACHE_DIR_NAME);
+ if (!cacheDir.exists()) {
+ cacheDir.mkdirs();
+ }
+
+ tmpFile = new File(cacheDir, fileName);
+ tmpFile.createNewFile();
+
+ ParcelFileDescriptor pfd = context.getContentResolver().openFileDescriptor(uri, "r");
+
+ FileChannel src = new FileInputStream(pfd.getFileDescriptor()).getChannel();
+ FileChannel dst = new FileOutputStream(tmpFile).getChannel();
+ dst.transferFrom(src, 0, src.size());
+ src.close();
+ dst.close();
+ } catch (IOException ex) {
+ return null;
+ }
+ return tmpFile.getAbsolutePath();
+ }
+
/**
* Get the value of the data column for this Uri. This is useful for
* MediaStore Uris, and other file-based ContentProviders.
- }
- // File
- else if ("file".equalsIgnoreCase(uri.getScheme())) {
- return uri.getPath();
- }
-
- return null;
- }
-
- /**
- * Get the value of the data column for this Uri. This is useful for
- * MediaStore Uris, and other file-based ContentProviders.
- *
- * @param context The context.
- * @param uri The Uri to query.
- * @param selection (Optional) Filter used in the query.
- * @param selectionArgs (Optional) Selection arguments used in the query.
- * @return The value of the _data column, which is typically a file path.
- */
- public static String getDataColumn(Context context, Uri uri, String selection,
- String[] selectionArgs) {
-
- Cursor cursor = null;
- final String column = "_data";
- final String[] projection = {
- column
- };
-
- try {
- cursor = context.getContentResolver().query(uri, projection, selection, selectionArgs,
- null);
- if (cursor != null && cursor.moveToFirst()) {
- final int index = cursor.getColumnIndexOrThrow(column);
- return cursor.getString(index);
- }
- } finally {
- if (cursor != null)
- cursor.close();
- }
- return null;
- }
-
-
- /**
- * @param uri The Uri to check.
- * @return Whether the Uri authority is ExternalStorageProvider.
- */
- public static boolean isExternalStorageDocument(Uri uri) {
- return "com.android.externalstorage.documents".equals(uri.getAuthority());
- }
-
- /**
- * @param uri The Uri to check.
- * @return Whether the Uri authority is DownloadsProvider.
- */
- public static boolean isDownloadsDocument(Uri uri) {
- return "com.android.providers.downloads.documents".equals(uri.getAuthority());
- }
-
- /**
- * @param uri The Uri to check.
- * @return Whether the Uri authority is MediaProvider.
- */
- public static boolean isMediaDocument(Uri uri) {
- return "com.android.providers.media.documents".equals(uri.getAuthority());
- }
-
- /**
- * @param uri The Uri to check.
- * @return Whether the Uri authority is Google Photos.
- */
- public static boolean isGooglePhotosUri(@NonNull final Uri uri) {
- return "com.google.android.apps.photos.content".equals(uri.getAuthority());
- }
-
- /**
- * @param context The Application context
- * @param uri The Uri is checked by functions
- * @return Whether the Uri authority is FileProvider
- */
- public static boolean isFileProviderUri(@NonNull final Context context,
- @NonNull final Uri uri) {
- final String packageName = context.getPackageName();
- final String authority = new StringBuilder(packageName).append(".provider").toString();
- return authority.equals(uri.getAuthority());
- }
-
- /**
- * @param context The Application context
- * @param uri The Uri is checked by functions
- * @return File path or null if file is missing
- */
- public static @Nullable String getFileProviderPath(@NonNull final Context context,
- @NonNull final Uri uri)
- {
- final File appDir = context.getExternalFilesDir(Environment.DIRECTORY_PICTURES);
- final File file = new File(appDir, uri.getLastPathSegment());
- return file.exists() ? file.toString(): null;
- }
+ File f = new File(filename);
+ return f.getName();
+ }
}