diff --git a/CHANGELOG.md b/CHANGELOG.md index b98e4f991..fad40dad6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,37 @@ # Mattermost Mobile Apps Changelog +## 1.25.0 Release +- Release Date: November 16, 2019 +- Server Versions Supported: Server v5.9+ is required, Self-Signed SSL Certificates are not supported unless the user installs the CA certificate on their device + +### Compatibility + - Android operating system 7+ [is required by Google](https://android-developers.googleblog.com/2017/12/improving-app-security-and-performance.html). + - iPhone 5s devices and later with iOS 11+ is required. + +### Bug Fixes + - Fixed an issue where Mattermost monokai theme no longer worked properly on mobile apps. + - Fixed an issue on Android where the notification badge count didn't update when using multiple channels. + - Fixed an issue on Android where test notifications did not work properly. + - Fixed an issue where "In-app" notifications caused the app badge count to get out of sync. + - Fixed an issue on Android where email notification setting displayed was not updated when the setting was changed. + - Fixed an issue where Favorite channels list didn't update if the app was running in the background. + - Fixed an issue where the timezone setting did not update when changing it back to set automatically. + - Fixed an issue on iOS where clicking on a hashtag from "recent mentions" (or flagged posts) returned the user to the channel instead of displaying hashtag search results. + - Fixed an issue where tapping on a hashtag engaged a keyboard for a moment before displaying search results. + - Fixed an issue where posts of the same thread appeared to be from different threads if separated by a new message line. + - Fixed styling issues on iOS for Name, Purpose and Header information on the channel info screen. + - Fixed styling issues with bot posts timestamps in search results and pinned posts. + - Fixed styling issues on single sign-on screen in landscape view on iOS iPhone X and later. + - Fixed styling issues on iOS for the Helper text on Settings screens. + - Fixed an issue where the thread view header theme was inconsistent during transition back to main channel view. + - Fixed an issue on iOS where the navigation bar tucked under the phone's status bar when switching orientation. + - Fixed an issue on iOS where the keyboard flashed darker when Automatic Replies had been previously enabled. + - Fixed an issue on Android where uploading pictures from storage or camera required unwanted permissions. + - Fixed an issue where ``mobile.message_length.message`` did not match webapp's ``create_post.error_message``. + +### Known Issues + - App slows down when opening a channel with large number of animated emoji. [MM-15792](https://mattermost.atlassian.net/browse/MM-15792) + ## 1.24.0 Release - Release Date: October 16, 2019 - Server Versions Supported: Server v5.9+ is required, Self-Signed SSL Certificates are not supported unless the user installs the CA certificate on their device diff --git a/android/app/src/main/java/com/mattermost/rnbeta/CustomPushNotification.java b/android/app/src/main/java/com/mattermost/rnbeta/CustomPushNotification.java index 0c272ec8e..52573dcfe 100644 --- a/android/app/src/main/java/com/mattermost/rnbeta/CustomPushNotification.java +++ b/android/app/src/main/java/com/mattermost/rnbeta/CustomPushNotification.java @@ -26,8 +26,9 @@ import java.lang.reflect.Field; import java.util.ArrayList; import java.util.Collections; import java.util.Date; -import java.util.LinkedHashMap; +import java.util.HashMap; import java.util.List; +import java.util.Map; import com.wix.reactnativenotifications.core.notification.PushNotification; import com.wix.reactnativenotifications.core.NotificationIntentAdapter; @@ -47,13 +48,16 @@ public class CustomPushNotification extends PushNotification { public static final String KEY_TEXT_REPLY = "CAN_REPLY"; public static final String NOTIFICATION_REPLIED_EVENT_NAME = "notificationReplied"; + private static final String PUSH_TYPE_MESSAGE = "message"; + private static final String PUSH_TYPE_CLEAR = "clear"; private static final String PUSH_TYPE_ID_LOADED = "id_loaded"; + private static final String PUSH_TYPE_UPDATE_BADGE = "update_badge"; private NotificationChannel mHighImportanceChannel; private NotificationChannel mMinImportanceChannel; - private static LinkedHashMap channelIdToNotificationCount = new LinkedHashMap(); - private static LinkedHashMap> channelIdToNotification = new LinkedHashMap>(); + private static Map channelIdToNotificationCount = new HashMap(); + private static Map> channelIdToNotification = new HashMap>(); private static AppLifecycleFacade lifecycleFacade; private static Context context; private static int badgeCount = 0; @@ -66,11 +70,9 @@ public class CustomPushNotification extends PushNotification { public static void clearNotification(Context mContext, int notificationId, String channelId) { if (notificationId != -1) { - Object objCount = channelIdToNotificationCount.get(channelId); - Integer count = -1; - - if (objCount != null) { - count = (Integer)objCount; + Integer count = channelIdToNotificationCount.get(channelId); + if (count == null) { + count = -1; } channelIdToNotificationCount.remove(channelId); @@ -130,22 +132,25 @@ public class CustomPushNotification extends PushNotification { if (channelId != null) { notificationId = channelId.hashCode(); - Object objCount = channelIdToNotificationCount.get(channelId); - Integer count = 1; - if (objCount != null) { - count = (Integer)objCount + 1; - } - channelIdToNotificationCount.put(channelId, count); - Object bundleArray = channelIdToNotification.get(channelId); - List list = null; - if (bundleArray == null) { - list = Collections.synchronizedList(new ArrayList(0)); - } else { - list = Collections.synchronizedList((List)bundleArray); + synchronized (channelIdToNotificationCount) { + Integer count = channelIdToNotificationCount.get(channelId); + if (count == null) { + count = 0; + } + + count += 1; + + channelIdToNotificationCount.put(channelId, count); } - synchronized (list) { - if (!"clear".equals(type)) { + + synchronized (channelIdToNotification) { + List list = channelIdToNotification.get(channelId); + if (list == null) { + list = Collections.synchronizedList(new ArrayList(0)); + } + + if (PUSH_TYPE_MESSAGE.equals(type)) { String senderName = getSenderName(data); data.putLong("time", new Date().getTime()); data.putString("sender_name", senderName); @@ -156,10 +161,13 @@ public class CustomPushNotification extends PushNotification { } } - if ("clear".equals(type)) { - cancelNotification(data, notificationId); - } else { + switch(type) { + case PUSH_TYPE_MESSAGE: super.postNotification(notificationId); + break; + case PUSH_TYPE_CLEAR: + cancelNotification(data, notificationId); + break; } notifyReceivedToJS(); @@ -270,10 +278,9 @@ public class CustomPushNotification extends PushNotification { } private void setNotificationNumber(Notification.Builder notification, String channelId) { - Integer number = 1; - Object objCount = channelIdToNotificationCount.get(channelId); - if (objCount != null) { - number = (Integer)objCount; + Integer number = channelIdToNotificationCount.get(channelId); + if (number != null) { + number = 0; } notification.setNumber(number); } diff --git a/app/reducers/views/channel.js b/app/reducers/views/channel.js index 3ffce2343..f9c771fbb 100644 --- a/app/reducers/views/channel.js +++ b/app/reducers/views/channel.js @@ -348,6 +348,11 @@ function lastChannelViewTime(state = {}, action) { return state; } + case ChannelTypes.POST_UNREAD_SUCCESS: { + const data = action.data; + return {...state, [data.channelId]: data.lastViewedAt}; + } + default: return state; } diff --git a/app/screens/post_options/__snapshots__/post_options.test.js.snap b/app/screens/post_options/__snapshots__/post_options.test.js.snap index fc686fc99..c7c26d3a9 100644 --- a/app/screens/post_options/__snapshots__/post_options.test.js.snap +++ b/app/screens/post_options/__snapshots__/post_options.test.js.snap @@ -1,197 +1,6 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`PostOptions should match snapshot, no option for system message to user who doesn't have the permission to delete 1`] = ` - - - - - - - - -`; - -exports[`PostOptions should match snapshot, showing Delete option only for system message to user who has permission to delete 1`] = ` + + + + +`; + +exports[`PostOptions should match snapshot, showing Delete option only for system message to user who has permission to delete 1`] = ` + + + + + + + { - const actions = [ - this.getEditOption(), - this.getReplyOption(), - this.getFlagOption(), - this.getPinOption(), - this.getAddReactionOption(), - this.getCopyPermalink(), - this.getCopyText(), - this.getDeleteOption(), - ]; + getMarkAsUnreadOption = () => { + const {post, isLandscape, theme} = this.props; + const {formatMessage} = this.context.intl; - return actions.filter((a) => a !== null); - }; - - getOthersPostOptions = () => { - const actions = [ - this.getReplyOption(), - this.getFlagOption(), - this.getAddReactionOption(), - this.getPinOption(), - this.getCopyPermalink(), - this.getCopyText(), - this.getEditOption(), - this.getDeleteOption(), - ]; - - return actions.filter((a) => a !== null); + if (!isSystemMessage(post)) { + return ( + + ); + } + return null; }; getPostOptions = () => { - const {isMyPost} = this.props; + const actions = [ + this.getReplyOption(), + this.getAddReactionOption(), + this.getMarkAsUnreadOption(), + this.getCopyPermalink(), + this.getFlagOption(), + this.getCopyText(), + this.getPinOption(), + this.getEditOption(), + this.getDeleteOption(), + ]; - return isMyPost ? this.getMyPostOptions() : this.getOthersPostOptions(); + return actions.filter((a) => a !== null); }; handleAddReaction = () => { @@ -324,6 +326,15 @@ export default class PostOptions extends PureComponent { }); }; + handleMarkUnread = () => { + const {actions, post, currentUserId} = this.props; + + this.closeWithAnimation(); + requestAnimationFrame(() => { + actions.setUnreadPost(currentUserId, post.id); + }); + } + handlePostDelete = () => { const {formatMessage} = this.context.intl; const {actions, post} = this.props; diff --git a/app/screens/post_options/post_options.test.js b/app/screens/post_options/post_options.test.js index 9027ed419..742538106 100644 --- a/app/screens/post_options/post_options.test.js +++ b/app/screens/post_options/post_options.test.js @@ -20,6 +20,7 @@ describe('PostOptions', () => { removePost: jest.fn(), unflagPost: jest.fn(), unpinPost: jest.fn(), + setUnreadPost: jest.fn(), }; const post = { @@ -40,6 +41,7 @@ describe('PostOptions', () => { canEditUntil: -1, channelIsReadOnly: false, currentTeamUrl: 'http://localhost:8065/team-name', + currentUserId: 'user1', deviceHeight: 600, hasBeenDeleted: false, isFlagged: false, diff --git a/app/utils/push_notifications.js b/app/utils/push_notifications.js index f296c139c..6d8b480ea 100644 --- a/app/utils/push_notifications.js +++ b/app/utils/push_notifications.js @@ -75,7 +75,7 @@ class PushNotificationUtils { if (data.type === 'clear') { dispatch(markChannelViewedAndRead(data.channel_id, null, false)); - } else { + } else if (data.type === 'message') { // get the posts for the channel as soon as possible retryGetPostsAction(getPosts(data.channel_id), dispatch, getState); diff --git a/assets/base/i18n/en.json b/assets/base/i18n/en.json index c75414a53..c0b36cd34 100644 --- a/assets/base/i18n/en.json +++ b/assets/base/i18n/en.json @@ -360,6 +360,7 @@ "mobile.post_info.add_reaction": "Add Reaction", "mobile.post_info.copy_text": "Copy Text", "mobile.post_info.flag": "Flag", + "mobile.post_info.mark_unread": "Mark post as unread", "mobile.post_info.pin": "Pin to Channel", "mobile.post_info.reply": "Reply", "mobile.post_info.unflag": "Unflag", diff --git a/assets/base/images/post_menu/bookmark.png b/assets/base/images/post_menu/bookmark.png new file mode 100755 index 000000000..df28ccf58 Binary files /dev/null and b/assets/base/images/post_menu/bookmark.png differ diff --git a/assets/base/images/post_menu/bookmark@2x.png b/assets/base/images/post_menu/bookmark@2x.png new file mode 100755 index 000000000..7f1db3004 Binary files /dev/null and b/assets/base/images/post_menu/bookmark@2x.png differ diff --git a/assets/base/images/post_menu/bookmark@3x.png b/assets/base/images/post_menu/bookmark@3x.png new file mode 100755 index 000000000..805a4c668 Binary files /dev/null and b/assets/base/images/post_menu/bookmark@3x.png differ diff --git a/ios/Mattermost/AppDelegate.m b/ios/Mattermost/AppDelegate.m index f7c35abda..4ae336881 100644 --- a/ios/Mattermost/AppDelegate.m +++ b/ios/Mattermost/AppDelegate.m @@ -21,7 +21,9 @@ @implementation AppDelegate +NSString* const NOTIFICATION_MESSAGE_ACTION = @"message"; NSString* const NOTIFICATION_CLEAR_ACTION = @"clear"; +NSString* const NOTIFICATION_UPDATE_BADGE_ACTION = @"update_badge"; -(void)application:(UIApplication *)application handleEventsForBackgroundURLSession:(NSString *)identifier completionHandler:(void (^)(void))completionHandler { os_log(OS_LOG_DEFAULT, "Mattermost will attach session from handleEventsForBackgroundURLSession!! identifier=%{public}@", identifier); diff --git a/package-lock.json b/package-lock.json index c1aee1a7c..ad9b98cab 100644 --- a/package-lock.json +++ b/package-lock.json @@ -7821,8 +7821,8 @@ } }, "mattermost-redux": { - "version": "github:mattermost/mattermost-redux#ebd4fd361e982bf2f9b5737642dc5fcf6e1fa3f7", - "from": "github:mattermost/mattermost-redux#ebd4fd361e982bf2f9b5737642dc5fcf6e1fa3f7", + "version": "github:mattermost/mattermost-redux#1976433179ba69765623ce12194c4ec9fc93e580", + "from": "github:mattermost/mattermost-redux#1976433179ba69765623ce12194c4ec9fc93e580", "requires": { "form-data": "2.5.1", "gfycat-sdk": "1.4.18", diff --git a/package.json b/package.json index 19e8001e0..eb4896b36 100644 --- a/package.json +++ b/package.json @@ -24,7 +24,7 @@ "intl": "1.2.5", "jail-monkey": "2.3.0", "jsc-android": "241213.1.0", - "mattermost-redux": "github:mattermost/mattermost-redux#ebd4fd361e982bf2f9b5737642dc5fcf6e1fa3f7", + "mattermost-redux": "github:mattermost/mattermost-redux#1976433179ba69765623ce12194c4ec9fc93e580", "mime-db": "1.42.0", "moment-timezone": "0.5.27", "prop-types": "15.7.2", diff --git a/packager/moduleNames.js b/packager/moduleNames.js index d9ca811c3..20c3fdf83 100644 --- a/packager/moduleNames.js +++ b/packager/moduleNames.js @@ -269,6 +269,7 @@ module.exports = [ 'dist/assets/images/icons/word.png', 'dist/assets/images/post_header/flag.png', 'dist/assets/images/post_header/pin.png', + 'dist/assets/images/post_header/bookmark.png', 'dist/assets/images/profile.jpg', 'dist/assets/images/thumb.png', 'dist/assets/mattermost-fonts.json',