diff --git a/CHANGES.rst b/CHANGES.rst index 3677dfdb1..771691791 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -1,3 +1,31 @@ +Changes in 1.2.6 (2021-03-11) +================================================= + +✨ Features + * Improve the status of send messages (sending, sent, received, failed) (#4014) + * Retrying & deleting failed messages (#4013) + +🙌 Improvements + * + +🐛 Bugfix + * + +⚠️ API Changes + * + +🗣 Translations + * + +🧱 Build + * + +Others + * + +Improvements: + * Upgrade MatrixKit version ([v0.14.5](https://github.com/matrix-org/matrix-ios-kit/releases/tag/v0.14.5)). + Changes in 1.2.5 (2021-03-03) ================================================= diff --git a/Config/AppIdentifiers.xcconfig b/Config/AppIdentifiers.xcconfig index 0741a0bde..301b97518 100644 --- a/Config/AppIdentifiers.xcconfig +++ b/Config/AppIdentifiers.xcconfig @@ -22,8 +22,8 @@ APPLICATION_GROUP_IDENTIFIER = group.im.vector APPLICATION_SCHEME = element // Version -MARKETING_VERSION = 1.2.5 -CURRENT_PROJECT_VERSION = 1.2.5 +MARKETING_VERSION = 1.2.6 +CURRENT_PROJECT_VERSION = 1.2.6 // Team diff --git a/Gemfile.lock b/Gemfile.lock index bee81988c..fcfae5852 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -130,7 +130,10 @@ GEM xcodeproj (>= 1.13.0, < 2.0.0) xcpretty (~> 0.3.0) xcpretty-travis-formatter (>= 0.0.3) + fastlane-plugin-brew (0.1.1) fastlane-plugin-versioning (0.4.4) + fastlane-plugin-xcodegen (1.0.0) + fastlane-plugin-brew (~> 0.1.1) ffi (1.13.1) fourflusher (2.3.1) fuzzy_match (2.0.4) @@ -245,6 +248,7 @@ DEPENDENCIES cocoapods (~> 1.10.0) fastlane fastlane-plugin-versioning + fastlane-plugin-xcodegen xcode-install BUNDLED WITH diff --git a/Podfile b/Podfile index b224fdba6..0787c95a9 100644 --- a/Podfile +++ b/Podfile @@ -11,7 +11,7 @@ use_frameworks! # - `{ {kit spec hash} => {sdk spec hash}` to depend on specific pod options (:git => …, :podspec => …) for each repo. Used by Fastfile during CI # # Warning: our internal tooling depends on the name of this variable name, so be sure not to change it -$matrixKitVersion = '= 0.14.4' +$matrixKitVersion = '= 0.14.5' # $matrixKitVersion = :local # $matrixKitVersion = {'develop' => 'develop'} diff --git a/Podfile.lock b/Podfile.lock index b006b76a0..aafd81983 100644 --- a/Podfile.lock +++ b/Podfile.lock @@ -55,28 +55,28 @@ PODS: - MatomoTracker (7.2.2): - MatomoTracker/Core (= 7.2.2) - MatomoTracker/Core (7.2.2) - - MatrixKit (0.14.4): + - MatrixKit (0.14.5): - Down (~> 0.9.3) - DTCoreText (~> 1.6.23) - HPGrowingTextView (~> 1.1) - libPhoneNumber-iOS (~> 0.9.13) - - MatrixKit/Core (= 0.14.4) - - MatrixSDK (= 0.18.4) - - MatrixKit/Core (0.14.4): + - MatrixKit/Core (= 0.14.5) + - MatrixSDK (= 0.18.5) + - MatrixKit/Core (0.14.5): - Down (~> 0.9.3) - DTCoreText (~> 1.6.23) - HPGrowingTextView (~> 1.1) - libPhoneNumber-iOS (~> 0.9.13) - - MatrixSDK (= 0.18.4) - - MatrixSDK (0.18.4): - - MatrixSDK/Core (= 0.18.4) - - MatrixSDK/Core (0.18.4): + - MatrixSDK (= 0.18.5) + - MatrixSDK (0.18.5): + - MatrixSDK/Core (= 0.18.5) + - MatrixSDK/Core (0.18.5): - AFNetworking (~> 4.0.0) - GZIP (~> 1.3.0) - libbase58 (~> 0.1.4) - OLMKit (~> 3.2.2) - Realm (= 10.1.4) - - MatrixSDK/JingleCallStack (0.18.4): + - MatrixSDK/JingleCallStack (0.18.5): - JitsiMeetSDK (= 3.1.0) - MatrixSDK/Core - OLMKit (3.2.2): @@ -115,7 +115,7 @@ DEPENDENCIES: - KeychainAccess (~> 4.2.1) - KTCenterFlowLayout (~> 1.3.1) - MatomoTracker (~> 7.2.2) - - MatrixKit (= 0.14.4) + - MatrixKit (= 0.14.5) - MatrixSDK - MatrixSDK/JingleCallStack - OLMKit @@ -186,8 +186,8 @@ SPEC CHECKSUMS: LoggerAPI: ad9c4a6f1e32f518fdb43a1347ac14d765ab5e3d Logging: beeb016c9c80cf77042d62e83495816847ef108b MatomoTracker: a59ec4da0f580be57bdc6baa708a71a86532a832 - MatrixKit: fad56170110b7248cfdd5bf210f944c6fad04162 - MatrixSDK: d2cb905cf6afa5df63c5a76f7678456723923af5 + MatrixKit: f77c5aa1a236331665d3b1f25ed37ed8758eb23f + MatrixSDK: 013859281629b8cccd3a8af1dec48f36335c058c OLMKit: 20d1c564033a1ae7148f8f599378d4c798363905 ReadMoreTextView: 19147adf93abce6d7271e14031a00303fe28720d Realm: 80f4fb2971ccb9adc27a47d0955ae8e533a7030b @@ -199,6 +199,6 @@ SPEC CHECKSUMS: zxcvbn-ios: fef98b7c80f1512ff0eec47ac1fa399fc00f7e3c ZXingObjC: fdbb269f25dd2032da343e06f10224d62f537bdb -PODFILE CHECKSUM: c98fd507efbe607a5821e154e941210a093f54f9 +PODFILE CHECKSUM: e2cd2613ad0fe9313fc917b2c277836f16a6fbc3 COCOAPODS: 1.10.0 diff --git a/Riot/Assets/Images.xcassets/Common/error_icon.imageset/Contents.json b/Riot/Assets/Images.xcassets/Common/error_icon.imageset/Contents.json new file mode 100644 index 000000000..3979fbfbd --- /dev/null +++ b/Riot/Assets/Images.xcassets/Common/error_icon.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "filename" : "error_icon.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "error_icon@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "error_icon@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Riot/Assets/Images.xcassets/Common/error_icon.imageset/error_icon.png b/Riot/Assets/Images.xcassets/Common/error_icon.imageset/error_icon.png new file mode 100644 index 000000000..7be4fcb2b Binary files /dev/null and b/Riot/Assets/Images.xcassets/Common/error_icon.imageset/error_icon.png differ diff --git a/Riot/Assets/Images.xcassets/Common/error_icon.imageset/error_icon@2x.png b/Riot/Assets/Images.xcassets/Common/error_icon.imageset/error_icon@2x.png new file mode 100644 index 000000000..fcddaea56 Binary files /dev/null and b/Riot/Assets/Images.xcassets/Common/error_icon.imageset/error_icon@2x.png differ diff --git a/Riot/Assets/Images.xcassets/Common/error_icon.imageset/error_icon@3x.png b/Riot/Assets/Images.xcassets/Common/error_icon.imageset/error_icon@3x.png new file mode 100644 index 000000000..90dc3cf32 Binary files /dev/null and b/Riot/Assets/Images.xcassets/Common/error_icon.imageset/error_icon@3x.png differ diff --git a/Riot/Assets/Images.xcassets/Room/Activities/Contents.json b/Riot/Assets/Images.xcassets/Room/Activities/Contents.json index da4a164c9..73c00596a 100644 --- a/Riot/Assets/Images.xcassets/Room/Activities/Contents.json +++ b/Riot/Assets/Images.xcassets/Room/Activities/Contents.json @@ -1,6 +1,6 @@ { "info" : { - "version" : 1, - "author" : "xcode" + "author" : "xcode", + "version" : 1 } -} \ No newline at end of file +} diff --git a/Riot/Assets/Images.xcassets/Room/Activities/error_message_tick.imageset/Contents.json b/Riot/Assets/Images.xcassets/Room/Activities/error_message_tick.imageset/Contents.json new file mode 100644 index 000000000..009c55d10 --- /dev/null +++ b/Riot/Assets/Images.xcassets/Room/Activities/error_message_tick.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "filename" : "error_message_tick.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "error_message_tick@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "error_message_tick@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Riot/Assets/Images.xcassets/Room/Activities/error_message_tick.imageset/error_message_tick.png b/Riot/Assets/Images.xcassets/Room/Activities/error_message_tick.imageset/error_message_tick.png new file mode 100644 index 000000000..501fe2b20 Binary files /dev/null and b/Riot/Assets/Images.xcassets/Room/Activities/error_message_tick.imageset/error_message_tick.png differ diff --git a/Riot/Assets/Images.xcassets/Room/Activities/error_message_tick.imageset/error_message_tick@2x.png b/Riot/Assets/Images.xcassets/Room/Activities/error_message_tick.imageset/error_message_tick@2x.png new file mode 100644 index 000000000..b4511c955 Binary files /dev/null and b/Riot/Assets/Images.xcassets/Room/Activities/error_message_tick.imageset/error_message_tick@2x.png differ diff --git a/Riot/Assets/Images.xcassets/Room/Activities/error_message_tick.imageset/error_message_tick@3x.png b/Riot/Assets/Images.xcassets/Room/Activities/error_message_tick.imageset/error_message_tick@3x.png new file mode 100644 index 000000000..7f13e7653 Binary files /dev/null and b/Riot/Assets/Images.xcassets/Room/Activities/error_message_tick.imageset/error_message_tick@3x.png differ diff --git a/Riot/Assets/Images.xcassets/Room/Activities/room_activities_retry.imageset/Contents.json b/Riot/Assets/Images.xcassets/Room/Activities/room_activities_retry.imageset/Contents.json new file mode 100644 index 000000000..13c5ca50a --- /dev/null +++ b/Riot/Assets/Images.xcassets/Room/Activities/room_activities_retry.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "filename" : "room_activities_retry.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "room_activities_retry@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "room_activities_retry@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Riot/Assets/Images.xcassets/Room/Activities/room_activities_retry.imageset/room_activities_retry.png b/Riot/Assets/Images.xcassets/Room/Activities/room_activities_retry.imageset/room_activities_retry.png new file mode 100644 index 000000000..c23a356ee Binary files /dev/null and b/Riot/Assets/Images.xcassets/Room/Activities/room_activities_retry.imageset/room_activities_retry.png differ diff --git a/Riot/Assets/Images.xcassets/Room/Activities/room_activities_retry.imageset/room_activities_retry@2x.png b/Riot/Assets/Images.xcassets/Room/Activities/room_activities_retry.imageset/room_activities_retry@2x.png new file mode 100644 index 000000000..c40b768f7 Binary files /dev/null and b/Riot/Assets/Images.xcassets/Room/Activities/room_activities_retry.imageset/room_activities_retry@2x.png differ diff --git a/Riot/Assets/Images.xcassets/Room/Activities/room_activities_retry.imageset/room_activities_retry@3x.png b/Riot/Assets/Images.xcassets/Room/Activities/room_activities_retry.imageset/room_activities_retry@3x.png new file mode 100644 index 000000000..2adf43e33 Binary files /dev/null and b/Riot/Assets/Images.xcassets/Room/Activities/room_activities_retry.imageset/room_activities_retry@3x.png differ diff --git a/Riot/Assets/Images.xcassets/Room/Activities/sending_message_tick.imageset/Contents.json b/Riot/Assets/Images.xcassets/Room/Activities/sending_message_tick.imageset/Contents.json new file mode 100644 index 000000000..382cec00c --- /dev/null +++ b/Riot/Assets/Images.xcassets/Room/Activities/sending_message_tick.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "filename" : "sending_message_tick.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "sending_message_tick@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "sending_message_tick@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Riot/Assets/Images.xcassets/Room/Activities/sending_message_tick.imageset/sending_message_tick.png b/Riot/Assets/Images.xcassets/Room/Activities/sending_message_tick.imageset/sending_message_tick.png new file mode 100644 index 000000000..8ed0cddc3 Binary files /dev/null and b/Riot/Assets/Images.xcassets/Room/Activities/sending_message_tick.imageset/sending_message_tick.png differ diff --git a/Riot/Assets/Images.xcassets/Room/Activities/sending_message_tick.imageset/sending_message_tick@2x.png b/Riot/Assets/Images.xcassets/Room/Activities/sending_message_tick.imageset/sending_message_tick@2x.png new file mode 100644 index 000000000..7120c73f7 Binary files /dev/null and b/Riot/Assets/Images.xcassets/Room/Activities/sending_message_tick.imageset/sending_message_tick@2x.png differ diff --git a/Riot/Assets/Images.xcassets/Room/Activities/sending_message_tick.imageset/sending_message_tick@3x.png b/Riot/Assets/Images.xcassets/Room/Activities/sending_message_tick.imageset/sending_message_tick@3x.png new file mode 100644 index 000000000..7726943a6 Binary files /dev/null and b/Riot/Assets/Images.xcassets/Room/Activities/sending_message_tick.imageset/sending_message_tick@3x.png differ diff --git a/Riot/Assets/Images.xcassets/Room/Activities/sent_message_tick.imageset/Contents.json b/Riot/Assets/Images.xcassets/Room/Activities/sent_message_tick.imageset/Contents.json new file mode 100644 index 000000000..6472c5c7c --- /dev/null +++ b/Riot/Assets/Images.xcassets/Room/Activities/sent_message_tick.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "filename" : "sent_message_tick.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "sent_message_tick@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "sent_message_tick@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Riot/Assets/Images.xcassets/Room/Activities/sent_message_tick.imageset/sent_message_tick.png b/Riot/Assets/Images.xcassets/Room/Activities/sent_message_tick.imageset/sent_message_tick.png new file mode 100644 index 000000000..37ae5baf5 Binary files /dev/null and b/Riot/Assets/Images.xcassets/Room/Activities/sent_message_tick.imageset/sent_message_tick.png differ diff --git a/Riot/Assets/Images.xcassets/Room/Activities/sent_message_tick.imageset/sent_message_tick@2x.png b/Riot/Assets/Images.xcassets/Room/Activities/sent_message_tick.imageset/sent_message_tick@2x.png new file mode 100644 index 000000000..d72c36670 Binary files /dev/null and b/Riot/Assets/Images.xcassets/Room/Activities/sent_message_tick.imageset/sent_message_tick@2x.png differ diff --git a/Riot/Assets/Images.xcassets/Room/Activities/sent_message_tick.imageset/sent_message_tick@3x.png b/Riot/Assets/Images.xcassets/Room/Activities/sent_message_tick.imageset/sent_message_tick@3x.png new file mode 100644 index 000000000..d25108721 Binary files /dev/null and b/Riot/Assets/Images.xcassets/Room/Activities/sent_message_tick.imageset/sent_message_tick@3x.png differ diff --git a/Riot/Assets/Images.xcassets/Room/ContextMenu/room_context_menu_delete.imageset/Contents.json b/Riot/Assets/Images.xcassets/Room/ContextMenu/room_context_menu_delete.imageset/Contents.json new file mode 100644 index 000000000..bb4344e92 --- /dev/null +++ b/Riot/Assets/Images.xcassets/Room/ContextMenu/room_context_menu_delete.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "filename" : "room_context_menu_delete.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "room_context_menu_delete@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "room_context_menu_delete@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Riot/Assets/Images.xcassets/Room/ContextMenu/room_context_menu_delete.imageset/room_context_menu_delete.png b/Riot/Assets/Images.xcassets/Room/ContextMenu/room_context_menu_delete.imageset/room_context_menu_delete.png new file mode 100644 index 000000000..a31ba247b Binary files /dev/null and b/Riot/Assets/Images.xcassets/Room/ContextMenu/room_context_menu_delete.imageset/room_context_menu_delete.png differ diff --git a/Riot/Assets/Images.xcassets/Room/ContextMenu/room_context_menu_delete.imageset/room_context_menu_delete@2x.png b/Riot/Assets/Images.xcassets/Room/ContextMenu/room_context_menu_delete.imageset/room_context_menu_delete@2x.png new file mode 100644 index 000000000..5facd1748 Binary files /dev/null and b/Riot/Assets/Images.xcassets/Room/ContextMenu/room_context_menu_delete.imageset/room_context_menu_delete@2x.png differ diff --git a/Riot/Assets/Images.xcassets/Room/ContextMenu/room_context_menu_delete.imageset/room_context_menu_delete@3x.png b/Riot/Assets/Images.xcassets/Room/ContextMenu/room_context_menu_delete.imageset/room_context_menu_delete@3x.png new file mode 100644 index 000000000..47611a972 Binary files /dev/null and b/Riot/Assets/Images.xcassets/Room/ContextMenu/room_context_menu_delete.imageset/room_context_menu_delete@3x.png differ diff --git a/Riot/Assets/Images.xcassets/Room/ContextMenu/room_context_menu_retry.imageset/Contents.json b/Riot/Assets/Images.xcassets/Room/ContextMenu/room_context_menu_retry.imageset/Contents.json new file mode 100644 index 000000000..e9c2f10e7 --- /dev/null +++ b/Riot/Assets/Images.xcassets/Room/ContextMenu/room_context_menu_retry.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "filename" : "room_context_menu_retry.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "room_context_menu_retry@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "room_context_menu_retry@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Riot/Assets/Images.xcassets/Room/ContextMenu/room_context_menu_retry.imageset/room_context_menu_retry.png b/Riot/Assets/Images.xcassets/Room/ContextMenu/room_context_menu_retry.imageset/room_context_menu_retry.png new file mode 100644 index 000000000..62684d757 Binary files /dev/null and b/Riot/Assets/Images.xcassets/Room/ContextMenu/room_context_menu_retry.imageset/room_context_menu_retry.png differ diff --git a/Riot/Assets/Images.xcassets/Room/ContextMenu/room_context_menu_retry.imageset/room_context_menu_retry@2x.png b/Riot/Assets/Images.xcassets/Room/ContextMenu/room_context_menu_retry.imageset/room_context_menu_retry@2x.png new file mode 100644 index 000000000..6f6b583fa Binary files /dev/null and b/Riot/Assets/Images.xcassets/Room/ContextMenu/room_context_menu_retry.imageset/room_context_menu_retry@2x.png differ diff --git a/Riot/Assets/Images.xcassets/Room/ContextMenu/room_context_menu_retry.imageset/room_context_menu_retry@3x.png b/Riot/Assets/Images.xcassets/Room/ContextMenu/room_context_menu_retry.imageset/room_context_menu_retry@3x.png new file mode 100644 index 000000000..0ea40292b Binary files /dev/null and b/Riot/Assets/Images.xcassets/Room/ContextMenu/room_context_menu_retry.imageset/room_context_menu_retry@3x.png differ diff --git a/Riot/Assets/ar.lproj/InfoPlist.strings b/Riot/Assets/ar.lproj/InfoPlist.strings new file mode 100644 index 000000000..a0d41c0b3 --- /dev/null +++ b/Riot/Assets/ar.lproj/InfoPlist.strings @@ -0,0 +1,9 @@ + + +"NSContactsUsageDescription" = "لاِكتِشاف جِهات الاِتِّصال الَّتي تَستَخدِمُ Matrix بِالفِعل، يُمكِنُ لِـElement إرسَال عَناوين البَريد الإلِكتُرونيّ وأرقام الهَواتِف الَّتي في دَفتَرِ العَناوين الخاصِّ بِك إلى خادِمِ هُويَّة Matrix المُختار. يَتِّمُ تَجزِئة البَياناتِ الشَّخصيَّة قَبلَ إرسالِها حَيثُما كانَت مَدعُومَة - يُرجى مُراجَعَة سياسَة الخُصُوصيَّة الخاصَّة بِخادِم الهُويَّة لِلحُصُولِ عَلَى المَزيدِ مِنَ التَّفاصيل."; +"NSFaceIDUsageDescription" = "يُستخدَم الـFace ID لِلوُصُول إلى التَّطبيق الخاصّ بِك."; +"NSCalendarsUsageDescription" = "اطَّلِع عَلَى اِجتِماعاتِك المُجَدوَلَة في التَّطبيق."; +"NSMicrophoneUsageDescription" = "المِيكرُوفُون يُستَخدَم لاِلتِقاط المَقاطِع المَرئيَّة وإجراء المُكالَمات."; +// Permissions usage explanations +"NSCameraUsageDescription" = "تُستَخدَم الكاميرا لاِلتِقاط الصُّوَر، المَقاطِع المَرئيَّة وإجراءُ مُكالَمَةٍ مَرئيَّة."; +"NSPhotoLibraryUsageDescription" = "مَكتَبَة الصُّوَر تُستَخدَم لإرسال الصُّوَر وَالمَقاطِع المَرئيَّة."; diff --git a/Riot/Assets/ar.lproj/Localizable.strings b/Riot/Assets/ar.lproj/Localizable.strings new file mode 100644 index 000000000..05c668e49 --- /dev/null +++ b/Riot/Assets/ar.lproj/Localizable.strings @@ -0,0 +1,113 @@ + + +/* Message title for a specific person in a named room */ +"MSG_FROM_USER_IN_ROOM_TITLE" = "العُضو %@ في %@"; + +/** Single, unencrypted messages (where we can include the content */ + +/* New message from a specific person, not referencing a room. Content included. */ +"MSG_FROM_USER_WITH_CONTENT" = "العُضو %@: %@"; + +/* New action message from a specific person, not referencing a room. */ +"ACTION_FROM_USER" = "* %@ %@"; + +/** Key verification **/ + +"KEY_VERIFICATION_REQUEST_FROM_USER" = "إنَّ %@ يُريدُ التَّحَقُّق"; + +/* Incoming named video conference invite from a specific person */ +"VIDEO_CONF_NAMED_FROM_USER" = "مُكالَمَة مَرئيَّة جَماعِيَّة مِن %@: %@"; + +/* Incoming named voice conference invite from a specific person */ +"VOICE_CONF_NAMED_FROM_USER" = "مُحادَثَة جَماعِيَّة مِن %@: '%@'"; + +/* Incoming unnamed video conference invite from a specific person */ +"VIDEO_CONF_FROM_USER" = "مُكالَمَة مَرئيَّة جَماعِيَّة مِن %@"; + +/* Incoming unnamed voice conference invite from a specific person */ +"VOICE_CONF_FROM_USER" = "مُحادَثَة جَماعِيَّة مِن %@"; + +/* Incoming one-to-one video call */ +"VIDEO_CALL_FROM_USER" = "مُكالَمَة مَرئيَّة مِن %@"; + +/** Calls **/ + +/* Incoming one-to-one voice call */ +"VOICE_CALL_FROM_USER" = "مُكالَمَة مِن %@"; + +/* A user has invited you to a named room */ +"USER_INVITE_TO_NAMED_ROOM" = "لَقَد دَعاكَ %@ إلى %@"; + +/* A user has invited you to an (unamed) group chat */ +"USER_INVITE_TO_CHAT_GROUP_CHAT" = "لَقَد دَعاكَ %@ إلى مُحادَثَة جَماعِيَّة"; + +/** Invites **/ + +/* A user has invited you to a chat */ +"USER_INVITE_TO_CHAT" = "لَقَد دَعاكَ %@ إلى مُحادَثَة"; + +/* Look, stuff's happened, alright? Just open the app. */ +"MSGS_IN_TWO_PLUS_ROOMS" = "عَدَد %@ رِسالَة جَديدَة في %@، %@ وَأُخرَى"; + +/* Multiple messages in two rooms */ +"MSGS_IN_TWO_ROOMS" = "عَدَد %@ رِسالَة جَديدَة في %@ وَ %@"; + +/* Multiple unread messages from two plus people (ie. for 4+ people: 'others' replaces the third person) */ +"MSGS_FROM_TWO_PLUS_USERS" = "عَدَد %@ رِسالَة جَديدَة مِن %@، %@ وَآخرُون"; + +/* Multiple unread messages from three people */ +"MSGS_FROM_THREE_USERS" = "عَدَد %@ رِسالَة جَديدَة مِن %@، %@ وَ %@"; + +/* Multiple unread messages from two people */ +"MSGS_FROM_TWO_USERS" = "عَدَد %@ رِسالَة جَديدَة مِن %@ وَ %@"; + +/* Multiple unread messages from a specific person, not referencing a room */ +"MSGS_FROM_USER" = "عَدَد %@ رِسالَة جَديدَة في %@"; + +/** Coalesced messages **/ + +/* Multiple unread messages in a room */ +"UNREAD_IN_ROOM" = "عَدَد %@ رِسالَة جَديدَة في %@"; +"MESSAGE_PROTECTED" = "رِسالَة جَديدَة"; + +/* New message indicator on a room */ +"MESSAGE_IN_X" = "رِسالَة في %@"; + +/* New message indicator from a DM */ +"MESSAGE_FROM_X" = "رِسالَة مِن %@"; + +/** Notification messages **/ + +/* New message indicator on unknown room */ +"MESSAGE" = "رِسالة"; + +/* Sticker from a specific person, not referencing a room. */ +"STICKER_FROM_USER" = "لَقَد أرسَلَ %@ مُلصَق"; + +/* A single unread message */ +"SINGLE_UNREAD" = "أنتَ قَد اِستَلَمتَ رِسالَة"; + +/* A single unread message in a room */ +"SINGLE_UNREAD_IN_ROOM" = "أنتَ قَد اِستَلَمتَ رِسالَة في %@"; + +/* New action message from a specific person in a named room. */ +"IMAGE_FROM_USER_IN_ROOM" = "لَقَد نَشَرَ %@ صُّورَة %@ في %@"; + +/** Image Messages **/ + +/* New action message from a specific person, not referencing a room. */ +"IMAGE_FROM_USER" = "لَقَد أرسَلَ %@ صُّورة %@"; + +/* New action message from a specific person in a named room. */ +"ACTION_FROM_USER_IN_ROOM" = "العُضو %@: * %@ %@"; + +/* New message from a specific person in a named room. Content included. */ +"MSG_FROM_USER_IN_ROOM_WITH_CONTENT" = "العُضو %@ في %@: %@"; + +/* New message from a specific person in a named room */ +"MSG_FROM_USER_IN_ROOM" = "لَقَد نَشَرَ %@ في %@"; + +/** Single, end-to-end encrypted messages (ie. we don't know what they say) */ + +/* New message from a specific person, not referencing a room */ +"MSG_FROM_USER" = "لَقَد أرسَلَ %@ رِسالة"; diff --git a/Riot/Assets/bg.lproj/Vector.strings b/Riot/Assets/bg.lproj/Vector.strings index a1bb6c3f7..186c3ef67 100644 --- a/Riot/Assets/bg.lproj/Vector.strings +++ b/Riot/Assets/bg.lproj/Vector.strings @@ -209,8 +209,8 @@ "encrypted_room_message_placeholder" = "Изпрати шифровано съобщение…"; "room_message_short_placeholder" = "Изпрати съобщение…"; "room_offline_notification" = "Връзката със сървъра е изгубена."; -"room_unsent_messages_notification" = "Съобщенията не са изпратени. %@ или %@?"; -"room_unsent_messages_unknown_devices_notification" = "Съобщението не е изпратено поради наличието на непознати сесии. %@ или %@?"; +"room_unsent_messages_notification" = "Съобщенията не са изпратени."; +"room_unsent_messages_unknown_devices_notification" = "Съобщението не е изпратено поради наличието на непознати сесии."; "room_ongoing_conference_call" = "Текущ групов разговор. Присъедини се с %@ или %@."; "room_ongoing_conference_call_with_close" = "Текущ групов разговор. Присъедини се с %@ или %@. %@ го."; "room_ongoing_conference_call_close" = "Затвори"; diff --git a/Riot/Assets/ca.lproj/Vector.strings b/Riot/Assets/ca.lproj/Vector.strings index 699284b81..f11645924 100644 --- a/Riot/Assets/ca.lproj/Vector.strings +++ b/Riot/Assets/ca.lproj/Vector.strings @@ -204,8 +204,8 @@ "encrypted_room_message_placeholder" = "Envia un missatge encriptat…"; "room_message_short_placeholder" = "Envia un missatge…"; "room_offline_notification" = "S'ha perdut la connexió amb el servidor."; -"room_unsent_messages_notification" = "Missatges no enviats. %@ o %@ ara?"; -"room_unsent_messages_unknown_devices_notification" = "El missatge no s'ha enviat perquè hi ha presents dispositius desconeguts. %@ o %@ ara?"; +"room_unsent_messages_notification" = "Missatges no enviats."; +"room_unsent_messages_unknown_devices_notification" = "El missatge no s'ha enviat perquè hi ha presents dispositius desconeguts."; "room_ongoing_conference_call" = "Conferència en curs. Unir-te com a %@ o %@."; "room_ongoing_conference_call_with_close" = "Conferència en curs. Unir-te com a %@ o %@. %@."; "room_ongoing_conference_call_close" = "Tancar"; diff --git a/Riot/Assets/cy.lproj/Vector.strings b/Riot/Assets/cy.lproj/Vector.strings index 01258fa76..e1524bbc8 100644 --- a/Riot/Assets/cy.lproj/Vector.strings +++ b/Riot/Assets/cy.lproj/Vector.strings @@ -237,8 +237,8 @@ "room_message_short_placeholder" = "Anfon neges…"; "room_message_reply_to_short_placeholder" = "Anfon ateb…"; "room_offline_notification" = "Collwyd cysylltedd â'r gweinydd."; -"room_unsent_messages_notification" = "Negeseuon heb eu hanfon. %@ neu %@ rwan?"; -"room_unsent_messages_unknown_devices_notification" = "Ni anfonwyd neges oherwydd bod sesiynau anhysbys yn bresennol. %@ neu %@ rwan?"; +"room_unsent_messages_notification" = "Negeseuon heb eu hanfon."; +"room_unsent_messages_unknown_devices_notification" = "Ni anfonwyd neges oherwydd bod sesiynau anhysbys yn bresennol."; "room_ongoing_conference_call" = "Galwad gynhadledd ar y gweill. Ymunwch fel %@ neu %@."; "room_ongoing_conference_call_with_close" = "Galwad gynhadledd ar y gweill. Ymunwch fel %@ neu %@. %@ o."; "room_ongoing_conference_call_close" = "Cau"; diff --git a/Riot/Assets/de.lproj/InfoPlist.strings b/Riot/Assets/de.lproj/InfoPlist.strings index 0b9843e72..e9c801f7e 100644 --- a/Riot/Assets/de.lproj/InfoPlist.strings +++ b/Riot/Assets/de.lproj/InfoPlist.strings @@ -4,4 +4,4 @@ "NSMicrophoneUsageDescription" = "Das Mikrofon wird verwendet, um Videos aufzunehmen sowie Gespräche zu führen."; "NSContactsUsageDescription" = "Element kann E-Mail-Adressen und Telefonnummern aus deinem Adressbuch zu deinem Matrix-Identitätsserver schicken, um Kontakte zu finden, die bereits Matrix verwenden. Wenn verfügbar, werden persönliche Daten vor dem Versand gehasht. Für weitere Informationen schaue bitte in die Datenschutzerklärung deines Identitätsservers."; "NSCalendarsUsageDescription" = "Sieh dir deine geplanten Meetings in der App an."; -"NSFaceIDUsageDescription" = "Face ID wird zum Zugriff auf deine App verwendet."; +"NSFaceIDUsageDescription" = "Face-ID wird zum Zugriff auf deine App verwendet."; diff --git a/Riot/Assets/de.lproj/Vector.strings b/Riot/Assets/de.lproj/Vector.strings index 35921b1e6..a4f929dcd 100644 --- a/Riot/Assets/de.lproj/Vector.strings +++ b/Riot/Assets/de.lproj/Vector.strings @@ -157,8 +157,8 @@ "encrypted_room_message_placeholder" = "Verschlüsselte Nachricht…"; "room_message_short_placeholder" = "Sende eine Nachricht…"; "room_offline_notification" = "Verbindung zum Server wurde unterbrochen."; -"room_unsent_messages_notification" = "Nachrichten wurden nicht gesendet. Jetzt %@ oder %@?"; -"room_unsent_messages_unknown_devices_notification" = "Nachrichten wurden nicht gesendet, da unbekannte Sitzungen vorhanden waren. Jetzt %@ oder %@?"; +"room_unsent_messages_notification" = "Nachrichten wurden nicht gesendet."; +"room_unsent_messages_unknown_devices_notification" = "Nachrichten wurden nicht gesendet, da unbekannte Sitzungen vorhanden waren."; "room_prompt_resend" = "alle erneut senden"; "room_prompt_cancel" = "alles abbrechen"; "room_resend_unsent_messages" = "Ungesendete Nachrichten erneut senden"; diff --git a/Riot/Assets/en.lproj/Vector.strings b/Riot/Assets/en.lproj/Vector.strings index bce441f81..49354fd09 100644 --- a/Riot/Assets/en.lproj/Vector.strings +++ b/Riot/Assets/en.lproj/Vector.strings @@ -328,8 +328,10 @@ Tap the + to start adding people."; "room_message_short_placeholder" = "Send a message…"; "room_message_reply_to_short_placeholder" = "Send a reply…"; "room_offline_notification" = "Connectivity to the server has been lost."; -"room_unsent_messages_notification" = "Messages not sent. %@ or %@ now?"; -"room_unsent_messages_unknown_devices_notification" = "Message not sent due to unknown sessions being present. %@ or %@ now?"; +"room_unsent_messages_notification" = "Messages failed to send."; +"room_unsent_messages_unknown_devices_notification" = "Message failed to send due to unknown sessions being present."; +"room_unsent_messages_cancel_title" = "Delete unsent messages"; +"room_unsent_messages_cancel_message" = "Are you sure you want to delete all unsent messages in this room?"; "room_ongoing_conference_call" = "Ongoing conference call. Join as %@ or %@."; "room_ongoing_conference_call_with_close" = "Ongoing conference call. Join as %@ or %@. %@ it."; "room_ongoing_conference_call_close" = "Close"; @@ -354,6 +356,8 @@ Tap the + to start adding people."; "room_event_action_save" = "Save"; "room_event_action_resend" = "Resend"; "room_event_action_delete" = "Delete"; +"room_event_action_delete_confirmation_title" = "Delete unsent message"; +"room_event_action_delete_confirmation_message" = "Are you sure you want to delete this unsent message?"; "room_event_action_cancel_send" = "Cancel Send"; "room_event_action_cancel_download" = "Cancel Download"; "room_event_action_view_encryption" = "Encryption Information"; @@ -1592,14 +1596,14 @@ Tap the + to start adding people."; "room_intro_cell_add_participants_action" = "Add people"; -"room_intro_cell_information_room_sentence1_part1" = "This is the begining of "; +"room_intro_cell_information_room_sentence1_part1" = "This is the beginning of "; "room_intro_cell_information_room_sentence1_part3" = ". "; "room_intro_cell_information_room_with_topic_sentence2" = "Topic: %@"; "room_intro_cell_information_room_without_topic_sentence2_part1" = "Add a topic"; "room_intro_cell_information_room_without_topic_sentence2_part2" = " to let people know what this room is about."; -"room_intro_cell_information_dm_sentence1_part1" = "This is the begining of your direct message with "; +"room_intro_cell_information_dm_sentence1_part1" = "This is the beginning of your direct message with "; "room_intro_cell_information_dm_sentence1_part3" = ". "; "room_intro_cell_information_dm_sentence2" = "Only the two of you are in this conversation, no one else can join."; diff --git a/Riot/Assets/eo.lproj/InfoPlist.strings b/Riot/Assets/eo.lproj/InfoPlist.strings index 8b1378917..f6470d19a 100644 --- a/Riot/Assets/eo.lproj/InfoPlist.strings +++ b/Riot/Assets/eo.lproj/InfoPlist.strings @@ -1 +1,9 @@ + +"NSFaceIDUsageDescription" = "Identigilo de vizaĝo uziĝas por aliri vian aplikaĵon."; +"NSCalendarsUsageDescription" = "Vidu viajn planitajn renkontiĝojn en la aplikaĵo."; +"NSContactsUsageDescription" = "Por trovi kontaktojn, kiuj jam estas ĉe Matrix, Element povas sendi retpoŝtadresojn kaj telefonnumerojn el via adresaro al via elektita identigila servilo de Matrix. Kiam eblas, personaj datumoj estas haketitaj antaŭ forsendo – bonvolu kontroli la politikon pri privateco de via identiga servilo por pliaj detaloj."; +"NSMicrophoneUsageDescription" = "La mikrofono estas uzata por filmi, kaj ankaŭ por voki."; +"NSPhotoLibraryUsageDescription" = "La fotujo estas uzata por sendi fotojn kaj filmojn."; +// Permissions usage explanations +"NSCameraUsageDescription" = "La filmilo estas uzata por foti kaj filmi, kaj ankaŭ por vidvoki."; diff --git a/Riot/Assets/es.lproj/Vector.strings b/Riot/Assets/es.lproj/Vector.strings index bd78b6061..de769fa05 100644 --- a/Riot/Assets/es.lproj/Vector.strings +++ b/Riot/Assets/es.lproj/Vector.strings @@ -216,8 +216,8 @@ "room_message_short_placeholder" = "Enviar un mensaje…"; "room_message_reply_to_short_placeholder" = "Enviar una respuesta…"; "room_offline_notification" = "Se perdió la conexión con el servidor."; -"room_unsent_messages_notification" = "Los mensajes no se enviaron. ¿%@ o %@ ahora?"; -"room_unsent_messages_unknown_devices_notification" = "No se envió el mensaje debido a dispositivos desconocidos presentes. ¿%@ o %@ ahora?"; +"room_unsent_messages_notification" = "Los mensajes no se enviaron."; +"room_unsent_messages_unknown_devices_notification" = "No se envió el mensaje debido a dispositivos desconocidos presentes."; "room_recents_server_notice_section" = "ALERTAS DE SISTEMA"; "room_ongoing_conference_call" = "Llamada de conferencia en curso. Unirse con %@ o %@."; "room_ongoing_conference_call_with_close" = "Llamada de conferencia en curso. Unirse con %@ o %@. %@ la."; diff --git a/Riot/Assets/et.lproj/Vector.strings b/Riot/Assets/et.lproj/Vector.strings index d898f4a65..522656a5a 100644 --- a/Riot/Assets/et.lproj/Vector.strings +++ b/Riot/Assets/et.lproj/Vector.strings @@ -242,8 +242,8 @@ "room_message_short_placeholder" = "Saada sõnum…"; "room_message_reply_to_short_placeholder" = "Saada vastus…"; "room_offline_notification" = "Ühendus sinu serveriga on katkenud."; -"room_unsent_messages_notification" = "Sõnumid pole saadetud. %@ või %@ nüüd?"; -"room_unsent_messages_unknown_devices_notification" = "Kuna leidub tundmatuid sessioone, siis sõnumid pole saadetud. %@ või %@ nüüd?"; +"room_unsent_messages_notification" = "Sõnumid pole saadetud."; +"room_unsent_messages_unknown_devices_notification" = "Kuna leidub tundmatuid sessioone, siis sõnumid pole saadetud."; "room_ongoing_conference_call" = "Konverentsikõne on käsil. Liitu kas %@ või %@."; "room_ongoing_conference_call_with_close" = "Konverentsikõne on käsil. Liitu kas %@ või %@. %@ seda."; "room_ongoing_conference_call_close" = "Sulge"; diff --git a/Riot/Assets/eu.lproj/Vector.strings b/Riot/Assets/eu.lproj/Vector.strings index ef60098fd..38a6e4d34 100644 --- a/Riot/Assets/eu.lproj/Vector.strings +++ b/Riot/Assets/eu.lproj/Vector.strings @@ -286,8 +286,8 @@ "room_two_users_are_typing" = "%@ eta %@ idazten ari dira…"; "room_many_users_are_typing" = "%@, %@, eta beste batzuk idazten ari dira…"; "room_message_short_placeholder" = "Bidali mezu bat…"; -"room_unsent_messages_notification" = "Bidali gabeko mezuak daude. %@ edo %@ orain?"; -"room_unsent_messages_unknown_devices_notification" = "Mezua ez da bidali saio ezezagunak daudelako. %@ edo %@ orain?"; +"room_unsent_messages_notification" = "Bidali gabeko mezuak daude."; +"room_unsent_messages_unknown_devices_notification" = "Mezua ez da bidali saio ezezagunak daudelako."; "room_ongoing_conference_call" = "Konferentzia deia abioan. Elkartu %@ edo %@ erabiliz."; "room_event_action_more" = "Gehiago"; "room_event_action_save" = "Gorde"; diff --git a/Riot/Assets/fr.lproj/InfoPlist.strings b/Riot/Assets/fr.lproj/InfoPlist.strings index c08f8a1b4..2cd54982e 100644 --- a/Riot/Assets/fr.lproj/InfoPlist.strings +++ b/Riot/Assets/fr.lproj/InfoPlist.strings @@ -1,7 +1,7 @@ // Permissions usage explanations -"NSCameraUsageDescription" = "L'appareil photo est utilisé pour prendre des photos et des vidéos et pour passer des appels vidéo."; +"NSCameraUsageDescription" = "L’appareil photo est utilisé pour prendre des photos, des vidéos et pour passer des appels vidéo."; "NSPhotoLibraryUsageDescription" = "La photothèque est utilisée pour envoyer des photos et des vidéos."; "NSMicrophoneUsageDescription" = "Le microphone est utilisé pour prendre des vidéos et passer des appels."; -"NSContactsUsageDescription" = "Pour découvrir vos contacts qui utilisent déjà Matrix, Element peut envoyer les adresses e-mail et les numéros de téléphone de votre carnet d’adresse à votre serveur d’identité Matrix. Si votre serveur d’identité le prend en charge, les données personnelles sont hachées avant l’envoi − vérifiez sa politique de vie privée pour plus de détails."; -"NSCalendarsUsageDescription" = "Voir vos rendez-vous prévus dans l’application."; +"NSContactsUsageDescription" = "Pour découvrir vos contacts qui utilisent déjà Matrix, Element peut envoyer les adresses e-mail et les numéros de téléphone de votre carnet d’adresse à votre serveur d’identité Matrix. Si votre serveur d’identité le prend en charge, les données personnelles sont hachées avant l’envoi − vérifiez sa politique de confidentialité pour plus de détails."; +"NSCalendarsUsageDescription" = "Voir vos rendez-vous dans l’application."; "NSFaceIDUsageDescription" = "Face ID est utilisé pour accéder à votre application."; diff --git a/Riot/Assets/fr.lproj/Localizable.strings b/Riot/Assets/fr.lproj/Localizable.strings index 10dcf5630..c8188f312 100644 --- a/Riot/Assets/fr.lproj/Localizable.strings +++ b/Riot/Assets/fr.lproj/Localizable.strings @@ -3,13 +3,13 @@ /* New message from a specific person in a named room */ "MSG_FROM_USER_IN_ROOM" = "%@ a posté dans %@"; /* New message from a specific person, not referencing a room. Content included. */ -"MSG_FROM_USER_WITH_CONTENT" = "%@ : %@"; +"MSG_FROM_USER_WITH_CONTENT" = "%@ : %@"; /* New message from a specific person in a named room. Content included. */ -"MSG_FROM_USER_IN_ROOM_WITH_CONTENT" = "%@ dans %@ : %@"; +"MSG_FROM_USER_IN_ROOM_WITH_CONTENT" = "%@ dans %@ : %@"; /* New action message from a specific person, not referencing a room. */ "ACTION_FROM_USER" = "* %@ %@"; /* New action message from a specific person in a named room. */ -"ACTION_FROM_USER_IN_ROOM" = "%@ : * %@ %@"; +"ACTION_FROM_USER_IN_ROOM" = "%@ : * %@ %@"; /* New action message from a specific person, not referencing a room. */ "IMAGE_FROM_USER" = "%@ a envoyé une image %@"; /* New action message from a specific person in a named room. */ @@ -27,11 +27,11 @@ /* Multiple unread messages from three people */ "MSGS_FROM_THREE_USERS" = "%@ nouveaux messages de %@, %@ et %@"; /* Multiple unread messages from two plus people (ie. for 4+ people: 'others' replaces the third person) */ -"MSGS_FROM_TWO_PLUS_USERS" = "%@ nouveaux messages de %@, %@ et d'autres"; +"MSGS_FROM_TWO_PLUS_USERS" = "%@ nouveaux messages de %@, %@ et d’autres"; /* Multiple messages in two rooms */ "MSGS_IN_TWO_ROOMS" = "%@ nouveaux messages dans %@ et %@"; /* Look, stuff's happened, alright? Just open the app. */ -"MSGS_IN_TWO_PLUS_ROOMS" = "%@ nouveaux messages dans %@, %@ et d'autres"; +"MSGS_IN_TWO_PLUS_ROOMS" = "%@ nouveaux messages dans %@, %@ et d’autres"; /* A user has invited you to a chat */ "USER_INVITE_TO_CHAT" = "%@ vous a invité dans une discussion"; /* A user has invited you to an (unamed) group chat */ @@ -43,17 +43,17 @@ /* Incoming one-to-one video call */ "VIDEO_CALL_FROM_USER" = "Appel vidéo de %@"; /* Incoming unnamed voice conference invite from a specific person */ -"VOICE_CONF_FROM_USER" = "Téléconférence vocale de %@"; +"VOICE_CONF_FROM_USER" = "Téléconférence audio de %@"; /* Incoming unnamed video conference invite from a specific person */ "VIDEO_CONF_FROM_USER" = "Téléconférence vidéo de %@"; /* Incoming named voice conference invite from a specific person */ -"VOICE_CONF_NAMED_FROM_USER" = "Téléconférence vocale de %@ : '%@'"; +"VOICE_CONF_NAMED_FROM_USER" = "Téléconférence audio de %@ : « %@ »"; /* Incoming named video conference invite from a specific person */ -"VIDEO_CONF_NAMED_FROM_USER" = "Téléconférence vidéo de %@ : '%@'"; +"VIDEO_CONF_NAMED_FROM_USER" = "Téléconférence vidéo de %@ : « %@ »"; /* Message title for a specific person in a named room */ "MSG_FROM_USER_IN_ROOM_TITLE" = "%@ dans %@"; /* Sticker from a specific person, not referencing a room. */ -"STICKER_FROM_USER" = "%@ a envoyé un sticker"; +"STICKER_FROM_USER" = "%@ a envoyé un autocollant"; "KEY_VERIFICATION_REQUEST_FROM_USER" = "%@ veut vérifier"; /* New message indicator on unknown room */ "MESSAGE" = "Message"; @@ -61,3 +61,4 @@ "MESSAGE_FROM_X" = "Message de %@"; /* New message indicator on a room */ "MESSAGE_IN_X" = "Message dans %@"; +"MESSAGE_PROTECTED" = "Nouveau message"; diff --git a/Riot/Assets/fr.lproj/Vector.strings b/Riot/Assets/fr.lproj/Vector.strings index 1d129967c..c53844af1 100644 --- a/Riot/Assets/fr.lproj/Vector.strings +++ b/Riot/Assets/fr.lproj/Vector.strings @@ -195,8 +195,8 @@ "encrypted_room_message_placeholder" = "Envoyer un message chiffré…"; "room_message_short_placeholder" = "Envoyer un message…"; "room_offline_notification" = "La connexion au serveur a été perdue."; -"room_unsent_messages_notification" = "Messages non envoyés. %@ ou %@ maintenant ?"; -"room_unsent_messages_unknown_devices_notification" = "Message non envoyé car des sessions inconnues sont présentes. %@ ou %@ maintenant ?"; +"room_unsent_messages_notification" = "Messages non envoyés."; +"room_unsent_messages_unknown_devices_notification" = "Message non envoyé car des sessions inconnues sont présentes."; "room_ongoing_conference_call" = "Téléconférence en cours. Rejoindre en %@ ou %@."; "room_prompt_resend" = "Tout renvoyer"; "room_prompt_cancel" = "tout annuler"; diff --git a/Riot/Assets/hu.lproj/Vector.strings b/Riot/Assets/hu.lproj/Vector.strings index 22903436c..cb403d9af 100644 --- a/Riot/Assets/hu.lproj/Vector.strings +++ b/Riot/Assets/hu.lproj/Vector.strings @@ -210,8 +210,8 @@ "room_message_short_placeholder" = "Üzenet küldése…"; "room_message_reply_to_short_placeholder" = "Válasz küldése…"; "room_offline_notification" = "A szerverrel megszakadt a kapcsolat."; -"room_unsent_messages_notification" = "Üzenet nincs elküldve. Most %@ vagy %@?"; -"room_unsent_messages_unknown_devices_notification" = "Az üzenet nincs elküldve, mert ismeretlen munkamenet van jelen. Most %@ vagy %@?"; +"room_unsent_messages_notification" = "Üzenet nincs elküldve."; +"room_unsent_messages_unknown_devices_notification" = "Az üzenet nincs elküldve, mert ismeretlen munkamenet van jelen."; "room_ongoing_conference_call" = "Konferencia hívás van folyamatban. Csatlakozol mint %@ vagy %@."; "room_ongoing_conference_call_with_close" = "Konferencia hívás van folyamatban. Csatlakozol mint %@ vagy %@. %@."; "room_ongoing_conference_call_close" = "Bezár"; diff --git a/Riot/Assets/is.lproj/Vector.strings b/Riot/Assets/is.lproj/Vector.strings index 841930c74..f6fcd78c3 100644 --- a/Riot/Assets/is.lproj/Vector.strings +++ b/Riot/Assets/is.lproj/Vector.strings @@ -401,8 +401,8 @@ "room_participants_remove_prompt_msg" = "Ertu viss um að þú viljir fjarlægja %@ úr þessu spjalli?"; "room_participants_invite_prompt_msg" = "Ertu viss um að þú viljir bjóða %@ á þetta spjall?"; "room_participants_invite_malformed_id" = "Rangt formað auðkenni. Ætti að vera tölvupóstfang eða Matrix-auðkenni á borð við'@sérheiti:lén'"; -"room_unsent_messages_notification" = "Skilaboð ekki send. %@ eða %@ núna?"; -"room_unsent_messages_unknown_devices_notification" = "Skilaboð ekki send vegna þess að vart var við óþekkt tæki. %@ eða %@ núna?"; +"room_unsent_messages_notification" = "Skilaboð ekki send."; +"room_unsent_messages_unknown_devices_notification" = "Skilaboð ekki send vegna þess að vart var við óþekkt tæki."; "room_ongoing_conference_call" = "Símafundur í gangi. Taka þátt með %@ eða %@."; "room_ongoing_conference_call_with_close" = "Símafundur í gangi. Taka þátt með %@ eða %@. %@ því."; "room_conference_call_no_power" = "Þú þarft aðgangsheimildir til að sýsla með símafundi á þessari spjallrás"; diff --git a/Riot/Assets/it.lproj/Vector.strings b/Riot/Assets/it.lproj/Vector.strings index 0afba28e8..aa6b12724 100644 --- a/Riot/Assets/it.lproj/Vector.strings +++ b/Riot/Assets/it.lproj/Vector.strings @@ -216,8 +216,8 @@ "room_message_short_placeholder" = "Invia un messaggio…"; "room_message_reply_to_short_placeholder" = "Invia una risposta…"; "room_offline_notification" = "La connessione al server è stata persa."; -"room_unsent_messages_notification" = "Messaggi non inviati. %1$s o %2$s ora?"; -"room_unsent_messages_unknown_devices_notification" = "Messaggi non inviati a causa della presenza di sessioni sconosciute. %1$s o %2$s ora?"; +"room_unsent_messages_notification" = "Messaggi non inviati."; +"room_unsent_messages_unknown_devices_notification" = "Messaggi non inviati a causa della presenza di sessioni sconosciute."; "room_ongoing_conference_call" = "Avvio conferenza. Unisciti come %@ o %@."; "room_ongoing_conference_call_with_close" = "Avvio conferenza. Unisciti come %@ o %@. %@."; "room_ongoing_conference_call_close" = "Chiudi"; diff --git a/Riot/Assets/ja.lproj/Vector.strings b/Riot/Assets/ja.lproj/Vector.strings index e977a6d7b..7c9dd284b 100644 --- a/Riot/Assets/ja.lproj/Vector.strings +++ b/Riot/Assets/ja.lproj/Vector.strings @@ -200,8 +200,8 @@ "encrypted_room_message_placeholder" = "暗号文を送信…"; "room_message_short_placeholder" = "ここに送信文を入力…"; "room_offline_notification" = "サーバとの接続が失われました."; -"room_unsent_messages_notification" = "文章が送信できませんでした。現在 %@ または %@ ?"; -"room_unsent_messages_unknown_devices_notification" = "未知のセッションが存在するために文章が送信されませんでした。現在%@ or %@ ?"; +"room_unsent_messages_notification" = "文章が送信できませんでした."; +"room_unsent_messages_unknown_devices_notification" = "未知のセッションが存在するために文章が送信されませんでした."; "room_ongoing_conference_call" = "会議通話実施中。 %@ または %@で参加してください。"; "room_ongoing_conference_call_with_close" = "会議通話実施中。%@または%@で参加してください。 %@。"; "room_ongoing_conference_call_close" = "閉じる"; diff --git a/Riot/Assets/nl.lproj/InfoPlist.strings b/Riot/Assets/nl.lproj/InfoPlist.strings index 29c21ea59..f1fa9fb74 100644 --- a/Riot/Assets/nl.lproj/InfoPlist.strings +++ b/Riot/Assets/nl.lproj/InfoPlist.strings @@ -18,5 +18,6 @@ "NSCameraUsageDescription" = "De camera wordt gebruikt om foto’s en video’s te maken, en voor videogesprekken."; "NSPhotoLibraryUsageDescription" = "De fotogalerij wordt gebruikt om foto’s en video’s te versturen."; "NSMicrophoneUsageDescription" = "De microfoon wordt gebruikt om video’s te maken, en voor spraakoproepen."; -"NSContactsUsageDescription" = "Om u te kunnen tonen welke van uw contacten reeds Matrix gebruiken, kan Element de e-mailadressen en telefoonnummers in uw adresboek naar uw gekozen Matrix-identiteitsserver sturen. Waar mogelijk worden persoonlijke gegevens gehasht voor verzenden - bekijk het privacybeleid van uw identiteitsserver voor meer informatie."; +"NSContactsUsageDescription" = "Om u te kunnen tonen welke van uw contacten reeds Matrix gebruiken, kan Element de e-mailadressen en telefoonnummers in uw adresboek naar uw gekozen Matrix-identiteitsserver sturen. Waar mogelijk worden persoonlijke gegevens gehasht voor verzending - bekijk het privacybeleid van uw identiteitsserver voor meer informatie."; "NSCalendarsUsageDescription" = "Bekijk uw geplande afspraken in de app."; +"NSFaceIDUsageDescription" = "Face ID wordt gebruikt om toegang te krijgen tot uw app."; diff --git a/Riot/Assets/nl.lproj/Localizable.strings b/Riot/Assets/nl.lproj/Localizable.strings index 57f2d717f..aa136cda1 100644 --- a/Riot/Assets/nl.lproj/Localizable.strings +++ b/Riot/Assets/nl.lproj/Localizable.strings @@ -19,7 +19,7 @@ /* New message from a specific person, not referencing a room */ "MSG_FROM_USER" = "%@ heeft een bericht gestuurd"; /* New message from a specific person in a named room */ -"MSG_FROM_USER_IN_ROOM" = "%@ geplaatst in %@"; +"MSG_FROM_USER_IN_ROOM" = "%@ heeft een bericht in %@ geplaatst"; /** Single, unencrypted messages (where we can include the content */ /* New message from a specific person, not referencing a room. Content included. */ @@ -33,7 +33,7 @@ /** Image Messages **/ /* New action message from a specific person, not referencing a room. */ -"IMAGE_FROM_USER" = "%@ heeft een afbeelding %@ gestuurd"; +"IMAGE_FROM_USER" = "%@ heeft een afbeelding gestuurd %@"; /* New action message from a specific person in a named room. */ "IMAGE_FROM_USER_IN_ROOM" = "%@ heeft een afbeelding %@ in %@ geplaatst"; /** Coalesced messages **/ @@ -83,3 +83,15 @@ /* Sticker from a specific person, not referencing a room. */ "STICKER_FROM_USER" = "%@ heeft een sticker gestuurd"; "KEY_VERIFICATION_REQUEST_FROM_USER" = "%@ wil verifiëren"; +"MESSAGE_PROTECTED" = "Nieuw Bericht"; + +/* New message indicator on a room */ +"MESSAGE_IN_X" = "Bericht in %@"; + +/* New message indicator from a DM */ +"MESSAGE_FROM_X" = "Bericht van %@"; + +/** Notification messages **/ + +/* New message indicator on unknown room */ +"MESSAGE" = "Bericht"; diff --git a/Riot/Assets/nl.lproj/Vector.strings b/Riot/Assets/nl.lproj/Vector.strings index ce589eb60..d193952d4 100644 --- a/Riot/Assets/nl.lproj/Vector.strings +++ b/Riot/Assets/nl.lproj/Vector.strings @@ -204,8 +204,8 @@ "encrypted_room_message_placeholder" = "Stuur een versleuteld bericht…"; "room_message_short_placeholder" = "Stuur een bericht…"; "room_offline_notification" = "De verbinding met de server is verbroken."; -"room_unsent_messages_notification" = "Berichten niet verstuurd. Nu %@ of %@?"; -"room_unsent_messages_unknown_devices_notification" = "Bericht is niet verstuurd doordat er onbekende apparaten aanwezig zijn. Nu %@ of %@?"; +"room_unsent_messages_notification" = "Berichten niet verstuurd."; +"room_unsent_messages_unknown_devices_notification" = "Bericht is niet verstuurd doordat er onbekende apparaten aanwezig zijn."; "room_ongoing_conference_call" = "Er is een vergadergesprek gaande. Neem deel met %@ of %@."; "room_prompt_resend" = "alles opnieuw versturen"; "room_prompt_cancel" = "alles annuleren"; diff --git a/Riot/Assets/pl.lproj/Vector.strings b/Riot/Assets/pl.lproj/Vector.strings index 70058ef7a..9d4cb8526 100644 --- a/Riot/Assets/pl.lproj/Vector.strings +++ b/Riot/Assets/pl.lproj/Vector.strings @@ -392,7 +392,7 @@ "contacts_address_book_no_contact" = "Brak lokalnych kontaktów"; "auth_msisdn_validation_error" = "Nie można zweryfikować numeru telefonu."; "room_participants_invite_malformed_id" = "Uszkodzony ID. Powinien być adres e-mail lub Matrix ID podobny do '@localpart:domain'"; -"room_unsent_messages_notification" = "Wiadomość nie została wysłana. Czy %@ lub %@ teraz?"; +"room_unsent_messages_notification" = "Wiadomość nie została wysłana."; "room_ongoing_conference_call_with_close" = "Przychodzące połączenie grupowe. Dołącz z %@ lub z %@. %@ to."; "directory_search_results_title" = "Przeglądaj wyniki katalogów"; "room_event_action_kick_prompt_reason" = "Powód wyrzucenia użytkownika"; diff --git a/Riot/Assets/ru.lproj/Vector.strings b/Riot/Assets/ru.lproj/Vector.strings index 3ad1ec882..b9f7a1582 100644 --- a/Riot/Assets/ru.lproj/Vector.strings +++ b/Riot/Assets/ru.lproj/Vector.strings @@ -185,8 +185,8 @@ "encrypted_room_message_placeholder" = "Отправить зашифрованное сообщение…"; "room_message_short_placeholder" = "Отправить сообщение…"; "room_offline_notification" = "Связь с сервером потеряна."; -"room_unsent_messages_notification" = "Сообщения не отправлены. %@ или %@ сейчас?"; -"room_unsent_messages_unknown_devices_notification" = "Сообщение не отправлено из-за присутствия неизвестных сеансов. %@ или %@ сейчас?"; +"room_unsent_messages_notification" = "Сообщения не отправлены."; +"room_unsent_messages_unknown_devices_notification" = "Сообщение не отправлено из-за присутствия неизвестных сеансов."; "room_ongoing_conference_call" = "Текущий групповой вызов. Войти как %@ или %@."; "room_prompt_resend" = "Отправить все"; "room_prompt_cancel" = "отменить все"; diff --git a/Riot/Assets/si.lproj/InfoPlist.strings b/Riot/Assets/si.lproj/InfoPlist.strings new file mode 100644 index 000000000..8b1378917 --- /dev/null +++ b/Riot/Assets/si.lproj/InfoPlist.strings @@ -0,0 +1 @@ + diff --git a/Riot/Assets/si.lproj/Localizable.strings b/Riot/Assets/si.lproj/Localizable.strings new file mode 100644 index 000000000..432026982 --- /dev/null +++ b/Riot/Assets/si.lproj/Localizable.strings @@ -0,0 +1,19 @@ + + +"MESSAGE_PROTECTED" = "නව පණිවිඩය"; + +/** Notification messages **/ + +/* New message indicator on unknown room */ +"MESSAGE" = "පණිවිඩය"; + +/* New action message from a specific person in a named room. */ +"ACTION_FROM_USER_IN_ROOM" = "%@: * %@ %@"; + +/* New action message from a specific person, not referencing a room. */ +"ACTION_FROM_USER" = "* %@ %@"; + +/** Single, unencrypted messages (where we can include the content */ + +/* New message from a specific person, not referencing a room. Content included. */ +"MSG_FROM_USER_WITH_CONTENT" = "%@: %@"; diff --git a/Riot/Assets/sq.lproj/Vector.strings b/Riot/Assets/sq.lproj/Vector.strings index 112644270..e6057953a 100644 --- a/Riot/Assets/sq.lproj/Vector.strings +++ b/Riot/Assets/sq.lproj/Vector.strings @@ -173,7 +173,7 @@ "encrypted_room_message_placeholder" = "Dërgoni një mesazhi të fshehtëzuar…"; "room_message_short_placeholder" = "Dërgoni një mesazh…"; "room_offline_notification" = "Humbi lidhja me shërbyesin."; -"room_unsent_messages_notification" = "Mesazhet s’u dërguan. %@ apo %@ tani?"; +"room_unsent_messages_notification" = "Mesazhet s’u dërguan."; "room_ongoing_conference_call_close" = "Mbylle"; "room_prompt_resend" = "Ridërgoji krejt"; "room_prompt_cancel" = "anuloji krejt"; diff --git a/Riot/Assets/vi.lproj/Vector.strings b/Riot/Assets/vi.lproj/Vector.strings index 601be0bdc..9b2d328c4 100644 --- a/Riot/Assets/vi.lproj/Vector.strings +++ b/Riot/Assets/vi.lproj/Vector.strings @@ -200,8 +200,8 @@ "encrypted_room_message_placeholder" = "Gửi tin nhắn đã mã hoá…"; "room_message_short_placeholder" = "Gửi tin nhắn…"; "room_offline_notification" = "Kết nối tới máy chủ thất bại."; -"room_unsent_messages_notification" = "Các tin nhắn chưa được gửi. %@ hoặc %@ ngay bây giờ?"; -"room_unsent_messages_unknown_devices_notification" = "Các tin nhắn chưa được gửi tới các thiết bị không xác định hiện hành. %@ hoặc %@ ngay bây giờ?"; +"room_unsent_messages_notification" = "Các tin nhắn chưa được gửi."; +"room_unsent_messages_unknown_devices_notification" = "Các tin nhắn chưa được gửi tới các thiết bị không xác định hiện hành."; "room_ongoing_conference_call" = "Cuộc gọi hội nghị đang diễn ra. Tham gia như %@ hoặc %@."; "room_ongoing_conference_call_with_close" = "Cuộc gọi hội nghị đang diễn ra. Tham gia như %@ hoặc %@. %@ nó."; "room_ongoing_conference_call_close" = "Đóng"; diff --git a/Riot/Assets/zh_Hans.lproj/Vector.strings b/Riot/Assets/zh_Hans.lproj/Vector.strings index 2dcf813a0..46cf235d1 100644 --- a/Riot/Assets/zh_Hans.lproj/Vector.strings +++ b/Riot/Assets/zh_Hans.lproj/Vector.strings @@ -189,8 +189,8 @@ "encrypted_room_message_placeholder" = "发送加密消息…"; "room_message_short_placeholder" = "发送消息…"; "room_offline_notification" = "到服务器的连接已经丢失。"; -"room_unsent_messages_notification" = "消息没有发送。现在 %@ 或 %@ ?"; -"room_unsent_messages_unknown_devices_notification" = "由于未知会话在线所以消息没有发送。现在 %@ 或 %@?"; +"room_unsent_messages_notification" = "消息没有发送。"; +"room_unsent_messages_unknown_devices_notification" = "由于未知会话在线所以消息没有发送。"; "room_ongoing_conference_call" = "收到会议通话。以 %@ 或 %@ 加入。"; "room_prompt_resend" = "全部重新发送"; "room_prompt_cancel" = "全部取消"; diff --git a/Riot/Assets/zh_Hant.lproj/Vector.strings b/Riot/Assets/zh_Hant.lproj/Vector.strings index 3092bb226..685722293 100644 --- a/Riot/Assets/zh_Hant.lproj/Vector.strings +++ b/Riot/Assets/zh_Hant.lproj/Vector.strings @@ -266,8 +266,8 @@ "room_two_users_are_typing" = "%@ 和 %@ 正在輸入…"; "room_many_users_are_typing" = "%@、%@ 和 %@ 正在輸入…"; "room_message_short_placeholder" = "傳送訊息…"; -"room_unsent_messages_notification" = "訊息未被傳送。現在 %@ 或 %@ 嗎?"; -"room_unsent_messages_unknown_devices_notification" = "由於存在未知的工作階段導致訊息未被傳送。現在 %@ 或 %@ 嗎?"; +"room_unsent_messages_notification" = "訊息未被傳送。"; +"room_unsent_messages_unknown_devices_notification" = "由於存在未知的工作階段導致訊息未被傳送。"; "room_conference_call_no_power" = "您需要管理此聊天室群組通話的權限"; "room_prompt_resend" = "全部重新傳送"; "room_prompt_cancel" = "全部取消"; diff --git a/Riot/Categories/MXKRoomBubbleTableViewCell+Riot.h b/Riot/Categories/MXKRoomBubbleTableViewCell+Riot.h index ac04ef63f..6bc374644 100644 --- a/Riot/Categories/MXKRoomBubbleTableViewCell+Riot.h +++ b/Riot/Categories/MXKRoomBubbleTableViewCell+Riot.h @@ -16,6 +16,8 @@ #import +@class CircleProgressView; + /** Action identifier used when the user pressed edit button displayed in front of a selected event. @@ -148,6 +150,13 @@ extern NSString *const kMXKRoomBubbleCellCallBackButtonPressed; */ + (CGFloat)attachmentBubbleCellHeightForCellData:(MXKCellData *)cellData withMaximumWidth:(CGFloat)maxWidth; +- (IBAction)onProgressLongPressGesture:(UILongPressGestureRecognizer*)recognizer; + +/** + update tick view(s) according to the current sent state. + */ +- (void)updateTickViewWithFailedEventIds:(NSSet *)failedEventIds; + /** Blur the view by adding a transparent overlay. Default is NO. */ @@ -163,4 +172,9 @@ extern NSString *const kMXKRoomBubbleCellCallBackButtonPressed; */ @property (nonatomic) UIView *markerView; +/** + Message tick views (sending, sent) displayed alongside the related component. + */ +@property (nonatomic) NSArray *messageStatusViews; + @end diff --git a/Riot/Categories/MXKRoomBubbleTableViewCell+Riot.m b/Riot/Categories/MXKRoomBubbleTableViewCell+Riot.m index f48054f7f..b1fa86843 100644 --- a/Riot/Categories/MXKRoomBubbleTableViewCell+Riot.m +++ b/Riot/Categories/MXKRoomBubbleTableViewCell+Riot.m @@ -452,6 +452,16 @@ NSString *const kMXKRoomBubbleCellCallBackButtonPressed = @"kMXKRoomBubbleCellCa return objc_getAssociatedObject(self, @selector(markerView)); } +- (void)setMessageStatusViews:(NSArray *)arrayOfViews +{ + objc_setAssociatedObject(self, @selector(messageStatusViews), arrayOfViews, OBJC_ASSOCIATION_RETAIN_NONATOMIC); +} + +-(NSArray *)messageStatusViews +{ + return objc_getAssociatedObject(self, @selector(messageStatusViews)); +} + - (void)updateUserNameColor { static UserNameColorGenerator *userNameColorGenerator; @@ -659,6 +669,82 @@ NSString *const kMXKRoomBubbleCellCallBackButtonPressed = @"kMXKRoomBubbleCellCa return rowHeight; } +- (void)updateTickViewWithFailedEventIds:(NSSet *)failedEventIds +{ + for (UIView *tickView in self.messageStatusViews) + { + [tickView removeFromSuperview]; + } + self.messageStatusViews = nil; + + NSMutableArray *statusViews = [NSMutableArray new]; + UIView *tickView = nil; + + if ([bubbleData isKindOfClass:RoomBubbleCellData.class] + && ((RoomBubbleCellData*)bubbleData).componentIndexOfSentMessageTick >= 0) + { + UIImage *image = [UIImage imageNamed:@"sent_message_tick"]; + image = [image imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate]; + tickView = [[UIImageView alloc] initWithImage:image]; + tickView.tintColor = ThemeService.shared.theme.messageTickColor; + [statusViews addObject:tickView]; + [self addTickView:tickView atIndex:((RoomBubbleCellData*)bubbleData).componentIndexOfSentMessageTick]; + } + + NSInteger index = bubbleData.bubbleComponents.count; + while (index--) + { + MXKRoomBubbleComponent *component = bubbleData.bubbleComponents[index]; + NSArray *receipts = bubbleData.readReceipts[component.event.eventId]; + if (receipts.count == 0) { + if (component.event.sentState == MXEventSentStateUploading + || component.event.sentState == MXEventSentStateEncrypting + || component.event.sentState == MXEventSentStatePreparing + || component.event.sentState == MXEventSentStateSending) + { + if ([failedEventIds containsObject:component.event.eventId] || (bubbleData.attachment && component.event.sentState != MXEventSentStateSending)) + { + UIView *progressContentView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 40, 40)]; + CircleProgressView *progressView = [[CircleProgressView alloc] initWithFrame:CGRectMake(24, 24, 16, 16)]; + progressView.lineColor = ThemeService.shared.theme.messageTickColor; + [progressContentView addSubview:progressView]; + self.progressChartView = progressView; + + tickView = progressContentView; + + [progressView startAnimating]; + + UILongPressGestureRecognizer *longPress = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(onProgressLongPressGesture:)]; + [tickView addGestureRecognizer:longPress]; + } + else + { + UIImage *image = [UIImage imageNamed:@"sending_message_tick"]; + image = [image imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate]; + tickView = [[UIImageView alloc] initWithImage:image]; + tickView.tintColor = ThemeService.shared.theme.messageTickColor; + } + + [statusViews addObject:tickView]; + [self addTickView:tickView atIndex:index]; + } + } + + if (component.event.sentState == MXEventSentStateFailed) + { + tickView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"error_message_tick"]]; + [statusViews addObject:tickView]; + [self addTickView:tickView atIndex:index]; + } + } + + + if (statusViews.count) + { + self.messageStatusViews = statusViews; + } +} + #pragma mark - User actions - (IBAction)onEditButtonPressed:(id)sender @@ -694,6 +780,15 @@ NSString *const kMXKRoomBubbleCellCallBackButtonPressed = @"kMXKRoomBubbleCellCa #pragma mark - Internals +- (void)addTickView:(UIView *)tickView atIndex:(NSInteger)index +{ + CGRect componentFrame = [self componentFrameInContentViewForIndex: index]; + + tickView.frame = CGRectMake(self.contentView.bounds.size.width - tickView.frame.size.width - 2 * RoomBubbleCellLayout.readReceiptsViewRightMargin, CGRectGetMaxY(componentFrame) - tickView.frame.size.height, tickView.frame.size.width, tickView.frame.size.height); + + [self.contentView addSubview:tickView]; +} + - (void)addEditButtonForComponent:(NSUInteger)componentIndex completion:(void (^ __nullable)(BOOL finished))completion { MXKRoomBubbleComponent *component = bubbleData.bubbleComponents[componentIndex]; @@ -758,4 +853,12 @@ NSString *const kMXKRoomBubbleCellCallBackButtonPressed = @"kMXKRoomBubbleCellCa self.editButton = editButton; } +- (IBAction)onProgressLongPressGesture:(UILongPressGestureRecognizer*)recognizer +{ + if (recognizer.state == UIGestureRecognizerStateBegan && self.delegate) + { + [self.delegate cell:self didRecognizeAction:kMXKRoomBubbleCellLongPressOnProgressView userInfo:nil]; + } +} + @end diff --git a/Riot/Categories/MXRoom+Riot.h b/Riot/Categories/MXRoom+Riot.h index 42a6a6c01..dcf4860a3 100644 --- a/Riot/Categories/MXRoom+Riot.h +++ b/Riot/Categories/MXRoom+Riot.h @@ -19,6 +19,13 @@ #import "UserEncryptionTrustLevel.h" +typedef NS_ENUM(NSUInteger, RoomSentStatus) +{ + RoomSentStatusOk, + RoomSentStatusSentFailed, + RoomSentStatusSentFailedDueToUnknownDevices +}; + /** Define a `MXRoom` category at Riot level. */ @@ -44,6 +51,9 @@ */ @property (nonatomic) id notificationCenterDidUpdateObserver; +/// Check if all messages have been sent. +@property (nonatomic, readonly) RoomSentStatus sentStatus; + /** Update the room tag. diff --git a/Riot/Categories/MXRoom+Riot.m b/Riot/Categories/MXRoom+Riot.m index 449bcbc84..de4d67a54 100644 --- a/Riot/Categories/MXRoom+Riot.m +++ b/Riot/Categories/MXRoom+Riot.m @@ -656,4 +656,30 @@ return objc_getAssociatedObject(self, @selector(notificationCenterDidUpdateObserver)); } +#pragma mark - Unread messages + +- (RoomSentStatus)sentStatus +{ + RoomSentStatus status = RoomSentStatusOk; + NSArray *outgoingMsgs = self.outgoingMessages; + + for (MXEvent *event in outgoingMsgs) + { + if (event.sentState == MXEventSentStateFailed) + { + status = RoomSentStatusSentFailed; + + // Check if the error is due to unknown devices + if ([event.sentError.domain isEqualToString:MXEncryptingErrorDomain] + && event.sentError.code == MXEncryptingErrorUnknownDeviceCode) + { + status = RoomSentStatusSentFailedDueToUnknownDevices; + break; + } + } + } + + return status; +} + @end diff --git a/Riot/Generated/Images.swift b/Riot/Generated/Images.swift index 283b5ac63..5793e9185 100644 --- a/Riot/Generated/Images.swift +++ b/Riot/Generated/Images.swift @@ -50,6 +50,7 @@ internal enum Asset { internal static let chevron = ImageAsset(name: "chevron") internal static let closeButton = ImageAsset(name: "close_button") internal static let disclosureIcon = ImageAsset(name: "disclosure_icon") + internal static let errorIcon = ImageAsset(name: "error_icon") internal static let faceidIcon = ImageAsset(name: "faceid_icon") internal static let group = ImageAsset(name: "group") internal static let monitor = ImageAsset(name: "monitor") @@ -98,12 +99,18 @@ internal enum Asset { internal static let peopleEmptyScreenArtworkDark = ImageAsset(name: "people_empty_screen_artwork_dark") internal static let peopleFloatingAction = ImageAsset(name: "people_floating_action") internal static let error = ImageAsset(name: "error") + internal static let errorMessageTick = ImageAsset(name: "error_message_tick") + internal static let roomActivitiesRetry = ImageAsset(name: "room_activities_retry") internal static let scrolldown = ImageAsset(name: "scrolldown") + internal static let sendingMessageTick = ImageAsset(name: "sending_message_tick") + internal static let sentMessageTick = ImageAsset(name: "sent_message_tick") internal static let typing = ImageAsset(name: "typing") internal static let roomContextMenuCopy = ImageAsset(name: "room_context_menu_copy") + internal static let roomContextMenuDelete = ImageAsset(name: "room_context_menu_delete") internal static let roomContextMenuEdit = ImageAsset(name: "room_context_menu_edit") internal static let roomContextMenuMore = ImageAsset(name: "room_context_menu_more") internal static let roomContextMenuReply = ImageAsset(name: "room_context_menu_reply") + internal static let roomContextMenuRetry = ImageAsset(name: "room_context_menu_retry") internal static let uploadIcon = ImageAsset(name: "upload_icon") internal static let voiceCallHangonIcon = ImageAsset(name: "voice_call_hangon_icon") internal static let voiceCallHangupIcon = ImageAsset(name: "voice_call_hangup_icon") diff --git a/Riot/Generated/Strings.swift b/Riot/Generated/Strings.swift index 31f1c3260..218303eaf 100644 --- a/Riot/Generated/Strings.swift +++ b/Riot/Generated/Strings.swift @@ -2722,6 +2722,14 @@ internal enum VectorL10n { internal static var roomEventActionDelete: String { return VectorL10n.tr("Vector", "room_event_action_delete") } + /// Are you sure you want to delete this unsent message? + internal static var roomEventActionDeleteConfirmationMessage: String { + return VectorL10n.tr("Vector", "room_event_action_delete_confirmation_message") + } + /// Delete unsent message + internal static var roomEventActionDeleteConfirmationTitle: String { + return VectorL10n.tr("Vector", "room_event_action_delete_confirmation_title") + } /// Edit internal static var roomEventActionEdit: String { return VectorL10n.tr("Vector", "room_event_action_edit") @@ -2818,7 +2826,7 @@ internal enum VectorL10n { internal static var roomIntroCellAddParticipantsAction: String { return VectorL10n.tr("Vector", "room_intro_cell_add_participants_action") } - /// This is the begining of your direct message with + /// This is the beginning of your direct message with internal static var roomIntroCellInformationDmSentence1Part1: String { return VectorL10n.tr("Vector", "room_intro_cell_information_dm_sentence1_part1") } @@ -2834,7 +2842,7 @@ internal enum VectorL10n { internal static var roomIntroCellInformationMultipleDmSentence2: String { return VectorL10n.tr("Vector", "room_intro_cell_information_multiple_dm_sentence2") } - /// This is the begining of + /// This is the beginning of internal static var roomIntroCellInformationRoomSentence1Part1: String { return VectorL10n.tr("Vector", "room_intro_cell_information_room_sentence1_part1") } @@ -3326,13 +3334,21 @@ internal enum VectorL10n { internal static func roomTwoUsersAreTyping(_ p1: String, _ p2: String) -> String { return VectorL10n.tr("Vector", "room_two_users_are_typing", p1, p2) } - /// Messages not sent. %@ or %@ now? - internal static func roomUnsentMessagesNotification(_ p1: String, _ p2: String) -> String { - return VectorL10n.tr("Vector", "room_unsent_messages_notification", p1, p2) + /// Are you sure you want to delete all unsent messages in this room? + internal static var roomUnsentMessagesCancelMessage: String { + return VectorL10n.tr("Vector", "room_unsent_messages_cancel_message") } - /// Message not sent due to unknown sessions being present. %@ or %@ now? - internal static func roomUnsentMessagesUnknownDevicesNotification(_ p1: String, _ p2: String) -> String { - return VectorL10n.tr("Vector", "room_unsent_messages_unknown_devices_notification", p1, p2) + /// Delete unsent messages + internal static var roomUnsentMessagesCancelTitle: String { + return VectorL10n.tr("Vector", "room_unsent_messages_cancel_title") + } + /// Messages failed to send. + internal static var roomUnsentMessagesNotification: String { + return VectorL10n.tr("Vector", "room_unsent_messages_notification") + } + /// Message failed to send due to unknown sessions being present. + internal static var roomUnsentMessagesUnknownDevicesNotification: String { + return VectorL10n.tr("Vector", "room_unsent_messages_unknown_devices_notification") } /// End-to-end encryption is in beta and may not be reliable.\n\nYou should not yet trust it to secure data.\n\nDevices will not yet be able to decrypt history from before they joined the room.\n\nEncrypted messages will not be visible on clients that do not yet implement encryption. internal static var roomWarningAboutEncryption: String { diff --git a/Riot/Managers/Theme/Theme.swift b/Riot/Managers/Theme/Theme.swift index e0b1c5ba4..865e87faf 100644 --- a/Riot/Managers/Theme/Theme.swift +++ b/Riot/Managers/Theme/Theme.swift @@ -63,6 +63,8 @@ import UIKit var selectedBackgroundColor: UIColor { get } + var messageTickColor: UIColor { get } + // MARK: - Call Screen Specific Colors var callScreenButtonTintColor: UIColor { get } diff --git a/Riot/Managers/Theme/Themes/DarkTheme.swift b/Riot/Managers/Theme/Themes/DarkTheme.swift index 85804a69e..c0a9bfce5 100644 --- a/Riot/Managers/Theme/Themes/DarkTheme.swift +++ b/Riot/Managers/Theme/Themes/DarkTheme.swift @@ -84,6 +84,8 @@ class DarkTheme: NSObject, Theme { var matrixSearchBackgroundImageTintColor: UIColor = UIColor(rgb: 0x7E7E7E) var secondaryCircleButtonBackgroundColor: UIColor = UIColor(rgb: 0xE3E8F0) + var messageTickColor: UIColor = .white + func applyStyle(onTabBar tabBar: UITabBar) { tabBar.unselectedItemTintColor = self.tabBarUnselectedItemTintColor tabBar.tintColor = self.tintColor diff --git a/Riot/Managers/Theme/Themes/DefaultTheme.swift b/Riot/Managers/Theme/Themes/DefaultTheme.swift index 38e1ab12b..c45a25666 100644 --- a/Riot/Managers/Theme/Themes/DefaultTheme.swift +++ b/Riot/Managers/Theme/Themes/DefaultTheme.swift @@ -52,6 +52,8 @@ class DefaultTheme: NSObject, Theme { var warningColor: UIColor = UIColor(rgb: 0xFF4B55) + var messageTickColor: UIColor = UIColor(rgb: 0xC1C6CD) + var avatarColors: [UIColor] = [ UIColor(rgb: 0x03B381), UIColor(rgb: 0x368BD6), diff --git a/Riot/Modules/BadgeLabel/BadgeLabel.swift b/Riot/Modules/BadgeLabel/BadgeLabel.swift new file mode 100644 index 000000000..f52409871 --- /dev/null +++ b/Riot/Modules/BadgeLabel/BadgeLabel.swift @@ -0,0 +1,112 @@ +// +// Copyright 2021 New Vector Ltd +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import UIKit + +@IBDesignable +@objcMembers +class BadgeLabel: UILabel { + + // MARK: - Public properties + + @IBInspectable var badgeColor: UIColor = .red { + didSet { + setNeedsDisplay() + } + } + + @IBInspectable var borderWidth: CGFloat = 0 { + didSet { + invalidateIntrinsicContentSize() + } + } + + @IBInspectable var borderColor: UIColor = .white { + didSet { + invalidateIntrinsicContentSize() + } + } + + @IBInspectable var padding: CGSize = CGSize(width: 10, height: 2) { + didSet { + invalidateIntrinsicContentSize() + } + } + + // MARK: - Lifecycle + + required init?(coder: NSCoder) { + super.init(coder: coder) + setupView() + } + + override init(frame: CGRect) { + super.init(frame: frame) + setupView() + } + + override func layoutSubviews() { + super.layoutSubviews() + + self.layer.cornerRadius = self.bounds.size.height / 2 + } + + override var intrinsicContentSize: CGSize { + var intrinsicSize = super.intrinsicContentSize + intrinsicSize.height = max(intrinsicSize.height + padding.height, intrinsicSize.height) + borderWidth / 2 + intrinsicSize.width = max(intrinsicSize.width + padding.width, intrinsicSize.height) + return intrinsicSize + } + + override func draw(_ rect: CGRect) { + if let context = UIGraphicsGetCurrentContext() { + context.saveGState() + + let rect = self.bounds.insetBy(dx: borderWidth / 2, dy: borderWidth / 2) + let cornerRadius = rect.height / 2 + let path = UIBezierPath(roundedRect: rect, cornerRadius: cornerRadius) + + context.addPath(path.cgPath) + context.setLineWidth(borderWidth) + context.setStrokeColor(borderColor.cgColor) + context.setFillColor(badgeColor.cgColor) + + if borderWidth > 0 { + context.drawPath(using: .fillStroke) + } else { + context.drawPath(using: .fill) + } + + context.restoreGState() + } + + super.draw(rect) + } + + // MARK: - Interface Builder + + override func prepareForInterfaceBuilder() { + super.prepareForInterfaceBuilder() + setupView() + } + + // MARK: - Private methods + + private func setupView() { + self.textAlignment = .center + self.textColor = .white + } +} diff --git a/Riot/Modules/Common/Recents/DataSources/RecentsDataSource.h b/Riot/Modules/Common/Recents/DataSources/RecentsDataSource.h index 92c74d8cb..2e1510949 100644 --- a/Riot/Modules/Common/Recents/DataSources/RecentsDataSource.h +++ b/Riot/Modules/Common/Recents/DataSources/RecentsDataSource.h @@ -179,6 +179,11 @@ extern NSString *const kRecentsDataSourceTapOnDirectoryServerChange; */ @property (nonatomic, readonly) NSUInteger missedHighlightDirectDiscussionsCount; +/** + The current number of the direct chats with unsent messages. + */ +@property (nonatomic, readonly) NSUInteger unsentMessagesDirectDiscussionsCount; + /** The current number of the group chats with missed notifications, including the invites. */ @@ -189,4 +194,9 @@ extern NSString *const kRecentsDataSourceTapOnDirectoryServerChange; */ @property (nonatomic, readonly) NSUInteger missedHighlightGroupDiscussionsCount; +/** + The current number of the group chats with unsent messages. + */ +@property (nonatomic, readonly) NSUInteger unsentMessagesGroupDiscussionsCount; + @end diff --git a/Riot/Modules/Common/Recents/DataSources/RecentsDataSource.m b/Riot/Modules/Common/Recents/DataSources/RecentsDataSource.m index 1e1cdb7f4..497f80fce 100644 --- a/Riot/Modules/Common/Recents/DataSources/RecentsDataSource.m +++ b/Riot/Modules/Common/Recents/DataSources/RecentsDataSource.m @@ -1146,6 +1146,8 @@ NSString *const kRecentsDataSourceTapOnDirectoryServerChange = @"kRecentsDataSou _missedFavouriteDiscussionsCount = _missedHighlightFavouriteDiscussionsCount = 0; _missedDirectDiscussionsCount = _missedHighlightDirectDiscussionsCount = 0; _missedGroupDiscussionsCount = _missedHighlightGroupDiscussionsCount = 0; + _unsentMessagesDirectDiscussionsCount = 0; + _unsentMessagesGroupDiscussionsCount = 0; secureBackupBannerSection = directorySection = favoritesSection = peopleSection = conversationSection = lowPrioritySection = serverNoticeSection = invitesSection = -1; @@ -1282,6 +1284,18 @@ NSString *const kRecentsDataSourceTapOnDirectoryServerChange = @"kRecentsDataSou } } + if (room.sentStatus != RoomSentStatusOk) + { + if (room.isDirect) + { + _unsentMessagesDirectDiscussionsCount ++; + } + else + { + _unsentMessagesGroupDiscussionsCount ++; + } + } + } if (_recentsDataSourceMode == RecentsDataSourceModeHome) @@ -1295,6 +1309,18 @@ NSString *const kRecentsDataSourceTapOnDirectoryServerChange = @"kRecentsDataSou // Sort each rooms collection by considering first the rooms with some missed notifs, the rooms with unread, then the others. comparator = ^NSComparisonResult(id recentCellData1, id recentCellData2) { + if (recentCellData1.roomSummary.room.sentStatus != RoomSentStatusOk + && recentCellData2.roomSummary.room.sentStatus == RoomSentStatusOk) + { + return NSOrderedAscending; + } + + if (recentCellData2.roomSummary.room.sentStatus != RoomSentStatusOk + && recentCellData1.roomSummary.room.sentStatus == RoomSentStatusOk) + { + return NSOrderedDescending; + } + if (recentCellData1.highlightCount) { if (recentCellData2.highlightCount) @@ -1352,6 +1378,18 @@ NSString *const kRecentsDataSourceTapOnDirectoryServerChange = @"kRecentsDataSou // Sort each rooms collection by considering first the rooms with some unread messages then the others. comparator = ^NSComparisonResult(id recentCellData1, id recentCellData2) { + if (recentCellData1.roomSummary.room.sentStatus != RoomSentStatusOk + && recentCellData2.roomSummary.room.sentStatus == RoomSentStatusOk) + { + return NSOrderedAscending; + } + + if (recentCellData2.roomSummary.room.sentStatus != RoomSentStatusOk + && recentCellData1.roomSummary.room.sentStatus == RoomSentStatusOk) + { + return NSOrderedDescending; + } + if (recentCellData1.hasUnread) { if (recentCellData2.hasUnread) @@ -1391,6 +1429,25 @@ NSString *const kRecentsDataSourceTapOnDirectoryServerChange = @"kRecentsDataSou }]; } + else if (conversationCellDataArray.count > 0 && (_recentsDataSourceMode == RecentsDataSourceModeRooms || _recentsDataSourceMode == RecentsDataSourceModePeople)) + { + [conversationCellDataArray sortUsingComparator:^NSComparisonResult(id recentCellData1, id recentCellData2) { + + if (recentCellData1.roomSummary.room.sentStatus != RoomSentStatusOk + && recentCellData2.roomSummary.room.sentStatus == RoomSentStatusOk) + { + return NSOrderedAscending; + } + + if (recentCellData2.roomSummary.room.sentStatus != RoomSentStatusOk + && recentCellData1.roomSummary.room.sentStatus == RoomSentStatusOk) + { + return NSOrderedDescending; + } + + return NSOrderedAscending; + }]; + } } NSLog(@"[RecentsDataSource] refreshRoomsSections: Done in %.0fms", [[NSDate date] timeIntervalSinceDate:startDate] * 1000); diff --git a/Riot/Modules/Common/Recents/Views/RecentTableViewCell.h b/Riot/Modules/Common/Recents/Views/RecentTableViewCell.h index 7e8d85a08..d2e776b92 100644 --- a/Riot/Modules/Common/Recents/Views/RecentTableViewCell.h +++ b/Riot/Modules/Common/Recents/Views/RecentTableViewCell.h @@ -30,4 +30,7 @@ @property (weak, nonatomic) IBOutlet UIView *missedNotifAndUnreadBadgeBgView; @property (weak, nonatomic) IBOutlet NSLayoutConstraint *missedNotifAndUnreadBadgeBgViewWidthConstraint; +@property (weak, nonatomic) IBOutlet NSLayoutConstraint *lastEventDecriptionLabelTrailingConstraint; +@property (weak, nonatomic) IBOutlet UIImageView *unsentImageView; + @end diff --git a/Riot/Modules/Common/Recents/Views/RecentTableViewCell.m b/Riot/Modules/Common/Recents/Views/RecentTableViewCell.m index a33e0e5e0..b8a4aae9a 100644 --- a/Riot/Modules/Common/Recents/Views/RecentTableViewCell.m +++ b/Riot/Modules/Common/Recents/Views/RecentTableViewCell.m @@ -20,6 +20,7 @@ #import "AvatarGenerator.h" #import "MXEvent.h" +#import "MXRoom+Riot.h" #import "ThemeService.h" #import "Riot-Swift.h" @@ -102,6 +103,9 @@ static const CGFloat kDirectRoomBorderWidth = 3.0; self.lastEventDescription.text = roomCellData.lastEventTextMessage; } + self.unsentImageView.hidden = roomCellData.roomSummary.room.sentStatus == RoomSentStatusOk; + self.lastEventDecriptionLabelTrailingConstraint.constant = self.unsentImageView.hidden ? 10 : 30; + // Notify unreads and bing if (roomCellData.hasUnread) { diff --git a/Riot/Modules/Common/Recents/Views/RecentTableViewCell.xib b/Riot/Modules/Common/Recents/Views/RecentTableViewCell.xib index 8fd41d271..8765ab55e 100644 --- a/Riot/Modules/Common/Recents/Views/RecentTableViewCell.xib +++ b/Riot/Modules/Common/Recents/Views/RecentTableViewCell.xib @@ -1,11 +1,9 @@ - - - - + + - + @@ -15,7 +13,7 @@ - + + @@ -114,10 +120,11 @@ + - + @@ -129,6 +136,7 @@ + @@ -136,10 +144,13 @@ + + + diff --git a/Riot/Modules/Home/Views/RoomCollectionViewCell.h b/Riot/Modules/Home/Views/RoomCollectionViewCell.h index e28aa6642..fd5c93afc 100644 --- a/Riot/Modules/Home/Views/RoomCollectionViewCell.h +++ b/Riot/Modules/Home/Views/RoomCollectionViewCell.h @@ -16,6 +16,8 @@ #import +@class BadgeLabel; + /** 'RoomCollectionViewCell' class is used to display a room in a collection view. */ @@ -38,9 +40,7 @@ @property (weak, nonatomic) IBOutlet MXKImageView *roomAvatar; @property (weak, nonatomic) IBOutlet UIImageView *encryptedRoomIcon; -@property (weak, nonatomic) IBOutlet UILabel *missedNotifAndUnreadBadgeLabel; -@property (weak, nonatomic) IBOutlet UIView *missedNotifAndUnreadBadgeBgView; -@property (weak, nonatomic) IBOutlet NSLayoutConstraint *missedNotifAndUnreadBadgeBgViewWidthConstraint; +@property (weak, nonatomic) IBOutlet BadgeLabel *badgeLabel; @property (nonatomic, readonly) NSString *roomId; diff --git a/Riot/Modules/Home/Views/RoomCollectionViewCell.m b/Riot/Modules/Home/Views/RoomCollectionViewCell.m index 34ccbb861..203b80530 100644 --- a/Riot/Modules/Home/Views/RoomCollectionViewCell.m +++ b/Riot/Modules/Home/Views/RoomCollectionViewCell.m @@ -22,6 +22,7 @@ #import "Riot-Swift.h" #import "MXRoomSummary+Riot.h" +#import "MXRoom+Riot.h" #import "MXTools.h" @@ -42,10 +43,6 @@ static const CGFloat kDirectRoomBorderWidth = 3.0; [_roomAvatar.layer setCornerRadius:_roomAvatar.frame.size.width / 2]; _roomAvatar.clipsToBounds = YES; - // Initialize unread count badge - [_missedNotifAndUnreadBadgeBgView.layer setCornerRadius:10]; - _missedNotifAndUnreadBadgeBgViewWidthConstraint.constant = 0; - // Disable the user interaction on the room avatar. self.roomAvatar.userInteractionEnabled = NO; @@ -69,7 +66,6 @@ static const CGFloat kDirectRoomBorderWidth = 3.0; self.roomTitle.textColor = ThemeService.shared.theme.textPrimaryColor; self.roomTitle1.textColor = ThemeService.shared.theme.textPrimaryColor; self.roomTitle2.textColor = ThemeService.shared.theme.textPrimaryColor; - self.missedNotifAndUnreadBadgeLabel.textColor = ThemeService.shared.theme.baseTextPrimaryColor; // Prepare direct room border CGColorRef directRoomBorderColor = CGColorCreateCopyWithAlpha(ThemeService.shared.theme.tintColor.CGColor, kDirectRoomBorderColorAlpha); @@ -94,8 +90,7 @@ static const CGFloat kDirectRoomBorderWidth = 3.0; - (void)render:(MXKCellData *)cellData { // Hide by default missed notifications and unread widgets - self.missedNotifAndUnreadBadgeBgView.hidden = YES; - self.missedNotifAndUnreadBadgeBgViewWidthConstraint.constant = 0; + self.badgeLabel.hidden = YES; roomCellData = (id)cellData; if (roomCellData) @@ -121,31 +116,24 @@ static const CGFloat kDirectRoomBorderWidth = 3.0; } // Notify unreads and bing - if (roomCellData.hasUnread) + if (roomCellData.roomSummary.room.summary.membership == MXMembershipInvite + || roomCellData.roomSummary.room.sentStatus != RoomSentStatusOk) { - if (0 < roomCellData.notificationCount) - { - self.missedNotifAndUnreadBadgeBgView.hidden = NO; - self.missedNotifAndUnreadBadgeBgView.backgroundColor = roomCellData.highlightCount ? ThemeService.shared.theme.noticeColor : ThemeService.shared.theme.noticeSecondaryColor; - - self.missedNotifAndUnreadBadgeLabel.text = roomCellData.notificationCountStringValue; - [self.missedNotifAndUnreadBadgeLabel sizeToFit]; - - self.missedNotifAndUnreadBadgeBgViewWidthConstraint.constant = self.missedNotifAndUnreadBadgeLabel.frame.size.width + 18; - } - + self.badgeLabel.hidden = NO; + self.badgeLabel.badgeColor = ThemeService.shared.theme.noticeColor; + self.badgeLabel.text = @"!"; + // Use bold font for the room title self.roomTitle.font = self.roomTitle1.font = self.roomTitle2.font = [UIFont systemFontOfSize:13 weight:UIFontWeightBold]; } - else if (roomCellData.roomSummary.room.summary.membership == MXMembershipInvite) + else if (roomCellData.hasUnread) { - self.missedNotifAndUnreadBadgeBgView.hidden = NO; - self.missedNotifAndUnreadBadgeBgView.backgroundColor = ThemeService.shared.theme.noticeColor; - - self.missedNotifAndUnreadBadgeLabel.text = @"!"; - [self.missedNotifAndUnreadBadgeLabel sizeToFit]; - - self.missedNotifAndUnreadBadgeBgViewWidthConstraint.constant = self.missedNotifAndUnreadBadgeLabel.frame.size.width + 18; + if (0 < roomCellData.notificationCount) + { + self.badgeLabel.hidden = NO; + self.badgeLabel.badgeColor = roomCellData.highlightCount ? ThemeService.shared.theme.noticeColor : ThemeService.shared.theme.noticeSecondaryColor; + self.badgeLabel.text = roomCellData.notificationCountStringValue; + } // Use bold font for the room title self.roomTitle.font = self.roomTitle1.font = self.roomTitle2.font = [UIFont systemFontOfSize:13 weight:UIFontWeightBold]; diff --git a/Riot/Modules/Home/Views/RoomCollectionViewCell.xib b/Riot/Modules/Home/Views/RoomCollectionViewCell.xib index 45366f0ff..837d0de4e 100644 --- a/Riot/Modules/Home/Views/RoomCollectionViewCell.xib +++ b/Riot/Modules/Home/Views/RoomCollectionViewCell.xib @@ -1,11 +1,9 @@ - - - - + + - + @@ -34,25 +32,12 @@ - + + + + + + + + + diff --git a/Riot/Modules/Room/CellData/RoomBubbleCellData.h b/Riot/Modules/Room/CellData/RoomBubbleCellData.h index e69076042..7637153f0 100644 --- a/Riot/Modules/Room/CellData/RoomBubbleCellData.h +++ b/Riot/Modules/Room/CellData/RoomBubbleCellData.h @@ -87,6 +87,11 @@ typedef NS_ENUM(NSInteger, RoomBubbleCellDataTag) */ @property(nonatomic) BOOL isKeyVerificationOperationPending; +/** + Index of the component which needs a sent tick displayed. -1 if none. + */ +@property(nonatomic) NSInteger componentIndexOfSentMessageTick; + /** Indicate to update additional content height. */ diff --git a/Riot/Modules/Room/CellData/RoomBubbleCellData.m b/Riot/Modules/Room/CellData/RoomBubbleCellData.m index 50444b09a..2f00e796b 100644 --- a/Riot/Modules/Room/CellData/RoomBubbleCellData.m +++ b/Riot/Modules/Room/CellData/RoomBubbleCellData.m @@ -53,6 +53,7 @@ static NSAttributedString *timestampVerticalWhitespace = nil; if (self) { _eventsToShowAllReactions = [NSMutableSet set]; + _componentIndexOfSentMessageTick = -1; } return self; } diff --git a/Riot/Modules/Room/ContextualMenu/RoomContextualMenuAction.swift b/Riot/Modules/Room/ContextualMenu/RoomContextualMenuAction.swift index f4898fca0..844ca035f 100644 --- a/Riot/Modules/Room/ContextualMenu/RoomContextualMenuAction.swift +++ b/Riot/Modules/Room/ContextualMenu/RoomContextualMenuAction.swift @@ -21,6 +21,8 @@ import Foundation case reply case edit case more + case resend + case delete // MARK: - Properties @@ -36,6 +38,10 @@ import Foundation title = VectorL10n.roomEventActionEdit case .more: title = VectorL10n.roomEventActionMore + case .resend: + title = VectorL10n.retry + case .delete: + title = VectorL10n.roomEventActionDelete } return title @@ -53,6 +59,10 @@ import Foundation image = Asset.Images.roomContextMenuEdit.image case .more: image = Asset.Images.roomContextMenuMore.image + case .resend: + image = Asset.Images.roomContextMenuRetry.image + case .delete: + image = Asset.Images.roomContextMenuDelete.image default: image = nil } diff --git a/Riot/Modules/Room/DataSources/RoomDataSource.h b/Riot/Modules/Room/DataSources/RoomDataSource.h index 37ec55cbf..f28f91fac 100644 --- a/Riot/Modules/Room/DataSources/RoomDataSource.h +++ b/Riot/Modules/Room/DataSources/RoomDataSource.h @@ -99,4 +99,7 @@ - (void)roomDataSource:(RoomDataSource*)roomDataSource didUpdateEncryptionTrustLevel:(RoomEncryptionTrustLevel)roomEncryptionTrustLevel; +- (void)roomDataSource:(RoomDataSource*)roomDataSource didCancel:(MXEvent *)event; + + @end diff --git a/Riot/Modules/Room/DataSources/RoomDataSource.m b/Riot/Modules/Room/DataSources/RoomDataSource.m index 94e976c24..520dcac6d 100644 --- a/Riot/Modules/Room/DataSources/RoomDataSource.m +++ b/Riot/Modules/Room/DataSources/RoomDataSource.m @@ -28,7 +28,6 @@ #import "MXRoom+Riot.h" - @interface RoomDataSource() { // Observe kThemeServiceDidChangeThemeNotification to handle user interface theme change. @@ -48,6 +47,8 @@ @property(nonatomic, readwrite) RoomEncryptionTrustLevel encryptionTrustLevel; +@property (nonatomic, strong) NSMutableSet *failedEventIds; + @property (nonatomic) RoomBubbleCellData *roomCreationCellData; @property (nonatomic) BOOL showRoomCreationCell; @@ -240,6 +241,7 @@ for (RoomBubbleCellData *cellData in bubbles) { cellData.containsLastMessage = NO; + cellData.componentIndexOfSentMessageTick = -1; } // The cell containing the last message is the last one with an actual display. @@ -253,6 +255,8 @@ break; } } + + [self updateStatusInfo]; } } @@ -609,8 +613,16 @@ // Auto animate the sticker in case of animated gif bubbleCell.isAutoAnimatedGif = (cellData.attachment && cellData.attachment.type == MXKAttachmentTypeSticker); + + [self applyMaskToAttachmentViewOfBubbleCell: bubbleCell]; [self setupAccessibilityForCell:bubbleCell withCellData:cellData]; + + // We are interested only by outgoing messages + if ([cellData.senderId isEqualToString: self.mxSession.credentials.userId]) + { + [bubbleCell updateTickViewWithFailedEventIds:self.failedEventIds]; + } } return cell; @@ -974,6 +986,57 @@ [self.delegate dataSource:self didRecognizeAction:kMXKRoomBubbleCellLongPressOnReactionView inCell:nil userInfo:@{ kMXKRoomBubbleCellEventIdKey: eventId }]; } +- (void)applyMaskToAttachmentViewOfBubbleCell:(MXKRoomBubbleTableViewCell *)cell +{ + if (cell.attachmentView && !cell.attachmentView.layer.mask) + { + UIBezierPath *myClippingPath = [UIBezierPath bezierPathWithRoundedRect:cell.attachmentView.bounds cornerRadius:6]; + CAShapeLayer *mask = [CAShapeLayer layer]; + mask.path = myClippingPath.CGPath; + cell.attachmentView.layer.mask = mask; + } +} + +#pragma mark - Message status management + +- (void)updateStatusInfo +{ + if (!self.failedEventIds) + { + self.failedEventIds = [NSMutableSet new]; + } + + NSInteger bubbleIndex = bubbles.count; + while (bubbleIndex--) + { + RoomBubbleCellData *cellData = bubbles[bubbleIndex]; + + NSInteger componentIndex = cellData.bubbleComponents.count; + while (componentIndex--) { + MXKRoomBubbleComponent *component = cellData.bubbleComponents[componentIndex]; + MXEventSentState eventState = component.event.sentState; + + if (eventState == MXEventSentStateFailed) + { + [self.failedEventIds addObject:component.event.eventId]; + continue; + } + + NSArray *receipts = cellData.readReceipts[component.event.eventId]; + if (receipts.count) + { + return; + } + + if (eventState == MXEventSentStateSent) + { + cellData.componentIndexOfSentMessageTick = componentIndex; + return; + } + } + } +} + #pragma mark - Room creation intro cell - (BOOL)canShowRoomCreationIntroCell diff --git a/Riot/Modules/Room/RoomViewController.m b/Riot/Modules/Room/RoomViewController.m index 02e745a60..611a64390 100644 --- a/Riot/Modules/Room/RoomViewController.m +++ b/Riot/Modules/Room/RoomViewController.m @@ -2433,7 +2433,7 @@ NSNotificationName const RoomCallTileTappedNotification = @"RoomCallTileTappedNo // Add actions for a failed event if (selectedEvent.sentState == MXEventSentStateFailed) { - [currentAlert addAction:[UIAlertAction actionWithTitle:NSLocalizedStringFromTable(@"room_event_action_resend", @"Vector", nil) + [currentAlert addAction:[UIAlertAction actionWithTitle:NSLocalizedStringFromTable(@"retry", @"Vector", nil) style:UIAlertActionStyleDefault handler:^(UIAlertAction * action) { @@ -4618,32 +4618,14 @@ NSNotificationName const RoomCallTileTappedNotification = @"RoomCallTileTappedNo -(BOOL)checkUnsentMessages { - BOOL hasUnsent = NO; - BOOL hasUnsentDueToUnknownDevices = NO; - + RoomSentStatus sentStatus = RoomSentStatusOk; if ([self.activitiesView isKindOfClass:RoomActivitiesView.class]) { - NSArray *outgoingMsgs = self.roomDataSource.room.outgoingMessages; - - for (MXEvent *event in outgoingMsgs) + sentStatus = self.roomDataSource.room.sentStatus; + + if (sentStatus != RoomSentStatusOk) { - if (event.sentState == MXEventSentStateFailed) - { - hasUnsent = YES; - - // Check if the error is due to unknown devices - if ([event.sentError.domain isEqualToString:MXEncryptingErrorDomain] - && event.sentError.code == MXEncryptingErrorUnknownDeviceCode) - { - hasUnsentDueToUnknownDevices = YES; - break; - } - } - } - - if (hasUnsent) - { - NSString *notification = hasUnsentDueToUnknownDevices ? + NSString *notification = sentStatus == RoomSentStatusSentFailedDueToUnknownDevices ? NSLocalizedStringFromTable(@"room_unsent_messages_unknown_devices_notification", @"Vector", nil) : NSLocalizedStringFromTable(@"room_unsent_messages_notification", @"Vector", nil); @@ -4713,7 +4695,7 @@ NSNotificationName const RoomCallTileTappedNotification = @"RoomCallTileTappedNo } } - return hasUnsent; + return sentStatus != RoomSentStatusOk; } - (void)eventDidChangeSentState:(NSNotification *)notif @@ -4851,19 +4833,32 @@ NSNotificationName const RoomCallTileTappedNotification = @"RoomCallTileTappedNo - (void)cancelAllUnsentMessages { - // Remove unsent event ids - for (NSUInteger index = 0; index < self.roomDataSource.room.outgoingMessages.count;) - { - MXEvent *event = self.roomDataSource.room.outgoingMessages[index]; - if (event.sentState == MXEventSentStateFailed) + currentAlert = [UIAlertController alertControllerWithTitle:NSLocalizedStringFromTable(@"room_unsent_messages_cancel_title", @"Vector", nil) message:NSLocalizedStringFromTable(@"room_unsent_messages_cancel_message", @"Vector", nil) preferredStyle:UIAlertControllerStyleAlert]; + + MXWeakify(self); + [currentAlert addAction:[UIAlertAction actionWithTitle:[NSBundle mxk_localizedStringForKey:@"cancel"] style:UIAlertActionStyleCancel handler:^(UIAlertAction * action) { + MXStrongifyAndReturnIfNil(self); + self->currentAlert = nil; + }]]; + + [currentAlert addAction:[UIAlertAction actionWithTitle:[NSBundle mxk_localizedStringForKey:@"delete"] style:UIAlertActionStyleDestructive handler:^(UIAlertAction * action) { + MXStrongifyAndReturnIfNil(self); + // Remove unsent event ids + for (NSUInteger index = 0; index < self.roomDataSource.room.outgoingMessages.count;) { - [self.roomDataSource removeEventWithEventId:event.eventId]; + MXEvent *event = self.roomDataSource.room.outgoingMessages[index]; + if (event.sentState == MXEventSentStateFailed) + { + [self.roomDataSource removeEventWithEventId:event.eventId]; + } + else + { + index ++; + } } - else - { - index ++; - } - } + }]]; + + [self presentViewController:currentAlert animated:YES completion:nil]; } # pragma mark - Encryption Information view @@ -5336,147 +5331,22 @@ NSNotificationName const RoomCallTileTappedNotification = @"RoomCallTileTappedNo - (NSArray*)contextualMenuItemsForEvent:(MXEvent*)event andCell:(id)cell { - NSString *eventId = event.eventId; - MXKRoomBubbleTableViewCell *roomBubbleTableViewCell = (MXKRoomBubbleTableViewCell *)cell; - MXKAttachment *attachment = roomBubbleTableViewCell.bubbleData.attachment; - - MXWeakify(self); - - // Copy action - - BOOL isCopyActionEnabled = !attachment || attachment.type != MXKAttachmentTypeSticker; - - if (attachment && !BuildSettings.messageDetailsAllowCopyMedia) + if (event.sentState == MXEventSentStateFailed) { - isCopyActionEnabled = NO; + return @[ + [self resendMenuItemWithEvent:event], + [self deleteMenuItemWithEvent:event], + [self editMenuItemWithEvent:event], + [self copyMenuItemWithEvent:event andCell:cell] + ]; } - if (isCopyActionEnabled) - { - switch (event.eventType) { - case MXEventTypeRoomMessage: - { - NSString *messageType = event.content[@"msgtype"]; - - if ([messageType isEqualToString:kMXMessageTypeKeyVerificationRequest]) - { - isCopyActionEnabled = NO; - } - break; - } - case MXEventTypeKeyVerificationStart: - case MXEventTypeKeyVerificationAccept: - case MXEventTypeKeyVerificationKey: - case MXEventTypeKeyVerificationMac: - case MXEventTypeKeyVerificationDone: - case MXEventTypeKeyVerificationCancel: - isCopyActionEnabled = NO; - break; - default: - break; - } - } - - RoomContextualMenuItem *copyMenuItem = [[RoomContextualMenuItem alloc] initWithMenuAction:RoomContextualMenuActionCopy]; - copyMenuItem.isEnabled = isCopyActionEnabled; - copyMenuItem.action = ^{ - MXStrongifyAndReturnIfNil(self); - - if (!attachment) - { - NSArray *components = roomBubbleTableViewCell.bubbleData.bubbleComponents; - MXKRoomBubbleComponent *selectedComponent; - for (selectedComponent in components) - { - if ([selectedComponent.event.eventId isEqualToString:event.eventId]) - { - break; - } - selectedComponent = nil; - } - NSString *textMessage = selectedComponent.textMessage; - - if (textMessage) - { - MXKPasteboardManager.shared.pasteboard.string = textMessage; - } - else - { - NSLog(@"[RoomViewController] Contextual menu copy failed. Text is nil for room id/event id: %@/%@", selectedComponent.event.roomId, selectedComponent.event.eventId); - } - - [self hideContextualMenuAnimated:YES]; - } - else if (attachment.type != MXKAttachmentTypeSticker) - { - [self hideContextualMenuAnimated:YES completion:^{ - [self startActivityIndicator]; - - [attachment copy:^{ - - [self stopActivityIndicator]; - - } failure:^(NSError *error) { - - [self stopActivityIndicator]; - - //Alert user - [[AppDelegate theDelegate] showErrorAsAlert:error]; - }]; - - // Start animation in case of download during attachment preparing - [roomBubbleTableViewCell startProgressUI]; - }]; - } - }; - - // Reply action - - RoomContextualMenuItem *replyMenuItem = [[RoomContextualMenuItem alloc] initWithMenuAction:RoomContextualMenuActionReply]; - replyMenuItem.isEnabled = [self.roomDataSource canReplyToEventWithId:eventId]; - replyMenuItem.action = ^{ - MXStrongifyAndReturnIfNil(self); - - [self hideContextualMenuAnimated:YES cancelEventSelection:NO completion:nil]; - [self selectEventWithId:eventId inputToolBarSendMode:RoomInputToolbarViewSendModeReply showTimestamp:NO]; - - // And display the keyboard - [self.inputToolbarView becomeFirstResponder]; - }; - - // Edit action - - RoomContextualMenuItem *editMenuItem = [[RoomContextualMenuItem alloc] initWithMenuAction:RoomContextualMenuActionEdit]; - editMenuItem.action = ^{ - MXStrongifyAndReturnIfNil(self); - [self hideContextualMenuAnimated:YES cancelEventSelection:NO completion:nil]; - [self editEventContentWithId:eventId]; - - // And display the keyboard - [self.inputToolbarView becomeFirstResponder]; - }; - - editMenuItem.isEnabled = [self.roomDataSource canEditEventWithId:eventId]; - - // More action - - RoomContextualMenuItem *moreMenuItem = [[RoomContextualMenuItem alloc] initWithMenuAction:RoomContextualMenuActionMore]; - moreMenuItem.action = ^{ - MXStrongifyAndReturnIfNil(self); - [self hideContextualMenuAnimated:YES completion:nil]; - [self showAdditionalActionsMenuForEvent:event inCell:cell animated:YES]; - }; - - // Actions list - - NSArray *actionItems = @[ - copyMenuItem, - replyMenuItem, - editMenuItem, - moreMenuItem - ]; - - return actionItems; + return @[ + [self copyMenuItemWithEvent:event andCell:cell], + [self replyMenuItemWithEvent:event], + [self editMenuItemWithEvent:event], + [self moreMenuItemWithEvent:event andCell:cell] + ]; } - (void)showContextualMenuForEvent:(MXEvent*)event fromSingleTapGesture:(BOOL)usedSingleTapGesture cell:(id)cell animated:(BOOL)animated @@ -5585,6 +5455,197 @@ NSNotificationName const RoomCallTileTappedNotification = @"RoomCallTileTappedNo self.overlayContainerView.userInteractionEnabled = enableOverlayContainerUserInteractions; } +- (RoomContextualMenuItem *)resendMenuItemWithEvent:(MXEvent*)event +{ + MXWeakify(self); + + RoomContextualMenuItem *resendMenuItem = [[RoomContextualMenuItem alloc] initWithMenuAction:RoomContextualMenuActionResend]; + resendMenuItem.action = ^{ + MXStrongifyAndReturnIfNil(self); + [self hideContextualMenuAnimated:YES cancelEventSelection:NO completion:nil]; + [self cancelEventSelection]; + [self.roomDataSource resendEventWithEventId:event.eventId success:nil failure:nil]; + }; + + return resendMenuItem; +} + +- (RoomContextualMenuItem *)deleteMenuItemWithEvent:(MXEvent*)event +{ + MXWeakify(self); + + RoomContextualMenuItem *deleteMenuItem = [[RoomContextualMenuItem alloc] initWithMenuAction:RoomContextualMenuActionDelete]; + deleteMenuItem.action = ^{ + MXStrongifyAndReturnIfNil(self); + + MXWeakify(self); + [self hideContextualMenuAnimated:YES cancelEventSelection:YES completion:^{ + MXStrongifyAndReturnIfNil(self); + + self->currentAlert = [UIAlertController alertControllerWithTitle:NSLocalizedStringFromTable(@"room_event_action_delete_confirmation_title", @"Vector", nil) message:NSLocalizedStringFromTable(@"room_event_action_delete_confirmation_message", @"Vector", nil) preferredStyle:UIAlertControllerStyleAlert]; + + [self->currentAlert addAction:[UIAlertAction actionWithTitle:[NSBundle mxk_localizedStringForKey:@"cancel"] style:UIAlertActionStyleDefault handler:^(UIAlertAction * action) { + }]]; + + [self->currentAlert addAction:[UIAlertAction actionWithTitle:[NSBundle mxk_localizedStringForKey:@"delete"] style:UIAlertActionStyleDestructive handler:^(UIAlertAction * action) { + [self.roomDataSource removeEventWithEventId:event.eventId]; + }]]; + + [self presentViewController:self->currentAlert animated:YES completion:nil]; + }]; + }; + + return deleteMenuItem; +} + +- (RoomContextualMenuItem *)editMenuItemWithEvent:(MXEvent*)event +{ + MXWeakify(self); + + RoomContextualMenuItem *editMenuItem = [[RoomContextualMenuItem alloc] initWithMenuAction:RoomContextualMenuActionEdit]; + editMenuItem.action = ^{ + MXStrongifyAndReturnIfNil(self); + [self hideContextualMenuAnimated:YES cancelEventSelection:NO completion:nil]; + [self editEventContentWithId:event.eventId]; + + // And display the keyboard + [self.inputToolbarView becomeFirstResponder]; + }; + + editMenuItem.isEnabled = [self.roomDataSource canEditEventWithId:event.eventId]; + + return editMenuItem; +} + +- (RoomContextualMenuItem *)copyMenuItemWithEvent:(MXEvent*)event andCell:(id)cell +{ + MXKRoomBubbleTableViewCell *roomBubbleTableViewCell = (MXKRoomBubbleTableViewCell *)cell; + MXKAttachment *attachment = roomBubbleTableViewCell.bubbleData.attachment; + + MXWeakify(self); + + BOOL isCopyActionEnabled = !attachment || attachment.type != MXKAttachmentTypeSticker; + + if (attachment && !BuildSettings.messageDetailsAllowCopyMedia) + { + isCopyActionEnabled = NO; + } + + if (isCopyActionEnabled) + { + switch (event.eventType) { + case MXEventTypeRoomMessage: + { + NSString *messageType = event.content[@"msgtype"]; + + if ([messageType isEqualToString:kMXMessageTypeKeyVerificationRequest]) + { + isCopyActionEnabled = NO; + } + break; + } + case MXEventTypeKeyVerificationStart: + case MXEventTypeKeyVerificationAccept: + case MXEventTypeKeyVerificationKey: + case MXEventTypeKeyVerificationMac: + case MXEventTypeKeyVerificationDone: + case MXEventTypeKeyVerificationCancel: + isCopyActionEnabled = NO; + break; + default: + break; + } + } + + RoomContextualMenuItem *copyMenuItem = [[RoomContextualMenuItem alloc] initWithMenuAction:RoomContextualMenuActionCopy]; + copyMenuItem.isEnabled = isCopyActionEnabled; + copyMenuItem.action = ^{ + MXStrongifyAndReturnIfNil(self); + + if (!attachment) + { + NSArray *components = roomBubbleTableViewCell.bubbleData.bubbleComponents; + MXKRoomBubbleComponent *selectedComponent; + for (selectedComponent in components) + { + if ([selectedComponent.event.eventId isEqualToString:event.eventId]) + { + break; + } + selectedComponent = nil; + } + NSString *textMessage = selectedComponent.textMessage; + + if (textMessage) + { + MXKPasteboardManager.shared.pasteboard.string = textMessage; + } + else + { + NSLog(@"[RoomViewController] Contextual menu copy failed. Text is nil for room id/event id: %@/%@", selectedComponent.event.roomId, selectedComponent.event.eventId); + } + + [self hideContextualMenuAnimated:YES]; + } + else if (attachment.type != MXKAttachmentTypeSticker) + { + [self hideContextualMenuAnimated:YES completion:^{ + [self startActivityIndicator]; + + [attachment copy:^{ + + [self stopActivityIndicator]; + + } failure:^(NSError *error) { + + [self stopActivityIndicator]; + + //Alert user + [[AppDelegate theDelegate] showErrorAsAlert:error]; + }]; + + // Start animation in case of download during attachment preparing + [roomBubbleTableViewCell startProgressUI]; + }]; + } + }; + + return copyMenuItem; +} + +- (RoomContextualMenuItem *)replyMenuItemWithEvent:(MXEvent*)event +{ + MXWeakify(self); + + RoomContextualMenuItem *replyMenuItem = [[RoomContextualMenuItem alloc] initWithMenuAction:RoomContextualMenuActionReply]; + replyMenuItem.isEnabled = [self.roomDataSource canReplyToEventWithId:event.eventId]; + replyMenuItem.action = ^{ + MXStrongifyAndReturnIfNil(self); + + [self hideContextualMenuAnimated:YES cancelEventSelection:NO completion:nil]; + [self selectEventWithId:event.eventId inputToolBarSendMode:RoomInputToolbarViewSendModeReply showTimestamp:NO]; + + // And display the keyboard + [self.inputToolbarView becomeFirstResponder]; + }; + + return replyMenuItem; +} + +- (RoomContextualMenuItem *)moreMenuItemWithEvent:(MXEvent*)event andCell:(id)cell +{ + MXWeakify(self); + + RoomContextualMenuItem *moreMenuItem = [[RoomContextualMenuItem alloc] initWithMenuAction:RoomContextualMenuActionMore]; + moreMenuItem.action = ^{ + MXStrongifyAndReturnIfNil(self); + [self hideContextualMenuAnimated:YES completion:nil]; + [self showAdditionalActionsMenuForEvent:event inCell:cell animated:YES]; + }; + + return moreMenuItem; +} + #pragma mark - RoomContextualMenuViewControllerDelegate - (void)roomContextualMenuViewControllerDidTapBackgroundOverlay:(RoomContextualMenuViewController *)viewController diff --git a/Riot/Modules/Room/Views/Activities/RoomActivitiesView.h b/Riot/Modules/Room/Views/Activities/RoomActivitiesView.h index f28e47c44..54eb3feb7 100644 --- a/Riot/Modules/Room/Views/Activities/RoomActivitiesView.h +++ b/Riot/Modules/Room/Views/Activities/RoomActivitiesView.h @@ -30,6 +30,11 @@ @property (weak, nonatomic) IBOutlet NSLayoutConstraint *mainHeightConstraint; +@property (weak, nonatomic) IBOutlet UIView *unsentMessagesContentView; +@property (weak, nonatomic) IBOutlet UIButton *resendButton; +@property (weak, nonatomic) IBOutlet UIButton *deleteButton; +@property (weak, nonatomic) IBOutlet UILabel *unsentMessageLabel; + /** Notify that some messages are not sent. Replace the current notification if any. diff --git a/Riot/Modules/Room/Views/Activities/RoomActivitiesView.m b/Riot/Modules/Room/Views/Activities/RoomActivitiesView.m index b29435bb4..fa6ebd452 100644 --- a/Riot/Modules/Room/Views/Activities/RoomActivitiesView.m +++ b/Riot/Modules/Room/Views/Activities/RoomActivitiesView.m @@ -118,64 +118,51 @@ { self.messageLabel.textColor = ThemeService.shared.theme.textSecondaryColor; } + + [self.resendButton.layer setCornerRadius:5]; + self.resendButton.clipsToBounds = YES; + [self.resendButton setTitle:NSLocalizedStringFromTable(@"retry", @"Vector", nil) forState:UIControlStateNormal]; + self.resendButton.backgroundColor = ThemeService.shared.theme.tintColor; + + UIImage *image = [[UIImage imageNamed:@"room_context_menu_delete"] imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate]; + [self.deleteButton setImage:image forState:UIControlStateNormal]; + self.deleteButton.tintColor = ThemeService.shared.theme.warningColor; + + self.unsentMessageLabel.textColor = ThemeService.shared.theme.textPrimaryColor; + self.unsentMessagesContentView.backgroundColor = ThemeService.shared.theme.backgroundColor; } #pragma mark - +- (IBAction)onCancelSendingPressed:(id)sender +{ + void (^onCancelLinkPressed)(void) = objc_getAssociatedObject(self.deleteButton, "onCancelLinkPressed"); + if (onCancelLinkPressed) + { + onCancelLinkPressed (); + } +} + +- (IBAction)onResendMessagesPressed:(id)sender +{ + void (^onResendLinkPressed)(void) = objc_getAssociatedObject(self.resendButton, "onResendLinkPressed"); + if (onResendLinkPressed) + { + onResendLinkPressed(); + } +} + - (void)displayUnsentMessagesNotification:(NSString*)notification withResendLink:(void (^)(void))onResendLinkPressed andCancelLink:(void (^)(void))onCancelLinkPressed andIconTapGesture:(void (^)(void))onIconTapGesture { [self reset]; - + if (onResendLinkPressed && onCancelLinkPressed) { - NSString *resendLink = NSLocalizedStringFromTable(@"room_prompt_resend", @"Vector", nil); - NSString *cancelLink = NSLocalizedStringFromTable(@"room_prompt_cancel", @"Vector", nil); - - NSString *notif = [NSString stringWithFormat:notification, resendLink, cancelLink]; - NSMutableAttributedString *tappableNotif = [[NSMutableAttributedString alloc] initWithString:notif]; + self.unsentMessagesContentView.hidden = NO; + self.unsentMessageLabel.text = notification; - objc_setAssociatedObject(self.messageTextView, "onResendLinkPressed", [onResendLinkPressed copy], OBJC_ASSOCIATION_RETAIN_NONATOMIC); - objc_setAssociatedObject(self.messageTextView, "onCancelLinkPressed", [onCancelLinkPressed copy], OBJC_ASSOCIATION_RETAIN_NONATOMIC); - - NSRange range = [notif rangeOfString:resendLink]; - [tappableNotif addAttribute:NSUnderlineStyleAttributeName value:@(NSUnderlineStyleSingle) range:range]; - [tappableNotif addAttribute:NSLinkAttributeName value:@"onResendLink" range:range]; - - range = [notif rangeOfString:cancelLink]; - [tappableNotif addAttribute:NSUnderlineStyleAttributeName value:@(NSUnderlineStyleSingle) range:range]; - [tappableNotif addAttribute:NSLinkAttributeName value:@"onCancelLink" range:range]; - - NSRange wholeString = NSMakeRange(0, tappableNotif.length); - [tappableNotif addAttribute:NSForegroundColorAttributeName value:ThemeService.shared.theme.warningColor range:wholeString]; - [tappableNotif addAttribute:NSFontAttributeName value:[UIFont systemFontOfSize:15] range:wholeString]; - - self.messageTextView.attributedText = tappableNotif; - self.messageTextView.tintColor = ThemeService.shared.theme.warningColor; - self.messageTextView.hidden = NO; - self.messageTextView.backgroundColor = [UIColor clearColor]; - } - else - { - self.messageLabel.text = notification; - self.messageLabel.textColor = ThemeService.shared.theme.warningColor; - self.messageLabel.hidden = NO; - } - - self.iconImageView.image = [UIImage imageNamed:@"error"]; - self.iconImageView.tintColor = ThemeService.shared.theme.tintColor; - self.iconImageView.hidden = NO; - - if (onIconTapGesture) - { - objc_setAssociatedObject(self.iconImageView, "onIconTapGesture", [onIconTapGesture copy], OBJC_ASSOCIATION_RETAIN_NONATOMIC); - - // Listen to icon tap - UITapGestureRecognizer *tapGesture = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(onIconTap:)]; - [tapGesture setNumberOfTouchesRequired:1]; - [tapGesture setNumberOfTapsRequired:1]; - [tapGesture setDelegate:self]; - [self.iconImageView addGestureRecognizer:tapGesture]; - self.iconImageView.userInteractionEnabled = YES; + objc_setAssociatedObject(self.resendButton, "onResendLinkPressed", [onResendLinkPressed copy], OBJC_ASSOCIATION_RETAIN_NONATOMIC); + objc_setAssociatedObject(self.deleteButton, "onCancelLinkPressed", [onCancelLinkPressed copy], OBJC_ASSOCIATION_RETAIN_NONATOMIC); } [self checkHeight:YES]; @@ -517,6 +504,7 @@ - (void)reset { self.separatorView.hidden = NO; + self.unsentMessagesContentView.hidden = YES; self.backgroundColor = UIColor.clearColor; diff --git a/Riot/Modules/Room/Views/Activities/RoomActivitiesView.xib b/Riot/Modules/Room/Views/Activities/RoomActivitiesView.xib index b7286bd60..8f491dc8c 100644 --- a/Riot/Modules/Room/Views/Activities/RoomActivitiesView.xib +++ b/Riot/Modules/Room/Views/Activities/RoomActivitiesView.xib @@ -1,11 +1,10 @@ - - - - + + - + + @@ -56,16 +55,71 @@ + + + + + @@ -87,15 +141,26 @@ + + + + + - + + + + + + + diff --git a/Riot/Modules/Room/Views/BubbleCells/RoomIncomingAttachmentBubbleCell.xib b/Riot/Modules/Room/Views/BubbleCells/RoomIncomingAttachmentBubbleCell.xib index b95e7301d..1fad6a9b2 100644 --- a/Riot/Modules/Room/Views/BubbleCells/RoomIncomingAttachmentBubbleCell.xib +++ b/Riot/Modules/Room/Views/BubbleCells/RoomIncomingAttachmentBubbleCell.xib @@ -1,12 +1,9 @@ - - - - - + + + - - + @@ -16,7 +13,7 @@ - + @@ -40,7 +37,7 @@ - + @@ -65,7 +62,7 @@ @@ -119,11 +104,13 @@ + + @@ -135,7 +122,6 @@ - @@ -157,10 +143,10 @@ - + diff --git a/Riot/Modules/Room/Views/BubbleCells/RoomIncomingAttachmentWithPaginationTitleBubbleCell.xib b/Riot/Modules/Room/Views/BubbleCells/RoomIncomingAttachmentWithPaginationTitleBubbleCell.xib index 537a4d374..3ce5fb0a0 100644 --- a/Riot/Modules/Room/Views/BubbleCells/RoomIncomingAttachmentWithPaginationTitleBubbleCell.xib +++ b/Riot/Modules/Room/Views/BubbleCells/RoomIncomingAttachmentWithPaginationTitleBubbleCell.xib @@ -1,12 +1,9 @@ - - - - + + - - + @@ -16,7 +13,7 @@ - + @@ -70,7 +67,7 @@ - + @@ -87,7 +84,7 @@ @@ -155,6 +140,7 @@ + @@ -164,10 +150,10 @@ + - @@ -193,10 +179,10 @@ - + diff --git a/Riot/Modules/Room/Views/BubbleCells/RoomIncomingAttachmentWithoutSenderInfoBubbleCell.xib b/Riot/Modules/Room/Views/BubbleCells/RoomIncomingAttachmentWithoutSenderInfoBubbleCell.xib index f6bb536ce..b66e44cea 100644 --- a/Riot/Modules/Room/Views/BubbleCells/RoomIncomingAttachmentWithoutSenderInfoBubbleCell.xib +++ b/Riot/Modules/Room/Views/BubbleCells/RoomIncomingAttachmentWithoutSenderInfoBubbleCell.xib @@ -1,12 +1,9 @@ - - - - - + + + - - + @@ -16,11 +13,11 @@ - + - + @@ -45,7 +42,7 @@ + @@ -99,11 +85,11 @@ + - @@ -123,8 +109,8 @@ - + diff --git a/Riot/Modules/Room/Views/BubbleCells/RoomOutgoingAttachmentBubbleCell.xib b/Riot/Modules/Room/Views/BubbleCells/RoomOutgoingAttachmentBubbleCell.xib index 61e1214eb..7d8f2e78f 100644 --- a/Riot/Modules/Room/Views/BubbleCells/RoomOutgoingAttachmentBubbleCell.xib +++ b/Riot/Modules/Room/Views/BubbleCells/RoomOutgoingAttachmentBubbleCell.xib @@ -1,12 +1,9 @@ - - - - - + + + - - + @@ -16,7 +13,7 @@ - + @@ -40,7 +37,7 @@ - + @@ -71,7 +68,7 @@ @@ -164,12 +138,11 @@ - - + diff --git a/Riot/Modules/Room/Views/BubbleCells/RoomOutgoingAttachmentWithPaginationTitleBubbleCell.xib b/Riot/Modules/Room/Views/BubbleCells/RoomOutgoingAttachmentWithPaginationTitleBubbleCell.xib index 2b196aeb8..c6d5de460 100644 --- a/Riot/Modules/Room/Views/BubbleCells/RoomOutgoingAttachmentWithPaginationTitleBubbleCell.xib +++ b/Riot/Modules/Room/Views/BubbleCells/RoomOutgoingAttachmentWithPaginationTitleBubbleCell.xib @@ -1,12 +1,9 @@ - - - - + + - - + @@ -16,7 +13,7 @@ - + @@ -70,7 +67,7 @@ - + @@ -87,7 +84,7 @@ @@ -155,6 +129,7 @@ + @@ -175,7 +150,6 @@ - @@ -200,12 +174,11 @@ - - + diff --git a/Riot/Modules/Room/Views/BubbleCells/RoomOutgoingAttachmentWithoutSenderInfoBubbleCell.xib b/Riot/Modules/Room/Views/BubbleCells/RoomOutgoingAttachmentWithoutSenderInfoBubbleCell.xib index bf85536e6..e31cd5257 100644 --- a/Riot/Modules/Room/Views/BubbleCells/RoomOutgoingAttachmentWithoutSenderInfoBubbleCell.xib +++ b/Riot/Modules/Room/Views/BubbleCells/RoomOutgoingAttachmentWithoutSenderInfoBubbleCell.xib @@ -1,12 +1,9 @@ - - - - - + + + - - + @@ -16,11 +13,11 @@ - + - + @@ -51,7 +48,7 @@ + @@ -111,7 +86,6 @@ - @@ -130,10 +104,9 @@ - - + diff --git a/Riot/Modules/Room/Views/CircleProgressView/CircleProgressView.swift b/Riot/Modules/Room/Views/CircleProgressView/CircleProgressView.swift new file mode 100644 index 000000000..3fa8dc790 --- /dev/null +++ b/Riot/Modules/Room/Views/CircleProgressView/CircleProgressView.swift @@ -0,0 +1,147 @@ +// +// Copyright 2021 New Vector Ltd +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import UIKit + +@IBDesignable +@objcMembers +class CircleProgressView: MXKPieChartView { + // MARK: - Constants + + private static let minStrokeEnd: CGFloat = 0.000000000001 + private static let maxStrokeEnd: CGFloat = 1 + + // MARK: - Public properties + + @IBInspectable var lineColor: UIColor = .lightGray { + didSet { + shapeLayer?.strokeColor = lineColor.cgColor + } + } + @IBInspectable var lineWidth: CGFloat = 2 { + didSet { + shapeLayer?.lineWidth = lineWidth + } + } + var value: CGFloat = 0 { + didSet { + stopAnimating() + strokeEnd = max(min(value, CircleProgressView.maxStrokeEnd), CircleProgressView.minStrokeEnd) + } + } + override var progress: CGFloat { + get { + return value + } + set { + value = newValue + } + } + + // MARK: - Private members + + private weak var shapeLayer: CAShapeLayer? + private var strokeEnd: CGFloat = minStrokeEnd { + didSet { + shapeLayer?.strokeEnd = strokeEnd + } + } + private var startAngle: CGFloat = -.pi/2 + private(set) var isAnimating = false + + // MARK: - Lifecycle + + required init?(coder: NSCoder) { + super.init(coder: coder) + initLayer() + initPath() + } + + override init(frame: CGRect) { + super.init(frame: frame) + initLayer() + initPath() + } + + override func layoutSubviews() { + super.layoutSubviews() + + shapeLayer?.frame = self.layer.bounds + initPath() + } + + // MARK: - Interface Builder + + override func prepareForInterfaceBuilder() { + value = 0.8 + } + + // MARK: - Animation management + + func startAnimating() { + guard !isAnimating else { + return + } + + isAnimating = true + + let rotationAnimation = CABasicAnimation(keyPath: "transform.rotation.z") + rotationAnimation.fromValue = CGFloat.pi / 2 + rotationAnimation.toValue = CGFloat.pi * 2.5 + rotationAnimation.repeatCount = .infinity + rotationAnimation.duration = 2 + shapeLayer?.add(rotationAnimation, forKey: "rotationAnimation") + + let strokeAnimation = CABasicAnimation(keyPath: "strokeEnd") + strokeAnimation.fromValue = 0 + strokeAnimation.toValue = 0.9 + strokeAnimation.repeatCount = .infinity + strokeAnimation.duration = 0.9 + strokeAnimation.autoreverses = true + strokeAnimation.timingFunction = CAMediaTimingFunction(name: .easeInEaseOut) + shapeLayer?.add(strokeAnimation, forKey: "path") + } + + func stopAnimating() { + guard isAnimating else { + return + } + + shapeLayer?.removeAllAnimations() + isAnimating = false + } + + // MARK: - Private methods + + private func initLayer() { + let layer = CAShapeLayer() + layer.fillColor = UIColor.clear.cgColor + layer.strokeColor = lineColor.cgColor + layer.lineCap = .round + layer.lineWidth = lineWidth + layer.allowsEdgeAntialiasing = true + layer.strokeEnd = strokeEnd + + self.layer.insertSublayer(layer, at: 0) + shapeLayer = layer + } + + private func initPath() { + let endAngle: CGFloat = startAngle + .pi * 2 + let path = UIBezierPath(arcCenter: CGPoint(x: self.bounds.midX, y: self.bounds.midY), radius: (self.bounds.width - lineWidth) / 2, startAngle: startAngle, endAngle: endAngle, clockwise: true) + shapeLayer?.path = path.cgPath + } +} diff --git a/Riot/Modules/TabBar/MasterTabBarController.m b/Riot/Modules/TabBar/MasterTabBarController.m index 2b3d68aa6..d532bfdca 100644 --- a/Riot/Modules/TabBar/MasterTabBarController.m +++ b/Riot/Modules/TabBar/MasterTabBarController.m @@ -888,21 +888,43 @@ withBadgeColor:(recentsDataSource.missedHighlightFavouriteDiscussionsCount ? ThemeService.shared.theme.noticeColor : ThemeService.shared.theme.noticeSecondaryColor)]; // Update the badge on People and Rooms tabs - [self setMissedDiscussionsCount:recentsDataSource.missedDirectDiscussionsCount - onTabBarItem:TABBAR_PEOPLE_INDEX - withBadgeColor:(recentsDataSource.missedHighlightDirectDiscussionsCount ? ThemeService.shared.theme.noticeColor : ThemeService.shared.theme.noticeSecondaryColor)]; - [self setMissedDiscussionsCount:recentsDataSource.missedGroupDiscussionsCount - onTabBarItem:TABBAR_ROOMS_INDEX - withBadgeColor:(recentsDataSource.missedHighlightGroupDiscussionsCount ? ThemeService.shared.theme.noticeColor : ThemeService.shared.theme.noticeSecondaryColor)]; + if (recentsDataSource.unsentMessagesDirectDiscussionsCount) + { + [self setBadgeValue:@"!" + onTabBarItem:TABBAR_PEOPLE_INDEX + withBadgeColor:ThemeService.shared.theme.noticeColor]; + } + else + { + [self setMissedDiscussionsCount:recentsDataSource.missedDirectDiscussionsCount + onTabBarItem:TABBAR_PEOPLE_INDEX + withBadgeColor:(recentsDataSource.missedHighlightDirectDiscussionsCount ? ThemeService.shared.theme.noticeColor : ThemeService.shared.theme.noticeSecondaryColor)]; + } + + if (recentsDataSource.unsentMessagesGroupDiscussionsCount) + { + [self setMissedDiscussionsCount:recentsDataSource.unsentMessagesGroupDiscussionsCount + onTabBarItem:TABBAR_ROOMS_INDEX + withBadgeColor:ThemeService.shared.theme.noticeColor]; + } + else + { + [self setMissedDiscussionsCount:recentsDataSource.missedGroupDiscussionsCount + onTabBarItem:TABBAR_ROOMS_INDEX + withBadgeColor:(recentsDataSource.missedHighlightGroupDiscussionsCount ? ThemeService.shared.theme.noticeColor : ThemeService.shared.theme.noticeSecondaryColor)]; + } } - (void)setMissedDiscussionsCount:(NSUInteger)count onTabBarItem:(NSUInteger)index withBadgeColor:(UIColor*)badgeColor { - if (count) + [self setBadgeValue:count ? [self tabBarBadgeStringValue:count] : nil onTabBarItem:index withBadgeColor:badgeColor]; +} + +- (void)setBadgeValue:(NSString *)value onTabBarItem:(NSUInteger)index withBadgeColor:(UIColor*)badgeColor +{ + if (value) { - NSString *badgeValue = [self tabBarBadgeStringValue:count]; - - self.tabBar.items[index].badgeValue = badgeValue; + self.tabBar.items[index].badgeValue = value; self.tabBar.items[index].badgeColor = badgeColor; diff --git a/Riot/Utils/EventFormatter.m b/Riot/Utils/EventFormatter.m index 6dd3aa6ce..532d26162 100644 --- a/Riot/Utils/EventFormatter.m +++ b/Riot/Utils/EventFormatter.m @@ -360,9 +360,9 @@ static NSString *const kEventFormatterTimeFormat = @"HH:mm"; self.subTitleTextColor = ThemeService.shared.theme.textSecondaryColor; self.prefixTextColor = ThemeService.shared.theme.textSecondaryColor; self.bingTextColor = ThemeService.shared.theme.noticeColor; - self.encryptingTextColor = ThemeService.shared.theme.tintColor; - self.sendingTextColor = ThemeService.shared.theme.textSecondaryColor; - self.errorTextColor = ThemeService.shared.theme.warningColor; + self.encryptingTextColor = ThemeService.shared.theme.textPrimaryColor; + self.sendingTextColor = ThemeService.shared.theme.textPrimaryColor; + self.errorTextColor = ThemeService.shared.theme.textPrimaryColor; self.showEditionMention = YES; self.editionMentionTextColor = ThemeService.shared.theme.textSecondaryColor;