diff --git a/CHANGES.rst b/CHANGES.rst index cb519d96a..f024fa61d 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -1,3 +1,44 @@ +Changes in 1.3.0 (2021-04-09) +================================================= + +✨ Features + * Composer Update - Typing and sending a message (#4085) + * Switching composer between text mode & action mode (#4087) + * Explore typing notifications inspired by web (#4134) + +🙌 Improvements + * Make the application settings more configurable (#4171) + * Possibility to lock some room creation parameters from settings (#4181) + * Enable / disable external friends invite (#4173) + * Composer update - UI enhancements (#4133) + * Increase grow/shrink animation speed in new composer (#4187) + * Limit typing notifications timeline jumps (#4176) + * Consider displaying names in typing notifications (#4175) + +🐛 Bugfix + * If you start typing while the new attachment sending mode is on, the send button appears (#4155) + * The final frames of the appearance animation of the new composer buttons are missing (#4160) + * Crash in [RoomViewController setupActions] (#4162) + * Too much vertical whitespace when replying (#4164) + * Black theme uses dark background for composer (#4192) + * Vertical layout of typing notifs can go wonky (#4159) + * Crash in [RoomViewController refreshTypingNotification] (#4161) + +⚠️ API Changes + * + +🗣 Translations + * + +🧱 Build + * + +Others + * + +Improvements: + * Upgrade MatrixKit version ([v0.14.7](https://github.com/matrix-org/matrix-ios-kit/releases/tag/v0.14.7)). + Changes in 1.2.8 (2021-03-26) ================================================= @@ -61,6 +102,7 @@ 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) + * Composer Update - Typing and sending a message (#4085) 🙌 Improvements * diff --git a/Config/AppIdentifiers.xcconfig b/Config/AppIdentifiers.xcconfig index ebcc71705..5a71f7c55 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.8 -CURRENT_PROJECT_VERSION = 1.2.8 +MARKETING_VERSION = 1.3.0 +CURRENT_PROJECT_VERSION = 1.3.0 // Team diff --git a/Config/BuildSettings.swift b/Config/BuildSettings.swift index d06d29747..90d9b4ac4 100644 --- a/Config/BuildSettings.swift +++ b/Config/BuildSettings.swift @@ -109,6 +109,8 @@ final class BuildSettings: NSObject { // MARK: - Legal URLs + + // Note: Set empty strings to hide the related entry in application settings static let applicationCopyrightUrlString = "https://element.io/copyright" static let applicationPrivacyPolicyUrlString = "https://element.io/privacy" static let applicationTermsConditionsUrlString = "https://element.io/terms-of-service" @@ -193,6 +195,8 @@ final class BuildSettings: NSObject { static let allowLocalContactsAccess: Bool = true + static let allowInviteExernalUsers: Bool = true + // MARK: - Feature Specifics /// Not allowed pin codes. User won't be able to select one of the pin in the list. @@ -225,10 +229,27 @@ final class BuildSettings: NSObject { static let settingsScreenAllowChangingCrashUsageDataSettings: Bool = true static let settingsScreenAllowBugReportingManually: Bool = true static let settingsScreenAllowDeactivatingAccount: Bool = true - + static let settingsScreenShowChangePassword:Bool = true + static let settingsScreenShowInviteFriends:Bool = true + static let settingsScreenShowEnableStunServerFallback: Bool = true + static let settingsSecurityScreenShowSessions:Bool = true + static let settingsSecurityScreenShowSetupBackup:Bool = true + static let settingsSecurityScreenShowRestoreBackup:Bool = true + static let settingsSecurityScreenShowDeleteBackup:Bool = true + static let settingsSecurityScreenShowCryptographyInfo:Bool = true + static let settingsSecurityScreenShowCryptographyExport:Bool = true + static let settingsSecurityScreenShowAdvancedUnverifiedDevices:Bool = true + // MARK: - Timeline settings static let roomInputToolbarCompressionMode = MXKRoomInputToolbarCompressionModePrompt + // MARK: - Room Creation Screen + + static let roomCreationScreenAllowEncryptionConfiguration: Bool = true + static let roomCreationScreenRoomIsEncrypted: Bool = true + static let roomCreationScreenAllowRoomTypeConfiguration: Bool = true + static let roomCreationScreenRoomIsPublic: Bool = false + // MARK: - Room Settings Screen static let roomSettingsScreenShowLowPriorityOption: Bool = true diff --git a/Podfile b/Podfile index 4b19feccf..cb8bf6df9 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.6' +$matrixKitVersion = '= 0.14.7' # $matrixKitVersion = :local # $matrixKitVersion = {'develop' => 'develop'} diff --git a/Podfile.lock b/Podfile.lock index c3b2e1c20..9105e7aeb 100644 --- a/Podfile.lock +++ b/Podfile.lock @@ -55,28 +55,28 @@ PODS: - MatomoTracker (7.4.1): - MatomoTracker/Core (= 7.4.1) - MatomoTracker/Core (7.4.1) - - MatrixKit (0.14.6): + - MatrixKit (0.14.7): - Down (~> 0.10.0) - DTCoreText (~> 1.6.25) - HPGrowingTextView (~> 1.1) - libPhoneNumber-iOS (~> 0.9.13) - - MatrixKit/Core (= 0.14.6) - - MatrixSDK (= 0.18.6) - - MatrixKit/Core (0.14.6): + - MatrixKit/Core (= 0.14.7) + - MatrixSDK (= 0.18.7) + - MatrixKit/Core (0.14.7): - Down (~> 0.10.0) - DTCoreText (~> 1.6.25) - HPGrowingTextView (~> 1.1) - libPhoneNumber-iOS (~> 0.9.13) - - MatrixSDK (= 0.18.6) - - MatrixSDK (0.18.6): - - MatrixSDK/Core (= 0.18.6) - - MatrixSDK/Core (0.18.6): + - MatrixSDK (= 0.18.7) + - MatrixSDK (0.18.7): + - MatrixSDK/Core (= 0.18.7) + - MatrixSDK/Core (0.18.7): - AFNetworking (~> 4.0.0) - GZIP (~> 1.3.0) - libbase58 (~> 0.1.4) - OLMKit (~> 3.2.2) - Realm (= 10.7.2) - - MatrixSDK/JingleCallStack (0.18.6): + - MatrixSDK/JingleCallStack (0.18.7): - JitsiMeetSDK (= 3.2.0) - MatrixSDK/Core - OLMKit (3.2.2): @@ -115,7 +115,7 @@ DEPENDENCIES: - KeychainAccess (~> 4.2.2) - KTCenterFlowLayout (~> 1.3.1) - MatomoTracker (~> 7.4.1) - - MatrixKit (= 0.14.6) + - MatrixKit (= 0.14.7) - MatrixSDK - MatrixSDK/JingleCallStack - OLMKit @@ -186,8 +186,8 @@ SPEC CHECKSUMS: LoggerAPI: ad9c4a6f1e32f518fdb43a1347ac14d765ab5e3d Logging: beeb016c9c80cf77042d62e83495816847ef108b MatomoTracker: 24a846c9d3aa76933183fe9d47fd62c9efa863fb - MatrixKit: 0bf9eb5a4375d829f7d8daf2b0995f630468b825 - MatrixSDK: 4437d3c4299b06954809e2eaa96f9193703e920e + MatrixKit: 65e08105b4501b97edba40602b1992203ab7f39a + MatrixSDK: 50d77fcdbde706d009f4cc86dc46f91f69b28a91 OLMKit: 20d1c564033a1ae7148f8f599378d4c798363905 ReadMoreTextView: 19147adf93abce6d7271e14031a00303fe28720d Realm: e523da9ade306c5ae87e85dc09fdef148d3e1cc1 @@ -199,6 +199,6 @@ SPEC CHECKSUMS: zxcvbn-ios: fef98b7c80f1512ff0eec47ac1fa399fc00f7e3c ZXingObjC: fdbb269f25dd2032da343e06f10224d62f537bdb -PODFILE CHECKSUM: 44dae827997fc5fc1a07fabc00a4870eb94a9956 +PODFILE CHECKSUM: d5892484a72d78c5d98faf60014db77b2d4a0565 COCOAPODS: 1.10.1 diff --git a/Riot/Assets/Base.lproj/Main.storyboard b/Riot/Assets/Base.lproj/Main.storyboard index b4e354b0f..ff80d131e 100644 --- a/Riot/Assets/Base.lproj/Main.storyboard +++ b/Riot/Assets/Base.lproj/Main.storyboard @@ -1,9 +1,9 @@ - + - + @@ -34,14 +34,6 @@ - - - - - - - - @@ -96,7 +88,7 @@ - + @@ -320,7 +312,7 @@ - + @@ -589,11 +581,10 @@ - - + + - @@ -602,5 +593,8 @@ + + + diff --git a/Riot/Assets/Images.xcassets/Encryption/encryption_normal.imageset/Contents.json b/Riot/Assets/Images.xcassets/Encryption/encryption_normal.imageset/Contents.json index 91448ccb6..3b025c1f0 100644 --- a/Riot/Assets/Images.xcassets/Encryption/encryption_normal.imageset/Contents.json +++ b/Riot/Assets/Images.xcassets/Encryption/encryption_normal.imageset/Contents.json @@ -1,23 +1,23 @@ { "images" : [ { - "idiom" : "universal", "filename" : "encryption_normal.png", + "idiom" : "universal", "scale" : "1x" }, { - "idiom" : "universal", "filename" : "encryption_normal@2x.png", + "idiom" : "universal", "scale" : "2x" }, { - "idiom" : "universal", "filename" : "encryption_normal@3x.png", + "idiom" : "universal", "scale" : "3x" } ], "info" : { - "version" : 1, - "author" : "xcode" + "author" : "xcode", + "version" : 1 } -} \ No newline at end of file +} diff --git a/Riot/Assets/Images.xcassets/Encryption/encryption_normal.imageset/encryption_normal.png b/Riot/Assets/Images.xcassets/Encryption/encryption_normal.imageset/encryption_normal.png index c628f322a..6222e3d16 100644 Binary files a/Riot/Assets/Images.xcassets/Encryption/encryption_normal.imageset/encryption_normal.png and b/Riot/Assets/Images.xcassets/Encryption/encryption_normal.imageset/encryption_normal.png differ diff --git a/Riot/Assets/Images.xcassets/Encryption/encryption_normal.imageset/encryption_normal@2x.png b/Riot/Assets/Images.xcassets/Encryption/encryption_normal.imageset/encryption_normal@2x.png index 07555f27b..e1ff17913 100644 Binary files a/Riot/Assets/Images.xcassets/Encryption/encryption_normal.imageset/encryption_normal@2x.png and b/Riot/Assets/Images.xcassets/Encryption/encryption_normal.imageset/encryption_normal@2x.png differ diff --git a/Riot/Assets/Images.xcassets/Encryption/encryption_normal.imageset/encryption_normal@3x.png b/Riot/Assets/Images.xcassets/Encryption/encryption_normal.imageset/encryption_normal@3x.png index 85e974954..ce4f7314a 100644 Binary files a/Riot/Assets/Images.xcassets/Encryption/encryption_normal.imageset/encryption_normal@3x.png and b/Riot/Assets/Images.xcassets/Encryption/encryption_normal.imageset/encryption_normal@3x.png differ diff --git a/Riot/Assets/Images.xcassets/Encryption/encryption_trusted.imageset/Contents.json b/Riot/Assets/Images.xcassets/Encryption/encryption_trusted.imageset/Contents.json index cfcf9df66..10f014560 100644 --- a/Riot/Assets/Images.xcassets/Encryption/encryption_trusted.imageset/Contents.json +++ b/Riot/Assets/Images.xcassets/Encryption/encryption_trusted.imageset/Contents.json @@ -1,23 +1,23 @@ { "images" : [ { - "idiom" : "universal", "filename" : "encryption_trusted.png", + "idiom" : "universal", "scale" : "1x" }, { - "idiom" : "universal", "filename" : "encryption_trusted@2x.png", + "idiom" : "universal", "scale" : "2x" }, { - "idiom" : "universal", "filename" : "encryption_trusted@3x.png", + "idiom" : "universal", "scale" : "3x" } ], "info" : { - "version" : 1, - "author" : "xcode" + "author" : "xcode", + "version" : 1 } -} \ No newline at end of file +} diff --git a/Riot/Assets/Images.xcassets/Encryption/encryption_trusted.imageset/encryption_trusted.png b/Riot/Assets/Images.xcassets/Encryption/encryption_trusted.imageset/encryption_trusted.png index 748d42a77..b1ad29a5c 100644 Binary files a/Riot/Assets/Images.xcassets/Encryption/encryption_trusted.imageset/encryption_trusted.png and b/Riot/Assets/Images.xcassets/Encryption/encryption_trusted.imageset/encryption_trusted.png differ diff --git a/Riot/Assets/Images.xcassets/Encryption/encryption_trusted.imageset/encryption_trusted@2x.png b/Riot/Assets/Images.xcassets/Encryption/encryption_trusted.imageset/encryption_trusted@2x.png index 02488c979..361df06c0 100644 Binary files a/Riot/Assets/Images.xcassets/Encryption/encryption_trusted.imageset/encryption_trusted@2x.png and b/Riot/Assets/Images.xcassets/Encryption/encryption_trusted.imageset/encryption_trusted@2x.png differ diff --git a/Riot/Assets/Images.xcassets/Encryption/encryption_trusted.imageset/encryption_trusted@3x.png b/Riot/Assets/Images.xcassets/Encryption/encryption_trusted.imageset/encryption_trusted@3x.png index 9154725c9..7abd021d4 100644 Binary files a/Riot/Assets/Images.xcassets/Encryption/encryption_trusted.imageset/encryption_trusted@3x.png and b/Riot/Assets/Images.xcassets/Encryption/encryption_trusted.imageset/encryption_trusted@3x.png differ diff --git a/Riot/Assets/Images.xcassets/Encryption/encryption_warning.imageset/Contents.json b/Riot/Assets/Images.xcassets/Encryption/encryption_warning.imageset/Contents.json index d71cc04ce..3198dc4ef 100644 --- a/Riot/Assets/Images.xcassets/Encryption/encryption_warning.imageset/Contents.json +++ b/Riot/Assets/Images.xcassets/Encryption/encryption_warning.imageset/Contents.json @@ -1,23 +1,23 @@ { "images" : [ { - "idiom" : "universal", "filename" : "encryption_warning.png", + "idiom" : "universal", "scale" : "1x" }, { - "idiom" : "universal", "filename" : "encryption_warning@2x.png", + "idiom" : "universal", "scale" : "2x" }, { - "idiom" : "universal", "filename" : "encryption_warning@3x.png", + "idiom" : "universal", "scale" : "3x" } ], "info" : { - "version" : 1, - "author" : "xcode" + "author" : "xcode", + "version" : 1 } -} \ No newline at end of file +} diff --git a/Riot/Assets/Images.xcassets/Encryption/encryption_warning.imageset/encryption_warning.png b/Riot/Assets/Images.xcassets/Encryption/encryption_warning.imageset/encryption_warning.png index 099c1c2a5..ef4d2426e 100644 Binary files a/Riot/Assets/Images.xcassets/Encryption/encryption_warning.imageset/encryption_warning.png and b/Riot/Assets/Images.xcassets/Encryption/encryption_warning.imageset/encryption_warning.png differ diff --git a/Riot/Assets/Images.xcassets/Encryption/encryption_warning.imageset/encryption_warning@2x.png b/Riot/Assets/Images.xcassets/Encryption/encryption_warning.imageset/encryption_warning@2x.png index 15bd69270..d99aa7fa1 100644 Binary files a/Riot/Assets/Images.xcassets/Encryption/encryption_warning.imageset/encryption_warning@2x.png and b/Riot/Assets/Images.xcassets/Encryption/encryption_warning.imageset/encryption_warning@2x.png differ diff --git a/Riot/Assets/Images.xcassets/Encryption/encryption_warning.imageset/encryption_warning@3x.png b/Riot/Assets/Images.xcassets/Encryption/encryption_warning.imageset/encryption_warning@3x.png index 2629a149a..33b1b4397 100644 Binary files a/Riot/Assets/Images.xcassets/Encryption/encryption_warning.imageset/encryption_warning@3x.png and b/Riot/Assets/Images.xcassets/Encryption/encryption_warning.imageset/encryption_warning@3x.png differ diff --git a/Riot/Assets/Images.xcassets/Room/Actions/Contents.json b/Riot/Assets/Images.xcassets/Room/Actions/Contents.json new file mode 100644 index 000000000..73c00596a --- /dev/null +++ b/Riot/Assets/Images.xcassets/Room/Actions/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Riot/Assets/Images.xcassets/Room/Actions/action_camera.imageset/Contents.json b/Riot/Assets/Images.xcassets/Room/Actions/action_camera.imageset/Contents.json new file mode 100644 index 000000000..aec39f6e4 --- /dev/null +++ b/Riot/Assets/Images.xcassets/Room/Actions/action_camera.imageset/Contents.json @@ -0,0 +1,26 @@ +{ + "images" : [ + { + "filename" : "action_camera.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "action_camera@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "action_camera@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "template-rendering-intent" : "template" + } +} diff --git a/Riot/Assets/Images.xcassets/Room/Actions/action_camera.imageset/action_camera.png b/Riot/Assets/Images.xcassets/Room/Actions/action_camera.imageset/action_camera.png new file mode 100644 index 000000000..799e2c02a Binary files /dev/null and b/Riot/Assets/Images.xcassets/Room/Actions/action_camera.imageset/action_camera.png differ diff --git a/Riot/Assets/Images.xcassets/Room/Actions/action_camera.imageset/action_camera@2x.png b/Riot/Assets/Images.xcassets/Room/Actions/action_camera.imageset/action_camera@2x.png new file mode 100644 index 000000000..191b388f9 Binary files /dev/null and b/Riot/Assets/Images.xcassets/Room/Actions/action_camera.imageset/action_camera@2x.png differ diff --git a/Riot/Assets/Images.xcassets/Room/Actions/action_camera.imageset/action_camera@3x.png b/Riot/Assets/Images.xcassets/Room/Actions/action_camera.imageset/action_camera@3x.png new file mode 100644 index 000000000..7af3968c9 Binary files /dev/null and b/Riot/Assets/Images.xcassets/Room/Actions/action_camera.imageset/action_camera@3x.png differ diff --git a/Riot/Assets/Images.xcassets/Room/Actions/action_file.imageset/Contents.json b/Riot/Assets/Images.xcassets/Room/Actions/action_file.imageset/Contents.json new file mode 100644 index 000000000..63ec74393 --- /dev/null +++ b/Riot/Assets/Images.xcassets/Room/Actions/action_file.imageset/Contents.json @@ -0,0 +1,26 @@ +{ + "images" : [ + { + "filename" : "action_file.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "action_file@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "action_file@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "template-rendering-intent" : "template" + } +} diff --git a/Riot/Assets/Images.xcassets/Room/Actions/action_file.imageset/action_file.png b/Riot/Assets/Images.xcassets/Room/Actions/action_file.imageset/action_file.png new file mode 100644 index 000000000..0d944b128 Binary files /dev/null and b/Riot/Assets/Images.xcassets/Room/Actions/action_file.imageset/action_file.png differ diff --git a/Riot/Assets/Images.xcassets/Room/Actions/action_file.imageset/action_file@2x.png b/Riot/Assets/Images.xcassets/Room/Actions/action_file.imageset/action_file@2x.png new file mode 100644 index 000000000..4f4522ab7 Binary files /dev/null and b/Riot/Assets/Images.xcassets/Room/Actions/action_file.imageset/action_file@2x.png differ diff --git a/Riot/Assets/Images.xcassets/Room/Actions/action_file.imageset/action_file@3x.png b/Riot/Assets/Images.xcassets/Room/Actions/action_file.imageset/action_file@3x.png new file mode 100644 index 000000000..13ed744fc Binary files /dev/null and b/Riot/Assets/Images.xcassets/Room/Actions/action_file.imageset/action_file@3x.png differ diff --git a/Riot/Assets/Images.xcassets/Room/Actions/action_media_library.imageset/Contents.json b/Riot/Assets/Images.xcassets/Room/Actions/action_media_library.imageset/Contents.json new file mode 100644 index 000000000..7854a9423 --- /dev/null +++ b/Riot/Assets/Images.xcassets/Room/Actions/action_media_library.imageset/Contents.json @@ -0,0 +1,26 @@ +{ + "images" : [ + { + "filename" : "action_media_library.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "action_media_library@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "action_media_library@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "template-rendering-intent" : "template" + } +} diff --git a/Riot/Assets/Images.xcassets/Room/Actions/action_media_library.imageset/action_media_library.png b/Riot/Assets/Images.xcassets/Room/Actions/action_media_library.imageset/action_media_library.png new file mode 100644 index 000000000..0adba836c Binary files /dev/null and b/Riot/Assets/Images.xcassets/Room/Actions/action_media_library.imageset/action_media_library.png differ diff --git a/Riot/Assets/Images.xcassets/Room/Actions/action_media_library.imageset/action_media_library@2x.png b/Riot/Assets/Images.xcassets/Room/Actions/action_media_library.imageset/action_media_library@2x.png new file mode 100644 index 000000000..aa6ddc4f9 Binary files /dev/null and b/Riot/Assets/Images.xcassets/Room/Actions/action_media_library.imageset/action_media_library@2x.png differ diff --git a/Riot/Assets/Images.xcassets/Room/Actions/action_media_library.imageset/action_media_library@3x.png b/Riot/Assets/Images.xcassets/Room/Actions/action_media_library.imageset/action_media_library@3x.png new file mode 100644 index 000000000..2dfd074ab Binary files /dev/null and b/Riot/Assets/Images.xcassets/Room/Actions/action_media_library.imageset/action_media_library@3x.png differ diff --git a/Riot/Assets/Images.xcassets/Room/Actions/action_sticker.imageset/Contents.json b/Riot/Assets/Images.xcassets/Room/Actions/action_sticker.imageset/Contents.json new file mode 100644 index 000000000..adf12f0d6 --- /dev/null +++ b/Riot/Assets/Images.xcassets/Room/Actions/action_sticker.imageset/Contents.json @@ -0,0 +1,26 @@ +{ + "images" : [ + { + "filename" : "action_sticker.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "action_sticker@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "action_sticker@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "template-rendering-intent" : "template" + } +} diff --git a/Riot/Assets/Images.xcassets/Room/Actions/action_sticker.imageset/action_sticker.png b/Riot/Assets/Images.xcassets/Room/Actions/action_sticker.imageset/action_sticker.png new file mode 100644 index 000000000..cb7a04d3f Binary files /dev/null and b/Riot/Assets/Images.xcassets/Room/Actions/action_sticker.imageset/action_sticker.png differ diff --git a/Riot/Assets/Images.xcassets/Room/Actions/action_sticker.imageset/action_sticker@2x.png b/Riot/Assets/Images.xcassets/Room/Actions/action_sticker.imageset/action_sticker@2x.png new file mode 100644 index 000000000..13a2b7511 Binary files /dev/null and b/Riot/Assets/Images.xcassets/Room/Actions/action_sticker.imageset/action_sticker@2x.png differ diff --git a/Riot/Assets/Images.xcassets/Room/Actions/action_sticker.imageset/action_sticker@3x.png b/Riot/Assets/Images.xcassets/Room/Actions/action_sticker.imageset/action_sticker@3x.png new file mode 100644 index 000000000..d000e8f4c Binary files /dev/null and b/Riot/Assets/Images.xcassets/Room/Actions/action_sticker.imageset/action_sticker@3x.png differ diff --git a/Riot/Assets/Images.xcassets/Room/Activities/scrolldown.imageset/Contents.json b/Riot/Assets/Images.xcassets/Room/Activities/scrolldown.imageset/Contents.json index 8d7bcd9ce..4e597c1e4 100644 --- a/Riot/Assets/Images.xcassets/Room/Activities/scrolldown.imageset/Contents.json +++ b/Riot/Assets/Images.xcassets/Room/Activities/scrolldown.imageset/Contents.json @@ -19,8 +19,5 @@ "info" : { "author" : "xcode", "version" : 1 - }, - "properties" : { - "template-rendering-intent" : "template" } } diff --git a/Riot/Assets/Images.xcassets/Room/Activities/scrolldown.imageset/scrolldown.png b/Riot/Assets/Images.xcassets/Room/Activities/scrolldown.imageset/scrolldown.png index ac61fd1ea..4e639fb9b 100644 Binary files a/Riot/Assets/Images.xcassets/Room/Activities/scrolldown.imageset/scrolldown.png and b/Riot/Assets/Images.xcassets/Room/Activities/scrolldown.imageset/scrolldown.png differ diff --git a/Riot/Assets/Images.xcassets/Room/Activities/scrolldown.imageset/scrolldown@2x.png b/Riot/Assets/Images.xcassets/Room/Activities/scrolldown.imageset/scrolldown@2x.png index 490d720c2..aabb2b121 100644 Binary files a/Riot/Assets/Images.xcassets/Room/Activities/scrolldown.imageset/scrolldown@2x.png and b/Riot/Assets/Images.xcassets/Room/Activities/scrolldown.imageset/scrolldown@2x.png differ diff --git a/Riot/Assets/Images.xcassets/Room/Activities/scrolldown.imageset/scrolldown@3x.png b/Riot/Assets/Images.xcassets/Room/Activities/scrolldown.imageset/scrolldown@3x.png index cd5dba5f1..81685ed6b 100644 Binary files a/Riot/Assets/Images.xcassets/Room/Activities/scrolldown.imageset/scrolldown@3x.png and b/Riot/Assets/Images.xcassets/Room/Activities/scrolldown.imageset/scrolldown@3x.png differ diff --git a/Riot/Assets/Images.xcassets/Room/Activities/scrolldown_dark.imageset/Contents.json b/Riot/Assets/Images.xcassets/Room/Activities/scrolldown_dark.imageset/Contents.json new file mode 100644 index 000000000..6dc294359 --- /dev/null +++ b/Riot/Assets/Images.xcassets/Room/Activities/scrolldown_dark.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "filename" : "scrolldown_dark.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "scrolldown_dark@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "scrolldown_dark@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Riot/Assets/Images.xcassets/Room/Activities/scrolldown_dark.imageset/scrolldown_dark.png b/Riot/Assets/Images.xcassets/Room/Activities/scrolldown_dark.imageset/scrolldown_dark.png new file mode 100644 index 000000000..740a562f4 Binary files /dev/null and b/Riot/Assets/Images.xcassets/Room/Activities/scrolldown_dark.imageset/scrolldown_dark.png differ diff --git a/Riot/Assets/Images.xcassets/Room/Activities/scrolldown_dark.imageset/scrolldown_dark@2x.png b/Riot/Assets/Images.xcassets/Room/Activities/scrolldown_dark.imageset/scrolldown_dark@2x.png new file mode 100644 index 000000000..1dfe3ab3d Binary files /dev/null and b/Riot/Assets/Images.xcassets/Room/Activities/scrolldown_dark.imageset/scrolldown_dark@2x.png differ diff --git a/Riot/Assets/Images.xcassets/Room/Activities/scrolldown_dark.imageset/scrolldown_dark@3x.png b/Riot/Assets/Images.xcassets/Room/Activities/scrolldown_dark.imageset/scrolldown_dark@3x.png new file mode 100644 index 000000000..14ae3aa1b Binary files /dev/null and b/Riot/Assets/Images.xcassets/Room/Activities/scrolldown_dark.imageset/scrolldown_dark@3x.png differ diff --git a/Riot/Assets/Images.xcassets/Room/Input/Contents.json b/Riot/Assets/Images.xcassets/Room/Input/Contents.json index da4a164c9..73c00596a 100644 --- a/Riot/Assets/Images.xcassets/Room/Input/Contents.json +++ b/Riot/Assets/Images.xcassets/Room/Input/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/Input/input_close_icon.imageset/Contents.json b/Riot/Assets/Images.xcassets/Room/Input/input_close_icon.imageset/Contents.json new file mode 100644 index 000000000..9c70622e2 --- /dev/null +++ b/Riot/Assets/Images.xcassets/Room/Input/input_close_icon.imageset/Contents.json @@ -0,0 +1,26 @@ +{ + "images" : [ + { + "filename" : "input_close_icon.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "input_close_icon@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "input_close_icon@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "template-rendering-intent" : "template" + } +} diff --git a/Riot/Assets/Images.xcassets/Room/Input/input_close_icon.imageset/input_close_icon.png b/Riot/Assets/Images.xcassets/Room/Input/input_close_icon.imageset/input_close_icon.png new file mode 100644 index 000000000..b78911642 Binary files /dev/null and b/Riot/Assets/Images.xcassets/Room/Input/input_close_icon.imageset/input_close_icon.png differ diff --git a/Riot/Assets/Images.xcassets/Room/Input/input_close_icon.imageset/input_close_icon@2x.png b/Riot/Assets/Images.xcassets/Room/Input/input_close_icon.imageset/input_close_icon@2x.png new file mode 100644 index 000000000..d27429876 Binary files /dev/null and b/Riot/Assets/Images.xcassets/Room/Input/input_close_icon.imageset/input_close_icon@2x.png differ diff --git a/Riot/Assets/Images.xcassets/Room/Input/input_close_icon.imageset/input_close_icon@3x.png b/Riot/Assets/Images.xcassets/Room/Input/input_close_icon.imageset/input_close_icon@3x.png new file mode 100644 index 000000000..6f628586d Binary files /dev/null and b/Riot/Assets/Images.xcassets/Room/Input/input_close_icon.imageset/input_close_icon@3x.png differ diff --git a/Riot/Assets/Images.xcassets/Room/Input/input_edit_icon.imageset/Contents.json b/Riot/Assets/Images.xcassets/Room/Input/input_edit_icon.imageset/Contents.json new file mode 100644 index 000000000..b4cbe6fdb --- /dev/null +++ b/Riot/Assets/Images.xcassets/Room/Input/input_edit_icon.imageset/Contents.json @@ -0,0 +1,26 @@ +{ + "images" : [ + { + "filename" : "input_edit_icon.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "input_edit_icon@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "input_edit_icon@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "template-rendering-intent" : "template" + } +} diff --git a/Riot/Assets/Images.xcassets/Room/Input/input_edit_icon.imageset/input_edit_icon.png b/Riot/Assets/Images.xcassets/Room/Input/input_edit_icon.imageset/input_edit_icon.png new file mode 100644 index 000000000..52860737e Binary files /dev/null and b/Riot/Assets/Images.xcassets/Room/Input/input_edit_icon.imageset/input_edit_icon.png differ diff --git a/Riot/Assets/Images.xcassets/Room/Input/input_edit_icon.imageset/input_edit_icon@2x.png b/Riot/Assets/Images.xcassets/Room/Input/input_edit_icon.imageset/input_edit_icon@2x.png new file mode 100644 index 000000000..919c867c7 Binary files /dev/null and b/Riot/Assets/Images.xcassets/Room/Input/input_edit_icon.imageset/input_edit_icon@2x.png differ diff --git a/Riot/Assets/Images.xcassets/Room/Input/input_edit_icon.imageset/input_edit_icon@3x.png b/Riot/Assets/Images.xcassets/Room/Input/input_edit_icon.imageset/input_edit_icon@3x.png new file mode 100644 index 000000000..4baa61fd8 Binary files /dev/null and b/Riot/Assets/Images.xcassets/Room/Input/input_edit_icon.imageset/input_edit_icon@3x.png differ diff --git a/Riot/Assets/Images.xcassets/Room/Input/input_reply_icon.imageset/Contents.json b/Riot/Assets/Images.xcassets/Room/Input/input_reply_icon.imageset/Contents.json new file mode 100644 index 000000000..5cbd96db1 --- /dev/null +++ b/Riot/Assets/Images.xcassets/Room/Input/input_reply_icon.imageset/Contents.json @@ -0,0 +1,26 @@ +{ + "images" : [ + { + "filename" : "input_reply_icon.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "input_reply_icon@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "input_reply_icon@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "template-rendering-intent" : "template" + } +} diff --git a/Riot/Assets/Images.xcassets/Room/Input/input_reply_icon.imageset/input_reply_icon.png b/Riot/Assets/Images.xcassets/Room/Input/input_reply_icon.imageset/input_reply_icon.png new file mode 100644 index 000000000..20bc5c005 Binary files /dev/null and b/Riot/Assets/Images.xcassets/Room/Input/input_reply_icon.imageset/input_reply_icon.png differ diff --git a/Riot/Assets/Images.xcassets/Room/Input/input_reply_icon.imageset/input_reply_icon@2x.png b/Riot/Assets/Images.xcassets/Room/Input/input_reply_icon.imageset/input_reply_icon@2x.png new file mode 100644 index 000000000..4d45eb912 Binary files /dev/null and b/Riot/Assets/Images.xcassets/Room/Input/input_reply_icon.imageset/input_reply_icon@2x.png differ diff --git a/Riot/Assets/Images.xcassets/Room/Input/input_reply_icon.imageset/input_reply_icon@3x.png b/Riot/Assets/Images.xcassets/Room/Input/input_reply_icon.imageset/input_reply_icon@3x.png new file mode 100644 index 000000000..d9999c5e3 Binary files /dev/null and b/Riot/Assets/Images.xcassets/Room/Input/input_reply_icon.imageset/input_reply_icon@3x.png differ diff --git a/Riot/Assets/Images.xcassets/Room/Input/input_text_background.imageset/Contents.json b/Riot/Assets/Images.xcassets/Room/Input/input_text_background.imageset/Contents.json new file mode 100644 index 000000000..390e9a858 --- /dev/null +++ b/Riot/Assets/Images.xcassets/Room/Input/input_text_background.imageset/Contents.json @@ -0,0 +1,26 @@ +{ + "images" : [ + { + "filename" : "input_text_background.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "input_text_background@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "input_text_background@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "template-rendering-intent" : "template" + } +} diff --git a/Riot/Assets/Images.xcassets/Room/Input/input_text_background.imageset/input_text_background.png b/Riot/Assets/Images.xcassets/Room/Input/input_text_background.imageset/input_text_background.png new file mode 100644 index 000000000..45de94894 Binary files /dev/null and b/Riot/Assets/Images.xcassets/Room/Input/input_text_background.imageset/input_text_background.png differ diff --git a/Riot/Assets/Images.xcassets/Room/Input/input_text_background.imageset/input_text_background@2x.png b/Riot/Assets/Images.xcassets/Room/Input/input_text_background.imageset/input_text_background@2x.png new file mode 100644 index 000000000..999413123 Binary files /dev/null and b/Riot/Assets/Images.xcassets/Room/Input/input_text_background.imageset/input_text_background@2x.png differ diff --git a/Riot/Assets/Images.xcassets/Room/Input/input_text_background.imageset/input_text_background@3x.png b/Riot/Assets/Images.xcassets/Room/Input/input_text_background.imageset/input_text_background@3x.png new file mode 100644 index 000000000..0855a9a23 Binary files /dev/null and b/Riot/Assets/Images.xcassets/Room/Input/input_text_background.imageset/input_text_background@3x.png differ diff --git a/Riot/Assets/Images.xcassets/Room/Input/save_icon.imageset/Contents.json b/Riot/Assets/Images.xcassets/Room/Input/save_icon.imageset/Contents.json new file mode 100644 index 000000000..222e068c2 --- /dev/null +++ b/Riot/Assets/Images.xcassets/Room/Input/save_icon.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "filename" : "save_icon.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "save_icon@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "save_icon@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Riot/Assets/Images.xcassets/Room/Input/save_icon.imageset/save_icon.png b/Riot/Assets/Images.xcassets/Room/Input/save_icon.imageset/save_icon.png new file mode 100644 index 000000000..3ff5eeaee Binary files /dev/null and b/Riot/Assets/Images.xcassets/Room/Input/save_icon.imageset/save_icon.png differ diff --git a/Riot/Assets/Images.xcassets/Room/Input/save_icon.imageset/save_icon@2x.png b/Riot/Assets/Images.xcassets/Room/Input/save_icon.imageset/save_icon@2x.png new file mode 100644 index 000000000..d900c15f5 Binary files /dev/null and b/Riot/Assets/Images.xcassets/Room/Input/save_icon.imageset/save_icon@2x.png differ diff --git a/Riot/Assets/Images.xcassets/Room/Input/save_icon.imageset/save_icon@3x.png b/Riot/Assets/Images.xcassets/Room/Input/save_icon.imageset/save_icon@3x.png new file mode 100644 index 000000000..233f9f8e1 Binary files /dev/null and b/Riot/Assets/Images.xcassets/Room/Input/save_icon.imageset/save_icon@3x.png differ diff --git a/Riot/Assets/Images.xcassets/Room/Input/send_icon.imageset/Contents.json b/Riot/Assets/Images.xcassets/Room/Input/send_icon.imageset/Contents.json new file mode 100644 index 000000000..82f51a5b7 --- /dev/null +++ b/Riot/Assets/Images.xcassets/Room/Input/send_icon.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "filename" : "send_icon.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "send_icon@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "send_icon@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Riot/Assets/Images.xcassets/Room/Input/send_icon.imageset/send_icon.png b/Riot/Assets/Images.xcassets/Room/Input/send_icon.imageset/send_icon.png new file mode 100644 index 000000000..02dc65a0b Binary files /dev/null and b/Riot/Assets/Images.xcassets/Room/Input/send_icon.imageset/send_icon.png differ diff --git a/Riot/Assets/Images.xcassets/Room/Input/send_icon.imageset/send_icon@2x.png b/Riot/Assets/Images.xcassets/Room/Input/send_icon.imageset/send_icon@2x.png new file mode 100644 index 000000000..28b0183c6 Binary files /dev/null and b/Riot/Assets/Images.xcassets/Room/Input/send_icon.imageset/send_icon@2x.png differ diff --git a/Riot/Assets/Images.xcassets/Room/Input/send_icon.imageset/send_icon@3x.png b/Riot/Assets/Images.xcassets/Room/Input/send_icon.imageset/send_icon@3x.png new file mode 100644 index 000000000..4e3719595 Binary files /dev/null and b/Riot/Assets/Images.xcassets/Room/Input/send_icon.imageset/send_icon@3x.png differ diff --git a/Riot/Assets/Images.xcassets/Room/Input/upload_icon.imageset/Contents.json b/Riot/Assets/Images.xcassets/Room/Input/upload_icon.imageset/Contents.json index b3ad81cfb..776550e12 100644 --- a/Riot/Assets/Images.xcassets/Room/Input/upload_icon.imageset/Contents.json +++ b/Riot/Assets/Images.xcassets/Room/Input/upload_icon.imageset/Contents.json @@ -19,8 +19,5 @@ "info" : { "author" : "xcode", "version" : 1 - }, - "properties" : { - "template-rendering-intent" : "template" } } diff --git a/Riot/Assets/Images.xcassets/Room/Input/upload_icon.imageset/upload_icon.png b/Riot/Assets/Images.xcassets/Room/Input/upload_icon.imageset/upload_icon.png index 8364d79ce..3ff1aefae 100644 Binary files a/Riot/Assets/Images.xcassets/Room/Input/upload_icon.imageset/upload_icon.png and b/Riot/Assets/Images.xcassets/Room/Input/upload_icon.imageset/upload_icon.png differ diff --git a/Riot/Assets/Images.xcassets/Room/Input/upload_icon.imageset/upload_icon@2x.png b/Riot/Assets/Images.xcassets/Room/Input/upload_icon.imageset/upload_icon@2x.png index 292fa17ea..7e5d7b972 100644 Binary files a/Riot/Assets/Images.xcassets/Room/Input/upload_icon.imageset/upload_icon@2x.png and b/Riot/Assets/Images.xcassets/Room/Input/upload_icon.imageset/upload_icon@2x.png differ diff --git a/Riot/Assets/Images.xcassets/Room/Input/upload_icon.imageset/upload_icon@3x.png b/Riot/Assets/Images.xcassets/Room/Input/upload_icon.imageset/upload_icon@3x.png index d97f3aa94..4f626f53b 100644 Binary files a/Riot/Assets/Images.xcassets/Room/Input/upload_icon.imageset/upload_icon@3x.png and b/Riot/Assets/Images.xcassets/Room/Input/upload_icon.imageset/upload_icon@3x.png differ diff --git a/Riot/Assets/Images.xcassets/Room/Input/upload_icon_dark.imageset/Contents.json b/Riot/Assets/Images.xcassets/Room/Input/upload_icon_dark.imageset/Contents.json new file mode 100644 index 000000000..c5bb14104 --- /dev/null +++ b/Riot/Assets/Images.xcassets/Room/Input/upload_icon_dark.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "filename" : "upload_icon_dark.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "upload_icon_dark@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "upload_icon_dark@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Riot/Assets/Images.xcassets/Room/Input/upload_icon_dark.imageset/upload_icon_dark.png b/Riot/Assets/Images.xcassets/Room/Input/upload_icon_dark.imageset/upload_icon_dark.png new file mode 100644 index 000000000..cb0316cf9 Binary files /dev/null and b/Riot/Assets/Images.xcassets/Room/Input/upload_icon_dark.imageset/upload_icon_dark.png differ diff --git a/Riot/Assets/Images.xcassets/Room/Input/upload_icon_dark.imageset/upload_icon_dark@2x.png b/Riot/Assets/Images.xcassets/Room/Input/upload_icon_dark.imageset/upload_icon_dark@2x.png new file mode 100644 index 000000000..b58c78907 Binary files /dev/null and b/Riot/Assets/Images.xcassets/Room/Input/upload_icon_dark.imageset/upload_icon_dark@2x.png differ diff --git a/Riot/Assets/Images.xcassets/Room/Input/upload_icon_dark.imageset/upload_icon_dark@3x.png b/Riot/Assets/Images.xcassets/Room/Input/upload_icon_dark.imageset/upload_icon_dark@3x.png new file mode 100644 index 000000000..b04f2a977 Binary files /dev/null and b/Riot/Assets/Images.xcassets/Room/Input/upload_icon_dark.imageset/upload_icon_dark@3x.png differ diff --git a/Riot/Assets/Images.xcassets/Room/Input/video_call.imageset/Contents.json b/Riot/Assets/Images.xcassets/Room/Input/video_call.imageset/Contents.json new file mode 100644 index 000000000..e4b7bd1e0 --- /dev/null +++ b/Riot/Assets/Images.xcassets/Room/Input/video_call.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "filename" : "video_call.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "video_call@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "video_call@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Riot/Assets/Images.xcassets/Room/Input/video_call.imageset/video_call.png b/Riot/Assets/Images.xcassets/Room/Input/video_call.imageset/video_call.png new file mode 100644 index 000000000..309ac3f6f Binary files /dev/null and b/Riot/Assets/Images.xcassets/Room/Input/video_call.imageset/video_call.png differ diff --git a/Riot/Assets/Images.xcassets/Room/Input/video_call.imageset/video_call@2x.png b/Riot/Assets/Images.xcassets/Room/Input/video_call.imageset/video_call@2x.png new file mode 100644 index 000000000..5e4d6597c Binary files /dev/null and b/Riot/Assets/Images.xcassets/Room/Input/video_call.imageset/video_call@2x.png differ diff --git a/Riot/Assets/Images.xcassets/Room/Input/video_call.imageset/video_call@3x.png b/Riot/Assets/Images.xcassets/Room/Input/video_call.imageset/video_call@3x.png new file mode 100644 index 000000000..adaaf0a70 Binary files /dev/null and b/Riot/Assets/Images.xcassets/Room/Input/video_call.imageset/video_call@3x.png differ diff --git a/Riot/Assets/ar.lproj/Vector.strings b/Riot/Assets/ar.lproj/Vector.strings index bdf811f06..389af1cc9 100644 --- a/Riot/Assets/ar.lproj/Vector.strings +++ b/Riot/Assets/ar.lproj/Vector.strings @@ -118,3 +118,14 @@ "store_full_description" = "Element هُوَ نَوعٌ جَديدٌ مِن تَطبيقاتِ المُراسَلَة وَالتَّعَاوُن الَّذي:\n\n1. يَمنَحُكَ التَّحَكُّم فِي لِلحِفاظِ عَلَى خُصُوصِيَّتِك\n2. يُتيحُ لَكَ التَّواصُلُ مَعَ أيّ شَخص عَلَى شَبَكَةِ Matrix ، وَحَتَّى خارِجَهَا مِن خِلال التَّكامُل مَعَ التَّطبيقات مِثل Slack\n3. يَحميكَ مِنَ الإعلانات، التَّنقيبُ عَنِ البَيانات، الأبوابُ الخَلفِيَّة وَعَمَلِيَّاتُ الحَدائِقِ المُسَوَّرَة\n4. يُؤمِنُكَ مِن خِلاِل تَعمِيَةِ النِّهايَة-إلى-النِّهايَة، مَعَ التَّوقيعِ المُتَبادَلِ لِلتَحَقُّقِ مِنَ الآخَرَين\n\nيَختَلِفُ Element تَمامًا عَن تَطبيقاتِ المُراسَلَةِ وَالتَّعاوُن الأُخرَى لأنَّهُ لا مَركَزي وَمَفتُوح المَصدَر.\n\nيُتيحُ لَكَ Element إمكانيةُ الاِستِضافَة الذّاتيَّة -أو اِختيارُ مُضيف- بِحَيث تَتَمَتَّع بِالخُصُوصِيَّة وَالمُلكيَّة وَالتَّحَكُّم فِي بَيانَاتك وَمُحَادَثاتك. يُتيحُ لَكَ الوُصُول إلى شَبَكَة مَفتُوحَة؛ لِذَلِكَ لا يَقتَصِرُ الأمر عَلَى التَّحدُث إلى مُستَخدِمي Element الآخَرين فَقَط. كَما اَنهُ آمِنٌ لِلغايَة.\n\nإنَّ Element قادِرٌ عَلَى إتاحَةِ كُلِ ذَلِك لِأنَهُ يَعمَلُ عَلَى Matrix -مِعيار التَّواصُل المَفتُوح اللَّامَركَزي.\n\nإنَّ Element يَمنَحُكَ زِمَامَ التَّحَكُم مِن خِلال السَّماح لَك بِاختيار مَن يَستَضيفُ المُحادَثاتِ الخَاصَّةِ بِك. مِن تَطبيقِ Element يُمكِنُكَ اِختيار الاِستِضافَة بِطُرُقٍ مُختَلِفَة:\n\n1. الحُصُول عَلَى حِساب مَجانيّ عَلَى الخادِم العَامّ matrix.org\n2. اِستِضافَة حِسابك بِنَفسِك عَن طَريق تَشغيل خادِم عَلَى أجهِزَتِكَ الخَاصَّة\n3. التَّسجيل لِلحُصُولِ عَلَى حِساب عَلَى خادِم مُخصص بمُجرد الاِشتِراك فِي مِنَصَّة اِستِضافَة Element Matrix Services\n\nلِمَاذَا تَختارُ Element؟\n\nتَملَّك بَياناتَك: أنتَ مَن تُقرر أين تَحتَفِظ ببياناتك ورسائلك. أنت تمتلكها وتتحكم فيها، وليس بعض الشركات الكُبرى الإحتكارية التي تُنقِّب عن بياناتك أو تُتيح الوصول إلى أطراف ثالثة.\n\n\nتَراسُل وَتَعَاوُن مَفتُوح يُمكِنُكَ مُحادَثَة أي شَخص آخَر عَلَى شَبَكَة Matrix، سَواءً كانُوا يَستَخدِمُونَ Element أو تَطبيقُ Matrix آخَر، وَحَتّى إذا كانُوا يَستَخدِمُونَ نِظامَ مُراسلةٍ مُختَلِف مِثل Slack أو IRC أو XMPP.\n\nالأمان-الخارِق: تَعميَة حَقيقيَة مِنَ النِّهايَة إلى النِّهايَة (فَقَط أطراف المُحادَثَة مَن يُمكِنَهُم فَكّ تَعميَة الرَّسائِل)، وَالتَّوقِيع المُتَبادَل لِلتحقق من أجهزة المُشاركين في المُحادثة.\n\nالتواصل الكامل: المُراسلة، المُكالمات الصوتية والمرئية، مُشاركة الملفات، مُشاركة الشاشة، مجموعة كاملة وكبيرة من عمليات التكامُل، الروبوتات والأدوات. بناء الغُرف، المُجتمعات، ابق على اتصال وأنجز المهام.\n\nأين ما كُنت: ابق على اتصال أينما كنت مع سجل الرسائل المتزامن بالكامل عبر جميع أجهزتك وفي الويب على https://app.element.io."; // String for App Store "store_short_description" = "مُحادَثَةٌ/VoIP آمنةٌ لَا مَركَزِيَّة"; +"auth_missing_email" = "عُنوان بَريد إلِكتُرونيّ مَفقُود"; +"auth_add_email_phone_message_2" = "عَيِّن بَريدً إلِكتُرونيًّ لِاِستِردَادِ الحِساب. لاحِقًا استخدم رَقم الهَاتِف أو البَريد الإلِكتُرونيّ لِتَكُونَ قابِلاً لِلاِكتِشاف اِختِيَاريًّا مِن قِبَلِ الأشخاصِ الَّذينَ يَعرِفُنَك."; +"auth_add_phone_message_2" = "عَيِّن رَقم هَاتِف لِاِستِردَادِ الحِساب، وَلاحِقًا لِتَكُونَ قابِلاً لِلاِكتِشاف اِختِيَاريًّا مِن قِبَلِ الأشخاصِ الَّذينَ يَعرِفُونَك."; + +// Accessibility +"accessibility_checkbox_label" = "مُرَبَعُ اِختِيارات"; +"callbar_return" = "الرُّجوع"; +"callbar_active_and_multiple_paused" = "مُكالَمَةٌ واحدةٌ نَشِطة (%@) · عَدَد %@ مُكالَمَة مُعلَّقة"; +"callbar_only_multiple_paused" = "عَدَد %@ مُكالَمَة مُعلَّقة"; +"callbar_active_and_single_paused" = "مُكالَمَةٌ واحدةٌ نَشِطة (%@) · مُكالَمَةٌ واحدةٌ مُعلَّقة"; +"callbar_only_single_paused" = "مُكالَمَة مُعلَّقة"; diff --git a/Riot/Assets/de.lproj/Vector.strings b/Riot/Assets/de.lproj/Vector.strings index a2fe5d961..5b4213b72 100644 --- a/Riot/Assets/de.lproj/Vector.strings +++ b/Riot/Assets/de.lproj/Vector.strings @@ -755,7 +755,7 @@ "widget_integrations_server_failed_to_connect" = "Verbindung zum Integrationsserver fehlgeschlagen"; "device_verification_security_advice" = "Für maximale Sicherheit empfehlen wir, dies persönlich zu tun oder ein anderes vertrauenswürdiges Kommunikationsmittel zu verwenden"; "device_verification_incoming_description_1" = "Überprüfe diese Sitzung, um sie als vertrauenswürdig zu markieren. Sitzungen von Partnern zu vertrauen gibt dir zusätzliche Sicherheit bei der Verwendung von Ende-zu-Ende verschlüsselten Nachrichten."; -"device_verification_incoming_description_2" = "Wenn du diese Sitzung verifizierst, wird es sie vertrauenswürdig und für dein Gegenüber als vertrauenswürdig gekennzeichnet."; +"device_verification_incoming_description_2" = "Wenn du diese Sitzung verifizierst, wird sie für dich und für dein Gegenüber als vertrauenswürdig gekennzeichnet."; // MARK: Start "device_verification_start_title" = "Verifizieren durch Vergleichen eines kurzen Textes"; "device_verification_start_wait_partner" = "Warten auf Annahme durch Partner…"; @@ -1058,7 +1058,7 @@ "key_verification_other_session_title" = "Sitzung verifizieren"; "key_verification_new_session_title" = "Neue Sitzung verifizieren"; "key_verification_this_session_title" = "Verifiziere diese Sitzung"; -"device_verification_security_advice_emoji" = "Vergleiche die einzigartigen Emoji und stell sicher, dass sie in derselben Reihenfolge angezeigt werden."; +"device_verification_security_advice_emoji" = "Vergleiche die einzigartigen Emojis und kontrolliere, dass sie in derselben Reihenfolge angezeigt werden."; "device_verification_security_advice_number" = "Vergleiche die Zahlen und stell sicher, dass sie in derselben Reihenfolge angezeigt werden."; "key_verification_self_verify_current_session_alert_title" = "Verifiziere diese Sitzung"; "key_verification_self_verify_current_session_alert_message" = "Andere Benutzer vertrauen ihr vielleicht nicht."; diff --git a/Riot/Assets/en.lproj/Vector.strings b/Riot/Assets/en.lproj/Vector.strings index 016c3195c..6b58da5c1 100644 --- a/Riot/Assets/en.lproj/Vector.strings +++ b/Riot/Assets/en.lproj/Vector.strings @@ -322,6 +322,8 @@ Tap the + to start adding people."; "room_message_placeholder" = "Send a message (unencrypted)…"; "room_message_reply_to_placeholder" = "Send a reply (unencrypted)…"; "room_message_unable_open_link_error_message" = "Unable to open the link."; +"room_message_editing" = "Editing"; +"room_message_replying_to" = "Replying to %@"; "room_do_not_have_permission_to_post" = "You do not have permission to post to this room"; "encrypted_room_message_placeholder" = "Send an encrypted message…"; "encrypted_room_message_reply_to_placeholder" = "Send an encrypted reply…"; @@ -389,6 +391,7 @@ Tap the + to start adding people."; "room_accessibility_integrations" = "Integrations"; "room_accessibility_upload" = "Upload"; "room_accessibility_call" = "Call"; +"room_accessibility_video_call" = "Video Call"; "room_accessibility_hangup" = "Hang up"; "room_place_voice_call" = "Voice call"; "room_open_dialpad" = "Dial pad"; @@ -403,6 +406,8 @@ Tap the + to start adding people."; "external_link_confirmation_title" = "Double-check this link"; "external_link_confirmation_message" = "The link %@ is taking you to another site: %@\n\nAre you sure you want to continue?"; +"room_multiple_typing_notification" = "%@ and others"; + // Unknown devices "unknown_devices_alert_title" = "Room contains unknown sessions"; "unknown_devices_alert" = "This room contains unknown sessions which have not been verified.\nThis means there is no guarantee that the sessions belong to the users they claim to.\nWe recommend you go through the verification process for each session before continuing, but you can resend the message without verifying if you prefer."; @@ -684,6 +689,8 @@ Tap the + to start adding people."; "room_details_title_for_dm" = "Details"; "room_details_people" = "Members"; "room_details_files" = "Uploads"; +"room_details_search" = "Search room"; +"room_details_integrations" = "Integrations"; "room_details_settings" = "Settings"; "room_details_photo" = "Room Photo"; "room_details_photo_for_dm" = "Photo"; diff --git a/Riot/Assets/eo.lproj/Vector.strings b/Riot/Assets/eo.lproj/Vector.strings index 2a7d6d0fd..71c9a87a2 100644 --- a/Riot/Assets/eo.lproj/Vector.strings +++ b/Riot/Assets/eo.lproj/Vector.strings @@ -128,8 +128,8 @@ "room_conference_call_no_power" = "Vi bezonas permeson administri grupvokojn en ĉi tiu ĉambro"; "room_ongoing_conference_call_close" = "Fermi"; "room_ongoing_conference_call" = "Daŭranta grupa voko. Aliĝi kiel %@ aŭ %@."; -"room_unsent_messages_unknown_devices_notification" = "Mesaĝoj ne sendiĝis pro ĉeesto de nekonataj salutaĵoj. Ĉu %@ aŭ %@ nun?"; -"room_unsent_messages_notification" = "Mesaĝoj ne sendiĝis. Ĉu %@ aŭ %@ nun?"; +"room_unsent_messages_unknown_devices_notification" = "Mesaĝoj ne sendiĝis pro ĉeesto de nekonataj salutaĵoj."; +"room_unsent_messages_notification" = "Mesaĝoj ne sendiĝis."; "room_offline_notification" = "Konekto al la servilo perdiĝis."; "room_accessiblity_scroll_to_bottom" = "Moviĝi ĝis planko"; "encrypted_room_message_placeholder" = "Sendi mesaĝon ĉifritan…"; @@ -305,7 +305,7 @@ "auth_untrusted_id_server" = "La identiga servilo ne estas fidata"; "auth_phone_is_required" = "Neniu identiga servilo estas agordita, do vi ne povas agordi telefonnumeron por laŭbezona rehavo de konto."; "auth_email_is_required" = "Neniu identiga servilo estas agordita, do vi ne povas agordi retpoŝtadreson por laŭbezona rehavo de konto."; -"auth_phone_in_use" = "Ĉi tiu telefonnumero jam estas uzata."; +"auth_phone_in_use" = "Ĉi tiu telefonnumero jam estas uzata"; "auth_email_in_use" = "Ĉi tiu retpoŝtadreso jam estas uzata"; "auth_add_email_phone_message_2" = "Agordu retpoŝtadreson por rehavo de konto. Uzu poste retpoŝtadreson aŭ telefonon por esti laŭplaĉe trovebla de personoj, kiuj vin konas."; "auth_add_phone_message_2" = "Agordu telefonon, por via (laŭplaĉa) trovebleco de personoj, kiuj vin konas."; @@ -313,7 +313,7 @@ "joined" = "Aliĝita"; "device_verification_self_verify_wait_recover_secrets_with_passphrase" = "Uzi rehavajn pasfrazon aŭ ŝlosilon"; "device_verification_self_verify_wait_recover_secrets_without_passphrase" = "Uzi rehavan ŝlosilon"; -"device_verification_self_verify_wait_additional_information" = "Ĉi tio funkcias por Element kaj aliaj klientoj kapablaj je delegaj subskriboj"; +"device_verification_self_verify_wait_additional_information" = "Ĉi tio funkcias por Element kaj aliaj klientoj kapablaj je delegaj subskriboj."; "device_verification_self_verify_wait_information" = "Kontrolu ĉi tiun saluton per unu el viaj aliaj salutaĵoj, dononte al ĝi aliron al ĉifritaj mesaĝoj.\n\nUzu la plej freŝan version de Element per viaj aliaj aparatoj:"; "device_verification_self_verify_wait_new_sign_in_title" = "Kontrolu ĉi tiun saluton"; @@ -964,7 +964,7 @@ //"settings_join_leave_rooms" = "When people join or leave rooms"; //"settings_call_invitations" = "Call invitations"; -"settings_enable_callkit" = ""; +"settings_enable_callkit" = "Integraj vokoj"; "settings_ui_theme_picker_title" = "Elektu haŭton"; "settings_ui_theme_black" = "Nigra"; "settings_ui_theme_dark" = "Malluma"; @@ -1075,3 +1075,411 @@ "room_resource_limit_exceeded_message_contact_3" = " por ke vi daŭre uzu la servilon."; "room_resource_limit_exceeded_message_contact_2_link" = "kontaktu vian serviladministranton"; "room_resource_limit_exceeded_message_contact_1" = " Bonvole "; +"room_intro_cell_information_dm_sentence1_part3" = ". "; +"room_intro_cell_information_dm_sentence2" = "Sole la paro de vi ĉeestas ĉi tiu ĉambro, neniu alia povas eniri."; +"room_intro_cell_information_multiple_dm_sentence2" = "Sole vi ĉeestas ĉi tiun ĉambron, krom se oni invitas aliulon."; +"device_verification_security_advice_emoji" = "Komparu la unikajn bildosignojn, certigante, ke ili aperas samorde."; +"key_verification_user_title" = "Kontrolu ĝin"; +"key_verification_this_session_title" = "Kontrolu ĉi tiun salutaĵon"; +"key_verification_new_session_title" = "Kontrolu vian novan salutaĵon"; + +// MARK: - Device Verification +"key_verification_other_session_title" = "Kontrolu salutaĵon"; +"sign_out_key_backup_in_progress_alert_cancel_action" = "Mi atendos"; +"sign_out_key_backup_in_progress_alert_discard_key_backup_action" = "Mi ne volas miajn ĉifritajn mesaĝojn"; +"sign_out_key_backup_in_progress_alert_title" = "Savkopiado progresas. Se vi adiaŭos nun, vi perdos aliron al viaj ĉifritaj mesaĝoj."; +"sign_out_non_existing_key_backup_sign_out_confirmation_alert_backup_action" = "Savkopii"; +"sign_out_non_existing_key_backup_sign_out_confirmation_alert_sign_out_action" = "Adiaŭi"; +"sign_out_non_existing_key_backup_sign_out_confirmation_alert_message" = "Vi perdos aliron al viaj ĉifritaj mesaĝoj, se vi ne savkopios viajn ŝlosilojn antaŭ adiaŭo."; +"sign_out_non_existing_key_backup_sign_out_confirmation_alert_title" = "Vi perdos viajn ĉifritajn mesaĝojn"; +"sign_out_non_existing_key_backup_alert_discard_key_backup_action" = "Mi ne volas miajn ĉifritajn mesaĝojn"; +"sign_out_non_existing_key_backup_alert_setup_secure_backup_action" = "Ekuzi sekuran savkopiadon"; +"sign_out_non_existing_key_backup_alert_title" = "Vi perdos aliron al viaj ĉifritaj mesaĝoj se vi nun adiaŭos"; +"sign_out_existing_key_backup_alert_sign_out_action" = "Adiaŭi"; + +// MARK: Sign out warning + +"sign_out_existing_key_backup_alert_title" = "Ĉu vi certe volas adiaŭi?"; +"key_backup_recover_done_action" = "Finite"; + +// Success + +"key_backup_recover_success_info" = "Savkopio rehaviĝis!"; +"key_backup_recover_from_recovery_key_lost_recovery_key_action" = "Ĉu vi perdis vian rehavan ŝlosilon? Vi povas agordi novan per la Agordoj."; +"key_backup_recover_from_recovery_key_recover_action" = "Malŝlosi historion"; +"key_backup_recover_from_recovery_key_recovery_key_placeholder" = "Enigu rehavan ŝlosilon"; +"key_backup_recover_from_recovery_key_recovery_key_title" = "Enigu"; + +// Recover from recovery key + +"key_backup_recover_from_recovery_key_info" = "Uzu vian rehavan ŝlosilon por malŝlosi vian historion de sekuraj mesaĝoj"; +"large_badge_value_k_format" = "%.1fK"; +"image_picker_action_library" = "Elekti el vidaŭdaĵujo"; +"media_picker_library" = "Vidaŭdaĵujo"; +"room_details_advanced_section" = "Altnivelaj"; +"room_details_banned_users_section" = "Forbaritaj uzantoj"; +"room_details_flair_invalid_id_prompt_msg" = "%@ ne estas valida identigilo por komunumo"; +"room_details_flair_invalid_id_prompt_title" = "Nevalida formo"; +"room_details_new_flair_placeholder" = "Aldoni novan identigilon de komunumo (ekz. +io%@)"; +"room_details_flair_section" = "Montri insignon por komunumoj"; +"room_details_addresses_disable_main_address_prompt_msg" = "Vi havos neniun specifitan ĉefan adreson. La implicita ĉefadreso por ĉi tiu ĉambro elektiĝos hazarde"; +"room_details_access_section_for_dm" = "Kiu rajtas aliri ĉi tion?"; +"room_details_access_section" = "Kiu rajtas aliri ĉi tiun ĉambron?"; +"room_details_direct_chat" = "Rekta babilo"; +"room_details_mute_notifs" = "Silentigi sciigojn"; +"room_details_low_priority_tag" = "Malalta prioritato"; +"room_details_favourite_tag" = "Elstarigita"; +"room_details_topic" = "Temo"; +"room_details_room_name_for_dm" = "Nomo"; +"room_details_room_name" = "Nomo de ĉambro"; +"room_details_photo_for_dm" = "Bildo"; +"room_details_photo" = "Bildo de ĉambro"; +"room_details_settings" = "Agordoj"; +"room_details_files" = "Alŝutoj"; +"room_details_people" = "Ĉambranoj"; +"room_details_title_for_dm" = "Detaloj"; + + +// Room Details +"room_details_title" = "Detaloj pri ĉambro"; +"identity_server_settings_alert_error_invalid_identity_server" = "%@ ne estas valida identiga servilo."; +"identity_server_settings_alert_error_terms_not_accepted" = "Vi devas akcepti la uzokondiĉojn de %@, por uzi ĝin kiel identiga servilo."; +"identity_server_settings_alert_disconnect" = "Ĉu malkonektu de la identiga servilo %@?"; +"identity_server_settings_alert_disconnect_title" = "Malkonektu identigan servilon"; +"identity_server_settings_alert_change" = "Ĉu malkonektu de identiga servilo %1$@ kaj anstataŭe konektu al %@$A?"; +"identity_server_settings_alert_change_title" = "Ŝanĝu identigan servilon"; +"identity_server_settings_alert_no_terms" = "Via elektita identiga servilo ne havas uzokondiĉojn. Daŭrigu nur se vi fidas la posedanton de tiu servilo."; +"identity_server_settings_alert_no_terms_title" = "Identiga servilo ne havas uzokondiĉojn"; +"identity_server_settings_disconnect" = "Malkonekti"; +"identity_server_settings_disconnect_info" = "Malkonektinte de via identiga servilo, vi ne troviĝus al aliaj uzantoj per, nek povos inviti uzantojn per, retpoŝtadresoj aŭ telefonnumeroj."; +"identity_server_settings_change" = "Ŝanĝi"; +"identity_server_settings_add" = "Aldoni"; +"identity_server_settings_place_holder" = "Enigu identigan servilon"; +"identity_server_settings_no_is_description" = "Vi nun ne uzas identigan servilon. Por troviĝi de kaj trovi jamajn kontaktojn, aldonu tian servilon ĉi-supre."; +"identity_server_settings_description" = "Vi uzas %@ por trovi kaj troviĝi de jamaj kontaktoj, kiujn vi konas."; + +// Identity server settings +"identity_server_settings_title" = "Identiga Servilo"; + +// AuthenticatedSessionViewControllerFactory +"authenticated_session_flow_not_supported" = "Ĉi tiu programo ne subtenas la identigan metodon de via hejmservilo."; +"manage_session_sign_out" = "Adiaŭi al ĉi tiu salutaĵo"; +"manage_session_not_trusted" = "Nefidataj"; +"manage_session_trusted" = "Fidataj de vi"; +"manage_session_name" = "Nomo de salutaĵo"; +"manage_session_info" = "INFORMOJ DE SALUTAĴO"; + +// Manage session +"manage_session_title" = "Administru salutaĵon"; +"security_settings_user_password_description" = "Konfirmi vian identon per enigo de via pasvorto"; +"security_settings_coming_soon" = "Bedaŭron. Ĉi tiu funkcio ne jam estas subtenata por Element iOS. Bonvolu uzi alian Matrix klienton por agordi ĝin. Element iOS sekvos ĝin."; +"security_settings_complete_security_alert_message" = "Vi kompletigu sekurigon je ĉi tiu salutaĵo unue."; +"security_settings_complete_security_alert_title" = "Kompletigi sekurigon"; +"security_settings_blacklist_unverified_devices_description" = "Kontroli ĉiujn salutaĵojn de uzanto, por marki ilin fidata kaj sendi mesaĝojn al ĝi."; +"security_settings_blacklist_unverified_devices" = "Neniam sendi mesaĝojn al nefidatajn salutaĵojn"; +"security_settings_advanced" = "ALTNIVELAJ"; +"security_settings_export_keys_manually" = "Elporti ŝlosilojn permane"; +"security_settings_cryptography" = "KRIPTOGRAFIO"; +"security_settings_crosssigning_complete_security" = "Kompletigi sekurigon"; +"security_settings_crosssigning_reset" = "Restarigi delegan subskribadon"; +"security_settings_crosssigning_bootstrap" = "Praŝargi delegan subskribadon"; +"security_settings_crosssigning_info_ok" = "Delega subskribado estas ŝaltita."; +"security_settings_crosssigning_info_trusted" = "Delega subskribado estas ŝaltita. Vi povas fidi aliajn uzantojn kaj viajn salutaĵojn per delegaj subskriboj, sed vi ne povas delege subskribi de ĉi tiu salutaĵo, ĉar mankas al ĝi ŝlosiloj privataj por delegaj subskriboj."; +"security_settings_crosssigning_info_exists" = "Via konto havas identon de delega subskribado, sed ne jam estas fidata de ĉi tiu salutaĵo. Kompletigu la sekurigon de ĉi tiu salutaĵo."; +"security_settings_crosssigning_info_not_bootstrapped" = "Delega subskribado ne jam agordiĝis."; +"security_settings_crosssigning" = "DELEGAJ SUBSKRIBOJ"; +"security_settings_backup" = "SAVKOPIADO DE MESAĜOJ"; +"security_settings_secure_backup_delete" = "Forigi"; +"security_settings_secure_backup_synchronise" = "Speguli"; +"security_settings_secure_backup_setup" = "Agordi"; +"security_settings_secure_backup_description" = "Malhelpu perdon de aliro al ĉifritaj mesaĝoj kaj datumoj per savkopiado de ĉifraj ŝlosiloj al via servilo."; +"security_settings_secure_backup" = "SAVKOPIADO"; +"security_settings_crypto_sessions_description_2" = "Se vi ne konas salutaĵojn, ŝanĝu vian pasvorton kaj restarigu savkopiadon."; +"security_settings_crypto_sessions_loading" = "Enlegante salutaĵojn…"; +"security_settings_crypto_sessions" = "MIAJ SALUTAĴOJ"; + +// Security settings +"security_settings_title" = "Sekureco"; +"settings_show_NSFW_public_rooms" = "Montru publikajn ĉambrojn konsternajn"; +"settings_identity_server_no_is_description" = "Vi uzas neniun identigan servilon. Por trovi kaj troviĝi de jamaj kontaktoj, kiujn vi konas, aldonu servilon supre."; +"settings_identity_server_no_is" = "Neniu identiga servilo estas agordita"; +"settings_identity_server_description" = "Uzante la identigan servilon supre agorditan, vi povas trovi kaj troviĝi de jamaj kontaktoj, kiujn vi konas."; +"settings_discovery_three_pid_details_enter_sms_code_action" = "Enigu SMS aktivigan kodon"; +"settings_discovery_three_pid_details_cancel_email_validation_action" = "Nuligi retpoŝtan kontrolon"; +"settings_discovery_three_pid_details_revoke_action" = "Senvalidigi"; +"settings_discovery_three_pid_details_share_action" = "Kunhavigi"; +"settings_discovery_three_pid_details_information_phone_number" = "Agordu ĉi tiun telefonnumeron, per kiu aliaj uzantoj povas trovi kaj inviti vin al ĉambroj. Aldonu aŭ forigu telefonnumerojn ĉe Kontoj."; +"settings_discovery_three_pid_details_title_phone_number" = "Agordu telefonnumeron"; +"settings_discovery_three_pid_details_information_email" = "Agordu ĉi tiun retpoŝtadreson, per kiu aliaj uzantoj povas trovi kaj inviti vin al ĉambroj. Aldonu aŭ forigu retpoŝtadresojn ĉe Kontoj."; +"settings_discovery_three_pid_details_title_email" = "Agordu retpoŝtadreson"; +"settings_discovery_error_message" = "Eraris iel. Bonvolu reprovi."; +"settings_discovery_three_pids_management_information_part3" = "."; +"settings_discovery_three_pids_management_information_part2" = "Agordoj de uzanto"; +"settings_discovery_three_pids_management_information_part1" = "Agordu la retpoŝtadresojn kaj telefonnumerojn per kiuj aliaj uzantoj povas trovi kaj inviti vin al ĉambroj. Aldonu aŭ forigu retpoŝtadresojn aŭ telefonnumerojn de ĉi tiu listo en "; +"settings_discovery_terms_not_signed" = "Konsentu la uzokondiĉojn de la identiga servilo (%@) por trovebligi vin per retpoŝtadreso aŭ telefonnumero."; +"settings_discovery_no_identity_server" = "Vi nun ne uzas identigan servilon. Por trovi kaj troviĝi de jamaj kontaktoj, kiujn vi konas, aldonu servilon."; +"settings_devices_description" = "Prezenta nomo de salutaĵo videblas al personoj kunbabilataj"; +"settings_key_backup_delete_confirmation_prompt_msg" = "Ĉu vi certas? Vi perdos aliron al viaj ĉifritaj mesaĝoj, se vi ne savkopios viajn ŝlosilojn."; +"settings_key_backup_delete_confirmation_prompt_title" = "Forigi savkopion"; +"settings_key_backup_button_connect" = "Konekti ĉi tiu salutaĵo savkopionte"; +"settings_key_backup_button_delete" = "Forigi savkopion"; +"settings_key_backup_button_restore" = "Rehavigi per savkopio"; +"settings_key_backup_button_create" = "Komenci savkopiojn"; +"settings_key_backup_info_trust_signature_invalid_device_unverified" = "Savkopio havas nevalidan subskribon de %@"; +"settings_key_backup_info_trust_signature_invalid_device_verified" = "Savkopio havas nevalidan subskribon de %@"; +"settings_key_backup_info_trust_signature_valid_device_unverified" = "Savkopio havas subskribon de %@"; +"settings_key_backup_info_trust_signature_valid_device_verified" = "Savkopio havas validan subskribon de %@"; +"settings_key_backup_info_trust_signature_valid" = "Savkopio havas validan subskribon de ĉi salutaĵo"; +"settings_key_backup_info_trust_signature_unknown" = "Savkopio havas subskribon de salutaĵo kun la identigilo: %@"; +"settings_key_backup_info_progress_done" = "Ĉiuj ŝlosiloj estas savkopiitaj"; +"settings_key_backup_info_progress" = "Savkopiante %@ ŝlosilojn…"; +"settings_key_backup_info_not_valid" = "Ĉi tiu salutaĵo ne savkopias viajn ŝlosilojn, sed vi havas restantan kopion kiun vi povas alreveni kaj uzi ekde nun."; +"settings_key_backup_info_valid" = "Ĉi tiu salutaĵo savkopias viajn ŝlosilojn."; +"settings_key_backup_info_algorithm" = "Algoritmo: %@"; +"settings_key_backup_info_version" = "Savkopia Versio: %@"; +"settings_key_backup_info_signout_warning" = "Konektu ĉi tiun salutaĵon savkopie antaŭ ol vi adiaŭas, por eviti la perdiĝon de ŝlosiloj nurlokaj."; +"settings_key_backup_info_none" = "Viaj ŝlosiloj estas nesavkopiataj por ĉi tiu salutaĵo."; +"settings_key_backup_info_checking" = "Kontrolante…"; +"settings_key_backup_info" = "Mesaĝoj en ĉifritaj ĉambroj estas sekurigitaj per tutvoja ĉifrado. Nur vi kaj la adresato(j) havas la ŝlosilojn por malĉifri tiujn ĉi mesaĝojn."; +"settings_deactivate_my_account" = "Malŝalti mian konton"; +"settings_crypto_blacklist_unverified_devices" = "Ĉifri nur al kontrolitaj salutaĵoj"; +"settings_crypto_export" = "Elporti ŝlosilojn"; +"settings_crypto_device_key" = "\nŜlosilo de salutaĵo:\n"; +"settings_crypto_device_id" = "\nIdentigilo de salutaĵo: "; +"settings_crypto_device_name" = "Nomo de salutaĵo: "; +"settings_add_3pid_invalid_password_message" = "Nevalida pasvorto"; +"settings_add_3pid_password_message" = "Por daŭrigi, bonvolu enigi vian pasvorton"; +"settings_add_3pid_password_title_msidsn" = "Aldoni telefonnumeron"; +"settings_add_3pid_password_title_email" = "Aldoni retpoŝtadreson"; +"settings_password_updated" = "Via pasvorto ĝisdatiĝis"; +"settings_fail_to_update_password" = "Eraris ĝisdatigante pasvorton"; +"settings_confirm_password" = "Konfirmu pasvorton"; +"settings_global_settings_info" = "Ĉieaj sciigoj uzeblas per via retkliento %@"; +"settings_fail_to_update_profile" = "Eraris ĝisdatigante profilon"; +"room_open_dialpad" = "Ciferilo"; +"room_place_voice_call" = "Voĉvoko"; +"room_event_action_delete_confirmation_message" = "Ĉu vi certas, ke vi volas forigi ĉi tiun nesenditan mesaĝon?"; +"room_event_action_delete_confirmation_title" = "Forigi nesenditan mesaĝon"; +"room_ongoing_conference_call_with_close" = "Daŭranta grupa voko. Aliĝi kiel %@ aŭ %@. %@."; +"room_unsent_messages_cancel_message" = "Ĉu vi certas, ke vi volas forigi ĉiujn nesenditajn mesaĝojn en ĉi tiu ĉambro?"; +"room_unsent_messages_cancel_title" = "Forigi nesenditajn mesaĝojn"; +"room_member_power_level_short_moderator" = "Reg"; +"room_participants_security_loading" = "Enlegante…"; +"room_participants_action_security_status_loading" = "Enlegante…"; +"room_participants_filter_room_members_for_dm" = "Filtri ĉambranojn"; +"room_participants_add_participant" = "Aldoni partoprenanton"; +"contacts_user_directory_offline_section" = "KATALOGO DE UZANTOJ (nefunkcia)"; +"contacts_address_book_permission_denied" = "Vi ne permesis al Element aliri viajn lokajn kontaktojn"; +"rooms_empty_view_information" = "Ĉambroj taŭgas por ajna grupbabilo, privata aŭ publika. Premu la + por trovi ekzistantaj ĉambroj, aŭ fari novajn."; +"rooms_empty_view_title" = "Ĉambroj"; +"people_empty_view_information" = "Sekure babili kun iu ajn.Premu la + por inviti personojn."; +"people_empty_view_title" = "Homoj"; +"social_login_button_title_sign_up" = "Registriĝi per %@"; +"social_login_button_title_sign_in" = "Saluti per %@"; +"social_login_button_title_continue" = "Saluti per %@"; +"social_login_list_title_sign_up" = "Aŭ registriĝi per"; +"social_login_list_title_sign_in" = "Aŭ saluti per"; + +// Social login + +"social_login_list_title_continue" = "Saluti per"; +"callbar_return" = "Reveni"; +"callbar_only_multiple_paused" = "%@ paŭzigitaj vokoj"; +"callbar_only_single_paused" = "Paŭzigita voko"; +"callbar_active_and_multiple_paused" = "1 aktiva voko (%@) · %@ paŭzigitaj vokoj"; +"callbar_active_and_single_paused" = "1 aktiva voko (%@) · 1 paŭzigita voko"; + +// Call Bar +"callbar_only_single_active" = "Aktiva voko (%@)"; +"less" = "Malpli"; +"more" = "Pli"; +"switch" = "Baskuli"; +"store_promotional_text" = "Privatecgardanta babila kaj kunlabora programo, uzanta malferman reton. Ĝi estas malcentra, do vi estas la stiranto. Neniu datumamasanto nek kaŝenirejo."; +"room_intro_cell_information_room_with_topic_sentence2" = "Temo: %@"; +"room_intro_cell_information_room_sentence1_part3" = ". "; +"room_intro_cell_information_room_sentence1_part1" = "Ĉi tio estas la komenco de "; + +// Mark: - Room creation introduction cell + +"room_intro_cell_add_participants_action" = "Aldoni personojn"; +"room_avatar_view_accessibility_hint" = "Ŝanĝi bildon de ĉambro"; + +// Mark: - Room avatar view + +"room_avatar_view_accessibility_label" = "bildo de ĉambro"; +"invite_friends_share_text" = "Saluton! Parolu kun mi per %@: %@"; + +// MARK: - Invite friends + +"invite_friends_action" = "Inviti amikojn al %@"; +"favourites_empty_view_information" = "Vi povas elstarigi kelkmaniere – plej rapide estas simple premi kaj teni. Tuŝetu la stelon por aperigi la elstarigaton ĉi tie."; + +// MARK: - Favourites + +"favourites_empty_view_title" = "Elstarigitaj ĉambroj kaj personoj"; +"home_empty_view_information" = "La ĉionhava sekura babililo por skipoj, amikoj, kaj organizaĵoj. Tuŝetu la ĉi-suban butonon + por aldoni personojn kaj ĉambrojn."; + +// MARK: - Home + +"home_empty_view_title" = "bonvenu al %@,\n%@"; +"call_transfer_error_title" = "Eraro"; +"call_transfer_contacts_all" = "Ĉiuj"; +"call_transfer_contacts_recent" = "Freŝdataj"; +"call_transfer_users" = "Uzantoj"; +"pin_protection_settings_change_pin" = "Ŝanĝi personan identigan numeron"; +"pin_protection_confirm_pin_to_change" = "Konfirmu personan identigan numeron por ĝin ŝanĝi"; +"key_verification_bootstrap_not_setup_message" = "Vi devas praŝargi delegan subskribadon unue."; +"error_not_supported_on_mobile" = "Vi ne povas tion fari per %@ je portebla aparato."; + +// Unverified sessions + +"key_verification_self_verify_unverified_sessions_alert_title" = "Kontrolu, kie vi salutis"; +"device_verification_incoming_description_1" = "Kontrolu ĉi tiun salutaĵon por marki ĝin fidata. Fidi salutaĵojn de kunuloj helpos vin resti pli trankvila kiam vi sendas tutvoje ĉifritajn mesaĝojn."; + +// Mark: Incoming +"device_verification_incoming_title" = "Envena kontrolpeto"; +"device_verification_error_cannot_load_device" = "Ne povas enlegi informojn pri salutaĵo."; +"device_verification_cancelled_by_me" = "La kontrolo nuliĝis. Kialo: %@"; +"device_verification_cancelled" = "La aliulo nuligis la kontrolon."; +"device_verification_security_advice_number" = "Komparu la numerojn, certigante, ke ili aperas samorde."; +"key_backup_recover_from_passphrase_lost_passphrase_action_part3" = "."; +"key_backup_recover_from_passphrase_lost_passphrase_action_part2" = "uzi vian rehavan ŝlosilon"; +"key_backup_recover_from_passphrase_lost_passphrase_action_part1" = "Ĉu vi ne konas vian rehavan pasfrazon? Vi povas "; +"key_backup_recover_from_passphrase_recover_action" = "Malŝlosi historion"; +"key_backup_recover_from_passphrase_passphrase_placeholder" = "Enigu pasfrazon"; +"key_backup_recover_from_passphrase_passphrase_title" = "Enigu"; + +// Recover from passphrase + +"key_backup_recover_from_passphrase_info" = "Uzu vian rehavan pasfrazon por malŝlosi vian historion de sekuraj mesaĝoj"; + +// Recover from private key +"key_backup_recover_from_private_key_info" = "Rehavante savkopion…"; +"key_backup_recover_invalid_recovery_key" = "Ne povis malĉifri savkopion per tiu ĉi ŝlosilo: bonvolu kontroli, ke vi enigis la ĝustan rehavan ŝlosilon."; +"key_backup_recover_invalid_recovery_key_title" = "Malakordo de rehava ŝlosilo"; +"key_backup_recover_invalid_passphrase" = "Ne povis malĉifri savkopion per ĉi tiu pasfrazo: bonvolu kontroli, ĉu vi ĝuste enigis la rehavan pasfrazon."; +"key_backup_recover_invalid_passphrase_title" = "Malĝusta rehava pasfrazo"; + +// MARK: Key backup recover + +"key_backup_recover_title" = "Sekuraj mesaĝoj"; +"key_backup_setup_success_from_recovery_key_made_copy_action" = "Mi faris kopion"; +"key_backup_setup_success_from_recovery_key_make_copy_action" = "Fari kopion"; +"key_backup_setup_success_from_recovery_key_recovery_key_title" = "Rehava ŝlosilo"; + +// Success from recovery key +"key_backup_setup_success_from_recovery_key_info" = "Viaj ŝlosiloj estas savkopiataj.\n\nFaru kopion de tiu ĉi rehava ŝlosilo kaj bone ĝin sekurigu."; +"key_backup_setup_success_from_passphrase_done_action" = "Finite"; +"key_backup_setup_success_from_passphrase_save_recovery_key_action" = "Konservi rehavan ŝlosilon"; + +// Success from passphrase +"key_backup_setup_success_from_passphrase_info" = "Viaj ŝlosiloj estas savkopiataj.\n\nVia rehava ŝlosilo estas formo de asekuro – vi povas ĝin uzi por rehavi aliron al viaj ĉifritaj mesaĝoj, se vi forgesos vian pasfrazon.\n\nTenu vian rehavan ŝlosilon en loko tre sekura, kiel ekzemple administrilo de pasvortoj (aŭ sekurkesto)."; + +// Success + +"key_backup_setup_success_title" = "Sukceso!"; +"key_backup_setup_passphrase_setup_recovery_key_action" = "(Altnivele) Agordi per rehava ŝlosilo"; +"key_backup_setup_passphrase_setup_recovery_key_info" = "Aŭ sekurigu vian savkopion per rehava ŝlosilo, konservante ĝin en sekura loko."; +"key_backup_setup_passphrase_set_passphrase_action" = "Agordi pasfrazon"; +"key_backup_setup_passphrase_confirm_passphrase_invalid" = "Pasfrazoj ne akordas"; +"key_backup_setup_passphrase_confirm_passphrase_valid" = "Bonege!"; +"key_backup_setup_passphrase_confirm_passphrase_placeholder" = "Konfirmu pasfrazon"; +"key_backup_setup_passphrase_confirm_passphrase_title" = "Konfirmi"; +"key_backup_setup_passphrase_passphrase_invalid" = "Provu aldoni vorton"; +"key_backup_setup_passphrase_passphrase_title" = "Enigi"; + +// Passphrase + +"key_backup_setup_passphrase_title" = "Sekurigu vian savkopion per pasfrazo"; +"key_backup_setup_intro_manual_export_info" = "(Altnivele)"; +"key_backup_setup_intro_setup_connect_action_with_existing_backup" = "Konektu ĉi tiun aparaton al savkopio de ŝlosiloj"; +"key_backup_setup_intro_setup_action_without_existing_backup" = "Ekuzu savkopiadon de ŝlosiloj"; + + +// MARK: Key backup setup + +"key_backup_setup_title" = "Savkopiado de ŝlosiloj"; + +// MARK: Secure backup setup + +// Intro + +"secure_key_backup_setup_intro_title" = "Sekura savkopiado"; +"room_widget_permission_information_title" = "Uzado povas havigi datumojn al %@:\n"; +"room_widget_permission_webview_information_title" = "Uzado povas meti kuketojn kaj havigi datumojn al %@:\n"; +"bug_report_background_mode" = "Daŭrigi fone"; +"e2e_key_backup_wrong_version" = "Nova savkopio de ŝlosiloj de sekuraj mesaĝoj troviĝis.\n\nSe vi ne faris ĝin, agordu novan pasfrazon per la Agordoj."; + +// Key backup wrong version +"e2e_key_backup_wrong_version_title" = "Nova savkopio de ŝlosiloj"; +"call_actions_unhold" = "Daŭrigi"; +"photo_library_access_not_granted" = "%@ ne havas permeson aliri la fotujon; bonvolu ŝanĝi agordojn de privateco"; +"event_formatter_call_back" = "Revoki"; +"event_formatter_call_you_declined" = "Vi rifuzis ĉi tiun vokon"; +"event_formatter_call_you_currently_in" = "Vi nun partoprenas ĉi tiun vokon"; +"event_formatter_call_has_ended" = "Ĉi tiu voko finiĝis"; +"event_formatter_call_video" = "Vidvoko"; +"event_formatter_call_voice" = "Voĉvoko"; + +// Media picker +"media_picker_title" = "Vidaŭdaĵoj"; +"room_details_set_main_address" = "Agordi kiel ĉefadreson"; +"room_details_save_changes_prompt" = "Ĉu vi volas konservi ŝanĝojn?"; +"room_details_fail_to_enable_encryption" = "Malsukcesis ŝalti ĉifradon en ĉi tiu ĉambro"; +"room_details_fail_to_update_room_direct" = "Malsukcesis ĝisdatigi la rektan markon de ĉi tiu ĉambro"; +"room_details_fail_to_update_room_communities" = "Malsukcesis ĝisdatigi la ritalajn komunumojn"; +"room_details_fail_to_update_room_canonical_alias" = "Malsukcesis ĝisdatigi la ĉefadreson"; +"room_details_fail_to_remove_room_aliases" = "Malsukcesis forigi la adresojn de ĉambro"; +"room_details_fail_to_add_room_aliases" = "Malsukcesis aldoni novajn adresojn de ĉambro"; +"room_details_fail_to_update_history_visibility" = "Malsukcesis ĝisdatigi videblecon de la historio"; +"room_details_fail_to_update_room_directory_visibility" = "Malsukcesis ĝisdatigi videblecon en la katalogo de ĉambroj"; +"room_details_fail_to_update_room_join_rule" = "Malsukcesis ĝisdatigi la regulon pri aliĝoj"; +"room_details_fail_to_update_room_guest_access" = "Malsukcesis ĝisdatigi aliron de gastoj al la ĉambro"; +"room_details_fail_to_update_topic" = "Malsukcesis ĝisdatigi la temon"; +"room_details_fail_to_update_room_name" = "Malsukcesis ĝisdatigi la nomon de ĉambro"; +"room_details_fail_to_update_avatar" = "Malsukcesis ĝisdatigi la bildon de ĉambro"; +"room_details_advanced_e2e_encryption_blacklist_unverified_devices" = "Ĉifri nur por kontrolitaj salutaĵoj"; +"room_details_advanced_e2e_encryption_disabled_for_dm" = "Ĉifrado ne estas ŝaltita ĉi tie."; +"room_details_advanced_e2e_encryption_disabled" = "Ĉifrado ne estas ŝaltita en ĉi tiu ĉambro."; +"room_details_advanced_e2e_encryption_enabled_for_dm" = "Ĉifrado estas ŝaltita ĉi tie"; +"room_details_advanced_e2e_encryption_enabled" = "Ĉifrado estas ŝaltita en ĉi tiu ĉambro"; +"room_details_advanced_enable_e2e_encryption" = "Ŝalti ĉifradon (averto: ne eblas malŝalti!)"; +"room_details_advanced_room_id_for_dm" = "Identigilo:"; +"room_details_advanced_room_id" = "Identigilo de ĉambro:"; +"identity_server_settings_alert_disconnect_still_sharing_3pid" = "Vi ankoraŭ kunhavigas personajn informojn per la identiga servilo %@.\n\nNi rekomendas, ke vi forigu viajn retpoŝtadresojn kaj telefonnumerojn de la identiga servilo, antaŭ malkonekto."; +"identity_server_settings_alert_disconnect_still_sharing_3pid_button" = "Malkonekti malgraŭe"; +"identity_server_settings_alert_disconnect_button" = "Malkonekti"; +"room_details_addresses_disable_main_address_prompt_title" = "Averto je la ĉefadreso"; +"room_details_addresses_invalid_address_prompt_msg" = "%@ ne estas valida formo por kromnomo"; +"room_details_addresses_invalid_address_prompt_title" = "Nevalido formo de kromnomo"; +"room_details_new_address_placeholder" = "Aldoni novan adreson (ekz. #io%@)"; +"room_details_new_address" = "Aldoni novan adreson"; +"room_details_no_local_addresses_for_dm" = "Ĉi tio ne havas lokajn adresojn"; +"room_details_no_local_addresses" = "Ĉi tiu ĉambro ne havas lokajn adresojn"; +"room_details_addresses_section" = "Adresoj"; +"room_details_history_section_prompt_msg" = "Ŝanĝoj al legebleco de historio nur aplikiĝos al venontaj mesaĝoj en ĉi tiu ĉambro. La videbleco de jama historio restos senŝanĝe."; +"room_details_history_section_prompt_title" = "Averto de privateco"; +"room_details_history_section_members_only_since_joined" = "Nur ĉambranoj (ekde sia aliĝo)"; +"room_details_history_section_members_only_since_invited" = "Nur ĉambranoj (ekde sia invitiĝo)"; +"room_details_history_section_members_only" = "Nur ĉambranoj (ekde ĉi tiu elekto)"; +"room_details_history_section_anyone" = "Ĉiu ajn"; +"room_details_history_section" = "Kiu rajtas legi historion?"; +"room_details_access_section_directory_toggle_for_dm" = "Listigi en katalogo de ĉambroj"; +"room_details_access_section_directory_toggle" = "Listigi ĉi tiun ĉambron en la katalogo de ĉambroj"; +"room_details_access_section_no_address_warning" = "Por ligi al ĉambro, ĝi bezonas adreson"; +"room_details_access_section_anyone_for_dm" = "Ĉiu, kiu konas la ligilon, inkluzive gastojn"; +"room_details_access_section_anyone" = "Ĉiu, kiu konas la ligilon de la ĉambro, inkluzive gastojn"; +"room_details_access_section_anyone_apart_from_guest_for_dm" = "Ĉiu, kiu konas la ligilon, krom gastoj"; +"room_details_access_section_anyone_apart_from_guest" = "Ĉiu, kiu konas la ligilon de la ĉambro, krom gastoj"; +"room_details_access_section_invited_only" = "Nur personoj, kiuj invitiĝis"; +"store_full_description" = "Element estas nova speco de mesaĝilo kaj kunlabora aplikaĵo, kiu:\n\n1. Stirigas vin por konservi vian privatecon\n2. Lasas vin komuniki kun ĉiu en la reto de Matrix, kaj eĉ ekstere, per kuniĝo kun aliaj aplikaĵoj, kiel ekzemple Slack\n3. Protektas vin de reklamoj, datumkolektado, kaŝenirejoj, kaj muritaj ĝardenoj\n4. Sekurigas vin per tutvoja ĉifrado, kun delegaj subskriboj por kontroli aliulojn\n\nElement tute malsamas de aliaj mesaĝiloj kaj kunlaboriloj, ĉar ĝi estas federa kaj malfermitkoda.\n\nElement lasas vin gastigi vin mem – aŭ elekti alian gastiganton – por ke vi havu privatecon, regon kaj kontrolon de viaj datumoj kaj interparoloj. Ĝi donas al vi aliron al malfermita reto, por ke via komunikado ne limiĝu al nur aliaj uzantoj de Element. Kaj ĝi estas tre sekura.\n\nElement povas fari ĉi ĉion, ĉar ĝi funkcias per Matrix – publika normo por malfermita, sencentra komunikado. \n\n\nElement lasas vi elekti, kiu gastigos viajn interparolojn. Per la aplikaĵo Element, vi povas elekti diversajn specojn de gastigado:\n\n1. Akiri senpagan konton ĉe la publika servilo matrix.org\n2. Memgastiĝi per via propra servilo ĉe via propra aparato\n3. Registriĝi ĉe propra servilo per simpla pagaliĝo al la gastiga platformo «Element Matrix Services»\n\nKial Element?\n\nPOSEDU VIAJN DATUMOJN: Vi decidu, kie vi tenu viajn datumojn kaj mesaĝojn. Vi posedas kaj regas ilin, ne iu granda komerca firmao, kiu kolektas viajn datumojn aŭ donas aliron al aliuloj.\n\nMALFERMAJ MESAĜADO KAJ KUNLABORADO: Vi povas babili kun ĉiu alia en la reto de Matrix, ĉu ĝi uzas Elementon aŭ alian aplikaĵon de Matrix, kaj eĉ se ĝi uzas tute alian mesaĝilon, kiel ekzemple Slack, IRC, aŭ XMPP.\n\nTRE SEKURA: Vera tutvoja ĉifrado (nur la interparolantoj povas malĉifri siajn mesaĝojn), kaj delegaj subskriboj por kontroli la aparatojn de partoprenantoj.\n\nSENMANKA KOMUNIKADO: Mesaĝoj, voĉvokoj kaj vidvokoj, havigado de dosieroj, ekrano, kaj multaj diversaj kunigoj, robotoj kaj fenestraĵoj. Kreu ĉambrojn, komunumojn, komuniku kaj kunlaboru.\n\nĈIE KUN VI: Tenu vin ĝisdata per historio de mesaĝoj plene spegulita trans ĉiuj viaj aparatoj, kaj sur la reto per https://app.element.io."; +"directory_server_all_native_rooms" = "Ĉiuj ĉambroj, propraj al Matrix"; +"call_transfer_error_message" = "Malsukcesis transdono de voko"; + +// MARK: - Call Transfer +"call_transfer_title" = "Transdono"; +"room_intro_cell_information_dm_sentence1_part1" = "Ĉi tie komencas historio de viaj rektaj mesaĝoj kun "; +"call_transfer_dialpad" = "Ciferplato"; + +// MARK: - Dial Pad +"dialpad_title" = "Ciferplato"; diff --git a/Riot/Assets/et.lproj/Vector.strings b/Riot/Assets/et.lproj/Vector.strings index f150ffbcf..e97d22d99 100644 --- a/Riot/Assets/et.lproj/Vector.strings +++ b/Riot/Assets/et.lproj/Vector.strings @@ -559,7 +559,7 @@ "identity_server_settings_place_holder" = "Sisesta isikutuvastusserver"; "identity_server_settings_add" = "Lisa"; "identity_server_settings_change" = "Muuda"; -"identity_server_settings_disconnect_info" = "Isikutuvastusserveri kasutamise lõpetamine tähendab, et sa ei ole leitav teiste kasutajate poolt ega sulle ei saa telefoninumbri või e-posti aadressi alusel kutset saata. Küll aga saab kutset saata Matrix'i kasutajatunnuse alusel."; +"identity_server_settings_disconnect_info" = "Isikutuvastusserveri kasutamise lõpetamine tähendab, et sa ei ole leitav nende kasutajate poolt, kes tahavad sulle telefoninumbri või e-posti aadressi alusel kutset saata. Küll aga saab alati kutset saata Matrix'i kasutajatunnuse alusel."; "room_details_history_section_prompt_msg" = "Kui muudad seda, kes saavad selle jututoa ajalugu lugeda, siis kehtib see vaid tulevaste sõnumite kohta. Senise ajaloo nähtavus sellega ei muutu."; // Read Receipts "read_receipts_list" = "Lugemisteatiste loend"; diff --git a/Riot/Assets/fa.lproj/Vector.strings b/Riot/Assets/fa.lproj/Vector.strings new file mode 100644 index 000000000..8b1378917 --- /dev/null +++ b/Riot/Assets/fa.lproj/Vector.strings @@ -0,0 +1 @@ + diff --git a/Riot/Assets/nl.lproj/Vector.strings b/Riot/Assets/nl.lproj/Vector.strings index 53182a22a..339fc17a5 100644 --- a/Riot/Assets/nl.lproj/Vector.strings +++ b/Riot/Assets/nl.lproj/Vector.strings @@ -1184,7 +1184,7 @@ "device_verification_self_verify_wait_recover_secrets_without_passphrase" = "Herstelsleutel gebruiken"; "device_verification_self_verify_wait_additional_information" = "Dit werkt met Element en andere Matrix-apps die kruislings ondertekenen ondersteunen."; "device_verification_self_verify_wait_information" = "Verifieer deze sessie vanaf een van uw andere sessies, om toegang te krijgen tot de versleutelde berichten.\n\nGebruik de laatste versie van Element op uw andere apparaten:"; -"device_verification_self_verify_wait_new_sign_in_title" = "Verifieer deze aanmelding"; +"device_verification_self_verify_wait_new_sign_in_title" = "Verifieer deze login"; // MARK: Self verification wait @@ -1312,7 +1312,7 @@ "identity_server_settings_alert_no_terms" = "De door u gekozen identiteitsserver heeft geen dienstvoorwaarden. Ga alleen door, wanneer u de eigenaar van de server vertrouwd."; "identity_server_settings_alert_no_terms_title" = "De identiteitsserver heeft geen dienstvoorwaarden"; "identity_server_settings_disconnect" = "Verbinding verbreken"; -"identity_server_settings_disconnect_info" = "De verbinding met uw identiteitsserver verbreken zal ertoe leiden dat u niet door andere gebruikers gevonden zal kunnen worden, en dat u anderen niet via e-mail of telefoon zal kunnen uitnodigen."; +"identity_server_settings_disconnect_info" = "De verbinding met uw identiteitsserver verbreken zal ertoe leiden dat u niet door andere mensen gevonden zal kunnen worden, en dat u anderen niet via e-mail of telefoon zal kunnen uitnodigen."; "identity_server_settings_change" = "Wijzigen"; "identity_server_settings_add" = "Toevoegen"; "identity_server_settings_place_holder" = "Voer een identiteitsserver in"; diff --git a/Riot/Assets/ru.lproj/Vector.strings b/Riot/Assets/ru.lproj/Vector.strings index b9f7a1582..9e2ac5980 100644 --- a/Riot/Assets/ru.lproj/Vector.strings +++ b/Riot/Assets/ru.lproj/Vector.strings @@ -1280,3 +1280,55 @@ "social_login_button_title_continue" = "Продолжить с %@"; "social_login_list_title_sign_up" = "Или зарегистрируйтесь через"; "social_login_list_title_sign_in" = "Или войдите через"; +"room_intro_cell_information_multiple_dm_sentence2" = "Только вы находитесь в этом разговоре, если только кто-то из вас не пригласит кого-то присоединиться."; +"room_intro_cell_information_dm_sentence2" = "Только двое из вас в этом разговоре, никто другой не может присоединиться."; +"room_intro_cell_information_dm_sentence1_part3" = ". "; +"room_intro_cell_information_dm_sentence1_part1" = "Это начало ваших личных сообщений с "; +"room_intro_cell_information_room_without_topic_sentence2_part2" = " чтобы люди знали, о чём эта комната."; +"room_intro_cell_information_room_without_topic_sentence2_part1" = "Добавьте тему"; +"room_intro_cell_information_room_with_topic_sentence2" = "Тема: %@"; +"room_intro_cell_information_room_sentence1_part3" = ". "; +"room_intro_cell_information_room_sentence1_part1" = "Это начало "; + +// Mark: - Room creation introduction cell + +"room_intro_cell_add_participants_action" = "Добавить людей"; +"room_avatar_view_accessibility_hint" = "Изменить аватар комнаты"; + +// Mark: - Room avatar view + +"room_avatar_view_accessibility_label" = "аватар"; +"call_transfer_error_message" = "Передача вызова не удалась"; +"call_transfer_error_title" = "Ошибка"; +"call_transfer_contacts_all" = "Все"; +"call_transfer_contacts_recent" = "Недавние"; +"call_transfer_dialpad" = "Панель набора номера"; +"call_transfer_users" = "Пользователи"; + +// MARK: - Call Transfer +"call_transfer_title" = "Перевод"; + +// MARK: - Dial Pad +"dialpad_title" = "Панель набора номера"; +"callbar_return" = "Вернуться"; +"call_actions_unhold" = "Возобновить"; +"event_formatter_call_back" = "Перезвонить"; +"event_formatter_call_you_declined" = "Вы отклонили этот вызов"; +"event_formatter_call_you_currently_in" = "Вы в этом вызове"; +"event_formatter_call_has_ended" = "Этот вызов закончился"; +"event_formatter_call_video" = "Видео вызов"; +"event_formatter_call_voice" = "Голосовой вызов"; +"settings_show_NSFW_public_rooms" = "Показать публичные комнаты с чувствительным контентом"; +"room_open_dialpad" = "Панель набора номера"; +"room_place_voice_call" = "Голосовой вызов"; +"room_event_action_delete_confirmation_message" = "Вы уверены, что хотите удалить это неотправленное сообщение?"; +"room_event_action_delete_confirmation_title" = "Удалить неотправленное сообщение"; +"room_unsent_messages_cancel_message" = "Вы уверены, что хотите удалить все неотправленные сообщения в этой комнате?"; +"room_unsent_messages_cancel_title" = "Удалить неотправленные сообщения"; +"callbar_only_multiple_paused" = "%@ приостановленных вызовов"; +"callbar_only_single_paused" = "Приостановленный вызов"; +"callbar_active_and_multiple_paused" = "1 активный вызов (%@) · %@ приостановленных вызовов"; +"callbar_active_and_single_paused" = "1 активный вызов (%@) · 1 приостановленный вызов"; + +// Call Bar +"callbar_only_single_active" = "Активный вызов (%@)"; diff --git a/Riot/Assets/sq.lproj/Vector.strings b/Riot/Assets/sq.lproj/Vector.strings index e6057953a..b8ea07cc1 100644 --- a/Riot/Assets/sq.lproj/Vector.strings +++ b/Riot/Assets/sq.lproj/Vector.strings @@ -476,7 +476,7 @@ "room_participants_remove_third_party_invite_msg" = "Heqja e ftesave nga palë të treta nuk mbulohet ende, derisa të kihet API"; "room_participants_invite_malformed_id" = "ID e keqformuar. Duhet të jetë një adresë email ose ID Matrix, si '@localpart:domain'"; "room_participants_action_ban" = "Dëboje nga kjo dhomë"; -"room_unsent_messages_unknown_devices_notification" = "Mesazhi s’u dërgua, për shkak të pranisë së sesioneve të panjohur. %@ ose %@ tani?"; +"room_unsent_messages_unknown_devices_notification" = "Mesazhi s’u dërgua, për shkak të pranisë së sesioneve të panjohur."; "room_ongoing_conference_call" = "Thirrje konference që po zhvillohet. Merrni pjesë si %@ ose %@."; "room_ongoing_conference_call_with_close" = "Thirrje konference që po zhvillohet. Merrni pjesë si %@ ose %@. %@."; "room_conference_call_no_power" = "Ju duhen leje për të administruar thirrje konferencë në këtë dhomë"; @@ -853,7 +853,7 @@ "settings_add_3pid_password_title_email" = "Shtoni adresë email"; "settings_add_3pid_password_title_msidsn" = "Shtoni numër telefoni"; "settings_add_3pid_password_message" = "Që të vazhdohet, ju lutemi, jepni fjalëkalimin tuaj"; -"settings_add_3pid_invalid_password_message" = "Fjalëkalim i pavlefshëm"; +"settings_add_3pid_invalid_password_message" = "Kredenciale të pavlefshme"; "settings_devices_description" = "Emri publik i një sesioni është i dukshëm për persona me të cilët komunikoni"; "settings_discovery_no_identity_server" = "S’po përdorni ndonjë shërbyes identitetesh. Që të jeni i zbulueshëm nga kontakte ekzistuese që njihni, shtoni një të tillë."; "settings_discovery_terms_not_signed" = "Pajtohuni me Kushtet e Shërbimit të Shërbyesit të Identiteteve që t’i lejoni vetes të jeni i zbulueshëm përmes adrese email ose numri telefoni."; @@ -1266,3 +1266,76 @@ "rooms_empty_view_title" = "Dhoma"; "people_empty_view_information" = "Fjalosuni në mënyrë të sigurt me këdo. Prekni + që të filloni të shtoni persona."; "people_empty_view_title" = "Njerëz"; +"room_intro_cell_information_multiple_dm_sentence2" = "Në këtë bisedë jeni vetëm ju, veç nëse cilido prej jush fton dikë tjetër të vijë."; +"room_intro_cell_information_dm_sentence2" = "Në këtë bisedë gjenden vetëm ju të dy, s’mund të vijë tjetërkush."; +"room_intro_cell_information_dm_sentence1_part3" = ". "; +"room_intro_cell_information_dm_sentence1_part1" = "Ky është fillimi i mesazhit tuaj të drejtpërdrejtë me "; +"room_intro_cell_information_room_without_topic_sentence2_part2" = " që t’u bëni me dije njerëzve se për çfarë është kjo dhomë."; +"room_intro_cell_information_room_without_topic_sentence2_part1" = "Shtoni një temë"; +"room_intro_cell_information_room_with_topic_sentence2" = "Temë: %@"; +"room_intro_cell_information_room_sentence1_part3" = ". "; +"room_intro_cell_information_room_sentence1_part1" = "Ky është fillimi i "; + +// Mark: - Room creation introduction cell + +"room_intro_cell_add_participants_action" = "Shtoni persona"; +"room_avatar_view_accessibility_hint" = "Ndryshoni avatar dhome"; + +// Mark: - Room avatar view + +"room_avatar_view_accessibility_label" = "avatar"; +"invite_friends_share_text" = "Hej, bisedoni me mua në %@: %@"; + +// MARK: - Invite friends + +"invite_friends_action" = "Ftoni shokë te %@"; +"call_transfer_error_message" = "S’u arrit të shpërngulej thirrje"; +"call_transfer_error_title" = "Gabim"; +"call_transfer_contacts_all" = "Krejt"; +"call_transfer_contacts_recent" = "Së fundi"; +"call_transfer_dialpad" = "Numërator"; +"call_transfer_users" = "Përdorues"; + +// MARK: - Call Transfer +"call_transfer_title" = "Shpërngule"; + +// MARK: - Dial Pad +"dialpad_title" = "Numërator"; +"pin_protection_settings_change_pin" = "Ndryshoni PIN-in"; +"pin_protection_confirm_pin_to_change" = "Ripohoni PIN-in që të ndryshohet PIN"; +"secrets_recovery_with_key_recovery_key_title" = "Jepeni"; +"secrets_recovery_with_passphrase_passphrase_title" = "Jepeni"; +"bug_report_background_mode" = "Vazhdo në prapaskenë"; +"call_actions_unhold" = "Rimerre"; +"event_formatter_call_back" = "Ktheji thirrjen"; +"event_formatter_call_you_declined" = "Hodhët poshtë këtë thirrje"; +"event_formatter_call_you_currently_in" = "Gjendeni në këtë thirrje"; +"event_formatter_call_has_ended" = "Kjo thirrje ka përfunduar"; +"event_formatter_call_video" = "Thirrje video"; +"event_formatter_call_voice" = "Thirrje audio"; +"security_settings_crosssigning_reset" = "Rikthe te parazgjedhjet cross-signing"; +"security_settings_crosssigning" = "CROSS-SIGNING"; +"settings_show_NSFW_public_rooms" = "Shfaq dhoma publike NSFW"; +"room_open_dialpad" = "Numërator"; +"room_place_voice_call" = "Thirrje audio"; +"room_event_action_delete_confirmation_message" = "Jeni i sigurt se doni të fshihet ky mesazh i padërguar?"; +"room_event_action_delete_confirmation_title" = "Fshi mesazh të padërguar"; +"room_unsent_messages_cancel_message" = "Jeni i sigurt se doni të fshihen krejt mesazhet e padërguar në këtë dhomë?"; +"room_unsent_messages_cancel_title" = "Fshi mesazhet e padërguar"; +"social_login_button_title_sign_up" = "Regjistrohuni me %@"; +"social_login_button_title_sign_in" = "Hyni me %@"; +"social_login_button_title_continue" = "Vazhdo me %@"; +"social_login_list_title_sign_up" = "Ose"; +"social_login_list_title_sign_in" = "Ose"; + +// Social login + +"social_login_list_title_continue" = "Vazhdoni me"; +"callbar_return" = "Rimerre"; +"callbar_only_multiple_paused" = "%@ thirrje të ndalura"; +"callbar_only_single_paused" = "Thirrje e ndalur"; +"callbar_active_and_multiple_paused" = "1 thirrje aktive (%@) · %@ thirrje të ndalura"; +"callbar_active_and_single_paused" = "1 thirrje aktive (%@) · 1 thirrje e ndalur"; + +// Call Bar +"callbar_only_single_active" = "Thirrje aktive (%@)"; diff --git a/Riot/Assets/uk.lproj/Vector.strings b/Riot/Assets/uk.lproj/Vector.strings index 0eb951189..a2942949a 100644 --- a/Riot/Assets/uk.lproj/Vector.strings +++ b/Riot/Assets/uk.lproj/Vector.strings @@ -37,7 +37,7 @@ // Authentication "auth_login" = "Увійти"; "auth_register" = "Зареєструватися"; -"auth_submit" = "Відправити"; +"auth_submit" = "Надіслати"; "auth_skip" = "Пропустити"; "auth_send_reset_email" = "Надіслати лист для відновлення"; "auth_return_to_login" = "Повернутися на екран входу"; @@ -161,7 +161,7 @@ "people_conversation_section" = "БАЛАЧКИ"; "people_no_conversation" = "Нема балачок"; "room_participants_leave_prompt_msg_for_dm" = "Ви впевненні, що бажаєте залишити?"; -"room_participants_leave_prompt_title_for_dm" = "Залишити"; +"room_participants_leave_prompt_title_for_dm" = "Вийти"; "client_android_name" = "Element Android"; "store_promotional_text" = "Додаток для чату та сумісної роботи, що зберігає конфіденційність у відкритій мережі. Децентралізований, щоб надати вам контроль над даними. Без обробки даних, без бекдорів, без доступу для третіх сторін."; "settings_three_pids_management_information_part3" = "."; @@ -288,3 +288,40 @@ "security_settings_secure_backup_delete" = "Видалити"; "security_settings_secure_backup_synchronise" = "Синхронізувати"; "security_settings_secure_backup_setup" = "Налаштувати"; +"people_empty_view_information" = "Безпечно спілкуйтеся з будь-ким. Торкніться +, щоб додати співрозмовників."; +"callbar_return" = "Повернути"; +"callbar_only_multiple_paused" = "%@ викликів у очікуванні"; +"callbar_only_single_paused" = "Виклики в очікуванні"; +"callbar_active_and_multiple_paused" = "1 активний виклик (%@) · %@ викликів у очікуванні"; +"callbar_active_and_single_paused" = "1 активний виклик (%@) · 1 виклик в очікуванні"; + +// Call Bar +"callbar_only_single_active" = "Активний виклик (%@)"; +"switch" = "Перемкнути"; +"room_event_action_resend" = "Повторно надіслати"; +"room_event_action_redact" = "Вилучити"; +"room_event_action_quote" = "Цитувати"; +"room_event_action_copy" = "Копіювати"; +"bug_report_send" = "Надіслати"; +"room_details_topic" = "Тема"; +"room_details_room_name_for_dm" = "Назва"; +"create_room_placeholder_topic" = "Тема"; +"create_room_section_header_topic" = "Тема кімнати (необов'язково)"; +"create_room_placeholder_name" = "Назва"; +"create_room_section_header_name" = "Назва кімнати"; + +// MARK: - Create Room + +"create_room_title" = "Нова кімната"; +"searchable_directory_search_placeholder" = "Ім'я або ID"; +"searchable_directory_x_network" = "%@ мережа"; + +// MARK: - Searchable Directory View Controller + +"searchable_directory_create_new_room" = "Створити нову кімнату"; +"biometrics_cant_unlocked_alert_message_retry" = "Повторити"; +"key_backup_setup_skip_alert_skip_action" = "Пропустити"; +"room_participants_action_remove" = "Вилучити з цієї кімнати"; +"room_participants_action_leave" = "Вийти з цієї кімнати"; +"room_participants_action_invite" = "Запросити"; +"room_ongoing_conference_call_close" = "Закрити"; diff --git a/Riot/Assets/zh_Hans.lproj/Localizable.strings b/Riot/Assets/zh_Hans.lproj/Localizable.strings index aea281b69..7ff0fb738 100644 --- a/Riot/Assets/zh_Hans.lproj/Localizable.strings +++ b/Riot/Assets/zh_Hans.lproj/Localizable.strings @@ -1,5 +1,5 @@ /* New message from a specific person, not referencing a room */ -"MSG_FROM_USER" = "%@发送了一条消息"; +"MSG_FROM_USER" = "%@ 发送了一条消息"; /* New message from a specific person in a named room */ "MSG_FROM_USER_IN_ROOM" = "%@ 在 %@ 发送了消息"; /* New message from a specific person, not referencing a room. Content included. */ @@ -27,11 +27,11 @@ /* Multiple unread messages from three people */ "MSGS_FROM_THREE_USERS" = "%@ 条未读消息 (来自 %@、%@ 和 %@)"; /* Multiple unread messages from two plus people (ie. for 4+ people: 'others' replaces the third person) */ -"MSGS_FROM_TWO_PLUS_USERS" = "%@ 条未读消息 (来自 %@、%@ 及更多)"; +"MSGS_FROM_TWO_PLUS_USERS" = "%@ 条未读消息 (来自 %@、%@ 与其他)"; /* Multiple messages in two rooms */ "MSGS_IN_TWO_ROOMS" = "%@ 条未读消息 (来自聊天室 %@ 和 %@)"; /* Look, stuff's happened, alright? Just open the app. */ -"MSGS_IN_TWO_PLUS_ROOMS" = "%@ 条未读消息 (来自聊天室 %@、%@ 和更多)"; +"MSGS_IN_TWO_PLUS_ROOMS" = "%@ 条未读消息 (来自 %@、%@ 与其他)"; /* A user has invited you to a chat */ "USER_INVITE_TO_CHAT" = "%@ 邀请您加入私聊"; /* A user has invited you to an (unamed) group chat */ @@ -54,4 +54,16 @@ "MSG_FROM_USER_IN_ROOM_TITLE" = "%@(来自 %@)"; /* Sticker from a specific person, not referencing a room. */ "STICKER_FROM_USER" = "%@发送了一张贴图"; -"KEY_VERIFICATION_REQUEST_FROM_USER" = "%@想要验证"; +"KEY_VERIFICATION_REQUEST_FROM_USER" = "%@ 想要验证"; +"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" = "消息"; diff --git a/Riot/Assets/zh_Hans.lproj/Vector.strings b/Riot/Assets/zh_Hans.lproj/Vector.strings index 46cf235d1..858143d2f 100644 --- a/Riot/Assets/zh_Hans.lproj/Vector.strings +++ b/Riot/Assets/zh_Hans.lproj/Vector.strings @@ -11,7 +11,7 @@ "continue" = "继续"; "create" = "新建"; "start" = "开始"; -"leave" = "退出"; +"leave" = "离开"; "remove" = "移除"; "invite" = "邀请"; "retry" = "重试"; @@ -27,7 +27,7 @@ "voice" = "语音"; "video" = "视频"; "active_call" = "当前通话"; -"active_call_details" = "当前通话 (%@)"; +"active_call_details" = "当前通话(%@)"; "later" = "稍后再说"; "rename" = "重命名"; // Authentication @@ -47,7 +47,7 @@ "auth_phone_placeholder" = "手机号码"; "auth_repeat_password_placeholder" = "重复密码"; "auth_repeat_new_password_placeholder" = "确认您的新密码"; -"auth_invalid_login_param" = "无效的用户名和/或密码"; +"auth_invalid_login_param" = "用户名和/或密码无效"; "auth_invalid_user_name" = "用户名只能包含字母、数字、短横(-)与下划线(_)"; "auth_invalid_password" = "密码太短(至少 6 个字符)"; "auth_invalid_email" = "这不像是一个有效的邮箱地址"; @@ -142,8 +142,8 @@ "room_participants_add_participant" = "添加参加者"; "room_participants_one_participant" = "1 个参加者"; "room_participants_multi_participants" = "%d 个参加者"; -"room_participants_leave_prompt_title" = "退出聊天室"; -"room_participants_leave_prompt_msg" = "您确定要退出此聊天室吗?"; +"room_participants_leave_prompt_title" = "离开聊天室"; +"room_participants_leave_prompt_msg" = "您确定要离开此聊天室吗?"; "room_participants_remove_prompt_title" = "确认"; "room_participants_remove_prompt_msg" = "您确定要将 %@ 从此聊天室里移除?"; "room_participants_remove_third_party_invite_msg" = "在 API 实现之前还不支持移除第三方邀请"; @@ -165,18 +165,18 @@ "room_participants_action_section_devices" = "会话"; "room_participants_action_section_other" = "选项"; "room_participants_action_invite" = "邀请"; -"room_participants_action_leave" = "退出此聊天室"; +"room_participants_action_leave" = "离开此聊天室"; "room_participants_action_remove" = "从此聊天室移除"; "room_participants_action_ban" = "从此聊天室封禁"; "room_participants_action_unban" = "解封"; "room_participants_action_ignore" = "隐藏此用户的所有信息"; "room_participants_action_unignore" = "显示此用户的所有信息"; "room_participants_action_set_default_power_level" = "重置到正常用户"; -"room_participants_action_set_moderator" = "使其成为主持人"; +"room_participants_action_set_moderator" = "使其成为协管员"; "room_participants_action_set_admin" = "使其成为管理员"; "room_participants_action_start_new_chat" = "发起新的聊天"; -"room_participants_action_start_voice_call" = "开始语音通话"; -"room_participants_action_start_video_call" = "开始视频通话"; +"room_participants_action_start_voice_call" = "发起语音通话"; +"room_participants_action_start_video_call" = "发起视频通话"; "room_participants_action_mention" = "提及"; // Chat "room_jump_to_first_unread" = "跳到第一条未读信息"; @@ -185,7 +185,7 @@ "room_one_user_is_typing" = "%@ 正在输入…"; "room_two_users_are_typing" = "%@ 和 %@ 正在输入…"; "room_many_users_are_typing" = "%@、%@ 和 %@ 正在输入…"; -"room_message_placeholder" = "发送消息(非加密)…"; +"room_message_placeholder" = "发送消息(未加密)…"; "encrypted_room_message_placeholder" = "发送加密消息…"; "room_message_short_placeholder" = "发送消息…"; "room_offline_notification" = "到服务器的连接已经丢失。"; @@ -252,7 +252,7 @@ "settings_labs" = "实验室"; "settings_devices" = "会话"; "settings_cryptography" = "加密"; -"settings_sign_out" = "退出"; +"settings_sign_out" = "离开"; "settings_sign_out_confirmation" = "你确定?"; "settings_sign_out_e2e_warn" = "您将丢失所有端对端加密密钥。这意味着在此设备上您将再也无法阅读已加密聊天室里的旧消息。"; "settings_profile_picture" = "档案图片"; @@ -326,7 +326,7 @@ "room_details_history_section_prompt_msg" = "改成谁都可以阅读历史只会应用于此聊天室未来的消息。已经存在的历史消息的可见性将不会改变。"; "room_details_new_address" = "添加新地址"; "room_details_new_address_placeholder" = "添加新地址(例如 #foo%@)"; -"room_details_addresses_invalid_address_prompt_title" = "无效的别名格式"; +"room_details_addresses_invalid_address_prompt_title" = "别名格式无效"; "room_details_addresses_invalid_address_prompt_msg" = "%@ 不是一个有效的别名格式"; "room_details_addresses_disable_main_address_prompt_title" = "主地址警告"; "room_details_addresses_disable_main_address_prompt_msg" = "您没有指定主地址。此聊天室的默认主地址会随机选取"; @@ -413,7 +413,7 @@ // Events formatter "event_formatter_member_updates" = "%tu 的成员身份变化"; "auth_home_server_placeholder" = "URL(例如 https://matrix.org)"; -"auth_identity_server_placeholder" = "网址(例如 https://vector.im)"; +"auth_identity_server_placeholder" = "URL(例如 https://vector.im)"; "contacts_user_directory_section" = "用户目录"; "contacts_user_directory_offline_section" = "用户目录(离线)"; "room_ongoing_conference_call_with_close" = "收到会议通话。以 %@ 或 %@.%@ 加入。"; @@ -454,8 +454,8 @@ "group_home_multi_rooms_format" = "%tu 个聊天室"; // Group participants "group_participants_add_participant" = "添加成员"; -"group_participants_leave_prompt_title" = "退出群组"; -"group_participants_leave_prompt_msg" = "你确定要退出此群组吗?"; +"group_participants_leave_prompt_title" = "离开群组"; +"group_participants_leave_prompt_msg" = "你确定要离开此群组吗?"; "group_participants_remove_prompt_title" = "选项"; "group_participants_remove_prompt_msg" = "你确定要从此群组中移除 %@ 吗?"; "group_participants_invite_prompt_title" = "选项"; @@ -466,18 +466,18 @@ "group_participants_invited_section" = "已邀请"; // Group rooms "group_rooms_filter_rooms" = "过滤社区聊天室"; -"event_formatter_widget_added" = "小部件 %@ 已被 %@ 添加"; -"event_formatter_widget_removed" = "小部件 %@ 已被 %@ 移除"; +"event_formatter_widget_added" = "挂件 %@ 已被 %@ 添加"; +"event_formatter_widget_removed" = "挂件 %@ 已被 %@ 移除"; "do_not_ask_again" = "不再询问"; "call_already_displayed" = "正在通话。"; // Widget Integration Manager "widget_integration_need_to_be_able_to_invite" = "你需要有邀请用户的权限才能进行此操作。"; -"widget_integration_unable_to_create" = "无法创建小部件。"; +"widget_integration_unable_to_create" = "无法创建挂件。"; "widget_integration_failed_to_send_request" = "发送请求失败。"; "widget_integration_room_not_recognised" = "无法识别此房间。"; "widget_integration_positive_power_level" = "权限级别必须是整数。"; "widget_integration_must_be_in_room" = "您不在此聊天室中。"; -"e2e_room_key_request_start_verification" = "开始验证……"; +"e2e_room_key_request_start_verification" = "开始验证…"; "e2e_room_key_request_share_without_verifying" = "在不验证的情况下分享"; "e2e_room_key_request_ignore_request" = "忽略请求"; "room_event_action_kick_prompt_reason" = "移除此用户的原因"; @@ -489,8 +489,8 @@ "group_invitation_format" = "%@ 邀请您加入此社区"; "group_participants_invite_malformed_id" = "ID 格式错误。一个 Matrix ID 看起来应该像是 “@localpart:domain”"; // Widget -"widget_no_power_to_manage" = "你需要相关权限以管理此聊天室的小部件"; -"widget_creation_failure" = "小部件创建失败"; +"widget_no_power_to_manage" = "你需要相关权限以管理此聊天室的挂件"; +"widget_creation_failure" = "挂件创建失败"; // Room key request dialog "e2e_room_key_request_title" = "密钥共享请求"; "room_recents_server_notice_section" = "系统警告"; @@ -527,20 +527,20 @@ "room_resource_usage_limit_reached_message_contact_3" = " 以提高限制。"; // String for App Store "store_short_description" = "安全、去中心化的聊天及 VoIP 应用"; -"store_full_description" = "沟通,由你掌控。\n\n一个聊天应用,由你掌控且完全灵活。Element 让你以你的方式沟通。为 [matrix] - 开放、去中心化的沟通而打造。\n\n获取一个免费的 matrix.org 账号,在 https://ems.element.io 获取一个你自己的服务器,或者使用其他 Matrix 服务器。\n\n为什么选择 Element?\n\n完整的沟通:围绕你的团队、你的朋友、你的社区创建聊天室 - 随你喜欢!聊天、分享文件、添加插件和语音视频通话 - 全部免费。\n\n强大的集成度:通过你了解和喜欢的工具来使用 Element 。你甚至可以在 Element 上与其他聊天应用的用户和群组聊天。\n\n隐私和安全:保守你对话的秘密。最先进的端到端加密技术可确保私密通信保持私密性。\n\n开放,而非封闭:开源,基于 Matrix。通过自己开设的服务器来拥有你自己的数据,或者选择你信赖的服务器。\n\n随处可及:在你所有的设备和线上页面 https://app.element.io 上通过完整的历史信息同步让你随处可及。"; +"store_full_description" = "Element 是一种新型的通讯与协作应用:\n\n1. 使您可以掌控您的隐私\n2. 使您与 Matrix 网络中的任何人交流,甚至可以通过集成功能与如 Slack 之类的其他应用通讯\n3. 保护您免受广告,大数据挖掘和封闭服务的侵害\n4. 通过端到端加密保证安全,通过交叉签名验证其他人\n\nElement 与其他通讯与协作应用完全不同,因为它是去中心化且开源的。\n\nElement 允许您自托管——或者选择托管商——因此,您能拥有数据和会话的隐私权,所有权和控制权。它允许您访问开放网络;因此,您可以与 Element 用户以外的人交流。并且它非常安全。\n\nElement 之所以可以做到这些,是因为它在 Matrix 上运行——开放,去中心化通讯的标准。\n\n通过让您选择由谁来托管您的会话,Element 让您掌控一切。在 Element 应用中,您可以选择不同的托管方式:\n\n1. 在由 Matrix 开发者托管的 matrix.org 公共服务器上获取免费帐户,或从志愿者托管的上千个公共服务器中选择\n2. 在您自己的硬件上运行服务器,自托管您的会话\n3. 通过订阅 Element Matrix Services 托管平台,简单地在自定义服务器上注册账户\n\n为什么选择 Element?\n\n掌控您的数据:您来决定存放您的数据和消息的位置。拥有并控制它的是您,而不是挖掘您的数据或与第三方分享的巨型企业。\n\n开放通讯与协作:您可以与 Matrix 网络中的任何人聊天,不论他们使用 Element 还是其他 Matrix 应用,甚至/即使他们在使用不同的通讯系统,例如 Slack,IRC 或 XMPP。\n\n超级安全:支持真正的端到端加密(仅有会话中的人可以解密消息),还有能够验证会话参与方的设备的交叉签名。\n\n完善的通讯方式:消息,语音和视频通话,文件共享,屏幕共享和大量集成功能,机器人和挂件。建立房间与社区,保持联系并完成工作。\n\n随时随地:消息历史可在您的全部设备和 https://app.element.io 网页端之间完全同步,无论您在哪里,都可以保持联系。"; "auth_accept_policies" = "请查看并接受此主页服务器的服务条款:"; "room_replacement_information" = "这个聊天室已被替换,不再有效。"; "settings_flair" = "在允许的地方显示个性徽章"; "settings_key_backup" = "密钥备份"; "settings_key_backup_info" = "消息已被端对端安全加密。只有您和持有密钥的接收方可以阅读这些消息。"; -"settings_key_backup_info_checking" = "检查中……"; +"settings_key_backup_info_checking" = "正在检查…"; "settings_key_backup_info_none" = "您的密钥未从此会话备份。"; "settings_key_backup_info_signout_warning" = "在登出账号之前把此会话关联到密钥备份以免丢失仅在此设备上的所有密钥。"; "settings_key_backup_info_version" = "密钥备份版本:%@"; "settings_key_backup_info_algorithm" = "算法:%@"; "settings_key_backup_info_valid" = "此会话正在备份密钥。"; "settings_key_backup_info_not_valid" = "此会话未在备份你的密钥,但你的确一个有可以恢复和继续添加的现有备份。"; -"settings_key_backup_info_progress" = "%@ 个密钥备份中……"; +"settings_key_backup_info_progress" = "正在备份 %@ 个密钥…"; "settings_key_backup_info_progress_done" = "所有密钥都已备份"; "settings_key_backup_info_trust_signature_unknown" = "备份具有一个 ID:%@ 的会话的签名"; "settings_key_backup_info_trust_signature_valid" = "备份具有此会话的一个有效签名"; @@ -558,8 +558,8 @@ "room_details_fail_to_update_room_direct" = "此聊天室的直接标记更新失败"; "event_formatter_jitsi_widget_removed" = "VoIP 会议已被 %@ 移除"; "room_does_not_exist" = "%@ 聊天室不存在"; -"call_incoming_voice" = "来电……"; -"call_incoming_video" = "视频通话来电……"; +"call_incoming_voice" = "收到来电…"; +"call_incoming_video" = "收到视频通话…"; // Key backup wrong version "e2e_key_backup_wrong_version_title" = "新建密钥备份"; "e2e_key_backup_wrong_version" = "一个新的安全消息密钥备份已被删除。\n\n如果这不是您的操作,请在设置中设定一个新的密码。"; @@ -591,7 +591,7 @@ "rerequest_keys_alert_message" = "请在另一台可以解密消息的设备上启动Element,这样它就可以将密钥发送到此会话。"; "key_backup_setup_title" = "密钥备份"; "key_backup_setup_skip_alert_title" = "您确定吗?"; -"key_backup_setup_skip_alert_message" = "如果您退出或丢失设备,可能会丢失安全消息。"; +"key_backup_setup_skip_alert_message" = "如果您登出或丢失设备,可能会丢失安全消息。"; "key_backup_setup_skip_alert_skip_action" = "跳过"; "key_backup_setup_intro_title" = "永不丢失加密消息"; "key_backup_setup_intro_info" = "加密聊天室中的消息使用端到端加密进行保护。 只有您和拥有密钥的收件人才能阅读这些消息。\n\n安全备份密钥以避免丢失密钥。"; @@ -659,7 +659,7 @@ "sign_out_key_backup_in_progress_alert_cancel_action" = "等待"; "auth_login_single_sign_on" = "使用单点登录方式登入"; "room_message_unable_open_link_error_message" = "无法打开链接。"; -"auth_autodiscover_invalid_response" = "无效的主服务器探测响应"; +"auth_autodiscover_invalid_response" = "主服务器探测响应无效"; "close" = "关闭"; // Accessibility "accessibility_checkbox_label" = "多选框"; @@ -670,7 +670,7 @@ "auth_phone_is_required" = "未设置身份认证服务器,所以你不能添加电话号码来重设你的密码。"; "auth_forgot_password_error_no_configured_identity_server" = "未设置身份认证服务器:添加服务器以重设你的密码。"; "auth_reset_password_error_is_required" = "未设置身份认证服务器:在服务器选项中添加以便重设你的密码。"; -"auth_softlogout_signed_out" = "你已经登出"; +"auth_softlogout_signed_out" = "你已登出"; "auth_softlogout_sign_in" = "登录"; "auth_softlogout_reason" = "你的主服务器(%1$@)管理员已将你的账号%2$@(%3$@)登出。"; "auth_softlogout_recover_encryption_keys" = "登录以恢复单独保存在此设备上的加密密钥。你需要它们才能阅读任何设备上的安全消息。"; @@ -682,7 +682,7 @@ "auth_softlogout_clear_data_sign_out_msg" = "你确定希望清空所有当前保存在此设备上数据吗?再次登录可以获取你的账号数据和消息。"; "auth_softlogout_clear_data_sign_out" = "登出"; // Errors -"error_user_already_logged_in" = "看上去你试图连接另一个主服务器。你想要登出吗?"; +"error_user_already_logged_in" = "您似乎正在尝试连接另一个主服务器。您想要登出吗?"; "room_creation_error_invite_user_by_email_without_identity_server" = "未设置身份认证服务器,所以你不能用邮箱添加参与者。"; "contacts_address_book_no_identity_server" = "未设置身份认证服务器"; "room_participants_remove_third_party_invite_prompt_msg" = "你确定想撤回这个邀请吗?"; @@ -691,9 +691,9 @@ "room_participants_action_security_status_verified" = "已验证"; "room_participants_action_security_status_verify" = "验证"; "room_participants_action_security_status_warning" = "警告"; -"room_participants_security_loading" = "载入中……"; -"room_participants_security_information_room_not_encrypted" = "本聊天室中的消息不是端到端加密的。"; -"room_participants_security_information_room_encrypted" = "本聊天室中的消息是端到端加密的。\n\n你的消息被加锁保护,并且只有你和收信人有唯一的解密密钥。"; +"room_participants_security_loading" = "正在载入…"; +"room_participants_security_information_room_not_encrypted" = "此聊天室中的消息未经端对端加密。"; +"room_participants_security_information_room_encrypted" = "本聊天室中的消息已被端对端加密。\n\n您的消息受加密保护,并且只有您和消息接收者拥有唯一解密密钥。"; "room_accessiblity_scroll_to_bottom" = "滚动到底部"; "room_event_action_reply" = "回复"; "room_event_action_edit" = "编辑"; @@ -731,7 +731,7 @@ "settings_add_3pid_password_title_email" = "添加邮箱地址"; "settings_add_3pid_password_title_msidsn" = "添加电话号码"; "settings_add_3pid_password_message" = "请填写你的密码以继续"; -"settings_add_3pid_invalid_password_message" = "无效密码"; +"settings_add_3pid_invalid_password_message" = "验证信息无效"; "settings_key_backup_button_connect" = "关联此会话到密钥备份"; "settings_devices_description" = "会话的公开名字会对你联络的人可见"; "settings_discovery_no_identity_server" = "你现在没有在用身份认证服务器。想要被你认识的现有联系人发现,请添加。"; @@ -754,7 +754,7 @@ // Security settings "security_settings_title" = "安全"; "security_settings_crypto_sessions" = "我的会话"; -"security_settings_crypto_sessions_loading" = "载入会话中……"; +"security_settings_crypto_sessions_loading" = "正在载入会话…"; "security_settings_crypto_sessions_description" = "信任会话以给与获得端到端加密的消息的权限。如果你没有认出某个会话,请更改你的登录密码并且重设你用于消息备的消息密码。"; "security_settings_backup" = "消息备份"; "security_settings_advanced" = "高级"; @@ -775,14 +775,14 @@ "identity_server_settings_place_holder" = "输入身份认证服务器"; "identity_server_settings_add" = "添加"; "identity_server_settings_change" = "更改"; -"identity_server_settings_disconnect_info" = "从你的身份认证服务器断开连接意味着你不会被其他用户所发现并且不会被其他人通过邮箱或者电话邀请。"; +"identity_server_settings_disconnect_info" = "从你的身份认证服务器断开连接意味着你无法被其他用户所发现,并且不会被其他人通过邮箱或者电话邀请。"; "identity_server_settings_disconnect" = "断开连接"; "identity_server_settings_alert_no_terms_title" = "身份认证服务器没有服务条款"; "identity_server_settings_alert_no_terms" = "你所选择的身份认证服务器并没有任何服务条款。请仅当你信任此服务器所有人时再继续。"; "identity_server_settings_alert_change_title" = "更改身份认证服务器"; -"identity_server_settings_alert_change" = "断开身份认证服务器%1$@的连接,而连接到%2$@?"; +"identity_server_settings_alert_change" = "断开与身份认证服务器 %1$@ 的连接,转而连接到 %2$@?"; "identity_server_settings_alert_disconnect_title" = "断开连接身份认证服务器"; -"identity_server_settings_alert_disconnect" = "断开到身份认证服务器%@的连接?"; +"identity_server_settings_alert_disconnect" = "断开到身份认证服务器 %@ 的连接?"; "identity_server_settings_alert_disconnect_button" = "断开连接"; "identity_server_settings_alert_disconnect_still_sharing_3pid" = "你仍在身份认证服务器%@分享你的个人数据。\n\n我们建议你在断开连接前从此身份认证服务器删除你的邮箱地址和电话号码。"; "identity_server_settings_alert_disconnect_still_sharing_3pid_button" = "仍然断开连接"; @@ -810,7 +810,7 @@ "widget_integration_manager_disabled" = "你需要在设置中打开“集成管理器”"; // Widget Picker "widget_picker_title" = "集成"; -"widget_picker_manage_integrations" = "管理集成……"; +"widget_picker_manage_integrations" = "管理集成…"; // Room widget permissions "room_widget_permission_title" = "载入小插件"; "room_widget_permission_creator_info_title" = "这个小插件的添加者是:"; @@ -849,13 +849,13 @@ "device_verification_incoming_description_2" = "验证此会话将标记它为已信任,而且同伴也会标记你的会话为已信任。"; // MARK: Start "device_verification_start_title" = "比较一段短文字来验证"; -"device_verification_start_wait_partner" = "等待同伴接受……"; +"device_verification_start_wait_partner" = "正在等待同伴接受…"; "device_verification_start_use_legacy" = "啥都没出现?并非所有客户端都支持交互式验证。使用旧的验证方法。"; "device_verification_start_verify_button" = "开始验证"; "device_verification_start_use_legacy_action" = "使用老式验证"; "device_verification_verify_title_emoji" = "通过确认以下emoji表情出现在同伴的屏幕上来验证此会话"; "device_verification_verify_title_number" = "通过确认以下数字出现在同伴的屏幕上来验证此会话"; -"device_verification_verify_wait_partner" = "等待同伴确认……"; +"device_verification_verify_wait_partner" = "正在等待同伴确认…"; "key_verification_verify_user_title_emoji" = "通过确认以下唯一的emoji表情,以同样顺序,出现在同伴的屏幕上来验证此用户。"; "key_verification_verify_user_title_number" = "通过确认以下数字,以同样顺序,出现在同伴的屏幕上来验证此用户。"; "device_verification_verified_title" = "已验证!"; @@ -949,8 +949,8 @@ "error_not_supported_on_mobile" = "你不能从移动版%@中进行此操作。"; "key_verification_tile_request_incoming_title" = "验证请求"; "key_verification_tile_request_outgoing_title" = "验证已发送"; -"key_verification_tile_request_status_data_loading" = "数据载入中……"; -"key_verification_tile_request_status_waiting" = "等待中……"; +"key_verification_tile_request_status_data_loading" = "正在载入数据…"; +"key_verification_tile_request_status_waiting" = "正在等待…"; "key_verification_tile_request_status_expired" = "已过期"; "key_verification_tile_request_status_cancelled_by_me" = "您已取消"; "key_verification_tile_request_status_cancelled" = "%@已取消"; @@ -963,7 +963,7 @@ "user_verification_start_verify_action" = "开始验证"; "user_verification_start_information_part1" = "为了额外的安全性,请验证: "; "user_verification_start_information_part2" = " 检查在你的两个设备上的一次性代码。"; -"user_verification_start_waiting_partner" = "等待%@……"; +"user_verification_start_waiting_partner" = "正在等待 %@…"; "user_verification_start_additional_information" = "为了安全,请当面或者使用其他通信方式进行此操作。"; "user_verification_sessions_list_user_trust_level_trusted_title" = "已信任"; "user_verification_sessions_list_user_trust_level_warning_title" = "警告"; @@ -989,16 +989,16 @@ "client_ios_name" = "Element iOS版"; "client_android_name" = "Element 安卓版"; "room_participants_action_security_status_complete_security" = "完整安全性"; -"room_participants_action_security_status_loading" = "加载中……"; -"room_member_power_level_admin_in" = "在 %@ 的管理员"; -"room_member_power_level_moderator_in" = "在 %@ 的主持人"; -"room_member_power_level_short_admin" = "管理员"; -"room_member_power_level_short_moderator" = "主持人"; +"room_participants_action_security_status_loading" = "正在加载…"; +"room_member_power_level_admin_in" = "管理员(%@)"; +"room_member_power_level_moderator_in" = "协管员(%@)"; +"room_member_power_level_short_admin" = "管理"; +"room_member_power_level_short_moderator" = "协管"; "security_settings_crosssigning" = "交叉签名"; "security_settings_crosssigning_info_not_bootstrapped" = "交叉签名还没有被设置。"; "security_settings_crosssigning_info_exists" = "您的帐户有一个交叉签名身份,但是还没有被这个会话信任。完全安全的会话。"; "skip" = "跳过"; -"room_member_power_level_custom_in" = "自定义(%@) 到%@"; +"room_member_power_level_custom_in" = "自定义(%@)(%@)"; "room_member_power_level_short_custom" = "自定义"; "security_settings_crosssigning_info_trusted" = "已启用交叉签名。您可以基于交叉签名信任其他用户和其他会话,但不能从此会话交叉签名,因为它没有交叉签名私钥。此会话完全安全。"; "security_settings_crosssigning_info_ok" = "交叉签名还没有被设置。"; @@ -1008,7 +1008,7 @@ "security_settings_cryptography" = "加密"; "security_settings_complete_security_alert_title" = "绝对安全"; "security_settings_complete_security_alert_message" = "您应该先完成当前会话的安全防护。"; -"security_settings_coming_soon" = "对不起。这个操作在Element iOS版本上还不可用。请使用另一个Matrix客户端来设置它。Elment iOS会使用它。"; +"security_settings_coming_soon" = "对不起。这个操作在 Element iOS 版本上还不可用。请使用另一个 Matrix 客户端来设置它。Elment iOS 会沿用其他客户端的设置。"; // Recover from private key "key_backup_recover_from_private_key_info" = "备份恢复中…"; // MARK: - Device Verification @@ -1023,7 +1023,7 @@ "device_verification_self_verify_alert_validate_action" = "验证"; "device_verification_self_verify_start_verify_action" = "开始验证"; "device_verification_self_verify_start_information" = "使用此会话验证您的新会话,并授予其访问加密信息的权限。"; -"device_verification_self_verify_start_waiting" = "等待中……"; +"device_verification_self_verify_start_waiting" = "正在等待…"; "key_verification_self_verify_current_session_alert_title" = "验证此会话"; "key_verification_self_verify_current_session_alert_message" = "其他用户可能不信任它。"; "key_verification_self_verify_current_session_alert_validate_action" = "验证"; @@ -1104,12 +1104,145 @@ "switch" = "开关"; "joined" = "已加入"; "store_promotional_text" = "在开放网络上保护隐私的聊天和协作应用程序。分散权力让你掌控一切。没有数据挖掘,没有后门,也没有第三方访问。"; -"social_login_button_title_sign_up" = "以 %@ 方式登录"; -"social_login_button_title_sign_in" = "以 %@ 方式登录"; -"social_login_button_title_continue" = "继续 %@ 以其他方式登录"; -"social_login_list_title_sign_in" = "利用社交媒体登录"; -"social_login_list_title_sign_up" = "利用社交媒体注册"; +"social_login_button_title_sign_up" = "使用 %@ 注册"; +"social_login_button_title_sign_in" = "使用 %@ 登录"; +"social_login_button_title_continue" = "使用 %@ 继续"; +"social_login_list_title_sign_in" = "或"; +"social_login_list_title_sign_up" = "或"; // Social login "social_login_list_title_continue" = "以其他方式登录"; +"security_settings_secure_backup_delete" = "删除"; +"security_settings_secure_backup_synchronise" = "同步"; +"security_settings_secure_backup_setup" = "设置"; +"security_settings_secure_backup_description" = "通过在您的服务器上备份加密密钥,防止失去对加密信息和数据的访问。"; +"security_settings_crypto_sessions_description_2" = "如果您未曾发起登录,请更改密码并重置安全备份。"; +"settings_show_NSFW_public_rooms" = "显示 NSFW 公共聊天室"; +"external_link_confirmation_message" = "此链接 %@ 会将您带至另一个网站:%@\n\n是否前往?"; +"external_link_confirmation_title" = "双击此链接"; +"room_open_dialpad" = "拨号键盘"; +"room_place_voice_call" = "语音通话"; +"room_event_action_delete_confirmation_message" = "您确定要删除这条未发送的消息吗?"; +"room_event_action_delete_confirmation_title" = "删除未发送的消息"; +"room_unsent_messages_cancel_message" = "您确定要删除此聊天室中所有未发送的消息吗?"; +"room_unsent_messages_cancel_title" = "删除未发送的消息"; +"room_participants_security_information_room_encrypted_for_dm" = "这里的消息已被端对端加密。\n\n您的消息受加密保护,并且只有您和消息接收者拥有唯一解密密钥。"; +"room_participants_security_information_room_not_encrypted_for_dm" = "此处的消息未经端对端加密。"; +"room_participants_filter_room_members_for_dm" = "筛选成员"; +"room_participants_leave_prompt_msg_for_dm" = "您确定要离开吗?"; +"room_participants_leave_prompt_title_for_dm" = "离开"; +"callbar_active_and_multiple_paused" = "1 个进行中通话(%@)· %@ 个已暂停通话"; +"callbar_active_and_single_paused" = "1 个进行中通话(%@)· 1 个已暂停通话"; + +// Call Bar +"callbar_only_single_active" = "进行中通话(%@)"; +"callbar_only_multiple_paused" = "%@ 个已暂停通话"; +"callbar_only_single_paused" = "已暂停通话"; +"callbar_return" = "返回"; +"room_intro_cell_information_room_sentence1_part3" = "。 "; +"room_intro_cell_information_dm_sentence1_part3" = "。 "; +"room_intro_cell_information_room_without_topic_sentence2_part1" = "添加话题"; +"room_intro_cell_information_room_with_topic_sentence2" = "话题:%@"; + +// Mark: - Room creation introduction cell + +"room_intro_cell_add_participants_action" = "添加人员"; +"room_avatar_view_accessibility_hint" = "修改聊天室头像"; + +// Mark: - Room avatar view + +"room_avatar_view_accessibility_label" = "头像"; + +// MARK: - Invite friends + +"invite_friends_action" = "邀请好友至 %@"; + +// MARK: - Favourites + +"favourites_empty_view_title" = "收藏聊天室与对话"; + +// MARK: - Home + +"home_empty_view_title" = "欢迎使用 %@,\n%@"; +"call_transfer_error_message" = "呼叫转移失败"; +"call_transfer_error_title" = "错误"; +"call_transfer_contacts_all" = "全部"; +"call_transfer_contacts_recent" = "最近"; +"call_transfer_dialpad" = "拨号键盘"; +"call_transfer_users" = "用户"; + +// MARK: - Call Transfer +"call_transfer_title" = "转移"; + +// MARK: - Dial Pad +"dialpad_title" = "拨号键盘"; +"room_info_list_section_other" = "其他"; +"create_room_section_footer_encryption" = "加密一经启用,便无法禁用。"; +"create_room_placeholder_address" = "#testroom:matrix.org"; +"create_room_section_header_address" = "聊天室地址"; +"create_room_section_header_type" = "聊天室类型"; +"create_room_enable_encryption" = "启用加密"; +"create_room_section_header_encryption" = "聊天室加密"; +"create_room_placeholder_topic" = "话题"; +"create_room_section_header_topic" = "聊天室话题(可选)"; +"create_room_placeholder_name" = "名称"; +"create_room_section_header_name" = "聊天室名称"; + +// MARK: - Create Room + +"create_room_title" = "新聊天室"; +"searchable_directory_search_placeholder" = "名称或 ID"; +"searchable_directory_x_network" = "%@ 网络"; + +// MARK: - Searchable Directory View Controller + +"searchable_directory_create_new_room" = "创建新聊天室"; +"biometrics_cant_unlocked_alert_message_retry" = "重试"; +"biometrics_desetup_disable_button_title_x" = "禁用 %@"; +"biometrics_desetup_title_x" = "禁用 %@"; +"biometrics_setup_enable_button_title_x" = "启用 %@"; +"biometrics_setup_title_x" = "启用 %@"; +"biometrics_settings_enable_x" = "启用 %@"; +"biometrics_mode_face_id" = "Face ID"; + +// MARK: - Biometrics Protection + +"biometrics_mode_touch_id" = "Touch ID"; +"pin_protection_mismatch_error_message" = "请再试一次"; +"pin_protection_reset_alert_action_reset" = "重置"; +"pin_protection_choose_pin_welcome_after_register" = "欢迎。"; + +// MARK: - PIN Protection + +"pin_protection_choose_pin_welcome_after_login" = "欢迎回来。"; +"major_update_learn_more_action" = "了解更多"; + +// MARK: - Major update + +"major_update_title" = "Riot 现已成为 Element"; +"secrets_reset_reset_action" = "重置"; +"secrets_setup_recovery_passphrase_summary_title" = "保存您的安全密语"; +"secrets_setup_recovery_passphrase_confirm_passphrase_placeholder" = "确认安全密语"; +"secrets_setup_recovery_passphrase_confirm_passphrase_title" = "确认"; +"secrets_setup_recovery_passphrase_additional_information" = "不要使用你的账号密码。"; +"secrets_setup_recovery_passphrase_validate_action" = "完成"; + +// Recovery passphrase + +"secrets_setup_recovery_passphrase_title" = "设置安全密语"; +"secrets_setup_recovery_key_done_action" = "完成"; +"secrets_setup_recovery_key_export_action" = "保存"; +"secrets_setup_recovery_key_loading" = "正在加载…"; + +// MARK: - Secrets set up + +// Recovery Key + +"secrets_setup_recovery_key_title" = "保存您的安全密钥"; +"room_info_list_several_members" = "%@ 位成员"; + +// MARK: - Room Info + +"room_info_list_one_member" = "1 位成员"; +"security_settings_secure_backup" = "安全备份"; diff --git a/Riot/Generated/Images.swift b/Riot/Generated/Images.swift index 5793e9185..7f973231f 100644 --- a/Riot/Generated/Images.swift +++ b/Riot/Generated/Images.swift @@ -98,10 +98,15 @@ internal enum Asset { internal static let peopleEmptyScreenArtwork = ImageAsset(name: "people_empty_screen_artwork") internal static let peopleEmptyScreenArtworkDark = ImageAsset(name: "people_empty_screen_artwork_dark") internal static let peopleFloatingAction = ImageAsset(name: "people_floating_action") + internal static let actionCamera = ImageAsset(name: "action_camera") + internal static let actionFile = ImageAsset(name: "action_file") + internal static let actionMediaLibrary = ImageAsset(name: "action_media_library") + internal static let actionSticker = ImageAsset(name: "action_sticker") 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 scrolldownDark = ImageAsset(name: "scrolldown_dark") 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") @@ -111,7 +116,15 @@ internal enum Asset { 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 inputCloseIcon = ImageAsset(name: "input_close_icon") + internal static let inputEditIcon = ImageAsset(name: "input_edit_icon") + internal static let inputReplyIcon = ImageAsset(name: "input_reply_icon") + internal static let inputTextBackground = ImageAsset(name: "input_text_background") + internal static let saveIcon = ImageAsset(name: "save_icon") + internal static let sendIcon = ImageAsset(name: "send_icon") internal static let uploadIcon = ImageAsset(name: "upload_icon") + internal static let uploadIconDark = ImageAsset(name: "upload_icon_dark") + internal static let videoCall = ImageAsset(name: "video_call") internal static let voiceCallHangonIcon = ImageAsset(name: "voice_call_hangon_icon") internal static let voiceCallHangupIcon = ImageAsset(name: "voice_call_hangup_icon") internal static let addMemberFloatingAction = ImageAsset(name: "add_member_floating_action") diff --git a/Riot/Generated/Strings.swift b/Riot/Generated/Strings.swift index 218303eaf..ab9947434 100644 --- a/Riot/Generated/Strings.swift +++ b/Riot/Generated/Strings.swift @@ -1514,7 +1514,7 @@ internal enum VectorL10n { internal static var identityServerSettingsDisconnect: String { return VectorL10n.tr("Vector", "identity_server_settings_disconnect") } - /// Disconnecting from your identity server will mean you won’t be discoverable by other users and be able to invite others by email or phone. + /// Disconnecting from your identity server will mean you won’t be discoverable by other users and be able to invite others by email or phone. internal static var identityServerSettingsDisconnectInfo: String { return VectorL10n.tr("Vector", "identity_server_settings_disconnect_info") } @@ -2294,6 +2294,10 @@ internal enum VectorL10n { internal static var roomAccessibilityUpload: String { return VectorL10n.tr("Vector", "room_accessibility_upload") } + /// Video Call + internal static var roomAccessibilityVideoCall: String { + return VectorL10n.tr("Vector", "room_accessibility_video_call") + } /// Scroll to bottom internal static var roomAccessiblityScrollToBottom: String { return VectorL10n.tr("Vector", "room_accessiblity_scroll_to_bottom") @@ -2614,6 +2618,10 @@ internal enum VectorL10n { internal static var roomDetailsHistorySectionPromptTitle: String { return VectorL10n.tr("Vector", "room_details_history_section_prompt_title") } + /// Integrations + internal static var roomDetailsIntegrations: String { + return VectorL10n.tr("Vector", "room_details_integrations") + } /// Low priority internal static var roomDetailsLowPriorityTag: String { return VectorL10n.tr("Vector", "room_details_low_priority_tag") @@ -2666,6 +2674,10 @@ internal enum VectorL10n { internal static var roomDetailsSaveChangesPrompt: String { return VectorL10n.tr("Vector", "room_details_save_changes_prompt") } + /// Search room + internal static var roomDetailsSearch: String { + return VectorL10n.tr("Vector", "room_details_search") + } /// Set as Main Address internal static var roomDetailsSetMainAddress: String { return VectorL10n.tr("Vector", "room_details_set_main_address") @@ -2894,6 +2906,10 @@ internal enum VectorL10n { internal static var roomMemberPowerLevelShortModerator: String { return VectorL10n.tr("Vector", "room_member_power_level_short_moderator") } + /// Editing + internal static var roomMessageEditing: String { + return VectorL10n.tr("Vector", "room_message_editing") + } /// Message edits internal static var roomMessageEditsHistoryTitle: String { return VectorL10n.tr("Vector", "room_message_edits_history_title") @@ -2910,6 +2926,10 @@ internal enum VectorL10n { internal static var roomMessageReplyToShortPlaceholder: String { return VectorL10n.tr("Vector", "room_message_reply_to_short_placeholder") } + /// Replying to %@ + internal static func roomMessageReplyingTo(_ p1: String) -> String { + return VectorL10n.tr("Vector", "room_message_replying_to", p1) + } /// Send a message… internal static var roomMessageShortPlaceholder: String { return VectorL10n.tr("Vector", "room_message_short_placeholder") @@ -2918,6 +2938,10 @@ internal enum VectorL10n { internal static var roomMessageUnableOpenLinkErrorMessage: String { return VectorL10n.tr("Vector", "room_message_unable_open_link_error_message") } + /// %@ and others + internal static func roomMultipleTypingNotification(_ p1: String) -> String { + return VectorL10n.tr("Vector", "room_multiple_typing_notification", p1) + } /// %d new message internal static func roomNewMessageNotification(_ p1: Int) -> String { return VectorL10n.tr("Vector", "room_new_message_notification", p1) diff --git a/Riot/Managers/Settings/RiotSettings.swift b/Riot/Managers/Settings/RiotSettings.swift index 5627d7369..64c53025e 100644 --- a/Riot/Managers/Settings/RiotSettings.swift +++ b/Riot/Managers/Settings/RiotSettings.swift @@ -37,6 +37,21 @@ final class RiotSettings: NSObject { static let hideReviewSessionsAlert = "hideReviewSessionsAlert" static let matrixApps = "matrixApps" static let showNSFWPublicRooms = "showNSFWPublicRooms" + static let settingsScreenShowChangePassword = "settingsScreenShowChangePassword" + static let settingsScreenShowInviteFriends = "settingsScreenShowInviteFriends" + static let settingsScreenShowEnableStunServerFallback = "settingsScreenShowEnableStunServerFallback" + static let settingsSecurityScreenShowSessions = "settingsSecurityScreenShowSessions" + static let settingsSecurityScreenShowSetupBackup = "settingsSecurityScreenShowSetupBackup" + static let settingsSecurityScreenShowRestoreBackup = "settingsSecurityScreenShowRestoreBackup" + static let settingsSecurityScreenShowDeleteBackup = "settingsSecurityScreenShowDeleteBackup" + static let settingsSecurityScreenShowCryptographyInfo = "settingsSecurityScreenShowCryptographyInfo" + static let settingsSecurityScreenShowCryptographyExport = "settingsSecurityScreenShowCryptographyExport" + static let settingsSecurityScreenShowAdvancedUnverifiedDevices = "settingsSecurityScreenShowAdvancedBlacklistUnverifiedDevices" + static let roomCreationScreenAllowEncryptionConfiguration = "roomCreationScreenAllowEncryptionConfiguration" + static let roomCreationScreenRoomIsEncrypted = "roomCreationScreenRoomIsEncrypted" + static let roomCreationScreenAllowRoomTypeConfiguration = "roomCreationScreenAllowRoomTypeConfiguration" + static let roomCreationScreenRoomIsPublic = "roomCreationScreenRoomIsPublic" + static let allowInviteExernalUsers = "allowInviteExernalUsers" } static let shared = RiotSettings() @@ -51,6 +66,20 @@ final class RiotSettings: NSObject { // MARK: - Public + func reset() { + defaults.removeObject(forKey: UserDefaultsKeys.settingsScreenShowChangePassword) + defaults.removeObject(forKey: UserDefaultsKeys.settingsScreenShowInviteFriends) + defaults.removeObject(forKey: UserDefaultsKeys.settingsScreenShowEnableStunServerFallback) + defaults.removeObject(forKey: UserDefaultsKeys.settingsSecurityScreenShowSessions) + defaults.removeObject(forKey: UserDefaultsKeys.settingsSecurityScreenShowSetupBackup) + defaults.removeObject(forKey: UserDefaultsKeys.settingsSecurityScreenShowRestoreBackup) + defaults.removeObject(forKey: UserDefaultsKeys.settingsSecurityScreenShowDeleteBackup) + defaults.removeObject(forKey: UserDefaultsKeys.settingsSecurityScreenShowCryptographyInfo) + defaults.removeObject(forKey: UserDefaultsKeys.settingsSecurityScreenShowCryptographyExport) + defaults.removeObject(forKey: UserDefaultsKeys.settingsSecurityScreenShowAdvancedUnverifiedDevices) + defaults.removeObject(forKey: UserDefaultsKeys.allowInviteExernalUsers) + } + // MARK: Servers var homeserverUrlString: String { @@ -212,4 +241,163 @@ final class RiotSettings: NSObject { defaults.set(newValue, forKey: UserDefaultsKeys.matrixApps) } } + + // MARK: - Room Creation Screen + + var roomCreationScreenAllowEncryptionConfiguration: Bool { + get { + guard defaults.object(forKey: UserDefaultsKeys.roomCreationScreenAllowEncryptionConfiguration) != nil else { + return BuildSettings.roomCreationScreenAllowEncryptionConfiguration + } + return defaults.bool(forKey: UserDefaultsKeys.roomCreationScreenAllowEncryptionConfiguration) + } set { + defaults.set(newValue, forKey: UserDefaultsKeys.roomCreationScreenAllowEncryptionConfiguration) + } + } + var roomCreationScreenRoomIsEncrypted: Bool { + get { + guard defaults.object(forKey: UserDefaultsKeys.roomCreationScreenRoomIsEncrypted) != nil else { + return BuildSettings.roomCreationScreenRoomIsEncrypted + } + return defaults.bool(forKey: UserDefaultsKeys.roomCreationScreenRoomIsEncrypted) + } set { + defaults.set(newValue, forKey: UserDefaultsKeys.roomCreationScreenRoomIsEncrypted) + } + } + var roomCreationScreenAllowRoomTypeConfiguration: Bool { + get { + guard defaults.object(forKey: UserDefaultsKeys.roomCreationScreenAllowRoomTypeConfiguration) != nil else { + return BuildSettings.roomCreationScreenAllowRoomTypeConfiguration + } + return defaults.bool(forKey: UserDefaultsKeys.roomCreationScreenAllowRoomTypeConfiguration) + } set { + defaults.set(newValue, forKey: UserDefaultsKeys.roomCreationScreenAllowRoomTypeConfiguration) + } + } + var roomCreationScreenRoomIsPublic: Bool { + get { + guard defaults.object(forKey: UserDefaultsKeys.roomCreationScreenRoomIsPublic) != nil else { + return BuildSettings.roomCreationScreenRoomIsPublic + } + return defaults.bool(forKey: UserDefaultsKeys.roomCreationScreenRoomIsPublic) + } set { + defaults.set(newValue, forKey: UserDefaultsKeys.roomCreationScreenRoomIsPublic) + } + } + + // MARK: Features + + var allowInviteExernalUsers: Bool { + get { + guard defaults.object(forKey: UserDefaultsKeys.allowInviteExernalUsers) != nil else { + return BuildSettings.allowInviteExernalUsers + } + return defaults.bool(forKey: UserDefaultsKeys.allowInviteExernalUsers) + } set { + defaults.set(newValue, forKey: UserDefaultsKeys.allowInviteExernalUsers) + } + } + + // MARK: General Settings + + var settingsScreenShowChangePassword: Bool { + get { + guard defaults.object(forKey: UserDefaultsKeys.settingsScreenShowChangePassword) != nil else { + return BuildSettings.settingsScreenShowChangePassword + } + return defaults.bool(forKey: UserDefaultsKeys.settingsScreenShowChangePassword) + } set { + defaults.set(newValue, forKey: UserDefaultsKeys.settingsScreenShowChangePassword) + } + } + var settingsScreenShowInviteFriends: Bool { + get { + guard defaults.object(forKey: UserDefaultsKeys.settingsScreenShowInviteFriends) != nil else { + return BuildSettings.settingsScreenShowInviteFriends + } + return defaults.bool(forKey: UserDefaultsKeys.settingsScreenShowInviteFriends) + } set { + defaults.set(newValue, forKey: UserDefaultsKeys.settingsScreenShowInviteFriends) + } + } + var settingsScreenShowEnableStunServerFallback: Bool { + get { + guard defaults.object(forKey: UserDefaultsKeys.settingsScreenShowInviteFriends) != nil else { + return BuildSettings.settingsScreenShowEnableStunServerFallback + } + return defaults.bool(forKey: UserDefaultsKeys.settingsScreenShowEnableStunServerFallback) + } set { + defaults.set(newValue, forKey: UserDefaultsKeys.settingsScreenShowEnableStunServerFallback) + } + } + var settingsSecurityScreenShowSessions: Bool { + get { + guard defaults.object(forKey: UserDefaultsKeys.settingsSecurityScreenShowSessions) != nil else { + return BuildSettings.settingsSecurityScreenShowSessions + } + return defaults.bool(forKey: UserDefaultsKeys.settingsSecurityScreenShowSessions) + } set { + defaults.set(newValue, forKey: UserDefaultsKeys.settingsSecurityScreenShowSessions) + } + } + var settingsSecurityScreenShowSetupBackup: Bool { + get { + guard defaults.object(forKey: UserDefaultsKeys.settingsSecurityScreenShowSetupBackup) != nil else { + return BuildSettings.settingsSecurityScreenShowSetupBackup + } + return defaults.bool(forKey: UserDefaultsKeys.settingsSecurityScreenShowSetupBackup) + } set { + defaults.set(newValue, forKey: UserDefaultsKeys.settingsSecurityScreenShowSetupBackup) + } + } + var settingsSecurityScreenShowRestoreBackup: Bool { + get { + guard defaults.object(forKey: UserDefaultsKeys.settingsSecurityScreenShowRestoreBackup) != nil else { + return BuildSettings.settingsSecurityScreenShowRestoreBackup + } + return defaults.bool(forKey: UserDefaultsKeys.settingsSecurityScreenShowRestoreBackup) + } set { + defaults.set(newValue, forKey: UserDefaultsKeys.settingsSecurityScreenShowRestoreBackup) + } + } + var settingsSecurityScreenShowDeleteBackup: Bool { + get { + guard defaults.object(forKey: UserDefaultsKeys.settingsSecurityScreenShowDeleteBackup) != nil else { + return BuildSettings.settingsSecurityScreenShowDeleteBackup + } + return defaults.bool(forKey: UserDefaultsKeys.settingsSecurityScreenShowDeleteBackup) + } set { + defaults.set(newValue, forKey: UserDefaultsKeys.settingsSecurityScreenShowDeleteBackup) + } + } + var settingsSecurityScreenShowCryptographyInfo: Bool { + get { + guard defaults.object(forKey: UserDefaultsKeys.settingsSecurityScreenShowCryptographyInfo) != nil else { + return BuildSettings.settingsSecurityScreenShowCryptographyInfo + } + return defaults.bool(forKey: UserDefaultsKeys.settingsSecurityScreenShowCryptographyInfo) + } set { + defaults.set(newValue, forKey: UserDefaultsKeys.settingsSecurityScreenShowCryptographyInfo) + } + } + var settingsSecurityScreenShowCryptographyExport: Bool { + get { + guard defaults.object(forKey: UserDefaultsKeys.settingsSecurityScreenShowCryptographyExport) != nil else { + return BuildSettings.settingsSecurityScreenShowCryptographyExport + } + return defaults.bool(forKey: UserDefaultsKeys.settingsSecurityScreenShowCryptographyExport) + } set { + defaults.set(newValue, forKey: UserDefaultsKeys.settingsSecurityScreenShowCryptographyExport) + } + } + var settingsSecurityScreenShowAdvancedUnverifiedDevices: Bool { + get { + guard defaults.object(forKey: UserDefaultsKeys.settingsSecurityScreenShowAdvancedUnverifiedDevices) != nil else { + return BuildSettings.settingsSecurityScreenShowAdvancedUnverifiedDevices + } + return defaults.bool(forKey: UserDefaultsKeys.settingsSecurityScreenShowAdvancedUnverifiedDevices) + } set { + defaults.set(newValue, forKey: UserDefaultsKeys.settingsSecurityScreenShowAdvancedUnverifiedDevices) + } + } } diff --git a/Riot/Managers/Theme/Theme.swift b/Riot/Managers/Theme/Theme.swift index 4fcb9ca5c..54815e497 100644 --- a/Riot/Managers/Theme/Theme.swift +++ b/Riot/Managers/Theme/Theme.swift @@ -70,6 +70,7 @@ import UIKit // MARK: - Appearance and style + var roomInputTextBorder: UIColor { get } /// Status bar style to use var statusBarStyle: UIStatusBarStyle { get } diff --git a/Riot/Managers/Theme/Themes/DarkTheme.swift b/Riot/Managers/Theme/Themes/DarkTheme.swift index c2da703ae..d3752abf4 100644 --- a/Riot/Managers/Theme/Themes/DarkTheme.swift +++ b/Riot/Managers/Theme/Themes/DarkTheme.swift @@ -52,6 +52,8 @@ class DarkTheme: NSObject, Theme { var noticeSecondaryColor: UIColor = UIColor(rgb: 0x61708B) var warningColor: UIColor = UIColor(rgb: 0xFF4B55) + + var roomInputTextBorder: UIColor = UIColor(rgb: 0x8D97A5).withAlphaComponent(0.2) var avatarColors: [UIColor] = [ UIColor(rgb: 0x03B381), diff --git a/Riot/Managers/Theme/Themes/DefaultTheme.swift b/Riot/Managers/Theme/Themes/DefaultTheme.swift index e2fea3979..eb0ed522e 100644 --- a/Riot/Managers/Theme/Themes/DefaultTheme.swift +++ b/Riot/Managers/Theme/Themes/DefaultTheme.swift @@ -52,8 +52,8 @@ class DefaultTheme: NSObject, Theme { var noticeSecondaryColor: UIColor = UIColor(rgb: 0x61708B) var warningColor: UIColor = UIColor(rgb: 0xFF4B55) - - var messageTickColor: UIColor = UIColor(rgb: 0xC1C6CD) + + var roomInputTextBorder: UIColor = UIColor(rgb: 0xE3E8F0) var avatarColors: [UIColor] = [ UIColor(rgb: 0x03B381), diff --git a/Riot/Modules/Application/LegacyAppDelegate.m b/Riot/Modules/Application/LegacyAppDelegate.m index 4c4ff28f3..501021ddc 100644 --- a/Riot/Modules/Application/LegacyAppDelegate.m +++ b/Riot/Modules/Application/LegacyAppDelegate.m @@ -2136,6 +2136,10 @@ NSString *const AppDelegateUniversalLinkDidChangeNotification = @"AppDelegateUni [self logoutSendingRequestServer:YES completion:^(BOOL isLoggedOut) { if (completion) { + if (isLoggedOut) + { + [RiotSettings.shared reset]; + } completion (YES); } }]; diff --git a/Riot/Modules/BadgeLabel/BadgeLabel.swift b/Riot/Modules/BadgeLabel/BadgeLabel.swift index f52409871..f61f4dd6a 100644 --- a/Riot/Modules/BadgeLabel/BadgeLabel.swift +++ b/Riot/Modules/BadgeLabel/BadgeLabel.swift @@ -40,7 +40,7 @@ class BadgeLabel: UILabel { } } - @IBInspectable var padding: CGSize = CGSize(width: 10, height: 2) { + @IBInspectable var padding: CGSize = CGSize(width: 10, height: 3) { didSet { invalidateIntrinsicContentSize() } diff --git a/Riot/Modules/Contacts/DataSources/ContactsDataSource.m b/Riot/Modules/Contacts/DataSources/ContactsDataSource.m index 72d2e4616..dea3437ff 100644 --- a/Riot/Modules/Contacts/DataSources/ContactsDataSource.m +++ b/Riot/Modules/Contacts/DataSources/ContactsDataSource.m @@ -499,7 +499,7 @@ if (section == searchInputSection) { - count = 1; + count = RiotSettings.shared.allowInviteExernalUsers ? 1 : 0; } else if (section == filteredLocalContactsSection && !(shrinkedSectionsBitMask & CONTACTSDATASOURCE_LOCALCONTACTS_BITWISE)) { diff --git a/Riot/Modules/CreateRoom/EnterNewRoomDetails/EnterNewRoomDetailsViewController.swift b/Riot/Modules/CreateRoom/EnterNewRoomDetails/EnterNewRoomDetailsViewController.swift index 7ec1cf9e8..d177dcd52 100644 --- a/Riot/Modules/CreateRoom/EnterNewRoomDetails/EnterNewRoomDetailsViewController.swift +++ b/Riot/Modules/CreateRoom/EnterNewRoomDetails/EnterNewRoomDetailsViewController.swift @@ -113,39 +113,51 @@ final class EnterNewRoomDetailsViewController: UIViewController { rows: [row_2_0], footer: nil) - let row_3_0 = Row(type: .withSwitch(isOn: viewModel.roomCreationParameters.isEncrypted, onValueChanged: { (theSwitch) in - self.viewModel.roomCreationParameters.isEncrypted = theSwitch.isOn - }), text: VectorL10n.createRoomEnableEncryption, accessoryType: .none) { - // no-op - } - let section3 = Section(header: VectorL10n.createRoomSectionHeaderEncryption, - rows: [row_3_0], - footer: VectorL10n.createRoomSectionFooterEncryption) - - let row_4_0 = Row(type: .default, text: VectorL10n.createRoomTypePrivate, accessoryType: viewModel.roomCreationParameters.isPublic ? .none : .checkmark) { - self.viewModel.roomCreationParameters.isPublic = false - self.updateSections() - } - let row_4_1 = Row(type: .default, text: VectorL10n.createRoomTypePublic, accessoryType: viewModel.roomCreationParameters.isPublic ? .checkmark : .none) { - self.viewModel.roomCreationParameters.isPublic = true - self.updateSections() - // scroll bottom to show user new fields - DispatchQueue.main.async { - self.mainTableView.scrollToRow(at: IndexPath(row: 0, section: 6), at: .bottom, animated: true) + var section3: Section? + if RiotSettings.shared.roomCreationScreenAllowEncryptionConfiguration { + let row_3_0 = Row(type: .withSwitch(isOn: viewModel.roomCreationParameters.isEncrypted, onValueChanged: { (theSwitch) in + self.viewModel.roomCreationParameters.isEncrypted = theSwitch.isOn + }), text: VectorL10n.createRoomEnableEncryption, accessoryType: .none) { + // no-op } + section3 = Section(header: VectorL10n.createRoomSectionHeaderEncryption, + rows: [row_3_0], + footer: VectorL10n.createRoomSectionFooterEncryption) + } + + var section4: Section? + if RiotSettings.shared.roomCreationScreenAllowRoomTypeConfiguration { + let row_4_0 = Row(type: .default, text: VectorL10n.createRoomTypePrivate, accessoryType: viewModel.roomCreationParameters.isPublic ? .none : .checkmark) { + self.viewModel.roomCreationParameters.isPublic = false + self.updateSections() + } + let row_4_1 = Row(type: .default, text: VectorL10n.createRoomTypePublic, accessoryType: viewModel.roomCreationParameters.isPublic ? .checkmark : .none) { + self.viewModel.roomCreationParameters.isPublic = true + self.updateSections() + // scroll bottom to show user new fields + DispatchQueue.main.async { + self.mainTableView.scrollToRow(at: IndexPath(row: 0, section: 6), at: .bottom, animated: true) + } + } + section4 = Section(header: VectorL10n.createRoomSectionHeaderType, + rows: [row_4_0, row_4_1], + footer: VectorL10n.createRoomSectionFooterType) } - let section4 = Section(header: VectorL10n.createRoomSectionHeaderType, - rows: [row_4_0, row_4_1], - footer: VectorL10n.createRoomSectionFooterType) var tmpSections: [Section] = [ section0, section1, - section2, - section3, - section4 + section2 ] + if let section3 = section3 { + tmpSections.append(section3) + } + + if let section4 = section4 { + tmpSections.append(section4) + } + if viewModel.roomCreationParameters.isPublic { let row_5_0 = Row(type: .withSwitch(isOn: viewModel.roomCreationParameters.showInDirectory, onValueChanged: { (theSwitch) in self.viewModel.roomCreationParameters.showInDirectory = theSwitch.isOn diff --git a/Riot/Modules/CreateRoom/EnterNewRoomDetails/EnterNewRoomDetailsViewModel.swift b/Riot/Modules/CreateRoom/EnterNewRoomDetails/EnterNewRoomDetailsViewModel.swift index 37ed2bed9..a9a48bdde 100644 --- a/Riot/Modules/CreateRoom/EnterNewRoomDetails/EnterNewRoomDetailsViewModel.swift +++ b/Riot/Modules/CreateRoom/EnterNewRoomDetails/EnterNewRoomDetailsViewModel.swift @@ -39,7 +39,8 @@ final class EnterNewRoomDetailsViewModel: EnterNewRoomDetailsViewModelType { init(session: MXSession) { self.session = session - roomCreationParameters.isEncrypted = session.vc_isE2EByDefaultEnabledByHSAdmin() + roomCreationParameters.isEncrypted = session.vc_isE2EByDefaultEnabledByHSAdmin() && RiotSettings.shared.roomCreationScreenRoomIsEncrypted + roomCreationParameters.isPublic = RiotSettings.shared.roomCreationScreenRoomIsPublic } deinit { diff --git a/Riot/Modules/DotsView/DotsView.swift b/Riot/Modules/DotsView/DotsView.swift new file mode 100644 index 000000000..adcf3ef49 --- /dev/null +++ b/Riot/Modules/DotsView/DotsView.swift @@ -0,0 +1,146 @@ +// +// 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 DotsView: UIView { + // MARK: - Public properties + + @IBInspectable var highlightedDotColor: UIColor = .darkGray + + @IBInspectable var dotColor: UIColor = .lightGray + + @IBInspectable var dotMaxWidth: CGFloat = 10 { + didSet { + self.sizeToFit() + } + } + + @IBInspectable var dotMinWidth: CGFloat = 8 { + didSet { + self.sizeToFit() + } + } + + @IBInspectable var numberOfDots: UInt = 3 { + didSet { + createDotViews() + } + } + + @IBInspectable var interSpaceMargin: CGFloat = 7 { + didSet { + self.sizeToFit() + } + } + + // MARK: - Private members + + private var dotLayers: Array = Array() + private var highlightedDotIndex: UInt = 0 { + didSet { + updateDotViews() + } + } + private let updateInterval: TimeInterval = 0.4 + private var lastUpdateDate: Date = Date() + private var animating: Bool = false { + didSet { + let displayLink = CADisplayLink(target: self, selector: #selector(fireTimer)) + displayLink.add(to: .current, forMode: .default) + } + } + + // MARK: - Lifecycle + + required init?(coder: NSCoder) { + super.init(coder: coder) + createDotViews() + } + + override init(frame: CGRect) { + super.init(frame: frame) + createDotViews() + } + + override func layoutSubviews() { + super.layoutSubviews() + + updateDotViews() + } + + override var intrinsicContentSize: CGSize { + return CGSize(width: dotMaxWidth + (CGFloat(numberOfDots) - 1) * (dotMinWidth + interSpaceMargin), height: dotMaxWidth) + } + + override func didMoveToSuperview() { + animating = superview != nil + } + + // MARK: - Interface Builder + + override func prepareForInterfaceBuilder() { + super.prepareForInterfaceBuilder() + createDotViews() + } + + // MARK: - Private methods + + private func createDotViews() { + while dotLayers.count > numberOfDots { + dotLayers.popLast()?.removeFromSuperlayer() + } + + while dotLayers.count < numberOfDots { + let dotLayer = CALayer() + dotLayer.masksToBounds = true + layer.addSublayer(dotLayer) + dotLayers.append(dotLayer) + } + + if highlightedDotIndex >= dotLayers.count { + highlightedDotIndex = 0 + updateDotViews() + } + } + + private func updateDotViews() { + CATransaction.begin() + CATransaction.setAnimationDuration(1) + var x: CGFloat = 0 + for (index, dotLayer) in dotLayers.enumerated() { + if index == highlightedDotIndex { + dotLayer.frame = CGRect(x: x, y: (bounds.height - dotMaxWidth) / 2, width: dotMaxWidth, height: dotMaxWidth) + dotLayer.backgroundColor = dotColor.cgColor + } else { + dotLayer.frame = CGRect(x: x, y: (bounds.height - dotMinWidth) / 2, width: dotMinWidth, height: dotMinWidth) + dotLayer.backgroundColor = index == ((highlightedDotIndex + 1) % numberOfDots) ? highlightedDotColor.cgColor : dotColor.cgColor + } + dotLayer.cornerRadius = dotLayer.bounds.height / 2 + x = dotLayer.frame.maxX + interSpaceMargin + } + lastUpdateDate = Date() + CATransaction.commit() + } + + @objc private func fireTimer() { + if Date().timeIntervalSince(lastUpdateDate) >= updateInterval { + self.highlightedDotIndex = (self.highlightedDotIndex + 1) % self.numberOfDots + } + } +} diff --git a/Riot/Modules/Room/DataSources/RoomDataSource.h b/Riot/Modules/Room/DataSources/RoomDataSource.h index f28f91fac..725171572 100644 --- a/Riot/Modules/Room/DataSources/RoomDataSource.h +++ b/Riot/Modules/Room/DataSources/RoomDataSource.h @@ -21,6 +21,8 @@ #import "MXRoomSummary+Riot.h" +#import "TypingUserInfo.h" + @protocol RoomDataSourceDelegate; /** @@ -48,6 +50,11 @@ */ @property(nonatomic, readonly) RoomEncryptionTrustLevel encryptionTrustLevel; +/** + List of members who are typing in the room. + */ +@property(nonatomic, nullable) NSArray *currentTypingUsers; + /** Check if there is an active jitsi widget in the room and return it. @@ -93,6 +100,8 @@ success:(void(^)(void))success failure:(void(^)(NSError*))failure; +- (void)resetTypingNotification; + @end @protocol RoomDataSourceDelegate diff --git a/Riot/Modules/Room/DataSources/RoomDataSource.m b/Riot/Modules/Room/DataSources/RoomDataSource.m index 8975281c8..5edf5ba24 100644 --- a/Riot/Modules/Room/DataSources/RoomDataSource.m +++ b/Riot/Modules/Room/DataSources/RoomDataSource.m @@ -27,6 +27,7 @@ #import "MXRoom+Riot.h" +const CGFloat kTypingCellHeight = 24; @interface RoomDataSource() { @@ -53,6 +54,8 @@ @property (nonatomic) BOOL showRoomCreationCell; +@property (nonatomic) NSInteger typingCellIndex; + @end @implementation RoomDataSource @@ -185,6 +188,16 @@ [self setNeedsUpdateAdditionalContentHeightForCellData:cellData]; } +- (CGFloat)cellHeightAtIndex:(NSInteger)index withMaximumWidth:(CGFloat)maxWidth +{ + if (index == self.typingCellIndex) + { + return kTypingCellHeight; + } + + return [super cellHeightAtIndex:index withMaximumWidth:maxWidth]; +} + - (void)setNeedsUpdateAdditionalContentHeightForCellData:(id)cellData { RoomBubbleCellData *roomBubbleCellData; @@ -261,16 +274,40 @@ [self updateStatusInfo]; } - // we may have changed the number of bubbles in this block, consider that change - return bubbles.count; + if (!self.currentTypingUsers) + { + self.typingCellIndex = -1; + + // we may have changed the number of bubbles in this block, consider that change + return bubbles.count; + } + + self.typingCellIndex = bubbles.count; + return bubbles.count + 1; } - // leave it as is, if coming as 0 from super - return count; + if (!self.currentTypingUsers) + { + self.typingCellIndex = -1; + + // leave it as is, if coming as 0 from super + return count; + } + + self.typingCellIndex = count; + return count + 1; } - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { + if (indexPath.row == self.typingCellIndex) + { + RoomTypingBubbleCell *cell = [tableView dequeueReusableCellWithIdentifier:RoomTypingBubbleCell.defaultReuseIdentifier forIndexPath:indexPath]; + [cell updateWithTheme:ThemeService.shared.theme]; + [cell updateTypingUsers:_currentTypingUsers mediaManager:self.mxSession.mediaManager]; + return cell; + } + // Do cell data customization that needs to be done before [MXKRoomBubbleTableViewCell render] RoomBubbleCellData *roomBubbleCellData = [self cellDataAtIndex:indexPath.row]; @@ -915,6 +952,10 @@ }]; } +- (void)resetTypingNotification { + self.currentTypingUsers = nil; +} + #pragma - Accessibility - (void)setupAccessibilityForCell:(MXKRoomBubbleTableViewCell *)cell withCellData:(RoomBubbleCellData*)cellData diff --git a/Riot/Modules/Room/DataSources/TypingUserInfo.h b/Riot/Modules/Room/DataSources/TypingUserInfo.h new file mode 100644 index 000000000..54914dc6e --- /dev/null +++ b/Riot/Modules/Room/DataSources/TypingUserInfo.h @@ -0,0 +1,34 @@ +// +// 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 +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface TypingUserInfo : NSObject + +@property (nonatomic, strong) NSString *userId; +@property (nonatomic, strong, nullable) NSString *displayName; +@property (nonatomic, strong, nullable) NSString *avatarUrl; + +- (instancetype) initWithMember:(MXRoomMember*)member; + +- (instancetype) initWithUserId:(NSString*)userId; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Riot/Modules/Room/DataSources/TypingUserInfo.m b/Riot/Modules/Room/DataSources/TypingUserInfo.m new file mode 100644 index 000000000..fa59cde01 --- /dev/null +++ b/Riot/Modules/Room/DataSources/TypingUserInfo.m @@ -0,0 +1,46 @@ +// +// 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 "TypingUserInfo.h" + +@implementation TypingUserInfo + +- (instancetype) initWithMember:(MXRoomMember*)member +{ + self = [self initWithUserId:member.userId]; + + if (self) + { + self.displayName = member.displayname; + self.avatarUrl = member.avatarUrl; + } + + return self; +} + +- (instancetype) initWithUserId:(NSString*)userId +{ + self = [super init]; + + if (self) + { + self.userId = userId; + } + + return self; +} + +@end diff --git a/Riot/Modules/Room/RoomInfo/RoomInfoCoordinator.swift b/Riot/Modules/Room/RoomInfo/RoomInfoCoordinator.swift index 3d9b179dc..f1efb7577 100644 --- a/Riot/Modules/Room/RoomInfo/RoomInfoCoordinator.swift +++ b/Riot/Modules/Room/RoomInfo/RoomInfoCoordinator.swift @@ -86,8 +86,13 @@ final class RoomInfoCoordinator: NSObject, RoomInfoCoordinatorType { // MARK: - Setup - init(parameters: RoomInfoCoordinatorParameters) { - self.navigationRouter = NavigationRouter(navigationController: RiotNavigationController()) + init(parameters: RoomInfoCoordinatorParameters, navigationRouter: NavigationRouterType? = nil) { + if let navigationRouter = navigationRouter { + self.navigationRouter = navigationRouter + } else { + self.navigationRouter = NavigationRouter(navigationController: RiotNavigationController()) + } + self.session = parameters.session self.room = parameters.room self.initialSection = parameters.initialSection @@ -102,8 +107,12 @@ final class RoomInfoCoordinator: NSObject, RoomInfoCoordinatorType { self.add(childCoordinator: rootCoordinator) - self.navigationRouter.setRootModule(rootCoordinator) - + if self.navigationRouter.modules.isEmpty == false { + self.navigationRouter.push(rootCoordinator.toPresentable(), animated: true, popCompletion: nil) + } else { + self.navigationRouter.setRootModule(rootCoordinator) + } + switch initialSection { case .addParticipants: self.showRoomDetails(with: .members, animated: false) @@ -129,13 +138,29 @@ final class RoomInfoCoordinator: NSObject, RoomInfoCoordinatorType { } private func showRoomDetails(with target: RoomInfoListTarget, animated: Bool) { - segmentedViewController.selectedIndex = target.tabIndex - - if case .settings(let roomSettingsField) = target { - roomSettingsViewController?.selectedRoomSettingsField = roomSettingsField + switch target { + case .integrations: + if let modularVC = IntegrationManagerViewController(for: session, inRoom: room.roomId, screen: kIntegrationManagerMainScreen, widgetId: nil) { + navigationRouter.present(modularVC, animated: true) + } + case .search: + MXKRoomDataSourceManager.sharedManager(forMatrixSession: session)?.roomDataSource(forRoom: self.room.roomId, create: false, onComplete: { (roomDataSource) in + guard let dataSource = roomDataSource else { return } + let storyboard = UIStoryboard(name: "Main", bundle: nil) + if let search = storyboard.instantiateViewController(withIdentifier: "RoomSearch") as? RoomSearchViewController { + search.roomDataSource = dataSource + self.navigationRouter.push(search, animated: animated, popCompletion: nil) + } + }) + default: + segmentedViewController.selectedIndex = target.tabIndex + + if case .settings(let roomSettingsField) = target { + roomSettingsViewController?.selectedRoomSettingsField = roomSettingsField + } + + navigationRouter.push(segmentedViewController, animated: animated, popCompletion: nil) } - - navigationRouter.push(segmentedViewController, animated: animated, popCompletion: nil) } } diff --git a/Riot/Modules/Room/RoomInfo/RoomInfoCoordinatorBridgePresenter.swift b/Riot/Modules/Room/RoomInfo/RoomInfoCoordinatorBridgePresenter.swift index 3503eb783..5348b9ab1 100644 --- a/Riot/Modules/Room/RoomInfo/RoomInfoCoordinatorBridgePresenter.swift +++ b/Riot/Modules/Room/RoomInfo/RoomInfoCoordinatorBridgePresenter.swift @@ -63,6 +63,16 @@ final class RoomInfoCoordinatorBridgePresenter: NSObject { self.coordinator = roomInfoCoordinator } + func push(from navigationController: UINavigationController, animated: Bool) { + let navigationRouter = NavigationRouter(navigationController: navigationController) + + let roomInfoCoordinator = RoomInfoCoordinator(parameters: self.coordinatorParameters, navigationRouter: navigationRouter) + roomInfoCoordinator.delegate = self + roomInfoCoordinator.start() + + self.coordinator = roomInfoCoordinator + } + func dismiss(animated: Bool, completion: (() -> Void)?) { guard let coordinator = self.coordinator else { return diff --git a/Riot/Modules/Room/RoomInfo/RoomInfoList/RoomInfoListViewAction.swift b/Riot/Modules/Room/RoomInfo/RoomInfoList/RoomInfoListViewAction.swift index 6d50c1551..945d2844a 100644 --- a/Riot/Modules/Room/RoomInfo/RoomInfoList/RoomInfoListViewAction.swift +++ b/Riot/Modules/Room/RoomInfo/RoomInfoList/RoomInfoListViewAction.swift @@ -18,11 +18,13 @@ import Foundation -enum RoomInfoListTarget { +enum RoomInfoListTarget: Equatable { case settings(_ field: RoomSettingsViewControllerField = RoomSettingsViewControllerFieldNone) case members case uploads - + case integrations + case search + var tabIndex: UInt { let tabIndex: UInt @@ -33,6 +35,10 @@ enum RoomInfoListTarget { tabIndex = 1 case .settings: tabIndex = 2 + case .integrations: + tabIndex = 3 + case .search: + tabIndex = 4 } return tabIndex diff --git a/Riot/Modules/Room/RoomInfo/RoomInfoList/RoomInfoListViewController.swift b/Riot/Modules/Room/RoomInfo/RoomInfoList/RoomInfoListViewController.swift index 0ce7f656d..67a75586a 100644 --- a/Riot/Modules/Room/RoomInfo/RoomInfoList/RoomInfoListViewController.swift +++ b/Riot/Modules/Room/RoomInfo/RoomInfoList/RoomInfoListViewController.swift @@ -157,11 +157,19 @@ final class RoomInfoListViewController: UIViewController { let rowUploads = Row(type: .default, icon: Asset.Images.scrollup.image, text: VectorL10n.roomDetailsFiles, accessoryType: .disclosureIndicator) { self.viewModel.process(viewAction: .navigate(target: .uploads)) } + let rowSearch = Row(type: .default, icon: Asset.Images.searchIcon.image, text: VectorL10n.roomDetailsSearch, accessoryType: .disclosureIndicator) { + self.viewModel.process(viewAction: .navigate(target: .search)) + } + let rowIntegrations = Row(type: .default, icon: Asset.Images.integrationsIcon.image, text: VectorL10n.roomDetailsIntegrations, accessoryType: .disclosureIndicator) { + self.viewModel.process(viewAction: .navigate(target: .integrations)) + } let sectionSettings = Section(header: VectorL10n.roomInfoListSectionOther, rows: [rowSettings, + rowIntegrations, rowMembers, - rowUploads], + rowUploads, + rowSearch], footer: nil) let leaveTitle = viewData.basicInfoViewData.isDirect ? @@ -205,7 +213,9 @@ final class RoomInfoListViewController: UIViewController { } private func setupViews() { - self.navigationItem.rightBarButtonItem = MXKBarButtonItem(customView: closeButton) + if navigationController?.viewControllers.count ?? 0 <= 1 { + self.navigationItem.rightBarButtonItem = MXKBarButtonItem(customView: closeButton) + } self.title = "" diff --git a/Riot/Modules/Room/RoomViewController.h b/Riot/Modules/Room/RoomViewController.h index 4e9864bfd..50e86e70b 100644 --- a/Riot/Modules/Room/RoomViewController.h +++ b/Riot/Modules/Room/RoomViewController.h @@ -27,6 +27,8 @@ #import "UIViewController+RiotSearch.h" +@class BadgeLabel; + /** Notification string used to indicate call tile tapped in a room. Notification object will be the `RoomBubbleCellData` object. */ @@ -46,6 +48,9 @@ extern NSNotificationName const RoomCallTileTappedNotification; @property (weak, nonatomic) IBOutlet UILabel *jumpToLastUnreadLabel; @property (weak, nonatomic) IBOutlet UIButton *resetReadMarkerButton; @property (weak, nonatomic) IBOutlet UIView *jumpToLastUnreadBannerSeparatorView; +@property (weak, nonatomic) IBOutlet UIView *inputBackgroundView; +@property (weak, nonatomic) IBOutlet UIButton *scrollToBottomButton; +@property (weak, nonatomic) IBOutlet BadgeLabel *scrollToBottomBadgeLabel; /** Preview data for a room invitation received by email, or a link to a room. @@ -72,5 +77,7 @@ extern NSNotificationName const RoomCallTileTappedNotification; */ - (IBAction)onButtonPressed:(id)sender; +- (IBAction)scrollToBottomAction:(id)sender; + @end diff --git a/Riot/Modules/Room/RoomViewController.m b/Riot/Modules/Room/RoomViewController.m index df28f443a..648c8ca90 100644 --- a/Riot/Modules/Room/RoomViewController.m +++ b/Riot/Modules/Room/RoomViewController.m @@ -124,9 +124,12 @@ #import "SettingsViewController.h" #import "SecurityViewController.h" +#import "TypingUserInfo.h" + #import "Riot-Swift.h" NSNotificationName const RoomCallTileTappedNotification = @"RoomCallTileTappedNotification"; +const NSTimeInterval kResizeComposerAnimationDuration = .05; @interface RoomViewController () *rightBarButtonItems; // Observe kThemeServiceDidChangeThemeNotification to handle user interface theme change. id kThemeServiceDidChangeThemeNotificationObserver; @@ -231,6 +229,8 @@ NSNotificationName const RoomCallTileTappedNotification = @"RoomCallTileTappedNo @property (nonatomic, strong) RoomCreationModalCoordinatorBridgePresenter *roomCreationModalCoordinatorBridgePresenter; @property (nonatomic, strong) RoomInfoCoordinatorBridgePresenter *roomInfoCoordinatorBridgePresenter; @property (nonatomic, strong) CustomSizedPresentationController *customSizedPresentationController; +@property (nonatomic, getter=isActivitiesViewExpanded) BOOL activitiesViewExpanded; +@property (nonatomic, getter=isScrollToBottomHidden) BOOL scrollToBottomHidden; @end @@ -289,13 +289,15 @@ NSNotificationName const RoomCallTileTappedNotification = @"RoomCallTileTappedNo { [super finalizeInit]; + self.resizeComposerAnimationDuration = kResizeComposerAnimationDuration; + // Setup `MXKViewControllerHandling` properties self.enableBarTintColorStatusChange = NO; self.rageShakeManager = [RageShakeManager sharedManager]; formattedBodyParser = [FormattedBodyParser new]; _showMissedDiscussionsBadge = YES; - + _scrollToBottomHidden = YES; // Listen to the event sent state changes [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(eventDidChangeSentState:) name:kMXEventDidChangeSentStateNotification object:nil]; @@ -371,7 +373,9 @@ NSNotificationName const RoomCallTileTappedNotification = @"RoomCallTileTappedNo // call cells [self.bubblesTableView registerClass:RoomDirectCallStatusBubbleCell.class forCellReuseIdentifier:RoomDirectCallStatusBubbleCell.defaultReuseIdentifier]; - [self.bubblesTableView registerClass:RoomCreationIntroCell.class forCellReuseIdentifier:RoomCreationIntroCell.defaultReuseIdentifier]; + [self.bubblesTableView registerClass:RoomCreationIntroCell.class forCellReuseIdentifier:RoomCreationIntroCell.defaultReuseIdentifier]; + + [self.bubblesTableView registerNib:RoomTypingBubbleCell.nib forCellReuseIdentifier:RoomTypingBubbleCell.defaultReuseIdentifier]; [self vc_removeBackTitle]; @@ -388,13 +392,6 @@ NSNotificationName const RoomCallTileTappedNotification = @"RoomCallTileTappedNo // Custom the event details view [self setEventDetailsViewClass:EventDetailsView.class]; - // Update navigation bar items - for (UIBarButtonItem *barButtonItem in self.navigationItem.rightBarButtonItems) - { - barButtonItem.target = self; - barButtonItem.action = @selector(onButtonPressed:); - } - // Prepare missed dicussion badge (if any) self.showMissedDiscussionsBadge = _showMissedDiscussionsBadge; @@ -420,6 +417,8 @@ NSNotificationName const RoomCallTileTappedNotification = @"RoomCallTileTappedNo }]; [self userInterfaceThemeDidChange]; + + [self setupActions]; } - (void)userInterfaceThemeDidChange @@ -436,7 +435,7 @@ NSNotificationName const RoomCallTileTappedNotification = @"RoomCallTileTappedNo { [ThemeService.shared.theme applyStyleOnNavigationBar:mainNavigationController.navigationBar]; } - + // Keep navigation bar transparent in some cases if (!self.previewHeaderContainer.hidden) { @@ -456,10 +455,6 @@ NSNotificationName const RoomCallTileTappedNotification = @"RoomCallTileTappedNo self.previewHeaderContainer.backgroundColor = ThemeService.shared.theme.headerBackgroundColor; - missedDiscussionsBadgeLabel.textColor = ThemeService.shared.theme.baseTextPrimaryColor; - missedDiscussionsBadgeLabel.font = [UIFont boldSystemFontOfSize:14]; - missedDiscussionsBadgeLabel.backgroundColor = [UIColor clearColor]; - // Check the table view style to select its bg color. self.bubblesTableView.backgroundColor = ((self.bubblesTableView.style == UITableViewStylePlain) ? ThemeService.shared.theme.backgroundColor : ThemeService.shared.theme.headerBackgroundColor); self.bubblesTableView.separatorColor = ThemeService.shared.theme.lineBreakColor; @@ -469,7 +464,28 @@ NSNotificationName const RoomCallTileTappedNotification = @"RoomCallTileTappedNo { [self.bubblesTableView reloadData]; } + + self.scrollToBottomButton.layer.shadowColor = [UIColor blackColor].CGColor; + self.scrollToBottomButton.layer.shadowOpacity = 0.2; + self.scrollToBottomButton.layer.shadowRadius = 6; + self.scrollToBottomButton.layer.shadowOffset = CGSizeMake(0, 4); + self.inputBackgroundView.backgroundColor = [ThemeService.shared.theme.backgroundColor colorWithAlphaComponent:0.98]; + + if ([ThemeService.shared.themeId isEqualToString:@"light"]) + { + [self.scrollToBottomButton setImage:[UIImage imageNamed:@"scrolldown"] forState:UIControlStateNormal]; + } + else if ([ThemeService.shared.themeId isEqualToString:@"dark"] || [ThemeService.shared.themeId isEqualToString:@"black"]) + { + [self.scrollToBottomButton setImage:[UIImage imageNamed:@"scrolldown_dark"] forState:UIControlStateNormal]; + } + else if (@available(iOS 12.0, *) && ThemeService.shared.theme.userInterfaceStyle == UIUserInterfaceStyleDark) { + [self.scrollToBottomButton setImage:[UIImage imageNamed:@"scrolldown_dark"] forState:UIControlStateNormal]; + } + + self.scrollToBottomBadgeLabel.badgeColor = ThemeService.shared.theme.tintColor; + [self setNeedsStatusBarAppearanceUpdate]; } @@ -487,7 +503,7 @@ NSNotificationName const RoomCallTileTappedNotification = @"RoomCallTileTappedNo - (void)viewWillAppear:(BOOL)animated { [super viewWillAppear:animated]; - + // Screen tracking [[Analytics sharedInstance] trackScreen:@"ChatRoom"]; @@ -500,6 +516,12 @@ NSNotificationName const RoomCallTileTappedNotification = @"RoomCallTileTappedNo [self refreshRoomInputToolbar]; } + // Reset typing notification in order to remove the allocated space + if ([self.roomDataSource isKindOfClass:RoomDataSource.class]) + { + [((RoomDataSource*)self.roomDataSource) resetTypingNotification]; + } + [self listenTypingNotifications]; [self listenCallNotifications]; [self listenWidgetNotifications]; @@ -558,7 +580,7 @@ NSNotificationName const RoomCallTileTappedNotification = @"RoomCallTileTappedNo [self removeWidgetNotificationsListeners]; [self removeTombstoneEventNotificationsListener]; [self removeMXSessionStateChangeNotificationsListener]; - + // Re-enable the read marker display, and disable its update. self.roomDataSource.showReadMarker = YES; self.updateRoomReadMarker = NO; @@ -589,9 +611,9 @@ NSNotificationName const RoomCallTileTappedNotification = @"RoomCallTileTappedNo // Observe missed notifications mxRoomSummaryDidChangeObserver = [[NSNotificationCenter defaultCenter] addObserverForName:kMXRoomSummaryDidChangeNotification object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *notif) { - + MXRoomSummary *roomSummary = notif.object; - + if ([roomSummary.roomId isEqualToString:self.roomDataSource.roomId]) { [self refreshMissedDiscussionsCount:NO]; @@ -622,7 +644,7 @@ NSNotificationName const RoomCallTileTappedNotification = @"RoomCallTileTappedNo [[NSNotificationCenter defaultCenter] removeObserver:mxRoomSummaryDidChangeObserver]; mxRoomSummaryDidChangeObserver = nil; } - + if (mxEventDidDecryptNotificationObserver) { [[NSNotificationCenter defaultCenter] removeObserver:mxEventDidDecryptNotificationObserver]; @@ -659,7 +681,7 @@ NSNotificationName const RoomCallTileTappedNotification = @"RoomCallTileTappedNo eventDetailsView = nil; } } - + // Check whether the preview header is visible if (previewHeader) { @@ -679,13 +701,13 @@ NSNotificationName const RoomCallTileTappedNotification = @"RoomCallTileTappedNo [previewHeader layoutIfNeeded]; } } - + self.edgesForExtendedLayout = UIRectEdgeAll; - + // Adjust the top constraint of the bubbles table CGRect frame = previewHeader.bottomBorderView.frame; self.previewHeaderContainerHeightConstraint.constant = frame.origin.y + frame.size.height; - + self.bubblesTableViewTopConstraint.constant = self.previewHeaderContainerHeightConstraint.constant - self.bubblesTableView.mxk_adjustedContentInset.top; self.jumpToLastUnreadBannerContainerTopConstraint.constant = self.previewHeaderContainerHeightConstraint.constant; } @@ -694,7 +716,7 @@ NSNotificationName const RoomCallTileTappedNotification = @"RoomCallTileTappedNo // In non expanded header mode, the navigation bar is opaque // The table view must not display behind it self.edgesForExtendedLayout = UIRectEdgeLeft | UIRectEdgeBottom | UIRectEdgeRight; - + self.jumpToLastUnreadBannerContainerTopConstraint.constant = self.bubblesTableView.mxk_adjustedContentInset.top; // no expanded } @@ -703,6 +725,19 @@ NSNotificationName const RoomCallTileTappedNotification = @"RoomCallTileTappedNo - (void)viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(id )coordinator { + if ([self.titleView isKindOfClass:RoomTitleView.class]) + { + RoomTitleView *roomTitleView = (RoomTitleView*)self.titleView; + if (UIInterfaceOrientationIsLandscape([UIApplication sharedApplication].statusBarOrientation)) + { + [roomTitleView updateLayoutForOrientation:UIInterfaceOrientationPortrait]; + } + else + { + [roomTitleView updateLayoutForOrientation:UIInterfaceOrientationLandscapeLeft]; + } + } + // Hide the expanded header or the preview in case of iPad and iPhone 6 plus. // On these devices, the display mode of the splitviewcontroller may change during screen rotation. // It may correspond to an overlay mode in portrait and a side-by-side mode in landscape. @@ -772,25 +807,25 @@ NSNotificationName const RoomCallTileTappedNotification = @"RoomCallTileTappedNo - (BOOL)accessibilityScroll:(UIAccessibilityScrollDirection)direction { BOOL canScroll = YES; - + // Scroll by one page CGFloat tableViewHeight = self.bubblesTableView.frame.size.height; - + CGPoint offset = self.bubblesTableView.contentOffset; switch (direction) { case UIAccessibilityScrollDirectionUp: offset.y -= tableViewHeight; break; - + case UIAccessibilityScrollDirectionDown: offset.y += tableViewHeight; break; - + default: break; } - + if (offset.y < 0 && ![self.roomDataSource.timeline canPaginate:MXTimelineDirectionBackwards]) { // Can't paginate more. Let's stick on the first item @@ -810,9 +845,9 @@ NSNotificationName const RoomCallTileTappedNotification = @"RoomCallTileTappedNo { // Disable VoiceOver while scrolling self.bubblesTableView.accessibilityElementsHidden = YES; - + [self setBubbleTableViewContentOffset:offset animated:NO]; - + NSEnumerator *cells; if (direction == UIAccessibilityScrollDirectionUp) { @@ -823,13 +858,13 @@ NSNotificationName const RoomCallTileTappedNotification = @"RoomCallTileTappedNo cells = self.bubblesTableView.visibleCells.reverseObjectEnumerator; } UIView *cell = [self firstCellWithAccessibilityDataInCells:cells]; - + self.bubblesTableView.accessibilityElementsHidden = NO; - + // Force VoiceOver to focus on a visible item UIAccessibilityPostNotification(UIAccessibilityLayoutChangedNotification, cell); } - + // If we cannot scroll, let VoiceOver indicates the border return canScroll; } @@ -837,7 +872,7 @@ NSNotificationName const RoomCallTileTappedNotification = @"RoomCallTileTappedNo - (UIView*)firstCellWithAccessibilityDataInCells:(NSEnumerator*)cells { UIView *view; - + for (UITableViewCell *cell in cells) { if (![cell isKindOfClass:[RoomEmptyBubbleCell class]]) @@ -846,7 +881,7 @@ NSNotificationName const RoomCallTileTappedNotification = @"RoomCallTileTappedNo break; } } - + return view; } @@ -894,7 +929,7 @@ NSNotificationName const RoomCallTileTappedNotification = @"RoomCallTileTappedNo if (self.roomDataSource) { [self listenToServerNotices]; - + self.eventsAcknowledgementEnabled = YES; // Set room title view @@ -1000,7 +1035,7 @@ NSNotificationName const RoomCallTileTappedNotification = @"RoomCallTileTappedNo Class roomInputToolbarViewClass = RoomInputToolbarView.class; BOOL shouldDismissContextualMenu = NO; - + // Check the user has enough power to post message if (self.roomDataSource.roomState) { @@ -1022,7 +1057,7 @@ NSNotificationName const RoomCallTileTappedNotification = @"RoomCallTileTappedNo shouldDismissContextualMenu = YES; } } - + // Do not show toolbar in case of preview if (self.isRoomPreview) { @@ -1047,7 +1082,7 @@ NSNotificationName const RoomCallTileTappedNotification = @"RoomCallTileTappedNo - (CGFloat)inputToolbarHeight { CGFloat height = 0; - + if ([self.inputToolbarView isKindOfClass:RoomInputToolbarView.class]) { height = ((RoomInputToolbarView*)self.inputToolbarView).mainToolbarHeightConstraint.constant; @@ -1056,7 +1091,7 @@ NSNotificationName const RoomCallTileTappedNotification = @"RoomCallTileTappedNo { height = ((DisabledRoomInputToolbarView*)self.inputToolbarView).mainToolbarMinHeightConstraint.constant; } - + return height; } @@ -1069,6 +1104,11 @@ NSNotificationName const RoomCallTileTappedNotification = @"RoomCallTileTappedNo } [super setRoomActivitiesViewClass:roomActivitiesViewClass]; + + if (!self.isActivitiesViewExpanded) + { + self.roomActivitiesContainerHeightConstraint.constant = 0; + } } - (BOOL)isIRCStyleCommand:(NSString*)string @@ -1119,7 +1159,9 @@ NSNotificationName const RoomCallTileTappedNotification = @"RoomCallTileTappedNo - (void)setKeyboardHeight:(CGFloat)keyboardHeight { [super setKeyboardHeight:keyboardHeight]; - + + self.inputToolbarView.maxHeight = round(([UIScreen mainScreen].bounds.size.height - keyboardHeight) * 0.7); + // Make the activity indicator follow the keyboard // At runtime, this creates a smooth animation CGPoint activityIndicatorCenter = self.activityIndicator.center; @@ -1176,9 +1218,9 @@ NSNotificationName const RoomCallTileTappedNotification = @"RoomCallTileTappedNo // Let the datasource send it and manage the local echo [self.roomDataSource sendTextMessage:msgTxt success:nil failure:^(NSError *error) { - // Just log the error. The message will be displayed in red in the room history - NSLog(@"[MXKRoomViewController] sendTextMessage failed."); - }]; + // Just log the error. The message will be displayed in red in the room history + NSLog(@"[MXKRoomViewController] sendTextMessage failed."); + }]; } if (customizedRoomDataSource.selectedEventId) @@ -1189,19 +1231,30 @@ NSNotificationName const RoomCallTileTappedNotification = @"RoomCallTileTappedNo - (void)setRoomTitleViewClass:(Class)roomTitleViewClass { - [super setRoomTitleViewClass:roomTitleViewClass]; + // Sanity check: accept only MXKRoomTitleView classes or sub-classes + NSParameterAssert([roomTitleViewClass isSubclassOfClass:MXKRoomTitleView.class]); + + MXKRoomTitleView *titleView = [roomTitleViewClass roomTitleView]; + [self setValue:titleView forKey:@"titleView"]; + titleView.delegate = self; + titleView.mxRoom = self.roomDataSource.room; + self.navigationItem.leftBarButtonItem = [[UIBarButtonItem alloc] initWithCustomView:titleView]; + + if ([titleView isKindOfClass:RoomTitleView.class]) + { + RoomTitleView *roomTitleView = (RoomTitleView*)self.titleView; + missedDiscussionsBadgeLabel = roomTitleView.missedDiscussionsBadgeLabel; + missedDiscussionsDotView = roomTitleView.dotView; + [roomTitleView updateLayoutForOrientation:[UIApplication sharedApplication].statusBarOrientation]; + } + + [self updateViewControllerAppearanceOnRoomDataSourceState]; [self updateTitleViewEncryptionDecoration]; } - (void)destroy { - rightBarButtonItems = nil; - for (UIBarButtonItem *barButtonItem in self.navigationItem.rightBarButtonItems) - { - barButtonItem.enabled = NO; - } - if (currentAlert) { [currentAlert dismissViewControllerAnimated:NO completion:nil]; @@ -1247,7 +1300,7 @@ NSNotificationName const RoomCallTileTappedNotification = @"RoomCallTileTappedNo [self removeTombstoneEventNotificationsListener]; [self removeMXSessionStateChangeNotificationsListener]; [self removeServerNoticesListener]; - + if (previewHeader) { // Here [destroy] is called before [viewWillDisappear:] @@ -1262,8 +1315,6 @@ NSNotificationName const RoomCallTileTappedNotification = @"RoomCallTileTappedNo roomPreviewData = nil; - missedDiscussionsBarButtonCustomView = nil; - missedDiscussionsBadgeLabelBgView = nil; missedDiscussionsBadgeLabel = nil; [[NSNotificationCenter defaultCenter] removeObserver:self name:kMXEventDidChangeSentStateNotification object:nil]; @@ -1272,60 +1323,47 @@ NSNotificationName const RoomCallTileTappedNotification = @"RoomCallTileTappedNo [super destroy]; } -#pragma mark - +#pragma mark - Properties + +-(void)setActivitiesViewExpanded:(BOOL)activitiesViewExpanded +{ + if (_activitiesViewExpanded != activitiesViewExpanded) + { + _activitiesViewExpanded = activitiesViewExpanded; + + self.roomActivitiesContainerHeightConstraint.constant = activitiesViewExpanded ? 53 : 0; + [super roomInputToolbarView:self.inputToolbarView heightDidChanged:[self inputToolbarHeight] completion:nil]; + } +} - (void)setShowMissedDiscussionsBadge:(BOOL)showMissedDiscussionsBadge { - _showMissedDiscussionsBadge = showMissedDiscussionsBadge; + missedDiscussionsBadgeLabel.hidden = !showMissedDiscussionsBadge; + missedDiscussionsDotView.hidden = !showMissedDiscussionsBadge; +} + +- (void)setScrollToBottomHidden:(BOOL)scrollToBottomHidden +{ + if (_scrollToBottomHidden != scrollToBottomHidden) + { + _scrollToBottomHidden = scrollToBottomHidden; + } - if (_showMissedDiscussionsBadge && !missedDiscussionsBarButtonCustomView) + if (!_scrollToBottomHidden && [self.roomDataSource isKindOfClass:RoomDataSource.class]) { - // Prepare missed dicussion badge - missedDiscussionsBarButtonCustomView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 0, 21)]; - missedDiscussionsBarButtonCustomView.backgroundColor = [UIColor clearColor]; - missedDiscussionsBarButtonCustomView.clipsToBounds = NO; - - NSLayoutConstraint *heightConstraint = [NSLayoutConstraint constraintWithItem:missedDiscussionsBarButtonCustomView - attribute:NSLayoutAttributeHeight - relatedBy:NSLayoutRelationEqual - toItem:nil - attribute:NSLayoutAttributeNotAnAttribute - multiplier:1.0 - constant:21]; - - missedDiscussionsBadgeLabelBgView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 21, 21)]; - [missedDiscussionsBadgeLabelBgView.layer setCornerRadius:10]; - - [missedDiscussionsBarButtonCustomView addSubview:missedDiscussionsBadgeLabelBgView]; - missedDiscussionsBarButtonCustomView.accessibilityIdentifier = @"RoomVCMissedDiscussionsBarButton"; - - missedDiscussionsBadgeLabel = [[UILabel alloc]initWithFrame:CGRectMake(2, 2, 17, 17)]; - missedDiscussionsBadgeLabel.translatesAutoresizingMaskIntoConstraints = NO; - [missedDiscussionsBadgeLabelBgView addSubview:missedDiscussionsBadgeLabel]; - - NSLayoutConstraint *centerXConstraint = [NSLayoutConstraint constraintWithItem:missedDiscussionsBadgeLabel - attribute:NSLayoutAttributeCenterX - relatedBy:NSLayoutRelationEqual - toItem:missedDiscussionsBadgeLabelBgView - attribute:NSLayoutAttributeCenterX - multiplier:1.0 - constant:0]; - NSLayoutConstraint *centerYConstraint = [NSLayoutConstraint constraintWithItem:missedDiscussionsBadgeLabel - attribute:NSLayoutAttributeCenterY - relatedBy:NSLayoutRelationEqual - toItem:missedDiscussionsBadgeLabelBgView - attribute:NSLayoutAttributeCenterY - multiplier:1.0 - constant:0]; - - [NSLayoutConstraint activateConstraints:@[heightConstraint, centerXConstraint, centerYConstraint]]; - } - else - { - missedDiscussionsBarButtonCustomView = nil; - missedDiscussionsBadgeLabelBgView = nil; - missedDiscussionsBadgeLabel = nil; + RoomDataSource *roomDataSource = (RoomDataSource *) self.roomDataSource; + if (roomDataSource.currentTypingUsers && !roomDataSource.currentTypingUsers.count) + { + [roomDataSource resetTypingNotification]; + NSInteger count = [self.bubblesTableView numberOfRowsInSection:0]; + [self.bubblesTableView deleteRowsAtIndexPaths:@[[NSIndexPath indexPathForRow:count - 1 inSection:0]] withRowAnimation:UITableViewRowAnimationNone]; + } } + + [UIView animateWithDuration:.2 animations:^{ + self.scrollToBottomBadgeLabel.alpha = (scrollToBottomHidden || !self.scrollToBottomBadgeLabel.text) ? 0 : 1; + self.scrollToBottomButton.alpha = scrollToBottomHidden ? 0 : 1; + }]; } #pragma mark - Internals @@ -1360,20 +1398,26 @@ NSNotificationName const RoomCallTileTappedNotification = @"RoomCallTileTappedNo return self.roomDataSource.room.summary.isEncrypted && self.mainSession.crypto != nil; } +- (BOOL)supportCallOption +{ + return BuildSettings.allowVoIPUsage && self.roomDataSource.mxSession.callManager && self.roomDataSource.room.summary.membersCount.joined >= 2; +} + +- (BOOL)isCallActive +{ + MXCall *callInRoom = [self.roomDataSource.mxSession.callManager callInRoom:self.roomDataSource.roomId]; + + return (callInRoom && callInRoom.state != MXCallStateEnded) + || [[AppDelegate theDelegate].jitsiViewController.widget.roomId isEqualToString:self.roomDataSource.roomId]; +} + - (void)refreshRoomTitle { - if (rightBarButtonItems && !self.navigationItem.rightBarButtonItems) - { - // Restore by default the search bar button. - self.navigationItem.rightBarButtonItems = rightBarButtonItems; - } + NSMutableArray *rightBarButtonItems = nil; // Set the right room title view if (self.isRoomPreview) { - // Do not show the right buttons - self.navigationItem.rightBarButtonItems = nil; - [self showPreviewHeader:YES]; } else if (self.roomDataSource) @@ -1382,107 +1426,73 @@ NSNotificationName const RoomCallTileTappedNotification = @"RoomCallTileTappedNo if (self.roomDataSource.isLive) { - // Enable the right buttons (Search and Integrations) - for (UIBarButtonItem *barButtonItem in self.navigationItem.rightBarButtonItems) + rightBarButtonItems = [NSMutableArray new]; + + UIEdgeInsets itemInsets = UIEdgeInsetsMake(0, -5, 0, 5); + if (self.supportCallOption) { - barButtonItem.enabled = YES; - } - - if (self.navigationItem.rightBarButtonItems.count == 2) - { - BOOL matrixAppsEnabled = [[NSUserDefaults standardUserDefaults] boolForKey:@"matrixApps"]; - if (!matrixAppsEnabled) - { - // If the setting is disabled, do not show the icon - self.navigationItem.rightBarButtonItems = @[self.navigationItem.rightBarButtonItem]; - } - else if ([self widgetsCount:NO]) - { - // Show there are widgets by changing the "apps" icon color - // Show it in red only for room widgets, not user's widgets - // TODO: Design must be reviewed - UIImage *icon = self.navigationItem.rightBarButtonItems[1].image; - icon = [MXKTools paintImage:icon withColor:ThemeService.shared.theme.warningColor]; - icon = [icon imageWithRenderingMode:UIImageRenderingModeAlwaysOriginal]; - - self.navigationItem.rightBarButtonItems[1].image = icon; - self.navigationItem.rightBarButtonItems[1].accessibilityLabel = NSLocalizedStringFromTable(@"room_accessibility_integrations", @"Vector", nil); - } - else - { - // Reset original icon - self.navigationItem.rightBarButtonItems[1].image = [UIImage imageNamed:@"integrations_icon"]; - self.navigationItem.rightBarButtonItems[1].accessibilityLabel = NSLocalizedStringFromTable(@"room_accessibility_integrations", @"Vector", nil); - } + UIBarButtonItem *item = [[UIBarButtonItem alloc] initWithImage:[UIImage imageNamed:@"voice_call_hangon_icon"] style:UIBarButtonItemStylePlain target:self action:@selector(onVoiceCallPressed:)]; + item.accessibilityLabel = NSLocalizedStringFromTable(@"room_accessibility_call", @"Vector", nil); + item.imageInsets = UIEdgeInsetsMake(0, -5, 0, 5); + [rightBarButtonItems addObject:item]; - self.navigationItem.rightBarButtonItems.firstObject.accessibilityLabel = NSLocalizedStringFromTable(@"room_accessibility_search", @"Vector", nil); + item = [[UIBarButtonItem alloc] initWithImage:[UIImage imageNamed:@"video_call"] style:UIBarButtonItemStylePlain target:self action:@selector(onVideoCallPressed:)]; + item.imageInsets = UIEdgeInsetsMake(0, 10, 0, -10); + item.accessibilityLabel = NSLocalizedStringFromTable(@"room_accessibility_video_call", @"Vector", nil); + [rightBarButtonItems addObject:item]; + itemInsets = UIEdgeInsetsMake(0, 20, 0, -20); } - + + if ([self widgetsCount:NO]) + { + UIBarButtonItem *item = [[UIBarButtonItem alloc] initWithImage:[UIImage imageNamed:@"integrations_icon"] style:UIBarButtonItemStylePlain target:self action:@selector(onIntegrationsPressed:)]; + item.imageInsets = itemInsets; + item.accessibilityLabel = NSLocalizedStringFromTable(@"room_accessibility_integrations", @"Vector", nil); + [rightBarButtonItems addObject:item]; + } + // Do not change title view class here if the expanded header is visible. [self setRoomTitleViewClass:RoomTitleView.class]; ((RoomTitleView*)self.titleView).tapGestureDelegate = self; } - else + + MXKImageView *userPictureView = ((RoomTitleView*)self.titleView).pictureView; + + // Set user picture in input toolbar + if (userPictureView) { - // Remove the search button temporarily - rightBarButtonItems = self.navigationItem.rightBarButtonItems; - self.navigationItem.rightBarButtonItems = nil; - - [self setRoomTitleViewClass:SimpleRoomTitleView.class]; - self.titleView.editable = NO; + [self.roomDataSource.room.summary setRoomAvatarImageIn:userPictureView]; } + + [self refreshMissedDiscussionsCount:YES]; } - else - { - self.navigationItem.rightBarButtonItem.enabled = NO; - } + + self.navigationItem.rightBarButtonItems = rightBarButtonItems; } - (void)refreshRoomInputToolbar { MXKImageView *userPictureView; - + // Check whether the input toolbar is ready before updating it. if (self.inputToolbarView && [self.inputToolbarView isKindOfClass:RoomInputToolbarView.class]) { RoomInputToolbarView *roomInputToolbarView = (RoomInputToolbarView*)self.inputToolbarView; - // Check whether the call option is supported - roomInputToolbarView.supportCallOption = BuildSettings.allowVoIPUsage && self.roomDataSource.mxSession.callManager && self.roomDataSource.room.summary.membersCount.joined >= 2; - - // Get user picture view in input toolbar - userPictureView = roomInputToolbarView.pictureView; - - // Show the hangup button if there is an active call or an active jitsi - // conference call in the current room - MXCall *callInRoom = [self.roomDataSource.mxSession.callManager callInRoom:self.roomDataSource.roomId]; - if ((callInRoom && callInRoom.state != MXCallStateEnded) - || [[AppDelegate theDelegate].jitsiViewController.widget.roomId isEqualToString:self.roomDataSource.roomId]) - { - roomInputToolbarView.activeCall = YES; - } - else - { - roomInputToolbarView.activeCall = NO; - - // Hide the call button if there is an active call in another room - roomInputToolbarView.supportCallOption &= ([[AppDelegate theDelegate] callStatusBarWindow] == nil); - } - // Update encryption decoration if needed [self updateEncryptionDecorationForRoomInputToolbar:roomInputToolbarView]; } else if (self.inputToolbarView && [self.inputToolbarView isKindOfClass:DisabledRoomInputToolbarView.class]) { DisabledRoomInputToolbarView *roomInputToolbarView = (DisabledRoomInputToolbarView*)self.inputToolbarView; - + // Get user picture view in input toolbar userPictureView = roomInputToolbarView.pictureView; - + // For the moment, there is only one reason to use `DisabledRoomInputToolbarView` [roomInputToolbarView setDisabledReason:NSLocalizedStringFromTable(@"room_do_not_have_permission_to_post", @"Vector", nil)]; } - + // Set user picture in input toolbar if (userPictureView) { @@ -1502,11 +1512,28 @@ NSNotificationName const RoomCallTileTappedNotification = @"RoomCallTileTappedNo } } -- (void)setInputToolBarSendMode:(RoomInputToolbarViewSendMode)sendMode +- (void)setInputToolBarSendMode:(RoomInputToolbarViewSendMode)sendMode forEventWithId:(NSString *)eventId { if (self.inputToolbarView && [self.inputToolbarView isKindOfClass:[RoomInputToolbarView class]]) { RoomInputToolbarView *roomInputToolbarView = (RoomInputToolbarView*)self.inputToolbarView; + if (eventId) + { + MXEvent *event = [self.roomDataSource eventWithEventId:eventId]; + MXRoomMember * roomMember = [self.roomDataSource.roomState.members memberWithUserId:event.sender]; + if (roomMember.displayname.length) + { + roomInputToolbarView.eventSenderDisplayName = roomMember.displayname; + } + else + { + roomInputToolbarView.eventSenderDisplayName = event.sender; + } + } + else + { + roomInputToolbarView.eventSenderDisplayName = nil; + } roomInputToolbarView.sendMode = sendMode; } } @@ -1519,7 +1546,7 @@ NSNotificationName const RoomCallTileTappedNotification = @"RoomCallTileTappedNo RoomInputToolbarView *roomInputToolbarView = (RoomInputToolbarView*)self.inputToolbarView; sendMode = roomInputToolbarView.sendMode; } - + return sendMode; } @@ -1581,7 +1608,6 @@ NSNotificationName const RoomCallTileTappedNotification = @"RoomCallTileTappedNo - (void)updateEncryptionDecorationForRoomInputToolbar:(RoomInputToolbarView*)roomInputToolbarView { roomInputToolbarView.isEncryptionEnabled = self.isEncryptionEnabled; - roomInputToolbarView.encryptedRoomIcon.image = self.roomEncryptionBadgeImage; } - (void)handleLongPressFromCell:(id)cell withTappedEvent:(MXEvent*)event @@ -1612,7 +1638,7 @@ NSNotificationName const RoomCallTileTappedNotification = @"RoomCallTileTappedNo CameraPresenter *cameraPresenter = [CameraPresenter new]; cameraPresenter.delegate = self; [cameraPresenter presentCameraFrom:self with:@[MXKUTI.image, MXKUTI.movie] animated:YES]; - + self.cameraPresenter = cameraPresenter; } @@ -1634,7 +1660,7 @@ NSNotificationName const RoomCallTileTappedNotification = @"RoomCallTileTappedNo { sourceView = self.inputToolbarView; } - + [mediaPickerPresenter presentFrom:self sourceView:sourceView sourceRect:sourceView.bounds animated:YES]; self.mediaPickerPresenter = mediaPickerPresenter; @@ -1694,7 +1720,123 @@ NSNotificationName const RoomCallTileTappedNotification = @"RoomCallTileTappedNo self.roomInfoCoordinatorBridgePresenter = [[RoomInfoCoordinatorBridgePresenter alloc] initWithParameters:parameters]; self.roomInfoCoordinatorBridgePresenter.delegate = self; - [self.roomInfoCoordinatorBridgePresenter presentFrom:self animated:YES]; + [self.roomInfoCoordinatorBridgePresenter pushFrom:self.navigationController animated:YES]; +} + +- (void)setupActions { + if (![self.inputToolbarView isKindOfClass:RoomInputToolbarView.class]) { + return; + } + + RoomInputToolbarView *roomInputView = ((RoomInputToolbarView *) self.inputToolbarView); + MXWeakify(self); + roomInputView.actionsBar.actionItems = @[ + [[RoomActionItem alloc] initWithImage:[UIImage imageNamed:@"action_camera"] andAction:^{ + MXStrongifyAndReturnIfNil(self); + if ([self.inputToolbarView isKindOfClass:RoomInputToolbarView.class]) { + ((RoomInputToolbarView *) self.inputToolbarView).actionMenuOpened = NO; + } + [self showCameraControllerAnimated:YES]; + }], + [[RoomActionItem alloc] initWithImage:[UIImage imageNamed:@"action_media_library"] andAction:^{ + MXStrongifyAndReturnIfNil(self); + if ([self.inputToolbarView isKindOfClass:RoomInputToolbarView.class]) { + ((RoomInputToolbarView *) self.inputToolbarView).actionMenuOpened = NO; + } + [self showMediaPickerAnimated:YES]; + }], + [[RoomActionItem alloc] initWithImage:[UIImage imageNamed:@"action_sticker"] andAction:^{ + MXStrongifyAndReturnIfNil(self); + if ([self.inputToolbarView isKindOfClass:RoomInputToolbarView.class]) { + ((RoomInputToolbarView *) self.inputToolbarView).actionMenuOpened = NO; + } + [self roomInputToolbarViewPresentStickerPicker]; + }], + [[RoomActionItem alloc] initWithImage:[UIImage imageNamed:@"action_file"] andAction:^{ + MXStrongifyAndReturnIfNil(self); + if ([self.inputToolbarView isKindOfClass:RoomInputToolbarView.class]) { + ((RoomInputToolbarView *) self.inputToolbarView).actionMenuOpened = NO; + } + [self roomInputToolbarViewDidTapFileUpload]; + }], + ]; +} + +- (void)roomInputToolbarViewPresentStickerPicker +{ + // Search for the sticker picker widget in the user account + Widget *widget = [[WidgetManager sharedManager] userWidgets:self.roomDataSource.mxSession ofTypes:@[kWidgetTypeStickerPicker]].firstObject; + + if (widget) + { + // Display the widget + [widget widgetUrl:^(NSString * _Nonnull widgetUrl) { + + StickerPickerViewController *stickerPickerVC = [[StickerPickerViewController alloc] initWithUrl:widgetUrl forWidget:widget]; + + stickerPickerVC.roomDataSource = self.roomDataSource; + + [self.navigationController pushViewController:stickerPickerVC animated:YES]; + } failure:^(NSError * _Nonnull error) { + + NSLog(@"[RoomVC] Cannot display widget %@", widget); + [[AppDelegate theDelegate] showErrorAsAlert:error]; + }]; + } + else + { + // The Sticker picker widget is not installed yet. Propose the user to install it + MXWeakify(self); + + [currentAlert dismissViewControllerAnimated:NO completion:nil]; + + NSString *alertMessage = [NSString stringWithFormat:@"%@\n%@", + NSLocalizedStringFromTable(@"widget_sticker_picker_no_stickerpacks_alert", @"Vector", nil), + NSLocalizedStringFromTable(@"widget_sticker_picker_no_stickerpacks_alert_add_now", @"Vector", nil) + ]; + + currentAlert = [UIAlertController alertControllerWithTitle:nil message:alertMessage preferredStyle:UIAlertControllerStyleAlert]; + + [currentAlert addAction:[UIAlertAction actionWithTitle:[NSBundle mxk_localizedStringForKey:@"no"] + style:UIAlertActionStyleCancel + handler:^(UIAlertAction * action) + { + MXStrongifyAndReturnIfNil(self); + self->currentAlert = nil; + + }]]; + + [currentAlert addAction:[UIAlertAction actionWithTitle:[NSBundle mxk_localizedStringForKey:@"yes"] + style:UIAlertActionStyleDefault + handler:^(UIAlertAction * action) + { + MXStrongifyAndReturnIfNil(self); + self->currentAlert = nil; + + // Show the sticker picker settings screen + IntegrationManagerViewController *modularVC = [[IntegrationManagerViewController alloc] + initForMXSession:self.roomDataSource.mxSession + inRoom:self.roomDataSource.roomId + screen:[IntegrationManagerViewController screenForWidget:kWidgetTypeStickerPicker] + widgetId:nil]; + + [self presentViewController:modularVC animated:NO completion:nil]; + }]]; + + [currentAlert mxk_setAccessibilityIdentifier:@"RoomVCStickerPickerAlert"]; + [self presentViewController:currentAlert animated:YES completion:nil]; + } +} + +- (void)roomInputToolbarViewDidTapFileUpload +{ + MXKDocumentPickerPresenter *documentPickerPresenter = [MXKDocumentPickerPresenter new]; + documentPickerPresenter.delegate = self; + + NSArray *allowedUTIs = @[MXKUTI.data]; + [documentPickerPresenter presentDocumentPickerWith:allowedUTIs from:self animated:YES completion:nil]; + + self.documentPickerPresenter = documentPickerPresenter; } #pragma mark - Dialpad @@ -1806,7 +1948,7 @@ NSNotificationName const RoomCallTileTappedNotification = @"RoomCallTileTappedNo } self.previewHeaderContainer.hidden = NO; - + // Finalize preview header display according to the screen orientation [self refreshPreviewHeader:UIInterfaceOrientationIsLandscape([[UIApplication sharedApplication] statusBarOrientation])]; } @@ -1829,8 +1971,6 @@ NSNotificationName const RoomCallTileTappedNotification = @"RoomCallTileTappedNo // Remove details icon RoomTitleView *roomTitleView = (RoomTitleView*)self.titleView; - [roomTitleView.roomDetailsIconImageView removeFromSuperview]; - roomTitleView.roomDetailsIconImageView = nil; // Remove the shadow image used to hide the bottom border of the navigation bar when the preview header is displayed [mainNavigationController.navigationBar setShadowImage:nil]; @@ -1838,19 +1978,19 @@ NSNotificationName const RoomCallTileTappedNotification = @"RoomCallTileTappedNo [UIView animateWithDuration:0.3 delay:0 options:UIViewAnimationOptionBeginFromCurrentState | UIViewAnimationOptionCurveEaseIn animations:^{ - - self.bubblesTableViewTopConstraint.constant = 0; - self.jumpToLastUnreadBannerContainerTopConstraint.constant = self.bubblesTableView.mxk_adjustedContentInset.top; - - // Force to render the view - [self forceLayoutRefresh]; - - } + + self.bubblesTableViewTopConstraint.constant = 0; + self.jumpToLastUnreadBannerContainerTopConstraint.constant = self.bubblesTableView.mxk_adjustedContentInset.top; + + // Force to render the view + [self forceLayoutRefresh]; + + } completion:^(BOOL finished){ - }]; + }]; } } - + // Consider the main navigation controller if the current view controller is embedded inside a split view controller. UINavigationController *mainNavigationController = self.navigationController; if (self.splitViewController.isCollapsed && self.splitViewController.viewControllers.count) @@ -1878,8 +2018,6 @@ NSNotificationName const RoomCallTileTappedNotification = @"RoomCallTileTappedNo // Remove details icon RoomTitleView *roomTitleView = (RoomTitleView*)self.titleView; - [roomTitleView.roomDetailsIconImageView removeFromSuperview]; - roomTitleView.roomDetailsIconImageView = nil; // Set preview data to provide the room name roomTitleView.roomPreviewData = roomPreviewData; @@ -1888,17 +2026,17 @@ NSNotificationName const RoomCallTileTappedNotification = @"RoomCallTileTappedNo { previewHeader.mainHeaderContainer.hidden = NO; previewHeader.mainHeaderBackgroundHeightConstraint.constant = previewHeader.mainHeaderContainer.frame.size.height; - + if ([previewHeader isKindOfClass:PreviewRoomTitleView.class]) { // In case of preview, update the header height so that we can // display as much as possible the room topic in this header. // Note: the header height is handled by the previewHeader.mainHeaderBackgroundHeightConstraint. PreviewRoomTitleView *previewRoomTitleView = (PreviewRoomTitleView *)previewHeader; - + // Compute the height required to display all the room topic CGSize sizeThatFitsTextView = [previewRoomTitleView.roomTopic sizeThatFits:CGSizeMake(previewRoomTitleView.roomTopic.frame.size.width, MAXFLOAT)]; - + // Increase the preview header height according to the room topic height // but limit it in order to let room for room messages at the screen bottom. // This free space depends on the device. @@ -1907,13 +2045,13 @@ NSNotificationName const RoomCallTileTappedNotification = @"RoomCallTileTappedNo // apply a factor to give more priority to the display of more messages. CGFloat screenHeight = [[UIScreen mainScreen] bounds].size.height; CGFloat maxRoomTopicHeight = 50 + (screenHeight - 568) / 3; - + CGFloat additionalHeight = MIN(maxRoomTopicHeight, sizeThatFitsTextView.height) - - previewRoomTitleView.roomTopic.frame.size.height; - + - previewRoomTitleView.roomTopic.frame.size.height; + previewHeader.mainHeaderBackgroundHeightConstraint.constant += additionalHeight; } - + [self setRoomTitleViewClass:RoomAvatarTitleView.class]; // Note the avatar title view does not define tap gesture. @@ -1959,18 +2097,18 @@ NSNotificationName const RoomCallTileTappedNotification = @"RoomCallTileTappedNo [UIView animateWithDuration:0.3 delay:0 options:UIViewAnimationOptionBeginFromCurrentState | UIViewAnimationOptionCurveEaseIn animations:^{ - - self.bubblesTableViewTopConstraint.constant = self.previewHeaderContainerHeightConstraint.constant - self.bubblesTableView.mxk_adjustedContentInset.top; - self.jumpToLastUnreadBannerContainerTopConstraint.constant = self.previewHeaderContainerHeightConstraint.constant; - - previewHeader.roomAvatar.alpha = 1; - - // Force to render the view - [self forceLayoutRefresh]; - - } + + self.bubblesTableViewTopConstraint.constant = self.previewHeaderContainerHeightConstraint.constant - self.bubblesTableView.mxk_adjustedContentInset.top; + self.jumpToLastUnreadBannerContainerTopConstraint.constant = self.previewHeaderContainerHeightConstraint.constant; + + previewHeader.roomAvatar.alpha = 1; + + // Force to render the view + [self forceLayoutRefresh]; + + } completion:^(BOOL finished){ - }]; + }]; } } @@ -2291,7 +2429,7 @@ NSNotificationName const RoomCallTileTappedNotification = @"RoomCallTileTappedNo RoomDataSource *roomDataSource = (RoomDataSource*)self.roomDataSource; [roomDataSource acceptVerificationRequestForEventId:eventId success:^{ - + } failure:^(NSError *error) { [[AppDelegate theDelegate] showErrorAsAlert:error]; }]; @@ -2391,7 +2529,7 @@ NSNotificationName const RoomCallTileTappedNotification = @"RoomCallTileTappedNo MXEvent *callInviteEvent = userInfo[kMXKRoomBubbleCellEventKey]; MXCallInviteEventContent *eventContent = [MXCallInviteEventContent modelFromJSON:callInviteEvent.content]; - [self roomInputToolbarView:self.inputToolbarView placeCallWithVideo2:eventContent.isVideoCall]; + [self placeCallWithVideo2:eventContent.isVideoCall]; } else if ([actionIdentifier isEqualToString:RoomCreationIntroCell.tapOnAvatarView]) { @@ -2439,33 +2577,33 @@ NSNotificationName const RoomCallTileTappedNotification = @"RoomCallTileTappedNo [currentAlert addAction:[UIAlertAction actionWithTitle:NSLocalizedStringFromTable(@"retry", @"Vector", nil) style:UIAlertActionStyleDefault handler:^(UIAlertAction * action) { - - if (weakSelf) - { - typeof(self) self = weakSelf; - - [self cancelEventSelection]; - - // Let the datasource resend. It will manage local echo, etc. - [self.roomDataSource resendEventWithEventId:selectedEvent.eventId success:nil failure:nil]; - } - - }]]; + + if (weakSelf) + { + typeof(self) self = weakSelf; + + [self cancelEventSelection]; + + // Let the datasource resend. It will manage local echo, etc. + [self.roomDataSource resendEventWithEventId:selectedEvent.eventId success:nil failure:nil]; + } + + }]]; [currentAlert addAction:[UIAlertAction actionWithTitle:NSLocalizedStringFromTable(@"room_event_action_delete", @"Vector", nil) style:UIAlertActionStyleDefault handler:^(UIAlertAction * action) { - - if (weakSelf) - { - typeof(self) self = weakSelf; - - [self cancelEventSelection]; - - [self.roomDataSource removeEventWithEventId:selectedEvent.eventId]; - } - - }]]; + + if (weakSelf) + { + typeof(self) self = weakSelf; + + [self cancelEventSelection]; + + [self.roomDataSource removeEventWithEventId:selectedEvent.eventId]; + } + + }]]; } // Add actions for text message @@ -2483,7 +2621,7 @@ NSNotificationName const RoomCallTileTappedNotification = @"RoomCallTileTappedNo selectedComponent = nil; } - + // Check status of the selected event if (selectedEvent.sentState == MXEventSentStatePreparing || selectedEvent.sentState == MXEventSentStateEncrypting || @@ -2493,41 +2631,41 @@ NSNotificationName const RoomCallTileTappedNotification = @"RoomCallTileTappedNo style:UIAlertActionStyleDefault handler:^(UIAlertAction * action) { - if (weakSelf) - { - typeof(self) self = weakSelf; - - self->currentAlert = nil; - - // Cancel and remove the outgoing message - [self.roomDataSource.room cancelSendingOperation:selectedEvent.eventId]; - [self.roomDataSource removeEventWithEventId:selectedEvent.eventId]; - - [self cancelEventSelection]; - } - - }]]; + if (weakSelf) + { + typeof(self) self = weakSelf; + + self->currentAlert = nil; + + // Cancel and remove the outgoing message + [self.roomDataSource.room cancelSendingOperation:selectedEvent.eventId]; + [self.roomDataSource removeEventWithEventId:selectedEvent.eventId]; + + [self cancelEventSelection]; + } + + }]]; } - + [currentAlert addAction:[UIAlertAction actionWithTitle:NSLocalizedStringFromTable(@"room_event_action_quote", @"Vector", nil) style:UIAlertActionStyleDefault handler:^(UIAlertAction * action) { - - if (weakSelf) - { - typeof(self) self = weakSelf; - - [self cancelEventSelection]; - - // Quote the message a la Markdown into the input toolbar composer - self.inputToolbarView.textMessage = [NSString stringWithFormat:@"%@\n>%@\n\n", self.inputToolbarView.textMessage, selectedComponent.textMessage]; - - // And display the keyboard - [self.inputToolbarView becomeFirstResponder]; - } - - }]]; - + + if (weakSelf) + { + typeof(self) self = weakSelf; + + [self cancelEventSelection]; + + // Quote the message a la Markdown into the input toolbar composer + self.inputToolbarView.textMessage = [NSString stringWithFormat:@"%@\n>%@\n\n", self.inputToolbarView.textMessage, selectedComponent.textMessage]; + + // And display the keyboard + [self.inputToolbarView becomeFirstResponder]; + } + + }]]; + if (BuildSettings.messageDetailsAllowShare) { [currentAlert addAction:[UIAlertAction actionWithTitle:NSLocalizedStringFromTable(@"room_event_action_share", @"Vector", nil) @@ -2597,7 +2735,7 @@ NSNotificationName const RoomCallTileTappedNotification = @"RoomCallTileTappedNo }]]; } } - + // Check status of the selected event if (selectedEvent.sentState == MXEventSentStatePreparing || selectedEvent.sentState == MXEventSentStateEncrypting || @@ -2611,37 +2749,37 @@ NSNotificationName const RoomCallTileTappedNotification = @"RoomCallTileTappedNo [currentAlert addAction:[UIAlertAction actionWithTitle:NSLocalizedStringFromTable(@"room_event_action_cancel_send", @"Vector", nil) style:UIAlertActionStyleDefault handler:^(UIAlertAction * action) { - - // Get again the loader - MXMediaLoader *loader = [MXMediaManager existingUploaderWithId:uploadId]; - if (loader) - { - [loader cancel]; - } - // Hide the progress animation - roomBubbleTableViewCell.progressView.hidden = YES; - - if (weakSelf) - { - typeof(self) self = weakSelf; - - self->currentAlert = nil; - - // Remove the outgoing message and its related cached file. - [[NSFileManager defaultManager] removeItemAtPath:roomBubbleTableViewCell.bubbleData.attachment.cacheFilePath error:nil]; - [[NSFileManager defaultManager] removeItemAtPath:roomBubbleTableViewCell.bubbleData.attachment.thumbnailCachePath error:nil]; - - // Cancel and remove the outgoing message - [self.roomDataSource.room cancelSendingOperation:selectedEvent.eventId]; - [self.roomDataSource removeEventWithEventId:selectedEvent.eventId]; - - [self cancelEventSelection]; - } - - }]]; + + // Get again the loader + MXMediaLoader *loader = [MXMediaManager existingUploaderWithId:uploadId]; + if (loader) + { + [loader cancel]; + } + // Hide the progress animation + roomBubbleTableViewCell.progressView.hidden = YES; + + if (weakSelf) + { + typeof(self) self = weakSelf; + + self->currentAlert = nil; + + // Remove the outgoing message and its related cached file. + [[NSFileManager defaultManager] removeItemAtPath:roomBubbleTableViewCell.bubbleData.attachment.cacheFilePath error:nil]; + [[NSFileManager defaultManager] removeItemAtPath:roomBubbleTableViewCell.bubbleData.attachment.thumbnailCachePath error:nil]; + + // Cancel and remove the outgoing message + [self.roomDataSource.room cancelSendingOperation:selectedEvent.eventId]; + [self.roomDataSource removeEventWithEventId:selectedEvent.eventId]; + + [self cancelEventSelection]; + } + + }]]; } } - + if (attachment.type != MXKAttachmentTypeSticker) { if (BuildSettings.messageDetailsAllowShare) @@ -2698,24 +2836,24 @@ NSNotificationName const RoomCallTileTappedNotification = @"RoomCallTileTappedNo [currentAlert addAction:[UIAlertAction actionWithTitle:NSLocalizedStringFromTable(@"room_event_action_cancel_download", @"Vector", nil) style:UIAlertActionStyleDefault handler:^(UIAlertAction * action) { - - if (weakSelf) - { - typeof(self) self = weakSelf; - - [self cancelEventSelection]; - - // Get again the loader - MXMediaLoader *loader = [MXMediaManager existingDownloaderWithIdentifier:downloadId]; - if (loader) - { - [loader cancel]; - } - // Hide the progress animation - roomBubbleTableViewCell.progressView.hidden = YES; - } - - }]]; + + if (weakSelf) + { + typeof(self) self = weakSelf; + + [self cancelEventSelection]; + + // Get again the loader + MXMediaLoader *loader = [MXMediaManager existingDownloaderWithIdentifier:downloadId]; + if (loader) + { + [loader cancel]; + } + // Hide the progress animation + roomBubbleTableViewCell.progressView.hidden = YES; + } + + }]]; } } @@ -2726,35 +2864,35 @@ NSNotificationName const RoomCallTileTappedNotification = @"RoomCallTileTappedNo [currentAlert addAction:[UIAlertAction actionWithTitle:NSLocalizedStringFromTable(@"room_event_action_redact", @"Vector", nil) style:UIAlertActionStyleDefault handler:^(UIAlertAction * action) { - - if (weakSelf) - { - typeof(self) self = weakSelf; - - [self cancelEventSelection]; - - [self startActivityIndicator]; - - [self.roomDataSource.room redactEvent:selectedEvent.eventId reason:nil success:^{ - - __strong __typeof(weakSelf)self = weakSelf; - [self stopActivityIndicator]; - - } failure:^(NSError *error) { - - __strong __typeof(weakSelf)self = weakSelf; - [self stopActivityIndicator]; - - NSLog(@"[RoomVC] Redact event (%@) failed", selectedEvent.eventId); - //Alert user - [[AppDelegate theDelegate] showErrorAsAlert:error]; - - }]; - } - - }]]; + + if (weakSelf) + { + typeof(self) self = weakSelf; + + [self cancelEventSelection]; + + [self startActivityIndicator]; + + [self.roomDataSource.room redactEvent:selectedEvent.eventId reason:nil success:^{ + + __strong __typeof(weakSelf)self = weakSelf; + [self stopActivityIndicator]; + + } failure:^(NSError *error) { + + __strong __typeof(weakSelf)self = weakSelf; + [self stopActivityIndicator]; + + NSLog(@"[RoomVC] Redact event (%@) failed", selectedEvent.eventId); + //Alert user + [[AppDelegate theDelegate] showErrorAsAlert:error]; + + }]; + } + + }]]; } - + if (BuildSettings.messageDetailsAllowPermalink) { [currentAlert addAction:[UIAlertAction actionWithTitle:NSLocalizedStringFromTable(@"room_event_action_permalink", @"Vector", nil) @@ -2789,12 +2927,12 @@ NSNotificationName const RoomCallTileTappedNotification = @"RoomCallTileTappedNo [currentAlert addAction:[UIAlertAction actionWithTitle:NSLocalizedStringFromTable(@"room_event_action_reaction_history", @"Vector", nil) style:UIAlertActionStyleDefault handler:^(UIAlertAction * action) { - - [self cancelEventSelection]; - - // Show reaction history - [self showReactionHistoryForEventId:selectedEvent.eventId animated:YES]; - }]]; + + [self cancelEventSelection]; + + // Show reaction history + [self showReactionHistoryForEventId:selectedEvent.eventId animated:YES]; + }]]; } if (BuildSettings.messageDetailsAllowViewSource) @@ -2814,8 +2952,8 @@ NSNotificationName const RoomCallTileTappedNotification = @"RoomCallTileTappedNo } }]]; - - + + // Add "View Decrypted Source" for e2ee event we can decrypt if (selectedEvent.isEncrypted && selectedEvent.clearEvent) { @@ -2946,37 +3084,37 @@ NSNotificationName const RoomCallTileTappedNotification = @"RoomCallTileTappedNo }]]; } - + if (self.roomDataSource.room.summary.isEncrypted) { [currentAlert addAction:[UIAlertAction actionWithTitle:NSLocalizedStringFromTable(@"room_event_action_view_encryption", @"Vector", nil) style:UIAlertActionStyleDefault handler:^(UIAlertAction * action) { - - if (weakSelf) - { - typeof(self) self = weakSelf; - [self cancelEventSelection]; - - // Display encryption details - [self showEncryptionInformation:selectedEvent]; - } - - }]]; + + if (weakSelf) + { + typeof(self) self = weakSelf; + [self cancelEventSelection]; + + // Display encryption details + [self showEncryptionInformation:selectedEvent]; + } + + }]]; } } [currentAlert addAction:[UIAlertAction actionWithTitle:NSLocalizedStringFromTable(@"cancel", @"Vector", nil) style:UIAlertActionStyleCancel handler:^(UIAlertAction * action) { - - if (weakSelf) - { - typeof(self) self = weakSelf; - [self hideContextualMenuAnimated:YES]; - } - - }]]; + + if (weakSelf) + { + typeof(self) self = weakSelf; + [self hideContextualMenuAnimated:YES]; + } + + }]]; // Do not display empty action sheet if (currentAlert.actions.count > 1) @@ -3083,7 +3221,7 @@ NSNotificationName const RoomCallTileTappedNotification = @"RoomCallTileTappedNo { NSString *eventId = arguments[1]; MXEvent *event = [self.roomDataSource eventWithEventId:eventId]; - + if (event) { [self reRequestKeysAndShowExplanationAlert:event]; @@ -3160,7 +3298,7 @@ NSNotificationName const RoomCallTileTappedNotification = @"RoomCallTileTappedNo shouldDoAction = NO; break; } - } + } } break; case UITextItemInteractionPresentActions: @@ -3200,7 +3338,7 @@ NSNotificationName const RoomCallTileTappedNotification = @"RoomCallTileTappedNo - (void)selectEventWithId:(NSString*)eventId inputToolBarSendMode:(RoomInputToolbarViewSendMode)inputToolBarSendMode showTimestamp:(BOOL)showTimestamp { - [self setInputToolBarSendMode:inputToolBarSendMode]; + [self setInputToolBarSendMode:inputToolBarSendMode forEventWithId:eventId]; customizedRoomDataSource.showBubbleDateTimeOnSelection = showTimestamp; customizedRoomDataSource.selectedEventId = eventId; @@ -3211,7 +3349,7 @@ NSNotificationName const RoomCallTileTappedNotification = @"RoomCallTileTappedNo - (void)cancelEventSelection { - [self setInputToolBarSendMode:RoomInputToolbarViewSendModeSend]; + [self setInputToolBarSendMode:RoomInputToolbarViewSendModeSend forEventWithId:nil]; if (currentAlert) { @@ -3325,100 +3463,14 @@ NSNotificationName const RoomCallTileTappedNotification = @"RoomCallTileTappedNo self.navigationItem.backBarButtonItem = [[UIBarButtonItem alloc] initWithTitle:@"" style:UIBarButtonItemStylePlain target:nil action:nil]; } -#pragma mark - RoomInputToolbarViewDelegate +#pragma mark - VoIP -- (void)roomInputToolbarViewPresentStickerPicker:(MXKRoomInputToolbarView*)toolbarView -{ - // Search for the sticker picker widget in the user account - Widget *widget = [[WidgetManager sharedManager] userWidgets:self.roomDataSource.mxSession ofTypes:@[kWidgetTypeStickerPicker]].firstObject; - - if (widget) - { - // Display the widget - [widget widgetUrl:^(NSString * _Nonnull widgetUrl) { - - StickerPickerViewController *stickerPickerVC = [[StickerPickerViewController alloc] initWithUrl:widgetUrl forWidget:widget]; - - stickerPickerVC.roomDataSource = self.roomDataSource; - - [self.navigationController pushViewController:stickerPickerVC animated:YES]; - } failure:^(NSError * _Nonnull error) { - - NSLog(@"[RoomVC] Cannot display widget %@", widget); - [[AppDelegate theDelegate] showErrorAsAlert:error]; - }]; - } - else - { - // The Sticker picker widget is not installed yet. Propose the user to install it - __weak typeof(self) weakSelf = self; - - [currentAlert dismissViewControllerAnimated:NO completion:nil]; - - NSString *alertMessage = [NSString stringWithFormat:@"%@\n%@", - NSLocalizedStringFromTable(@"widget_sticker_picker_no_stickerpacks_alert", @"Vector", nil), - NSLocalizedStringFromTable(@"widget_sticker_picker_no_stickerpacks_alert_add_now", @"Vector", nil) - ]; - - currentAlert = [UIAlertController alertControllerWithTitle:nil message:alertMessage preferredStyle:UIAlertControllerStyleAlert]; - - [currentAlert addAction:[UIAlertAction actionWithTitle:[NSBundle mxk_localizedStringForKey:@"no"] - style:UIAlertActionStyleCancel - handler:^(UIAlertAction * action) - { - if (weakSelf) - { - typeof(self) self = weakSelf; - self->currentAlert = nil; - } - - }]]; - - [currentAlert addAction:[UIAlertAction actionWithTitle:[NSBundle mxk_localizedStringForKey:@"yes"] - style:UIAlertActionStyleDefault - handler:^(UIAlertAction * action) - { - if (weakSelf) - { - typeof(self) self = weakSelf; - self->currentAlert = nil; - - // Show the sticker picker settings screen - IntegrationManagerViewController *modularVC = [[IntegrationManagerViewController alloc] - initForMXSession:self.roomDataSource.mxSession - inRoom:self.roomDataSource.roomId - screen:[IntegrationManagerViewController screenForWidget:kWidgetTypeStickerPicker] - widgetId:nil]; - - [self presentViewController:modularVC animated:NO completion:nil]; - } - }]]; - - [currentAlert mxk_setAccessibilityIdentifier:@"RoomVCStickerPickerAlert"]; - [self presentViewController:currentAlert animated:YES completion:nil]; - } -} - -#pragma mark - MXKRoomInputToolbarViewDelegate - -- (void)roomInputToolbarView:(MXKRoomInputToolbarView*)toolbarView isTyping:(BOOL)typing -{ - [super roomInputToolbarView:toolbarView isTyping:typing]; - - // Cancel potential selected event (to leave edition mode) - NSString *selectedEventId = customizedRoomDataSource.selectedEventId; - if (typing && selectedEventId && ![self.roomDataSource canReplyToEventWithId:selectedEventId]) - { - [self cancelEventSelection]; - } -} - -- (void)roomInputToolbarView:(MXKRoomInputToolbarView*)toolbarView placeCallWithVideo:(BOOL)video +- (void)placeCallWithVideo:(BOOL)video { __weak __typeof(self) weakSelf = self; - + NSString *appDisplayName = [[NSBundle mainBundle] infoDictionary][@"CFBundleDisplayName"]; - + // Check app permissions first [MXKTools checkAccessForCall:video manualChangeMessageForAudio:[NSString stringWithFormat:[NSBundle mxk_localizedStringForKey:@"microphone_access_not_granted_for_call"], appDisplayName] @@ -3433,15 +3485,15 @@ NSNotificationName const RoomCallTileTappedNotification = @"RoomCallTileTappedNo { if (video) { - [self roomInputToolbarView:toolbarView placeCallWithVideo2:video]; + [self placeCallWithVideo2:video]; } else if (self.mainSession.callManager.supportsPSTN) { - [self showVoiceCallActionSheetWith:toolbarView]; + [self showVoiceCallActionSheet]; } else { - [self roomInputToolbarView:toolbarView placeCallWithVideo2:NO]; + [self placeCallWithVideo2:NO]; } } else @@ -3452,149 +3504,145 @@ NSNotificationName const RoomCallTileTappedNotification = @"RoomCallTileTappedNo }]; } -- (void)showVoiceCallActionSheetWith:(MXKRoomInputToolbarView *)toolbarView +- (void)showVoiceCallActionSheet { // Ask the user the kind of the call: voice or dialpad? currentAlert = [UIAlertController alertControllerWithTitle:nil message:nil preferredStyle:UIAlertControllerStyleActionSheet]; - + __weak typeof(self) weakSelf = self; [currentAlert addAction:[UIAlertAction actionWithTitle:NSLocalizedStringFromTable(@"room_place_voice_call", @"Vector", nil) style:UIAlertActionStyleDefault handler:^(UIAlertAction * action) { - - if (weakSelf) - { - typeof(self) self = weakSelf; - self->currentAlert = nil; - - [self roomInputToolbarView:toolbarView placeCallWithVideo2:NO]; - } - - }]]; + + if (weakSelf) + { + typeof(self) self = weakSelf; + self->currentAlert = nil; + + [self placeCallWithVideo2:NO]; + } + + }]]; [currentAlert addAction:[UIAlertAction actionWithTitle:NSLocalizedStringFromTable(@"room_open_dialpad", @"Vector", nil) - style:UIAlertActionStyleDefault - handler:^(UIAlertAction * action) { - - if (weakSelf) - { - typeof(self) self = weakSelf; - self->currentAlert = nil; - - [self openDialpad]; - } - - }]]; + style:UIAlertActionStyleDefault + handler:^(UIAlertAction * action) { + + if (weakSelf) + { + typeof(self) self = weakSelf; + self->currentAlert = nil; + + [self openDialpad]; + } + + }]]; [currentAlert addAction:[UIAlertAction actionWithTitle:[NSBundle mxk_localizedStringForKey:@"cancel"] - style:UIAlertActionStyleCancel - handler:^(UIAlertAction * action) { - - if (weakSelf) - { - typeof(self) self = weakSelf; - self->currentAlert = nil; - } - - }]]; + style:UIAlertActionStyleCancel + handler:^(UIAlertAction * action) { + + if (weakSelf) + { + typeof(self) self = weakSelf; + self->currentAlert = nil; + } + + }]]; - if ([toolbarView isKindOfClass:[RoomInputToolbarView class]]) - { - RoomInputToolbarView *toolbar = (RoomInputToolbarView *)toolbarView; - [currentAlert popoverPresentationController].sourceView = toolbar.voiceCallButton; - [currentAlert popoverPresentationController].sourceRect = toolbar.voiceCallButton.bounds; - } + [currentAlert popoverPresentationController].barButtonItem = self.navigationItem.rightBarButtonItems.firstObject; + [currentAlert popoverPresentationController].permittedArrowDirections = UIPopoverArrowDirectionUp; [self presentViewController:currentAlert animated:YES completion:nil]; } -- (void)roomInputToolbarView:(MXKRoomInputToolbarView*)toolbarView placeCallWithVideo2:(BOOL)video +- (void)placeCallWithVideo2:(BOOL)video { - __weak __typeof(self) weakSelf = self; - + __weak __typeof(self) weakSelf = self; + // If there is already a jitsi widget, join it Widget *jitsiWidget = [customizedRoomDataSource jitsiWidget]; if (jitsiWidget) { [[AppDelegate theDelegate] displayJitsiViewControllerWithWidget:jitsiWidget andVideo:video]; } - + // If enabled, create the conf using jitsi widget and open it directly else if (RiotSettings.shared.createConferenceCallsWithJitsi && self.roomDataSource.room.summary.membersCount.joined > 2) { [self startActivityIndicator]; - + [[WidgetManager sharedManager] createJitsiWidgetInRoom:self.roomDataSource.room withVideo:video success:^(Widget *jitsiWidget) { - if (weakSelf) - { - typeof(self) self = weakSelf; - [self stopActivityIndicator]; - - [[AppDelegate theDelegate] displayJitsiViewControllerWithWidget:jitsiWidget andVideo:video]; - } - } + if (weakSelf) + { + typeof(self) self = weakSelf; + [self stopActivityIndicator]; + + [[AppDelegate theDelegate] displayJitsiViewControllerWithWidget:jitsiWidget andVideo:video]; + } + } failure:^(NSError *error) { - if (weakSelf) - { - typeof(self) self = weakSelf; - [self stopActivityIndicator]; - - [self showJitsiErrorAsAlert:error]; - } - }]; + if (weakSelf) + { + typeof(self) self = weakSelf; + [self stopActivityIndicator]; + + [self showJitsiErrorAsAlert:error]; + } + }]; } // Classic conference call is not supported in encrypted rooms else if (self.roomDataSource.room.summary.isEncrypted && self.roomDataSource.room.summary.membersCount.joined > 2) { [currentAlert dismissViewControllerAnimated:NO completion:nil]; - + currentAlert = [UIAlertController alertControllerWithTitle:[NSBundle mxk_localizedStringForKey:@"room_no_conference_call_in_encrypted_rooms"] message:nil preferredStyle:UIAlertControllerStyleAlert]; - + [currentAlert addAction:[UIAlertAction actionWithTitle:[NSBundle mxk_localizedStringForKey:@"ok"] style:UIAlertActionStyleDefault handler:^(UIAlertAction * action) { - if (weakSelf) - { - typeof(self) self = weakSelf; - self->currentAlert = nil; - } - - }]]; - + if (weakSelf) + { + typeof(self) self = weakSelf; + self->currentAlert = nil; + } + + }]]; + [currentAlert mxk_setAccessibilityIdentifier:@"RoomVCCallAlert"]; [self presentViewController:currentAlert animated:YES completion:nil]; } - + // In case of conference call, check that the user has enough power level else if (self.roomDataSource.room.summary.membersCount.joined > 2 && ![MXCallManager canPlaceConferenceCallInRoom:self.roomDataSource.room roomState:self.roomDataSource.roomState]) { [currentAlert dismissViewControllerAnimated:NO completion:nil]; - + currentAlert = [UIAlertController alertControllerWithTitle:[NSBundle mxk_localizedStringForKey:@"room_no_power_to_create_conference_call"] message:nil preferredStyle:UIAlertControllerStyleAlert]; - + [currentAlert addAction:[UIAlertAction actionWithTitle:[NSBundle mxk_localizedStringForKey:@"ok"] style:UIAlertActionStyleDefault handler:^(UIAlertAction * action) { - if (weakSelf) - { - typeof(self) self = weakSelf; - self->currentAlert = nil; - } - }]]; - + if (weakSelf) + { + typeof(self) self = weakSelf; + self->currentAlert = nil; + } + }]]; + [currentAlert mxk_setAccessibilityIdentifier:@"RoomVCCallAlert"]; [self presentViewController:currentAlert animated:YES completion:nil]; } - + // Classic 1:1 or group call can be done else { @@ -3602,7 +3650,7 @@ NSNotificationName const RoomCallTileTappedNotification = @"RoomCallTileTappedNo } } -- (void)roomInputToolbarViewHangupCall:(MXKRoomInputToolbarView *)toolbarView +- (void)hangupCall { MXCall *callInRoom = [self.roomDataSource.mxSession.callManager callInRoom:self.roomDataSource.roomId]; if (callInRoom) @@ -3613,11 +3661,25 @@ NSNotificationName const RoomCallTileTappedNotification = @"RoomCallTileTappedNo { [[AppDelegate theDelegate].jitsiViewController hangup]; } - + [self refreshActivitiesViewDisplay]; [self refreshRoomInputToolbar]; } +#pragma mark - MXKRoomInputToolbarViewDelegate + +- (void)roomInputToolbarView:(MXKRoomInputToolbarView*)toolbarView isTyping:(BOOL)typing +{ + [super roomInputToolbarView:toolbarView isTyping:typing]; + + // Cancel potential selected event (to leave edition mode) + NSString *selectedEventId = customizedRoomDataSource.selectedEventId; + if (typing && selectedEventId && ![self.roomDataSource canReplyToEventWithId:selectedEventId]) + { + [self cancelEventSelection]; + } +} + - (void)roomInputToolbarView:(MXKRoomInputToolbarView*)toolbarView heightDidChanged:(CGFloat)height completion:(void (^)(BOOL finished))completion { if (self.roomInputToolbarContainerHeightConstraint.constant != height) @@ -3640,32 +3702,16 @@ NSNotificationName const RoomCallTileTappedNotification = @"RoomCallTileTappedNo if (!toolbarView.placeholder) { // Restore the placeholder if any - toolbarView.placeholder = savedInputToolbarPlaceholder.length ? savedInputToolbarPlaceholder : nil; + toolbarView.placeholder = self->savedInputToolbarPlaceholder.length ? self->savedInputToolbarPlaceholder : nil; } - savedInputToolbarPlaceholder = nil; + self->savedInputToolbarPlaceholder = nil; }]; } } -- (void)roomInputToolbarViewDidTapFileUpload:(MXKRoomInputToolbarView *)toolbarView +- (void)roomInputToolbarViewDidTapCancel:(MXKRoomInputToolbarView*)toolbarView { - MXKDocumentPickerPresenter *documentPickerPresenter = [MXKDocumentPickerPresenter new]; - documentPickerPresenter.delegate = self; - - NSArray *allowedUTIs = @[MXKUTI.data]; - [documentPickerPresenter presentDocumentPickerWith:allowedUTIs from:self animated:YES completion:nil]; - - self.documentPickerPresenter = documentPickerPresenter; -} - -- (void)roomInputToolbarViewDidTapCamera:(MXKRoomInputToolbarView*)toolbarView -{ - [self showCameraControllerAnimated:YES]; -} - -- (void)roomInputToolbarViewDidTapMediaLibrary:(MXKRoomInputToolbarView*)toolbarView -{ - [self showMediaPickerAnimated:YES]; + [self cancelEventSelection]; } #pragma mark - RoomParticipantsViewControllerDelegate @@ -3689,50 +3735,54 @@ NSNotificationName const RoomCallTileTappedNotification = @"RoomCallTileTappedNo #pragma mark - Action +- (IBAction)onVoiceCallPressed:(id)sender +{ + if (self.isCallActive) + { + [self hangupCall]; + } + else + { + [self placeCallWithVideo:NO]; + } +} + +- (IBAction)onVideoCallPressed:(id)sender +{ + [self placeCallWithVideo:YES]; +} + +- (IBAction)onIntegrationsPressed:(id)sender +{ + WidgetPickerViewController *widgetPicker = [[WidgetPickerViewController alloc] initForMXSession:self.roomDataSource.mxSession + inRoom:self.roomDataSource.roomId]; + + [widgetPicker showInViewController:self]; +} + +- (void)scrollToBottomAction:(id)sender +{ + [self goBackToLive]; +} + - (IBAction)onButtonPressed:(id)sender { - // Search button - if (sender == self.navigationItem.rightBarButtonItem) - { - [self performSegueWithIdentifier:@"showRoomSearch" sender:self]; - } - // Matrix Apps button - else if (self.navigationItem.rightBarButtonItems.count == 2 && sender == self.navigationItem.rightBarButtonItems[1]) - { - if ([self widgetsCount:NO]) - { - WidgetPickerViewController *widgetPicker = [[WidgetPickerViewController alloc] initForMXSession:self.roomDataSource.mxSession - inRoom:self.roomDataSource.roomId]; - - [widgetPicker showInViewController:self]; - } - else - { - // No widgets -> Directly show the integration manager - IntegrationManagerViewController *modularVC = [[IntegrationManagerViewController alloc] initForMXSession:self.roomDataSource.mxSession - inRoom:self.roomDataSource.roomId - screen:kIntegrationManagerMainScreen - widgetId:nil]; - - [self presentViewController:modularVC animated:NO completion:nil]; - } - } - else if (sender == self.jumpToLastUnreadButton) + if (sender == self.jumpToLastUnreadButton) { // Dismiss potential keyboard. [self dismissKeyboard]; - + // Jump to the last unread event by using a temporary room data source initialized with the last unread event id. MXWeakify(self); [RoomDataSource loadRoomDataSourceWithRoomId:self.roomDataSource.roomId initialEventId:self.roomDataSource.room.accountData.readMarkerEventId andMatrixSession:self.mainSession onComplete:^(id roomDataSource) { MXStrongifyAndReturnIfNil(self); - + [roomDataSource finalizeInitialization]; - + // Center the bubbles table content on the bottom of the read marker event in order to display correctly the read marker view. self.centerBubblesTableViewContentOnTheInitialEventBottom = YES; [self displayRoom:roomDataSource]; - + // Give the data source ownership to the room view controller. self.hasRoomDataSourceOwnership = YES; }]; @@ -3928,12 +3978,12 @@ NSNotificationName const RoomCallTileTappedNotification = @"RoomCallTileTappedNo MXWeakify(self); [RoomDataSource loadRoomDataSourceWithRoomId:self.roomDataSource.roomId initialEventId:eventId andMatrixSession:self.mainSession onComplete:^(id roomDataSource) { MXStrongifyAndReturnIfNil(self); - + [roomDataSource finalizeInitialization]; ((RoomDataSource*)roomDataSource).markTimelineInitialEvent = YES; - + [self displayRoom:roomDataSource]; - + self.hasRoomDataSourceOwnership = YES; }]; } @@ -4007,7 +4057,7 @@ NSNotificationName const RoomCallTileTappedNotification = @"RoomCallTileTappedNo MXWeakify(self); [self.roomDataSource.room liveTimeline:^(MXEventTimeline *liveTimeline) { MXStrongifyAndReturnIfNil(self); - + [liveTimeline removeListener:self->typingNotifListener]; self->typingNotifListener = nil; }]; @@ -4025,7 +4075,7 @@ NSNotificationName const RoomCallTileTappedNotification = @"RoomCallTileTappedNo MXWeakify(self); self->typingNotifListener = [self.roomDataSource.room listenToEventsOfTypes:@[kMXEventTypeStringTypingNotification] onEvent:^(MXEvent *event, MXTimelineDirection direction, MXRoomState *roomState) { MXStrongifyAndReturnIfNil(self); - + // Handle only live events if (direction == MXTimelineDirectionForwards) { @@ -4037,7 +4087,7 @@ NSNotificationName const RoomCallTileTappedNotification = @"RoomCallTileTappedNo { [typingUsers removeObjectAtIndex:index]; } - + // Ignore this notification if both arrays are empty if (self->currentTypingUsers.count || typingUsers.count) { @@ -4046,7 +4096,7 @@ NSNotificationName const RoomCallTileTappedNotification = @"RoomCallTileTappedNo } } }]; - + // Retrieve the current typing users list NSMutableArray *typingUsers = [NSMutableArray arrayWithArray:self.roomDataSource.room.typingUsers]; // Remove typing info for the current user @@ -4062,52 +4112,51 @@ NSNotificationName const RoomCallTileTappedNotification = @"RoomCallTileTappedNo - (void)refreshTypingNotification { - if ([self.activitiesView isKindOfClass:RoomActivitiesView.class]) - { - // Prepare here typing notification - NSString* text = nil; - NSUInteger count = currentTypingUsers.count; - - // get the room member names - NSMutableArray *names = [[NSMutableArray alloc] init]; - - // keeps the only the first two users - for(int i = 0; i < MIN(count, 2); i++) + RoomDataSource *roomDataSource = (RoomDataSource *) self.roomDataSource; + BOOL needsUpdate = currentTypingUsers.count != roomDataSource.currentTypingUsers.count; + + NSMutableArray *typingUsers = [NSMutableArray new]; + for (NSUInteger i = 0 ; i < currentTypingUsers.count ; i++) { + NSString *userId = currentTypingUsers[i]; + MXRoomMember* member = [self.roomDataSource.roomState.members memberWithUserId:userId]; + TypingUserInfo *userInfo; + if (member) { - NSString* name = currentTypingUsers[i]; - - MXRoomMember* member = [self.roomDataSource.roomState.members memberWithUserId:name]; - - if (member && member.displayname.length) - { - name = member.displayname; - } - - // sanity check - if (name) - { - [names addObject:name]; - } - } - - if (0 == names.count) - { - // something to do ? - } - else if (1 == names.count) - { - text = [NSString stringWithFormat:NSLocalizedStringFromTable(@"room_one_user_is_typing", @"Vector", nil), names[0]]; - } - else if (2 == names.count) - { - text = [NSString stringWithFormat:NSLocalizedStringFromTable(@"room_two_users_are_typing", @"Vector", nil), names[0], names[1]]; + userInfo = [[TypingUserInfo alloc] initWithMember: member]; } else { - text = [NSString stringWithFormat:NSLocalizedStringFromTable(@"room_many_users_are_typing", @"Vector", nil), names[0], names[1]]; + userInfo = [[TypingUserInfo alloc] initWithUserId: userId]; + } + [typingUsers addObject:userInfo]; + needsUpdate = needsUpdate || userInfo.userId != ((MXRoomMember *) roomDataSource.currentTypingUsers[i]).userId; + } + + if (needsUpdate) + { + BOOL needsReload = roomDataSource.currentTypingUsers == nil; + roomDataSource.currentTypingUsers = typingUsers; + if (needsReload) + { + [self.bubblesTableView reloadData]; + } + else + { + NSInteger count = [self.bubblesTableView numberOfRowsInSection:0]; + NSIndexPath *lastIndexPath = [NSIndexPath indexPathForRow:count - 1 inSection:0]; + [self.bubblesTableView reloadRowsAtIndexPaths:@[lastIndexPath] withRowAnimation:UITableViewRowAnimationFade]; } - [((RoomActivitiesView*) self.activitiesView) displayTypingNotification:text]; + if (self.isScrollToBottomHidden + && !self.bubblesTableView.isDragging + && !self.bubblesTableView.isDecelerating) + { + NSInteger count = [self.bubblesTableView numberOfRowsInSection:0]; + if (count) + { + [self scrollBubblesTableViewToBottomAnimated:YES]; + } + } } } @@ -4202,7 +4251,7 @@ NSNotificationName const RoomCallTileTappedNotification = @"RoomCallTileTappedNo - (void)listenWidgetNotifications { kMXKWidgetManagerDidUpdateWidgetObserver = [[NSNotificationCenter defaultCenter] addObserverForName:kWidgetManagerDidUpdateWidgetNotification object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *notif) { - + Widget *widget = notif.object; if (widget.mxSession == self.roomDataSource.mxSession && [widget.roomId isEqualToString:customizedRoomDataSource.roomId]) @@ -4224,10 +4273,10 @@ NSNotificationName const RoomCallTileTappedNotification = @"RoomCallTileTappedNo error = [NSError errorWithDomain:error.domain code:error.code userInfo:@{ - NSLocalizedDescriptionKey: NSLocalizedStringFromTable(@"room_conference_call_no_power", @"Vector", nil) - }]; + NSLocalizedDescriptionKey: NSLocalizedStringFromTable(@"room_conference_call_no_power", @"Vector", nil) + }]; } - + // Alert user [[AppDelegate theDelegate] showErrorAsAlert:error]; } @@ -4241,7 +4290,7 @@ NSNotificationName const RoomCallTileTappedNotification = @"RoomCallTileTappedNo { widgetsCount += [[WidgetManager sharedManager] userWidgets:self.roomDataSource.room.mxSession].count; } - + return widgetsCount; } @@ -4252,38 +4301,41 @@ NSNotificationName const RoomCallTileTappedNotification = @"RoomCallTileTappedNo if ([self.activitiesView isKindOfClass:RoomActivitiesView.class]) { RoomActivitiesView *roomActivitiesView = (RoomActivitiesView*)self.activitiesView; - + // Reset gesture recognizers while (roomActivitiesView.gestureRecognizers.count) { [roomActivitiesView removeGestureRecognizer:roomActivitiesView.gestureRecognizers[0]]; } - + Widget *jitsiWidget = [customizedRoomDataSource jitsiWidget]; - + if ([self.roomDataSource.mxSession.syncError.errcode isEqualToString:kMXErrCodeStringResourceLimitExceeded]) { + self.activitiesViewExpanded = YES; [roomActivitiesView showResourceLimitExceededError:self.roomDataSource.mxSession.syncError.userInfo onAdminContactTapped:^(NSURL *adminContactURL) { [[UIApplication sharedApplication] vc_open:adminContactURL completionHandler:^(BOOL success) { - if (!success) - { + if (!success) + { NSLog(@"[RoomVC] refreshActivitiesViewDisplay: adminContact(%@) cannot be opened", adminContactURL); - } + } }]; }]; } else if ([AppDelegate theDelegate].isOffline) { + self.activitiesViewExpanded = YES; [roomActivitiesView displayNetworkErrorNotification:NSLocalizedStringFromTable(@"room_offline_notification", @"Vector", nil)]; } else if (customizedRoomDataSource.roomState.isObsolete) { + self.activitiesViewExpanded = YES; MXWeakify(self); [roomActivitiesView displayRoomReplacementWithRoomLinkTappedHandler:^{ MXStrongifyAndReturnIfNil(self); - + MXEvent *stoneTombEvent = [self->customizedRoomDataSource.roomState stateEventsWithType:kMXEventTypeStringRoomTombStone].lastObject; - + NSString *replacementRoomId = self->customizedRoomDataSource.roomState.tombStoneContent.replacementRoomId; if ([self.roomDataSource.mxSession roomWithRoomId:replacementRoomId]) { @@ -4295,20 +4347,20 @@ NSNotificationName const RoomCallTileTappedNotification = @"RoomCallTileTappedNo // Else auto join it via the server that sent the event NSLog(@"[RoomVC] Auto join an upgraded room: %@ -> %@. Sender: %@", self->customizedRoomDataSource.roomState.roomId, replacementRoomId, stoneTombEvent.sender); - + NSString *viaSenderServer = [MXTools serverNameInMatrixIdentifier:stoneTombEvent.sender]; - + if (viaSenderServer) { [self startActivityIndicator]; [self.roomDataSource.mxSession joinRoom:replacementRoomId viaServers:@[viaSenderServer] success:^(MXRoom *room) { [self stopActivityIndicator]; - + [[AppDelegate theDelegate] showRoom:replacementRoomId andEventId:nil withMatrixSession:self.roomDataSource.mxSession]; - + } failure:^(NSError *error) { [self stopActivityIndicator]; - + NSLog(@"[RoomVC] Failed to join an upgraded room. Error: %@", error); [[AppDelegate theDelegate] showErrorAsAlert:error]; @@ -4330,6 +4382,7 @@ NSNotificationName const RoomCallTileTappedNotification = @"RoomCallTileTappedNo } else { + self.activitiesViewExpanded = YES; [roomActivitiesView displayOngoingConferenceCall:^(BOOL video) { NSLog(@"[RoomVC] onOngoingConferenceCallPressed"); @@ -4356,54 +4409,55 @@ NSNotificationName const RoomCallTileTappedNotification = @"RoomCallTileTappedNo } else { + self.activitiesViewExpanded = YES; [roomActivitiesView displayOngoingConferenceCall:^(BOOL video) { - + NSLog(@"[RoomVC] onOngoingConferenceCallPressed (jitsi)"); - + __weak __typeof(self) weakSelf = self; NSString *appDisplayName = [[NSBundle mainBundle] infoDictionary][@"CFBundleDisplayName"]; - + // Check app permissions first [MXKTools checkAccessForCall:video manualChangeMessageForAudio:[NSString stringWithFormat:[NSBundle mxk_localizedStringForKey:@"microphone_access_not_granted_for_call"], appDisplayName] manualChangeMessageForVideo:[NSString stringWithFormat:[NSBundle mxk_localizedStringForKey:@"camera_access_not_granted_for_call"], appDisplayName] showPopUpInViewController:self completionHandler:^(BOOL granted) { - - if (weakSelf) - { - if (granted) - { - // Present the Jitsi view controller - [appDelegate displayJitsiViewControllerWithWidget:jitsiWidget andVideo:video]; - } - else - { - NSLog(@"[RoomVC] onOngoingConferenceCallPressed: Warning: The application does not have the perssion to join the call"); - } - } - }]; - + + if (weakSelf) + { + if (granted) + { + // Present the Jitsi view controller + [appDelegate displayJitsiViewControllerWithWidget:jitsiWidget andVideo:video]; + } + else + { + NSLog(@"[RoomVC] onOngoingConferenceCallPressed: Warning: The application does not have the perssion to join the call"); + } + } + }]; + } onClosePressed:^{ - + [self startActivityIndicator]; - + // Close the widget __weak __typeof(self) weakSelf = self; [[WidgetManager sharedManager] closeWidget:jitsiWidget.widgetId inRoom:self.roomDataSource.room success:^{ - + if (weakSelf) { typeof(self) self = weakSelf; [self stopActivityIndicator]; - + // The banner will automatically leave thanks to kWidgetManagerDidUpdateWidgetNotification } - + } failure:^(NSError *error) { if (weakSelf) { typeof(self) self = weakSelf; - + [self showJitsiErrorAsAlert:error]; [self stopActivityIndicator]; } @@ -4421,32 +4475,26 @@ NSNotificationName const RoomCallTileTappedNotification = @"RoomCallTileTappedNo // Retrieve the unread messages count NSUInteger unreadCount = self.roomDataSource.room.summary.localUnreadEventCount; - if (unreadCount == 0) - { - // Refresh the typing notification here - // We will keep visible this notification (if any) beside the "scroll to bottom" icon. - [self refreshTypingNotification]; - } - - [roomActivitiesView displayScrollToBottomIcon:unreadCount onIconTapGesture:^{ - - [self goBackToLive]; - - }]; + self.scrollToBottomBadgeLabel.text = unreadCount ? [NSString stringWithFormat:@"%lu", unreadCount] : nil; + self.scrollToBottomHidden = NO; } else if (serverNotices.usageLimit && serverNotices.usageLimit.isServerNoticeUsageLimit) { - [roomActivitiesView showResourceUsageLimitNotice:serverNotices.usageLimit onAdminContactTapped:^(NSURL *adminContactURL) { + self.scrollToBottomHidden = YES; + self.activitiesViewExpanded = YES; + [roomActivitiesView showResourceUsageLimitNotice:serverNotices.usageLimit onAdminContactTapped:^(NSURL *adminContactURL) { [[UIApplication sharedApplication] vc_open:adminContactURL completionHandler:^(BOOL success) { - if (!success) - { + if (!success) + { NSLog(@"[RoomVC] refreshActivitiesViewDisplay: adminContact(%@) cannot be opened", adminContactURL); - } + } }]; }]; } else { + self.scrollToBottomHidden = YES; + self.activitiesViewExpanded = NO; [self refreshTypingNotification]; } } @@ -4473,22 +4521,22 @@ NSNotificationName const RoomCallTileTappedNotification = @"RoomCallTileTappedNo { // Switch back to the room live timeline managed by MXKRoomDataSourceManager MXKRoomDataSourceManager *roomDataSourceManager = [MXKRoomDataSourceManager sharedManagerForMatrixSession:self.mainSession]; - + MXWeakify(self); [roomDataSourceManager roomDataSourceForRoom:self.roomDataSource.roomId create:YES onComplete:^(MXKRoomDataSource *roomDataSource) { MXStrongifyAndReturnIfNil(self); - + // Scroll to bottom the bubble history on the display refresh. self->shouldScrollToBottomOnTableRefresh = YES; - + [self displayRoom:roomDataSource]; - + // The room view controller do not have here the data source ownership. self.hasRoomDataSourceOwnership = NO; - + [self refreshActivitiesViewDisplay]; [self refreshJumpToLastUnreadBannerDisplay]; - + if (self.saveProgressTextInput) { // Restore the potential message partially typed before jump to last unread messages. @@ -4503,11 +4551,16 @@ NSNotificationName const RoomCallTileTappedNotification = @"RoomCallTileTappedNo - (void)refreshMissedDiscussionsCount:(BOOL)force { // Ignore this action when no room is displayed - if (!self.roomDataSource || !missedDiscussionsBarButtonCustomView) + if (!self.roomDataSource || !missedDiscussionsBadgeLabel + || [UIDevice currentDevice].userInterfaceIdiom != UIUserInterfaceIdiomPhone + || ([[UIScreen mainScreen] nativeBounds].size.height > 2532 && UIInterfaceOrientationIsLandscape([UIApplication sharedApplication].statusBarOrientation))) { + self.showMissedDiscussionsBadge = NO; return; } + self.showMissedDiscussionsBadge = YES; + NSUInteger highlightCount = 0; NSUInteger missedCount = [[AppDelegate theDelegate].masterTabBarController missedDiscussionsCount]; @@ -4541,8 +4594,6 @@ NSNotificationName const RoomCallTileTappedNotification = @"RoomCallTileTappedNo missedDiscussionsCount = missedCount; missedHighlightCount = highlightCount; - NSMutableArray *leftBarButtonItems = [NSMutableArray arrayWithArray: self.navigationItem.leftBarButtonItems]; - if (missedCount) { // Refresh missed discussions count label @@ -4555,65 +4606,12 @@ NSNotificationName const RoomCallTileTappedNotification = @"RoomCallTileTappedNo missedDiscussionsBadgeLabel.text = [NSString stringWithFormat:@"%tu", missedCount]; } - [missedDiscussionsBadgeLabel sizeToFit]; - - // Update the label background view frame - CGRect frame = missedDiscussionsBadgeLabelBgView.frame; - frame.size.width = round(missedDiscussionsBadgeLabel.frame.size.width + 18); - - if ([GBDeviceInfo deviceInfo].osVersion.major < 11) - { - // Consider the main navigation controller if the current view controller is embedded inside a split view controller. - UINavigationController *mainNavigationController = self.navigationController; - if (self.splitViewController.isCollapsed && self.splitViewController.viewControllers.count) - { - mainNavigationController = self.splitViewController.viewControllers.firstObject; - } - UINavigationItem *backItem = mainNavigationController.navigationBar.backItem; - UIBarButtonItem *backButton = backItem.backBarButtonItem; - - if (backButton && !backButton.title.length) - { - // Shift the badge on the left to be close the back icon - frame.origin.x = ([GBDeviceInfo deviceInfo].displayInfo.display > GBDeviceDisplay4Inch ? -35 : -25); - } - else - { - frame.origin.x = 0; - } - } - - // Caution: set label background view frame only in case of changes to prevent from looping on 'viewDidLayoutSubviews'. - if (!CGRectEqualToRect(missedDiscussionsBadgeLabelBgView.frame, frame)) - { - missedDiscussionsBadgeLabelBgView.frame = frame; - } - - // Set the right background color - if (highlightCount) - { - missedDiscussionsBadgeLabelBgView.backgroundColor = ThemeService.shared.theme.noticeColor; - } - else - { - missedDiscussionsBadgeLabelBgView.backgroundColor = ThemeService.shared.theme.noticeSecondaryColor; - } - - if (!missedDiscussionsButton || [leftBarButtonItems indexOfObject:missedDiscussionsButton] == NSNotFound) - { - missedDiscussionsButton = [[UIBarButtonItem alloc] initWithCustomView:missedDiscussionsBarButtonCustomView]; - - // Add it in left bar items - [leftBarButtonItems addObject:missedDiscussionsButton]; - } + missedDiscussionsDotView.alpha = highlightCount == 0 ? 0 : 1; } - else if (missedDiscussionsButton) + else { - [leftBarButtonItems removeObject:missedDiscussionsButton]; - missedDiscussionsButton = nil; + missedDiscussionsBadgeLabel.text = nil; } - - self.navigationItem.leftBarButtonItems = leftBarButtonItems; } } @@ -4625,7 +4623,7 @@ NSNotificationName const RoomCallTileTappedNotification = @"RoomCallTileTappedNo if ([self.activitiesView isKindOfClass:RoomActivitiesView.class]) { sentStatus = self.roomDataSource.room.sentStatus; - + if (sentStatus != RoomSentStatusOk) { NSString *notification = sentStatus == RoomSentStatusSentFailedDueToUnknownDevices ? @@ -4633,6 +4631,7 @@ NSNotificationName const RoomCallTileTappedNotification = @"RoomCallTileTappedNo NSLocalizedStringFromTable(@"room_unsent_messages_notification", @"Vector", nil); RoomActivitiesView *roomActivitiesView = (RoomActivitiesView*) self.activitiesView; + self.activitiesViewExpanded = YES; [roomActivitiesView displayUnsentMessagesNotification:notification withResendLink:^{ [self resendAllUnsentMessages]; @@ -4654,40 +4653,40 @@ NSNotificationName const RoomCallTileTappedNotification = @"RoomCallTileTappedNo [currentAlert addAction:[UIAlertAction actionWithTitle:NSLocalizedStringFromTable(@"room_resend_unsent_messages", @"Vector", nil) style:UIAlertActionStyleDefault handler:^(UIAlertAction * action) { - - if (weakSelf) - { - typeof(self) self = weakSelf; - [self resendAllUnsentMessages]; - self->currentAlert = nil; - } - - }]]; + + if (weakSelf) + { + typeof(self) self = weakSelf; + [self resendAllUnsentMessages]; + self->currentAlert = nil; + } + + }]]; [currentAlert addAction:[UIAlertAction actionWithTitle:NSLocalizedStringFromTable(@"room_delete_unsent_messages", @"Vector", nil) style:UIAlertActionStyleDefault handler:^(UIAlertAction * action) { - - if (weakSelf) - { - typeof(self) self = weakSelf; - [self cancelAllUnsentMessages]; - self->currentAlert = nil; - } - - }]]; + + if (weakSelf) + { + typeof(self) self = weakSelf; + [self cancelAllUnsentMessages]; + self->currentAlert = nil; + } + + }]]; [currentAlert addAction:[UIAlertAction actionWithTitle:NSLocalizedStringFromTable(@"cancel", @"Vector", nil) style:UIAlertActionStyleCancel handler:^(UIAlertAction * action) { - - if (weakSelf) - { - typeof(self) self = weakSelf; - self->currentAlert = nil; - } - - }]]; + + if (weakSelf) + { + typeof(self) self = weakSelf; + self->currentAlert = nil; + } + + }]]; [currentAlert mxk_setAccessibilityIdentifier:@"RoomVCUnsentMessagesMenuAlert"]; [currentAlert popoverPresentationController].sourceView = roomActivitiesView; @@ -4739,39 +4738,39 @@ NSNotificationName const RoomCallTileTappedNotification = @"RoomCallTileTappedNo [currentAlert addAction:[UIAlertAction actionWithTitle:[NSBundle mxk_localizedStringForKey:@"unknown_devices_verify"] style:UIAlertActionStyleDefault handler:^(UIAlertAction * action) { - - if (weakSelf) - { - typeof(self) self = weakSelf; - self->currentAlert = nil; - - [self performSegueWithIdentifier:@"showUnknownDevices" sender:self]; - } - - }]]; + + if (weakSelf) + { + typeof(self) self = weakSelf; + self->currentAlert = nil; + + [self performSegueWithIdentifier:@"showUnknownDevices" sender:self]; + } + + }]]; [currentAlert addAction:[UIAlertAction actionWithTitle:[NSBundle mxk_localizedStringForKey:@"unknown_devices_send_anyway"] style:UIAlertActionStyleDefault handler:^(UIAlertAction * action) { - - if (weakSelf) - { - typeof(self) self = weakSelf; - self->currentAlert = nil; - - // Acknowledge the existence of all devices - [self startActivityIndicator]; - [self.mainSession.crypto setDevicesKnown:self->unknownDevices complete:^{ - - self->unknownDevices = nil; - [self stopActivityIndicator]; - - // And resend pending messages - [self resendAllUnsentMessages]; - }]; - } - - }]]; + + if (weakSelf) + { + typeof(self) self = weakSelf; + self->currentAlert = nil; + + // Acknowledge the existence of all devices + [self startActivityIndicator]; + [self.mainSession.crypto setDevicesKnown:self->unknownDevices complete:^{ + + self->unknownDevices = nil; + [self stopActivityIndicator]; + + // And resend pending messages + [self resendAllUnsentMessages]; + }]; + } + + }]]; [currentAlert mxk_setAccessibilityIdentifier:@"RoomVCUnknownDevicesAlert"]; [self presentViewController:currentAlert animated:YES completion:nil]; @@ -4782,7 +4781,7 @@ NSNotificationName const RoomCallTileTappedNotification = @"RoomCallTileTappedNo { MXEvent *event = notif.object; NSString *previousId = notif.userInfo[kMXEventIdentifierKey]; - + if ([customizedRoomDataSource.selectedEventId isEqualToString:previousId]) { NSLog(@"[RoomVC] eventDidChangeIdentifier: Update selectedEventId"); @@ -4837,7 +4836,7 @@ NSNotificationName const RoomCallTileTappedNotification = @"RoomCallTileTappedNo - (void)cancelAllUnsentMessages { 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); @@ -4859,8 +4858,10 @@ NSNotificationName const RoomCallTileTappedNotification = @"RoomCallTileTappedNo index ++; } } + + [self refreshActivitiesViewDisplay]; }]]; - + [self presentViewController:currentAlert animated:YES completion:nil]; } @@ -4976,21 +4977,21 @@ NSNotificationName const RoomCallTileTappedNotification = @"RoomCallTileTappedNo [UIView animateWithDuration:1.5 delay:0 options:UIViewAnimationOptionBeginFromCurrentState | UIViewAnimationOptionCurveEaseIn animations:^{ - - readMarkerTableViewCell.readMarkerViewLeadingConstraint.constant = readMarkerTableViewCell.readMarkerViewTrailingConstraint.constant = readMarkerTableViewCell.bubbleOverlayContainer.frame.size.width / 2; - readMarkerTableViewCell.readMarkerView.alpha = 0; - - // Force to render the view - [readMarkerTableViewCell.bubbleOverlayContainer layoutIfNeeded]; - - } + + readMarkerTableViewCell.readMarkerViewLeadingConstraint.constant = readMarkerTableViewCell.readMarkerViewTrailingConstraint.constant = readMarkerTableViewCell.bubbleOverlayContainer.frame.size.width / 2; + readMarkerTableViewCell.readMarkerView.alpha = 0; + + // Force to render the view + [readMarkerTableViewCell.bubbleOverlayContainer layoutIfNeeded]; + + } completion:^(BOOL finished){ - - readMarkerTableViewCell.readMarkerView.hidden = YES; - readMarkerTableViewCell.readMarkerView.alpha = 1; - - readMarkerTableViewCell = nil; - }]; + + readMarkerTableViewCell.readMarkerView.hidden = YES; + readMarkerTableViewCell.readMarkerView.alpha = 1; + + readMarkerTableViewCell = nil; + }]; }); } @@ -5069,110 +5070,110 @@ NSNotificationName const RoomCallTileTappedNotification = @"RoomCallTileTappedNo [currentAlert addAction:[UIAlertAction actionWithTitle:[NSBundle mxk_localizedStringForKey:@"cancel"] style:UIAlertActionStyleCancel handler:^(UIAlertAction * action) { - - if (weakSelf) - { - typeof(self) self = weakSelf; - self->currentAlert = nil; - } - - }]]; + + if (weakSelf) + { + typeof(self) self = weakSelf; + self->currentAlert = nil; + } + + }]]; [currentAlert addAction:[UIAlertAction actionWithTitle:NSLocalizedStringFromTable(@"invite", @"Vector", nil) style:UIAlertActionStyleDefault handler:^(UIAlertAction * action) { - - // Sanity check - if (!weakSelf) - { - return; - } - - typeof(self) self = weakSelf; - self->currentAlert = nil; - - MXSession* session = self.roomDataSource.mxSession; - NSString* roomId = self.roomDataSource.roomId; - MXRoom *room = [session roomWithRoomId:roomId]; - - NSArray *identifiers = contact.matrixIdentifiers; - NSString *participantId; - - if (identifiers.count) - { - participantId = identifiers.firstObject; - - // Invite this user if a room is defined - [room inviteUser:participantId success:^{ - - // Refresh display by removing the contacts picker - [contactsTableViewController withdrawViewControllerAnimated:YES completion:nil]; - - } failure:^(NSError *error) { - - NSLog(@"[RoomVC] Invite %@ failed", participantId); - // Alert user - [[AppDelegate theDelegate] showErrorAsAlert:error]; - - }]; - } - else - { - if (contact.emailAddresses.count) - { - // This is a local contact, consider the first email by default. - // TODO: Prompt the user to select the right email. - MXKEmail *email = contact.emailAddresses.firstObject; - participantId = email.emailAddress; - } - else - { - // This is the text filled by the user. - participantId = contact.displayName; - } - - // Is it an email or a Matrix user ID? - if ([MXTools isEmailAddress:participantId]) - { - [room inviteUserByEmail:participantId success:^{ - - // Refresh display by removing the contacts picker - [contactsTableViewController withdrawViewControllerAnimated:YES completion:nil]; - - } failure:^(NSError *error) { - - NSLog(@"[RoomVC] Invite be email %@ failed", participantId); - // Alert user - if ([error.domain isEqualToString:kMXRestClientErrorDomain] - && error.code == MXRestClientErrorMissingIdentityServer) - { - NSString *message = [NSBundle mxk_localizedStringForKey:@"error_invite_3pid_with_no_identity_server"]; - [[AppDelegate theDelegate] showAlertWithTitle:message message:nil]; - } - else - { - [[AppDelegate theDelegate] showErrorAsAlert:error]; - } - }]; - } - else //if ([MXTools isMatrixUserIdentifier:participantId]) - { - [room inviteUser:participantId success:^{ - - // Refresh display by removing the contacts picker - [contactsTableViewController withdrawViewControllerAnimated:YES completion:nil]; - - } failure:^(NSError *error) { - - NSLog(@"[RoomVC] Invite %@ failed", participantId); - // Alert user - [[AppDelegate theDelegate] showErrorAsAlert:error]; - - }]; - } - } - - }]]; + + // Sanity check + if (!weakSelf) + { + return; + } + + typeof(self) self = weakSelf; + self->currentAlert = nil; + + MXSession* session = self.roomDataSource.mxSession; + NSString* roomId = self.roomDataSource.roomId; + MXRoom *room = [session roomWithRoomId:roomId]; + + NSArray *identifiers = contact.matrixIdentifiers; + NSString *participantId; + + if (identifiers.count) + { + participantId = identifiers.firstObject; + + // Invite this user if a room is defined + [room inviteUser:participantId success:^{ + + // Refresh display by removing the contacts picker + [contactsTableViewController withdrawViewControllerAnimated:YES completion:nil]; + + } failure:^(NSError *error) { + + NSLog(@"[RoomVC] Invite %@ failed", participantId); + // Alert user + [[AppDelegate theDelegate] showErrorAsAlert:error]; + + }]; + } + else + { + if (contact.emailAddresses.count) + { + // This is a local contact, consider the first email by default. + // TODO: Prompt the user to select the right email. + MXKEmail *email = contact.emailAddresses.firstObject; + participantId = email.emailAddress; + } + else + { + // This is the text filled by the user. + participantId = contact.displayName; + } + + // Is it an email or a Matrix user ID? + if ([MXTools isEmailAddress:participantId]) + { + [room inviteUserByEmail:participantId success:^{ + + // Refresh display by removing the contacts picker + [contactsTableViewController withdrawViewControllerAnimated:YES completion:nil]; + + } failure:^(NSError *error) { + + NSLog(@"[RoomVC] Invite be email %@ failed", participantId); + // Alert user + if ([error.domain isEqualToString:kMXRestClientErrorDomain] + && error.code == MXRestClientErrorMissingIdentityServer) + { + NSString *message = [NSBundle mxk_localizedStringForKey:@"error_invite_3pid_with_no_identity_server"]; + [[AppDelegate theDelegate] showAlertWithTitle:message message:nil]; + } + else + { + [[AppDelegate theDelegate] showErrorAsAlert:error]; + } + }]; + } + else //if ([MXTools isMatrixUserIdentifier:participantId]) + { + [room inviteUser:participantId success:^{ + + // Refresh display by removing the contacts picker + [contactsTableViewController withdrawViewControllerAnimated:YES completion:nil]; + + } failure:^(NSError *error) { + + NSLog(@"[RoomVC] Invite %@ failed", participantId); + // Alert user + [[AppDelegate theDelegate] showErrorAsAlert:error]; + + }]; + } + } + + }]]; [currentAlert mxk_setAccessibilityIdentifier:@"RoomVCInviteAlert"]; [self presentViewController:currentAlert animated:YES completion:nil]; @@ -5191,22 +5192,22 @@ NSNotificationName const RoomCallTileTappedNotification = @"RoomCallTileTappedNo [self presentReviewUnverifiedSessionsAlert]; return; } - + // Make the re-request [self.mainSession.crypto reRequestRoomKeyForEvent:event]; - + // Observe kMXEventDidDecryptNotification to remove automatically the dialog // if the user has shared the keys from another device mxEventDidDecryptNotificationObserver = [[NSNotificationCenter defaultCenter] addObserverForName:kMXEventDidDecryptNotification object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *notif) { MXStrongifyAndReturnIfNil(self); - + MXEvent *decryptedEvent = notif.object; - + if ([decryptedEvent.eventId isEqualToString:event.eventId]) { [[NSNotificationCenter defaultCenter] removeObserver:self->mxEventDidDecryptNotificationObserver]; self->mxEventDidDecryptNotificationObserver = nil; - + if (self->currentAlert == alert) { [self->currentAlert dismissViewControllerAnimated:YES completion:nil]; @@ -5214,51 +5215,51 @@ NSNotificationName const RoomCallTileTappedNotification = @"RoomCallTileTappedNo } } }]; - + // Show the explanation dialog alert = [UIAlertController alertControllerWithTitle:NSLocalizedStringFromTable(@"rerequest_keys_alert_title", @"Vector", nil) - message:NSLocalizedStringFromTable(@"rerequest_keys_alert_message", @"Vector", nil) - preferredStyle:UIAlertControllerStyleAlert]; + message:NSLocalizedStringFromTable(@"rerequest_keys_alert_message", @"Vector", nil) + preferredStyle:UIAlertControllerStyleAlert]; currentAlert = alert; - - + + [alert addAction:[UIAlertAction actionWithTitle:[NSBundle mxk_localizedStringForKey:@"ok"] - style:UIAlertActionStyleDefault - handler:^(UIAlertAction * action) - { - MXStrongifyAndReturnIfNil(self); - - [[NSNotificationCenter defaultCenter] removeObserver:self->mxEventDidDecryptNotificationObserver]; - self->mxEventDidDecryptNotificationObserver = nil; - - self->currentAlert = nil; - }]]; - + style:UIAlertActionStyleDefault + handler:^(UIAlertAction * action) + { + MXStrongifyAndReturnIfNil(self); + + [[NSNotificationCenter defaultCenter] removeObserver:self->mxEventDidDecryptNotificationObserver]; + self->mxEventDidDecryptNotificationObserver = nil; + + self->currentAlert = nil; + }]]; + [self presentViewController:currentAlert animated:YES completion:nil]; } - (void)presentReviewUnverifiedSessionsAlert { NSLog(@"[MasterTabBarController] presentReviewUnverifiedSessionsAlertWithSession"); - + [currentAlert dismissViewControllerAnimated:NO completion:nil]; - + UIAlertController *alert = [UIAlertController alertControllerWithTitle:NSLocalizedStringFromTable(@"key_verification_self_verify_unverified_sessions_alert_title", @"Vector", nil) message:NSLocalizedStringFromTable(@"key_verification_self_verify_unverified_sessions_alert_message", @"Vector", nil) preferredStyle:UIAlertControllerStyleAlert]; - + [alert addAction:[UIAlertAction actionWithTitle:NSLocalizedStringFromTable(@"key_verification_self_verify_unverified_sessions_alert_validate_action", @"Vector", nil) style:UIAlertActionStyleDefault handler:^(UIAlertAction * action) { - [self showSettingsSecurityScreen]; - }]]; - + [self showSettingsSecurityScreen]; + }]]; + [alert addAction:[UIAlertAction actionWithTitle:NSLocalizedStringFromTable(@"later", @"Vector", nil) style:UIAlertActionStyleCancel handler:nil]]; - + [self presentViewController:alert animated:YES completion:nil]; - + currentAlert = alert; } @@ -5309,12 +5310,12 @@ NSNotificationName const RoomCallTileTappedNotification = @"RoomCallTileTappedNo - (void)listenMXSessionStateChangeNotifications { kMXSessionStateDidChangeObserver = [[NSNotificationCenter defaultCenter] addObserverForName:kMXSessionStateDidChangeNotification object:self.roomDataSource.mxSession queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *notif) { - + if (self.roomDataSource.mxSession.state == MXSessionStateSyncError || self.roomDataSource.mxSession.state == MXSessionStateRunning) { [self refreshActivitiesViewDisplay]; - + // update inputToolbarView [self updateRoomInputToolbarViewClassIfNeeded]; } @@ -5411,10 +5412,10 @@ NSNotificationName const RoomCallTileTappedNotification = @"RoomCallTileTappedNo fromSingleTapGesture:usedSingleTapGesture animated:animated completion:^{ - }]; + }]; preventBubblesTableViewScroll = YES; - [self selectEventWithId:selectedEventId]; + [self selectEventWithId:selectedEventId]; } - (void)hideContextualMenuAnimated:(BOOL)animated @@ -5469,7 +5470,7 @@ NSNotificationName const RoomCallTileTappedNotification = @"RoomCallTileTappedNo [self cancelEventSelection]; [self.roomDataSource resendEventWithEventId:event.eventId success:nil failure:nil]; }; - + return resendMenuItem; } @@ -5497,7 +5498,7 @@ NSNotificationName const RoomCallTileTappedNotification = @"RoomCallTileTappedNo [self presentViewController:self->currentAlert animated:YES completion:nil]; }]; }; - + return deleteMenuItem; } @@ -5510,7 +5511,7 @@ NSNotificationName const RoomCallTileTappedNotification = @"RoomCallTileTappedNo MXStrongifyAndReturnIfNil(self); [self hideContextualMenuAnimated:YES cancelEventSelection:NO completion:nil]; [self editEventContentWithId:event.eventId]; - + // And display the keyboard [self.inputToolbarView becomeFirstResponder]; }; @@ -5524,7 +5525,7 @@ NSNotificationName const RoomCallTileTappedNotification = @"RoomCallTileTappedNo { MXKRoomBubbleTableViewCell *roomBubbleTableViewCell = (MXKRoomBubbleTableViewCell *)cell; MXKAttachment *attachment = roomBubbleTableViewCell.bubbleData.attachment; - + MXWeakify(self); BOOL isCopyActionEnabled = !attachment || attachment.type != MXKAttachmentTypeSticker; @@ -5612,7 +5613,7 @@ NSNotificationName const RoomCallTileTappedNotification = @"RoomCallTileTappedNo }]; } }; - + return copyMenuItem; } @@ -5627,7 +5628,7 @@ NSNotificationName const RoomCallTileTappedNotification = @"RoomCallTileTappedNo [self hideContextualMenuAnimated:YES cancelEventSelection:NO completion:nil]; [self selectEventWithId:event.eventId inputToolBarSendMode:RoomInputToolbarViewSendModeReply showTimestamp:NO]; - + // And display the keyboard [self.inputToolbarView becomeFirstResponder]; }; @@ -5645,7 +5646,7 @@ NSNotificationName const RoomCallTileTappedNotification = @"RoomCallTileTappedNo [self hideContextualMenuAnimated:YES completion:nil]; [self showAdditionalActionsMenuForEvent:event inCell:cell animated:YES]; }; - + return moreMenuItem; } @@ -5705,7 +5706,7 @@ NSNotificationName const RoomCallTileTappedNotification = @"RoomCallTileTappedNo if (cellRow >= 0) { - NSIndexPath *cellIndexPath = [NSIndexPath indexPathForRow:cellRow inSection:0]; + NSIndexPath *cellIndexPath = [NSIndexPath indexPathForRow:cellRow inSection:0]; UITableViewCell *cell = [self.bubblesTableView cellForRowAtIndexPath:cellIndexPath]; sourceView = cell; diff --git a/Riot/Modules/Room/RoomViewController.xib b/Riot/Modules/Room/RoomViewController.xib index 15ac46dc7..2371d6a0e 100644 --- a/Riot/Modules/Room/RoomViewController.xib +++ b/Riot/Modules/Room/RoomViewController.xib @@ -1,10 +1,11 @@ - + - + + @@ -13,6 +14,7 @@ + @@ -28,6 +30,8 @@ + + @@ -36,7 +40,7 @@ - + @@ -54,7 +58,7 @@ + + + + + + + + + diff --git a/Riot/Modules/Room/Views/Activities/RoomActivitiesView.h b/Riot/Modules/Room/Views/Activities/RoomActivitiesView.h index 54eb3feb7..b66c61a5f 100644 --- a/Riot/Modules/Room/Views/Activities/RoomActivitiesView.h +++ b/Riot/Modules/Room/Views/Activities/RoomActivitiesView.h @@ -73,16 +73,6 @@ */ - (void)displayOngoingConferenceCall:(void (^)(BOOL video))ongoingConferenceCallPressed onClosePressed:(void (^)(void))ongoingConferenceCallClosePressed; -/** - Display a "scroll to bottom" icon. - Replace the current notification if any. - - @param newMessagesCount the count of the unread messages. - @param onIconTapGesture block called when user taps on notification icon. - */ -- (void)displayScrollToBottomIcon:(NSUInteger)newMessagesCount onIconTapGesture:(void (^)(void))onIconTapGesture; - - /** Notify that the a room is obsolete and a replacement room is available. diff --git a/Riot/Modules/Room/Views/Activities/RoomActivitiesView.m b/Riot/Modules/Room/Views/Activities/RoomActivitiesView.m index fa6ebd452..1f65678ad 100644 --- a/Riot/Modules/Room/Views/Activities/RoomActivitiesView.m +++ b/Riot/Modules/Room/Views/Activities/RoomActivitiesView.m @@ -268,59 +268,6 @@ [self checkHeight:YES]; } -- (void)displayScrollToBottomIcon:(NSUInteger)newMessagesCount onIconTapGesture:(void (^)(void))onIconTapGesture -{ - if (newMessagesCount) - { - [self reset]; - - self.iconImageView.image = [UIImage imageNamed:@"scrolldown"]; - self.iconImageView.tintColor = ThemeService.shared.theme.noticeColor; - - NSString *notification; - if (newMessagesCount > 1) - { - notification = NSLocalizedStringFromTable(@"room_new_messages_notification", @"Vector", nil); - } - else - { - notification = NSLocalizedStringFromTable(@"room_new_message_notification", @"Vector", nil); - } - self.messageLabel.text = [NSString stringWithFormat:notification, newMessagesCount]; - self.messageLabel.textColor = ThemeService.shared.theme.warningColor; - self.messageLabel.hidden = NO; - } - else - { - // We keep the current message if any - [self resetIcon]; - - self.iconImageView.image = [UIImage imageNamed:@"scrolldown"]; - self.iconImageView.tintColor = ThemeService.shared.theme.textPrimaryColor; - } - self.iconImageView.hidden = NO; - - // Make VoiceOver consider it as a button - self.iconImageView.accessibilityLabel = NSLocalizedStringFromTable(@"room_accessiblity_scroll_to_bottom", @"Vector", nil); - self.iconImageView.isAccessibilityElement = YES; - self.iconImageView.accessibilityTraits = UIAccessibilityTraitButton; - - 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; - } - - [self checkHeight:YES]; -} - - (void)displayRoomReplacementWithRoomLinkTappedHandler:(void (^)(void))onRoomReplacementLinkTapped { [self reset]; diff --git a/Riot/Modules/Room/Views/Activities/RoomActivitiesView.xib b/Riot/Modules/Room/Views/Activities/RoomActivitiesView.xib index 8f491dc8c..cc44ee7e6 100644 --- a/Riot/Modules/Room/Views/Activities/RoomActivitiesView.xib +++ b/Riot/Modules/Room/Views/Activities/RoomActivitiesView.xib @@ -128,7 +128,7 @@ - + diff --git a/Riot/Modules/Room/Views/BubbleCells/RoomTypingBubbleCell.swift b/Riot/Modules/Room/Views/BubbleCells/RoomTypingBubbleCell.swift new file mode 100644 index 000000000..f0eba07b2 --- /dev/null +++ b/Riot/Modules/Room/Views/BubbleCells/RoomTypingBubbleCell.swift @@ -0,0 +1,143 @@ +// +// 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 + +@objcMembers +class RoomTypingBubbleCell: MXKTableViewCell, Themable { + // MARK: - Constants + + private enum Constants { + static let maxPictureCount = 4 + static let pictureSize: CGFloat = 24 + static let pictureMaxMargin: CGFloat = 16 + static let pictureMinMargin: CGFloat = 8 + } + + // MARK: - Outlets + + @IBOutlet private weak var additionalUsersLabel: UILabel! + @IBOutlet private weak var additionalUsersLabelLeadingConstraint: NSLayoutConstraint! + @IBOutlet private weak var dotsView: DotsView! + @IBOutlet private weak var dotsViewLeadingConstraint: NSLayoutConstraint! + + // MARK: - members + + private var userPictureViews: [MXKImageView] = [] + + // MARK: - Lifecycle + + override func awakeFromNib() { + super.awakeFromNib() + + update(theme: ThemeService.shared().theme) + } + + override func prepareForReuse() { + super.prepareForReuse() + + for pictureView in userPictureViews { + pictureView.removeFromSuperview() + } + } + + override func layoutSubviews() { + super.layoutSubviews() + + dotsView.isHidden = userPictureViews.count == 0 + + guard userPictureViews.count > 0 else { + return + } + + additionalUsersLabel?.sizeToFit() + + var pictureViewsMaxX: CGFloat = 0 + var xOffset: CGFloat = 0 + for pictureView in userPictureViews { + pictureView.center = CGPoint(x: Constants.pictureMaxMargin + xOffset + pictureView.bounds.midX, y: self.bounds.midY) + xOffset += round(pictureView.bounds.maxX * 2 / 3) + pictureViewsMaxX = pictureView.frame.maxX + } + + let leftMagin: CGFloat = pictureViewsMaxX + (userPictureViews.count == 1 ? Constants.pictureMaxMargin : Constants.pictureMinMargin) + additionalUsersLabelLeadingConstraint.constant = leftMagin + + dotsViewLeadingConstraint?.constant = additionalUsersLabel.text.isEmptyOrNil == true ? leftMagin : leftMagin + 8 + additionalUsersLabel.frame.width + } + + // MARK: - Overrides + + override class func defaultReuseIdentifier() -> String { + return String(describing: self) + } + + override class func nib() -> UINib { + return UINib(nibName: String(describing: self), bundle: nil) + } + + // MARK: - Themable + + func update(theme: Theme) { + additionalUsersLabel.textColor = theme.textSecondaryColor + dotsView.highlightedDotColor = theme.textTertiaryColor + dotsView.dotColor = theme.textSecondaryColor + } + + + // MARK: - Business methods + + func updateTypingUsers(_ typingUsers: [TypingUserInfo], mediaManager: MXMediaManager) { + for pictureView in userPictureViews { + pictureView.removeFromSuperview() + } + userPictureViews = [] + + for user in typingUsers { + if userPictureViews.count >= Constants.maxPictureCount { + break + } + + let pictureView = MXKImageView(frame: CGRect(x: 0, y: 0, width: Constants.pictureSize, height: Constants.pictureSize)) + pictureView.layer.masksToBounds = true + pictureView.layer.cornerRadius = pictureView.bounds.midX + + let defaultavatarImage = AvatarGenerator.generateAvatar(forMatrixItem: user.userId, withDisplayName: user.displayName) + pictureView.setImageURI(user.avatarUrl, withType: nil, andImageOrientation: .up, toFitViewSize: pictureView.bounds.size, with: MXThumbnailingMethodCrop, previewImage: defaultavatarImage, mediaManager: mediaManager) + + userPictureViews.append(pictureView) + self.contentView.addSubview(pictureView) + } + + switch typingUsers.count { + case 0: + additionalUsersLabel.text = nil + case 1: + additionalUsersLabel.text = firstUserNameFor(typingUsers) + default: + additionalUsersLabel.text = VectorL10n.roomMultipleTypingNotification(firstUserNameFor(typingUsers) ?? "") + } + self.setNeedsLayout() + } + + private func firstUserNameFor(_ typingUsers: Array) -> String? { + guard let firstUser = typingUsers.first else { + return nil + } + + return firstUser.displayName.isEmptyOrNil ? firstUser.userId : firstUser.displayName + } +} diff --git a/Riot/Modules/Room/Views/BubbleCells/RoomTypingBubbleCell.xib b/Riot/Modules/Room/Views/BubbleCells/RoomTypingBubbleCell.xib new file mode 100644 index 000000000..5cd15a92d --- /dev/null +++ b/Riot/Modules/Room/Views/BubbleCells/RoomTypingBubbleCell.xib @@ -0,0 +1,48 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Riot/Modules/Room/Views/InputToolbar/RoomActionItem.swift b/Riot/Modules/Room/Views/InputToolbar/RoomActionItem.swift new file mode 100644 index 000000000..773e4be3d --- /dev/null +++ b/Riot/Modules/Room/Views/InputToolbar/RoomActionItem.swift @@ -0,0 +1,30 @@ +// +// 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 + +@objcMembers +class RoomActionItem: NSObject { + let image: UIImage + let action: (() -> Void) + + init(image: UIImage, andAction action: @escaping () -> Void) { + self.image = image + self.action = action + + super.init() + } +} diff --git a/Riot/Modules/Room/Views/InputToolbar/RoomActionsBar.swift b/Riot/Modules/Room/Views/InputToolbar/RoomActionsBar.swift new file mode 100644 index 000000000..5482ab833 --- /dev/null +++ b/Riot/Modules/Room/Views/InputToolbar/RoomActionsBar.swift @@ -0,0 +1,132 @@ +// +// 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 + +@objcMembers +class RoomActionsBar: UIScrollView, Themable { + // MARK: - Properties + + var itemSpacing: CGFloat = 20 { + didSet { + self.setNeedsLayout() + } + } + + var actionItems: [RoomActionItem] = [] { + didSet { + var actionButtons: [UIButton] = [] + for (index, item) in actionItems.enumerated() { + let button = UIButton(type: .custom) + button.setImage(item.image, for: .normal) + button.addTarget(self, action: #selector(buttonAction(_:)), for: .touchUpInside) + button.tintColor = ThemeService.shared().theme.tintColor + button.tag = index + actionButtons.append(button) + addSubview(button) + } + self.actionButtons = actionButtons + self.lastBounds = .zero + self.setNeedsLayout() + } + } + + private var actionButtons: [UIButton] = [] { + willSet { + for button in actionButtons { + button.removeFromSuperview() + } + } + } + + private var lastBounds = CGRect.zero + + // MARK: - Lifecycle + + override init(frame: CGRect) { + super.init(frame: frame) + setupView() + } + + required init?(coder: NSCoder) { + super.init(coder: coder) + setupView() + } + + override func layoutSubviews() { + super.layoutSubviews() + + guard lastBounds != self.bounds else { + return + } + + lastBounds = self.bounds + + var currentX: CGFloat = 0 + for button in actionButtons { + button.transform = CGAffineTransform.identity + button.frame = CGRect(x: currentX, y: 0, width: self.bounds.height, height: self.bounds.height) + currentX = button.frame.maxX + itemSpacing + } + + self.contentSize = CGSize(width: currentX - itemSpacing, height: self.bounds.height) + } + + // MARK: - Themable + + func update(theme: Theme) { + for button in actionButtons { + button.tintColor = theme.tintColor + } + } + + // MARK: - Business methods + + func animate(showIn: Bool, completion: ((Bool) -> Void)? = nil) { + if showIn { + for button in actionButtons { + button.transform = CGAffineTransform(translationX: 0, y: self.bounds.height) + } + for (index, button) in actionButtons.enumerated() { + UIView.animate(withDuration: 0.3, delay: 0.05 * Double(index), usingSpringWithDamping: 0.45, initialSpringVelocity: 11, options: .curveEaseInOut) { + button.transform = CGAffineTransform.identity + } completion: { (finished) in + completion?(finished) + } + } + } else { + for (index, button) in actionButtons.enumerated() { + UIView.animate(withDuration: 0.25, delay: 0.05 * Double(index), options: .curveEaseInOut) { + button.transform = CGAffineTransform(translationX: 0, y: self.bounds.height) + } completion: { (finished) in + if index == self.actionButtons.count - 1 { + completion?(finished) + } + } + } + } + } + + // MARK: - Private methods + + @objc private func buttonAction(_ sender: UIButton) { + actionItems[sender.tag].action() + } + + private func setupView() { + self.showsHorizontalScrollIndicator = false + } +} diff --git a/Riot/Modules/Room/Views/InputToolbar/RoomInputToolbarView.h b/Riot/Modules/Room/Views/InputToolbar/RoomInputToolbarView.h index 41d4c0aff..b2ad883b8 100644 --- a/Riot/Modules/Room/Views/InputToolbar/RoomInputToolbarView.h +++ b/Riot/Modules/Room/Views/InputToolbar/RoomInputToolbarView.h @@ -18,6 +18,8 @@ #import "MediaPickerViewController.h" +@class RoomActionsBar; + /** Destination of the message in the composer */ @@ -32,32 +34,11 @@ typedef enum : NSUInteger @protocol RoomInputToolbarViewDelegate /** - Tells the delegate that the user wants to display the sticker picker. - - @param toolbarView the room input toolbar view. - */ -- (void)roomInputToolbarViewPresentStickerPicker:(MXKRoomInputToolbarView*)toolbarView; - -/** - Tells the delegate that the user wants to send external files. + Tells the delegate that the user wants to cancel the current edition / reply. @param toolbarView the room input toolbar view */ -- (void)roomInputToolbarViewDidTapFileUpload:(MXKRoomInputToolbarView*)toolbarView; - -/** - Tells the delegate that the user wants to take photo or video with camera. - - @param toolbarView the room input toolbar view - */ -- (void)roomInputToolbarViewDidTapCamera:(MXKRoomInputToolbarView*)toolbarView; - -/** - Tells the delegate that the user wants to show media library. - - @param toolbarView the room input toolbar view - */ -- (void)roomInputToolbarViewDidTapMediaLibrary:(MXKRoomInputToolbarView*)toolbarView; +- (void)roomInputToolbarViewDidTapCancel:(MXKRoomInputToolbarView*)toolbarView; @end @@ -74,11 +55,6 @@ typedef enum : NSUInteger @property (weak, nonatomic) IBOutlet UIView *mainToolbarView; -@property (weak, nonatomic) IBOutlet UIView *separatorView; -@property (strong, nonatomic) IBOutlet MXKImageView *pictureView; - -@property (strong, nonatomic) IBOutlet UIImageView *encryptedRoomIcon; - @property (weak, nonatomic) IBOutlet NSLayoutConstraint *mainToolbarMinHeightConstraint; @property (weak, nonatomic) IBOutlet NSLayoutConstraint *mainToolbarHeightConstraint; @@ -86,29 +62,33 @@ typedef enum : NSUInteger @property (weak, nonatomic) IBOutlet NSLayoutConstraint *messageComposerContainerTrailingConstraint; @property (weak, nonatomic) IBOutlet UIButton *attachMediaButton; -@property (weak, nonatomic) IBOutlet UIButton *voiceCallButton; -@property (weak, nonatomic) IBOutlet UIButton *hangupCallButton; -@property (weak, nonatomic) IBOutlet NSLayoutConstraint *voiceCallButtonWidthConstraint; +@property (weak, nonatomic) IBOutlet UIImageView *inputTextBackgroundView; -/** - Tell whether the call option is supported. YES by default. - */ -@property (nonatomic) BOOL supportCallOption; +@property (weak, nonatomic) IBOutlet NSLayoutConstraint *inputContextViewHeightConstraint; +@property (weak, nonatomic) IBOutlet UIImageView *inputContextImageView; +@property (weak, nonatomic) IBOutlet UILabel *inputContextLabel; +@property (weak, nonatomic) IBOutlet UIButton *inputContextButton; +@property (weak, nonatomic) IBOutlet RoomActionsBar *actionsBar; /** Tell whether the filled data will be sent encrypted. NO by default. */ @property (nonatomic) BOOL isEncryptionEnabled; +/** + Sender of the event being edited / replied. + */ +@property (nonatomic, strong) NSString *eventSenderDisplayName; + /** Destination of the message in the composer. */ @property (nonatomic) RoomInputToolbarViewSendMode sendMode; /** - Tell whether a call is active. + YES if action menu is opened. NO otherwise */ -@property (nonatomic) BOOL activeCall; +@property (nonatomic, getter=isActionMenuOpened) BOOL actionMenuOpened; @end diff --git a/Riot/Modules/Room/Views/InputToolbar/RoomInputToolbarView.m b/Riot/Modules/Room/Views/InputToolbar/RoomInputToolbarView.m index de0724e52..26a2f4c69 100644 --- a/Riot/Modules/Room/Views/InputToolbar/RoomInputToolbarView.m +++ b/Riot/Modules/Room/Views/InputToolbar/RoomInputToolbarView.m @@ -27,6 +27,14 @@ #import "WidgetManager.h" #import "IntegrationManagerViewController.h" +const double kContextBarHeight = 24; +const NSTimeInterval kSendModeAnimationDuration = .15; +const NSTimeInterval kActionMenuAttachButtonAnimationDuration = .4; +const CGFloat kActionMenuAttachButtonSpringVelocity = 7; +const CGFloat kActionMenuAttachButtonSpringDamping = .45; +const NSTimeInterval kActionMenuContentAlphaAnimationDuration = .2; +const NSTimeInterval kActionMenuComposerHeightAnimationDuration = .3; + @interface RoomInputToolbarView() { // The intermediate action sheet @@ -60,14 +68,12 @@ { [super awakeFromNib]; - _supportCallOption = YES; _sendMode = RoomInputToolbarViewSendModeSend; - - self.rightInputToolbarButton.hidden = YES; - - [self.rightInputToolbarButton setTitleColor:ThemeService.shared.theme.tintColor forState:UIControlStateNormal]; - [self.rightInputToolbarButton setTitleColor:ThemeService.shared.theme.tintColor forState:UIControlStateHighlighted]; - + self.inputContextViewHeightConstraint.constant = 0; + + [self.rightInputToolbarButton setTitle:nil forState:UIControlStateNormal]; + [self.rightInputToolbarButton setTitle:nil forState:UIControlStateHighlighted]; + self.isEncryptionEnabled = _isEncryptionEnabled; } @@ -80,8 +86,6 @@ // Remove default toolbar background color self.backgroundColor = [UIColor clearColor]; - self.separatorView.backgroundColor = ThemeService.shared.theme.lineBreakColor; - // Custom the growingTextView display growingTextView.layer.cornerRadius = 0; growingTextView.layer.borderWidth = 0; @@ -90,6 +94,8 @@ growingTextView.font = [UIFont systemFontOfSize:15]; growingTextView.textColor = ThemeService.shared.theme.textPrimaryColor; growingTextView.tintColor = ThemeService.shared.theme.tintColor; + growingTextView.placeholderColor = ThemeService.shared.theme.textTertiaryColor; + growingTextView.internalTextView.showsVerticalScrollIndicator = NO; growingTextView.internalTextView.keyboardAppearance = ThemeService.shared.theme.keyboardAppearance; if (growingTextView.isFirstResponder) @@ -99,33 +105,36 @@ } self.attachMediaButton.accessibilityLabel = NSLocalizedStringFromTable(@"room_accessibility_upload", @"Vector", nil); - self.voiceCallButton.accessibilityLabel = NSLocalizedStringFromTable(@"room_accessibility_call", @"Vector", nil); - self.hangupCallButton.accessibilityLabel = NSLocalizedStringFromTable(@"room_accessibility_hangup", @"Vector", nil); - self.hangupCallButton.tintColor = ThemeService.shared.theme.noticeColor; - self.voiceCallButton.tintColor = ThemeService.shared.theme.tintColor; - self.attachMediaButton.tintColor = ThemeService.shared.theme.tintColor; + UIImage *image = [UIImage imageNamed:@"input_text_background"]; + image = [image resizableImageWithCapInsets:UIEdgeInsetsMake(9, 15, 10, 16)]; + self.inputTextBackgroundView.image = image; + self.inputTextBackgroundView.tintColor = ThemeService.shared.theme.roomInputTextBorder; + + if ([ThemeService.shared.themeId isEqualToString:@"light"]) + { + [self.attachMediaButton setImage:[UIImage imageNamed:@"upload_icon"] forState:UIControlStateNormal]; + } + else if ([ThemeService.shared.themeId isEqualToString:@"dark"] || [ThemeService.shared.themeId isEqualToString:@"black"]) + { + [self.attachMediaButton setImage:[UIImage imageNamed:@"upload_icon_dark"] forState:UIControlStateNormal]; + } + else if (@available(iOS 12.0, *) && ThemeService.shared.theme.userInterfaceStyle == UIUserInterfaceStyleDark) { + [self.attachMediaButton setImage:[UIImage imageNamed:@"upload_icon_dark"] forState:UIControlStateNormal]; + } + + self.inputContextImageView.tintColor = ThemeService.shared.theme.textSecondaryColor; + self.inputContextLabel.textColor = ThemeService.shared.theme.textSecondaryColor; + self.inputContextButton.tintColor = ThemeService.shared.theme.textSecondaryColor; + [self.actionsBar updateWithTheme:ThemeService.shared.theme]; } #pragma mark - -- (void)setSupportCallOption:(BOOL)supportCallOption +- (void)setTextMessage:(NSString *)textMessage { - if (_supportCallOption != supportCallOption) - { - _supportCallOption = supportCallOption; - - if (supportCallOption) - { - self.voiceCallButtonWidthConstraint.constant = 46; - } - else - { - self.voiceCallButtonWidthConstraint.constant = 0; - } - - [self setNeedsUpdateConstraints]; - } + [self updateSendButtonWithMessage:textMessage]; + [super setTextMessage:textMessage]; } - (void)setIsEncryptionEnabled:(BOOL)isEncryptionEnabled @@ -137,31 +146,78 @@ - (void)setSendMode:(RoomInputToolbarViewSendMode)sendMode { + RoomInputToolbarViewSendMode previousMode = _sendMode; _sendMode = sendMode; + self.actionMenuOpened = NO; [self updatePlaceholder]; - [self updateToolbarButtonLabel]; + [self updateToolbarButtonLabelWithPreviousMode: previousMode]; } -- (void)updateToolbarButtonLabel +- (void)updateToolbarButtonLabelWithPreviousMode:(RoomInputToolbarViewSendMode)previousMode { - NSString *title; + UIImage *buttonImage; + double updatedHeight = self.mainToolbarHeightConstraint.constant; + switch (_sendMode) { case RoomInputToolbarViewSendModeReply: - title = NSLocalizedStringFromTable(@"room_action_reply", @"Vector", nil); + buttonImage = [UIImage imageNamed:@"send_icon"]; + self.inputContextImageView.image = [UIImage imageNamed:@"input_reply_icon"]; + self.inputContextLabel.text = [NSString stringWithFormat:NSLocalizedStringFromTable(@"room_message_replying_to", @"Vector", nil), self.eventSenderDisplayName]; + + self.inputContextViewHeightConstraint.constant = kContextBarHeight; + updatedHeight += kContextBarHeight; + self->growingTextView.maxHeight -= kContextBarHeight; break; case RoomInputToolbarViewSendModeEdit: - title = NSLocalizedStringFromTable(@"save", @"Vector", nil); + buttonImage = [UIImage imageNamed:@"save_icon"]; + self.inputContextImageView.image = [UIImage imageNamed:@"input_edit_icon"]; + self.inputContextLabel.text = NSLocalizedStringFromTable(@"room_message_editing", @"Vector", nil); + + self.inputContextViewHeightConstraint.constant = kContextBarHeight; + updatedHeight += kContextBarHeight; + self->growingTextView.maxHeight -= kContextBarHeight; break; default: - title = [NSBundle mxk_localizedStringForKey:@"send"]; + buttonImage = [UIImage imageNamed:@"send_icon"]; + + if (previousMode != _sendMode) + { + updatedHeight -= kContextBarHeight; + self->growingTextView.maxHeight += kContextBarHeight; + } + self.inputContextViewHeightConstraint.constant = 0; break; } + + [self.rightInputToolbarButton setImage:buttonImage forState:UIControlStateNormal]; + + if (self.maxHeight && updatedHeight > self.maxHeight) + { + growingTextView.maxHeight -= updatedHeight - self.maxHeight; + updatedHeight = self.maxHeight; + } - [self.rightInputToolbarButton setTitle:title forState:UIControlStateNormal]; - [self.rightInputToolbarButton setTitle:title forState:UIControlStateHighlighted]; + if (updatedHeight < self.mainToolbarMinHeightConstraint.constant) + { + updatedHeight = self.mainToolbarMinHeightConstraint.constant; + } + + if (self.mainToolbarHeightConstraint.constant != updatedHeight) + { + [UIView animateWithDuration:kSendModeAnimationDuration animations:^{ + self.mainToolbarHeightConstraint.constant = updatedHeight; + [self layoutIfNeeded]; + + // Update toolbar superview + if ([self.delegate respondsToSelector:@selector(roomInputToolbarView:heightDidChanged:completion:)]) + { + [self.delegate roomInputToolbarView:self heightDidChanged:updatedHeight completion:nil]; + } + }]; + } } - (void)updatePlaceholder @@ -219,19 +275,26 @@ self.placeholder = placeholder; } -- (void)setActiveCall:(BOOL)activeCall -{ - if (_activeCall != activeCall) - { - _activeCall = activeCall; +#pragma mark - Actions - self.voiceCallButton.hidden = (_activeCall || !self.rightInputToolbarButton.hidden); - self.hangupCallButton.hidden = (!_activeCall || !self.rightInputToolbarButton.hidden); +- (IBAction)cancelAction:(id)sender +{ + if ([self.delegate respondsToSelector:@selector(roomInputToolbarViewDidTapCancel:)]) + { + [self.delegate roomInputToolbarViewDidTapCancel:self]; } } #pragma mark - HPGrowingTextView delegate +- (BOOL)growingTextView:(HPGrowingTextView *)growingTextView shouldChangeTextInRange:(NSRange)range replacementText:(NSString *)text +{ + NSString *newText = [growingTextView.text stringByReplacingCharactersInRange:range withString:text]; + [self updateSendButtonWithMessage:newText]; + + return YES; +} + - (void)growingTextViewDidChange:(HPGrowingTextView *)hpGrowingTextView { // Clean the carriage return added on return press @@ -241,32 +304,19 @@ } [super growingTextViewDidChange:hpGrowingTextView]; - - if (self.rightInputToolbarButton.isEnabled && self.rightInputToolbarButton.isHidden) - { - self.rightInputToolbarButton.hidden = NO; - self.attachMediaButton.hidden = YES; - self.voiceCallButton.hidden = YES; - self.hangupCallButton.hidden = YES; - - self.messageComposerContainerTrailingConstraint.constant = self.frame.size.width - self.rightInputToolbarButton.frame.origin.x + 4; - } - else if (!self.rightInputToolbarButton.isEnabled && !self.rightInputToolbarButton.isHidden) - { - self.rightInputToolbarButton.hidden = YES; - self.attachMediaButton.hidden = NO; - self.voiceCallButton.hidden = _activeCall; - self.hangupCallButton.hidden = !_activeCall; - - self.messageComposerContainerTrailingConstraint.constant = self.frame.size.width - self.attachMediaButton.frame.origin.x + 4; - } } - (void)growingTextView:(HPGrowingTextView *)hpGrowingTextView willChangeHeight:(float)height { // Update height of the main toolbar (message composer) - CGFloat updatedHeight = height + (self.messageComposerContainerTopConstraint.constant + self.messageComposerContainerBottomConstraint.constant); + CGFloat updatedHeight = height + (self.messageComposerContainerTopConstraint.constant + self.messageComposerContainerBottomConstraint.constant) + self.inputContextViewHeightConstraint.constant; + if (self.maxHeight && updatedHeight > self.maxHeight) + { + hpGrowingTextView.maxHeight -= updatedHeight - self.maxHeight; + updatedHeight = self.maxHeight; + } + if (updatedHeight < self.mainToolbarMinHeightConstraint.constant) { updatedHeight = self.mainToolbarMinHeightConstraint.constant; @@ -287,152 +337,7 @@ { if (button == self.attachMediaButton) { - // Check whether media attachment is supported - if ([self.delegate respondsToSelector:@selector(roomInputToolbarView:presentViewController:)]) - { - // Ask the user the kind of the call: voice or video? - actionSheet = [UIAlertController alertControllerWithTitle:nil message:nil preferredStyle:UIAlertControllerStyleActionSheet]; - - __weak typeof(self) weakSelf = self; - - [actionSheet addAction:[UIAlertAction actionWithTitle:NSLocalizedStringFromTable(@"room_action_camera", @"Vector", nil) - style:UIAlertActionStyleDefault - handler:^(UIAlertAction * action) { - - if (weakSelf) - { - typeof(self) self = weakSelf; - self->actionSheet = nil; - - [self.delegate roomInputToolbarViewDidTapCamera:self]; - } - }]]; - - - [actionSheet addAction:[UIAlertAction actionWithTitle:NSLocalizedStringFromTable(@"room_action_send_photo_or_video", @"Vector", nil) - style:UIAlertActionStyleDefault - handler:^(UIAlertAction * action) { - - if (weakSelf) - { - typeof(self) self = weakSelf; - self->actionSheet = nil; - - [self.delegate roomInputToolbarViewDidTapMediaLibrary:self]; - } - - }]]; - - if (BuildSettings.allowSendingStickers) - { - [actionSheet addAction:[UIAlertAction actionWithTitle:NSLocalizedStringFromTable(@"room_action_send_sticker", @"Vector", nil) - style:UIAlertActionStyleDefault - handler:^(UIAlertAction * action) { - - if (weakSelf) - { - typeof(self) self = weakSelf; - self->actionSheet = nil; - - [self.delegate roomInputToolbarViewPresentStickerPicker:self]; - } - - }]]; - } - - [actionSheet addAction:[UIAlertAction actionWithTitle:NSLocalizedStringFromTable(@"room_action_send_file", @"Vector", nil) - style:UIAlertActionStyleDefault - handler:^(UIAlertAction * action) { - - if (weakSelf) - { - typeof(self) self = weakSelf; - self->actionSheet = nil; - - [self.delegate roomInputToolbarViewDidTapFileUpload:self]; - } - }]]; - - [actionSheet addAction:[UIAlertAction actionWithTitle:[NSBundle mxk_localizedStringForKey:@"cancel"] - style:UIAlertActionStyleCancel - handler:^(UIAlertAction * action) { - - if (weakSelf) - { - typeof(self) self = weakSelf; - self->actionSheet = nil; - } - - }]]; - - [actionSheet popoverPresentationController].sourceView = self.attachMediaButton; - [actionSheet popoverPresentationController].sourceRect = self.attachMediaButton.bounds; - [self.window.rootViewController presentViewController:actionSheet animated:YES completion:nil]; - } - else - { - NSLog(@"[RoomInputToolbarView] Attach media is not supported"); - } - } - else if (button == self.voiceCallButton) - { - if ([self.delegate respondsToSelector:@selector(roomInputToolbarView:placeCallWithVideo:)]) - { - // Ask the user the kind of the call: voice or video? - actionSheet = [UIAlertController alertControllerWithTitle:nil message:nil preferredStyle:UIAlertControllerStyleActionSheet]; - - __weak typeof(self) weakSelf = self; - [actionSheet addAction:[UIAlertAction actionWithTitle:NSLocalizedStringFromTable(@"voice", @"Vector", nil) - style:UIAlertActionStyleDefault - handler:^(UIAlertAction * action) { - - if (weakSelf) - { - typeof(self) self = weakSelf; - self->actionSheet = nil; - - [self.delegate roomInputToolbarView:self placeCallWithVideo:NO]; - } - - }]]; - - [actionSheet addAction:[UIAlertAction actionWithTitle:NSLocalizedStringFromTable(@"video", @"Vector", nil) - style:UIAlertActionStyleDefault - handler:^(UIAlertAction * action) { - - if (weakSelf) - { - typeof(self) self = weakSelf; - self->actionSheet = nil; - - [self.delegate roomInputToolbarView:self placeCallWithVideo:YES]; - } - - }]]; - - [actionSheet addAction:[UIAlertAction actionWithTitle:[NSBundle mxk_localizedStringForKey:@"cancel"] - style:UIAlertActionStyleCancel - handler:^(UIAlertAction * action) { - - if (weakSelf) - { - typeof(self) self = weakSelf; - self->actionSheet = nil; - } - - }]]; - - [actionSheet popoverPresentationController].sourceView = self.voiceCallButton; - [actionSheet popoverPresentationController].sourceRect = self.voiceCallButton.bounds; - [self.window.rootViewController presentViewController:actionSheet animated:YES completion:nil]; - } - } - else if (button == self.hangupCallButton) - { - if ([self.delegate respondsToSelector:@selector(roomInputToolbarViewHangupCall:)]) - { - [self.delegate roomInputToolbarViewHangupCall:self]; - } + self.actionMenuOpened = !self.isActionMenuOpened; } [super onTouchUpInside:button]; @@ -449,6 +354,75 @@ [super destroy]; } +- (void)updateSendButtonWithMessage:(NSString *)textMessage +{ + self.actionMenuOpened = NO; + + if (textMessage.length) + { + self.rightInputToolbarButton.alpha = 1; + self.messageComposerContainerTrailingConstraint.constant = self.frame.size.width - self.rightInputToolbarButton.frame.origin.x + 12; + } + else + { + self.rightInputToolbarButton.alpha = 0; + self.messageComposerContainerTrailingConstraint.constant = 12; + } + + [self layoutIfNeeded]; +} + +#pragma mark - properties + +- (void)setActionMenuOpened:(BOOL)actionMenuOpened +{ + if (_actionMenuOpened != actionMenuOpened) + { + _actionMenuOpened = actionMenuOpened; + + if (self->growingTextView.internalTextView.selectedRange.length > 0) + { + NSRange range = self->growingTextView.internalTextView.selectedRange; + range.location = range.location + range.length; + range.length = 0; + self->growingTextView.internalTextView.selectedRange = range; + } + + if (_actionMenuOpened) { + self.actionsBar.hidden = NO; + [self.actionsBar animateWithShowIn:_actionMenuOpened completion:nil]; + } + else + { + [self.actionsBar animateWithShowIn:_actionMenuOpened completion:^(BOOL finished) { + self.actionsBar.hidden = YES; + }]; + } + + [UIView animateWithDuration:kActionMenuAttachButtonAnimationDuration delay:0 usingSpringWithDamping:kActionMenuAttachButtonSpringDamping initialSpringVelocity:kActionMenuAttachButtonSpringVelocity options:UIViewAnimationOptionCurveEaseIn animations:^{ + self.attachMediaButton.transform = actionMenuOpened ? CGAffineTransformMakeRotation(M_PI * 3 / 4) : CGAffineTransformIdentity; + } completion:nil]; + + [UIView animateWithDuration:kActionMenuContentAlphaAnimationDuration delay:_actionMenuOpened ? 0 : .1 options:UIViewAnimationOptionCurveEaseIn animations:^{ + self->messageComposerContainer.alpha = actionMenuOpened ? 0 : 1; + self.rightInputToolbarButton.alpha = self->growingTextView.text.length == 0 || actionMenuOpened ? 0 : 1; + } completion:nil]; + + [UIView animateWithDuration:kActionMenuComposerHeightAnimationDuration animations:^{ + if (actionMenuOpened) + { + self.mainToolbarHeightConstraint.constant = self.mainToolbarMinHeightConstraint.constant; + } + else + { + [self->growingTextView refreshHeight]; + } + [self layoutIfNeeded]; + [self.delegate roomInputToolbarView:self heightDidChanged:self.mainToolbarHeightConstraint.constant completion:nil]; + }]; + } +} + #pragma mark - Clipboard - Handle image/data paste from general pasteboard - (void)paste:(id)sender diff --git a/Riot/Modules/Room/Views/InputToolbar/RoomInputToolbarView.xib b/Riot/Modules/Room/Views/InputToolbar/RoomInputToolbarView.xib index 8cab0f465..aef8b2f81 100644 --- a/Riot/Modules/Room/Views/InputToolbar/RoomInputToolbarView.xib +++ b/Riot/Modules/Room/Views/InputToolbar/RoomInputToolbarView.xib @@ -1,142 +1,126 @@ - + - + - + - + - - - + + - - - - - - - - + + + - + + + + + + + + + + + + + + + + + + + + + + + + + - + - - - - + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + @@ -152,31 +136,31 @@ + - - + + + + + - - - - - - + - - - - + + + + + diff --git a/Riot/Modules/Room/Views/Title/RoomTitleView.h b/Riot/Modules/Room/Views/Title/RoomTitleView.h index 882d79fda..d4295093f 100644 --- a/Riot/Modules/Room/Views/Title/RoomTitleView.h +++ b/Riot/Modules/Room/Views/Title/RoomTitleView.h @@ -35,9 +35,17 @@ @interface RoomTitleView : MXKRoomTitleView @property (weak, nonatomic) IBOutlet UIView *titleMask; -@property (weak, nonatomic) IBOutlet NSLayoutConstraint *displayNameCenterXConstraint; -@property (weak, nonatomic) IBOutlet UIImageView *roomDetailsIconImageView; @property (weak, nonatomic) IBOutlet UIImageView *badgeImageView; +@property (weak, nonatomic) IBOutlet MXKImageView *pictureView; +@property (weak, nonatomic) IBOutlet UILabel *missedDiscussionsBadgeLabel; +@property (weak, nonatomic) IBOutlet UILabel *typingLabel; +@property (weak, nonatomic) IBOutlet NSLayoutConstraint *displayNameCenterYConstraint; +@property (weak, nonatomic) IBOutlet UIView *dotView; +@property (weak, nonatomic) IBOutlet NSLayoutConstraint *missedDiscussionsBadgeLabelLeadingConstraint; +@property (weak, nonatomic) IBOutlet NSLayoutConstraint *pictureViewHeightConstraint; +@property (weak, nonatomic) IBOutlet NSLayoutConstraint *pictureViewWidthConstraint; +@property (weak, nonatomic) IBOutlet NSLayoutConstraint *dotViewCenterXConstraint; +@property (weak, nonatomic) IBOutlet NSLayoutConstraint *dotViewCenterYConstraint; /** The room preview data may be used when mxRoom instance is not available @@ -49,9 +57,19 @@ */ @property (nonatomic) id tapGestureDelegate; +/** + the typing notification string to be displayed (default nil if notification is hidden). + */ +@property (copy, nonatomic) NSString *typingNotificationString; + /** The method used to handle the gesture recognized by a receiver. */ - (void)reportTapGesture:(UITapGestureRecognizer*)tapGestureRecognizer; +/** + update the layout of the title view according to the target orientation + */ +- (void)updateLayoutForOrientation:(UIInterfaceOrientation)orientation; + @end diff --git a/Riot/Modules/Room/Views/Title/RoomTitleView.m b/Riot/Modules/Room/Views/Title/RoomTitleView.m index 679fd719f..4829578ed 100644 --- a/Riot/Modules/Room/Views/Title/RoomTitleView.m +++ b/Riot/Modules/Room/Views/Title/RoomTitleView.m @@ -47,6 +47,8 @@ [tap setDelegate:self]; [self.titleMask addGestureRecognizer:tap]; self.titleMask.userInteractionEnabled = YES; + self.dotView.layer.masksToBounds = YES; + self.dotView.layer.cornerRadius = CGRectGetMidX(self.dotView.bounds); } } @@ -54,8 +56,8 @@ { [super layoutSubviews]; - self.roomDetailsIconImageView.image = self.roomDetailsIconImageView.image; - + self.pictureView.layer.cornerRadius = self.pictureView.bounds.size.width / 2.; + if (self.superview) { // Force the title view layout by adding 2 new constraints on the UINavigationBarContentView instance. @@ -84,7 +86,9 @@ self.backgroundColor = UIColor.clearColor; self.displayNameTextField.textColor = (self.mxRoom.summary.displayname.length ? ThemeService.shared.theme.textPrimaryColor : ThemeService.shared.theme.textSecondaryColor); - self.roomDetailsIconImageView.tintColor = ThemeService.shared.theme.textPrimaryColor; + self.typingLabel.textColor = ThemeService.shared.theme.textSecondaryColor; + self.dotView.backgroundColor = ThemeService.shared.theme.warningColor; + self.missedDiscussionsBadgeLabel.textColor = ThemeService.shared.theme.tintColor; } - (void)setRoomPreviewData:(RoomPreviewData *)roomPreviewData @@ -133,4 +137,58 @@ } } +- (void)updateLayoutForOrientation:(UIInterfaceOrientation)orientation +{ + if (UIInterfaceOrientationIsLandscape(orientation)) + { + self.missedDiscussionsBadgeLabel.font = [UIFont systemFontOfSize:10]; + self.missedDiscussionsBadgeLabelLeadingConstraint.constant = -24; + self.pictureViewWidthConstraint.constant = 28; + self.pictureViewHeightConstraint.constant = 28; + self.displayNameTextField.font = [UIFont systemFontOfSize:14 weight:UIFontWeightMedium]; + self.typingLabel.font = [UIFont systemFontOfSize:10]; + self.dotViewCenterYConstraint.constant = -2; + } + else + { + self.missedDiscussionsBadgeLabel.font = [UIFont systemFontOfSize:15]; + self.missedDiscussionsBadgeLabelLeadingConstraint.constant = -32; + self.pictureViewWidthConstraint.constant = 32; + self.pictureViewHeightConstraint.constant = 32; + self.displayNameTextField.font = [UIFont systemFontOfSize:17 weight:UIFontWeightMedium]; + self.typingLabel.font = [UIFont systemFontOfSize:12]; + self.dotViewCenterYConstraint.constant = -1; + } +} + +- (void)setTypingNotificationString:(NSString *)typingNotificationString +{ + if (typingNotificationString.length > 0) + { + self.typingLabel.text = typingNotificationString; + [self layoutIfNeeded]; + + [UIView animateWithDuration:.1 animations:^{ + self.typingLabel.alpha = 1; + self.displayNameCenterYConstraint.constant = -8; + [self layoutIfNeeded]; + }]; + } + else + { + [UIView animateWithDuration:.1 animations:^{ + self.typingLabel.alpha = 0; + self.displayNameCenterYConstraint.constant = 0; + [self layoutIfNeeded]; + } completion:^(BOOL finished) { + self.typingLabel.text = nil; + }]; + } +} + +- (NSString *)typingNotificationString +{ + return self.typingLabel.text; +} + @end diff --git a/Riot/Modules/Room/Views/Title/RoomTitleView.xib b/Riot/Modules/Room/Views/Title/RoomTitleView.xib index 47d170601..d25cbdf79 100644 --- a/Riot/Modules/Room/Views/Title/RoomTitleView.xib +++ b/Riot/Modules/Room/Views/Title/RoomTitleView.xib @@ -1,9 +1,10 @@ - + - + + @@ -13,69 +14,101 @@ + + + + + + + + + + + + + + + + + + - + - - + + - + - - - - - - - - - + - - + + + - - - - - - + + + + - + + + + + - + - + - - + + + + + + + + + - - + + + + diff --git a/Riot/Modules/Settings/Security/SecurityViewController.m b/Riot/Modules/Settings/Security/SecurityViewController.m index 291a02d73..c087bdfb6 100644 --- a/Riot/Modules/Settings/Security/SecurityViewController.m +++ b/Riot/Modules/Settings/Security/SecurityViewController.m @@ -329,42 +329,60 @@ TableViewSectionsDelegate> // Crypto sessions section - Section *sessionsSection = [Section sectionWithTag:SECTION_CRYPTO_SESSIONS]; - - sessionsSection.headerTitle = NSLocalizedStringFromTable(@"security_settings_crypto_sessions", @"Vector", nil); - - NSUInteger sessionsSectionRowsCount; - - if (self.showLoadingDevicesInformation) + if (RiotSettings.shared.settingsSecurityScreenShowSessions) { - sessionsSectionRowsCount = 2; - } else { - sessionsSectionRowsCount = devicesArray.count + 1; - } + Section *sessionsSection = [Section sectionWithTag:SECTION_CRYPTO_SESSIONS]; + + sessionsSection.headerTitle = NSLocalizedStringFromTable(@"security_settings_crypto_sessions", @"Vector", nil); + + NSUInteger sessionsSectionRowsCount; + + if (self.showLoadingDevicesInformation) + { + sessionsSectionRowsCount = 2; + } + else + { + sessionsSectionRowsCount = devicesArray.count + 1; + } - [sessionsSection addRowsWithCount:sessionsSectionRowsCount]; - - [sections addObject:sessionsSection]; + [sessionsSection addRowsWithCount:sessionsSectionRowsCount]; + + [sections addObject:sessionsSection]; + } // Secure backup - Section *secureBackupSection = [Section sectionWithTag:SECTION_SECURE_BACKUP]; secureBackupSection.headerTitle = NSLocalizedStringFromTable(@"security_settings_secure_backup", @"Vector", nil); - + [secureBackupSection addRowsWithCount:[self numberOfRowsInSecureBackupSection]]; - - [sections addObject:secureBackupSection]; + + if (secureBackupSection.rows.count) + { + [sections addObject:secureBackupSection]; + } // Cryptograhpy Section *cryptograhpySection = [Section sectionWithTag:SECTION_CRYPTOGRAPHY]; cryptograhpySection.headerTitle = NSLocalizedStringFromTable(@"security_settings_cryptography", @"Vector", nil); - [cryptograhpySection addRowsWithCount:CRYPTOGRAPHY_COUNT]; - - [sections addObject:cryptograhpySection]; + if (RiotSettings.shared.settingsSecurityScreenShowCryptographyInfo) + { + [cryptograhpySection addRowWithTag:CRYPTOGRAPHY_INFO]; + } + if (RiotSettings.shared.settingsSecurityScreenShowCryptographyExport) + { + [cryptograhpySection addRowWithTag:CRYPTOGRAPHY_EXPORT]; + } + + if (cryptograhpySection.rows.count) + { + [sections addObject:cryptograhpySection]; + } + #ifdef CROSS_SIGNING_AND_BACKUP_DEV // Cross-Signing @@ -392,11 +410,17 @@ TableViewSectionsDelegate> Section *advancedSection = [Section sectionWithTag:SECTION_ADVANCED]; advancedSection.headerTitle = NSLocalizedStringFromTable(@"security_settings_advanced", @"Vector", nil); - [advancedSection addRowWithTag:ADVANCED_BLACKLIST_UNVERIFIED_DEVICES]; - [advancedSection addRowWithTag:ADVANCED_BLACKLIST_UNVERIFIED_DEVICES_DESCRIPTION]; + if (RiotSettings.shared.settingsSecurityScreenShowAdvancedUnverifiedDevices) + { + [advancedSection addRowWithTag:ADVANCED_BLACKLIST_UNVERIFIED_DEVICES]; + [advancedSection addRowWithTag:ADVANCED_BLACKLIST_UNVERIFIED_DEVICES_DESCRIPTION]; + } - [sections addObject:advancedSection]; - + if (advancedSection.rows.count) + { + [sections addObject:advancedSection]; + } + // Update sections self.tableViewSections.sections = sections; @@ -853,28 +877,36 @@ TableViewSectionsDelegate> - (void)refreshSecureBackupSectionData { MXRecoveryService *recoveryService = self.mainSession.crypto.recoveryService; + NSMutableArray *secureBackupSectionState = [NSMutableArray new]; if (recoveryService.hasRecovery) { - secureBackupSectionState = @[ - @(SECURE_BACKUP_RESTORE), - @(SECURE_BACKUP_DELETE), - @(SECURE_BACKUP_DESCRIPTION), - //@(SECURE_BACKUP_MANAGE_MANUALLY), - ]; + if (RiotSettings.shared.settingsSecurityScreenShowRestoreBackup) + { + [secureBackupSectionState addObject:@(SECURE_BACKUP_RESTORE)]; + } + if (RiotSettings.shared.settingsSecurityScreenShowDeleteBackup) + { + [secureBackupSectionState addObject:@(SECURE_BACKUP_DELETE)]; + } } else { - secureBackupSectionState = @[ - @(SECURE_BACKUP_SETUP), - @(SECURE_BACKUP_DESCRIPTION), - //@(SECURE_BACKUP_MANAGE_MANUALLY), - ]; + if (RiotSettings.shared.settingsSecurityScreenShowSetupBackup) + { + [secureBackupSectionState addObject:@(SECURE_BACKUP_SETUP)]; + } } + if (secureBackupSectionState.count) + { + [secureBackupSectionState addObject:@(SECURE_BACKUP_DESCRIPTION)]; + } + #ifdef CROSS_SIGNING_AND_BACKUP_DEV - secureBackupSectionState = [@[@(SECURE_BACKUP_INFO)] arrayByAddingObjectsFromArray:secureBackupSectionState]; + [secureBackupSectionState addObject:@(SECURE_BACKUP_INFO)]; #endif + self->secureBackupSectionState = secureBackupSectionState; } - (NSUInteger)secureBackupSectionEnumForRow:(NSUInteger)row diff --git a/Riot/Modules/Settings/SettingsViewController.m b/Riot/Modules/Settings/SettingsViewController.m index fdac6daff..12adb0364 100644 --- a/Riot/Modules/Settings/SettingsViewController.m +++ b/Riot/Modules/Settings/SettingsViewController.m @@ -300,7 +300,10 @@ TableViewSectionsDelegate> Section *sectionUserSettings = [Section sectionWithTag:SECTION_TAG_USER_SETTINGS]; [sectionUserSettings addRowWithTag:USER_SETTINGS_PROFILE_PICTURE_INDEX]; [sectionUserSettings addRowWithTag:USER_SETTINGS_DISPLAYNAME_INDEX]; - [sectionUserSettings addRowWithTag:USER_SETTINGS_CHANGE_PASSWORD_INDEX]; + if (RiotSettings.shared.settingsScreenShowChangePassword) + { + [sectionUserSettings addRowWithTag:USER_SETTINGS_CHANGE_PASSWORD_INDEX]; + } if (BuildSettings.settingsScreenShowUserFirstName) { [sectionUserSettings addRowWithTag:USER_SETTINGS_FIRST_NAME_INDEX]; @@ -332,8 +335,10 @@ TableViewSectionsDelegate> { [sectionUserSettings addRowWithTag:USER_SETTINGS_THREEPIDS_INFORMATION_INDEX]; } - - [sectionUserSettings addRowWithTag:USER_SETTINGS_INVITE_FRIENDS_INDEX]; + if (RiotSettings.shared.settingsScreenShowInviteFriends) + { + [sectionUserSettings addRowWithTag:USER_SETTINGS_INVITE_FRIENDS_INDEX]; + } sectionUserSettings.headerTitle = NSLocalizedStringFromTable(@"settings_user_settings", @"Vector", nil); [tmpSections addObject:sectionUserSettings]; @@ -355,10 +360,18 @@ TableViewSectionsDelegate> if (BuildSettings.allowVoIPUsage && BuildSettings.stunServerFallbackUrlString) { Section *sectionCalls = [Section sectionWithTag:SECTION_TAG_CALLS]; - [sectionCalls addRowWithTag:CALLS_ENABLE_STUN_SERVER_FALLBACK_INDEX]; - [sectionCalls addRowWithTag:CALLS_STUN_SERVER_FALLBACK_DESCRIPTION_INDEX]; sectionCalls.headerTitle = NSLocalizedStringFromTable(@"settings_calls_settings", @"Vector", nil); - [tmpSections addObject:sectionCalls]; + + if (RiotSettings.shared.settingsScreenShowEnableStunServerFallback) + { + [sectionCalls addRowWithTag:CALLS_ENABLE_STUN_SERVER_FALLBACK_INDEX]; + [sectionCalls addRowWithTag:CALLS_STUN_SERVER_FALLBACK_DESCRIPTION_INDEX]; + } + + if (sectionCalls.rows.count) + { + [tmpSections addObject:sectionCalls]; + } } if (BuildSettings.settingsScreenShowDiscoverySettings) @@ -432,9 +445,18 @@ TableViewSectionsDelegate> Section *sectionOther = [Section sectionWithTag:SECTION_TAG_OTHER]; [sectionOther addRowWithTag:OTHER_VERSION_INDEX]; [sectionOther addRowWithTag:OTHER_OLM_VERSION_INDEX]; - [sectionOther addRowWithTag:OTHER_COPYRIGHT_INDEX]; - [sectionOther addRowWithTag:OTHER_TERM_CONDITIONS_INDEX]; - [sectionOther addRowWithTag:OTHER_PRIVACY_INDEX]; + if (BuildSettings.applicationCopyrightUrlString.length) + { + [sectionOther addRowWithTag:OTHER_COPYRIGHT_INDEX]; + } + if (BuildSettings.applicationTermsConditionsUrlString.length) + { + [sectionOther addRowWithTag:OTHER_TERM_CONDITIONS_INDEX]; + } + if (BuildSettings.applicationPrivacyPolicyUrlString.length) + { + [sectionOther addRowWithTag:OTHER_PRIVACY_INDEX]; + } [sectionOther addRowWithTag:OTHER_THIRD_PARTY_INDEX]; [sectionOther addRowWithTag:OTHER_SHOW_NSFW_ROOMS_INDEX]; diff --git a/Riot/Modules/StartChat/StartChatViewController.m b/Riot/Modules/StartChat/StartChatViewController.m index eb35cd1fb..421ac343e 100644 --- a/Riot/Modules/StartChat/StartChatViewController.m +++ b/Riot/Modules/StartChat/StartChatViewController.m @@ -133,6 +133,12 @@ - (void)setupInviteFriendsHeaderView { + if (!RiotSettings.shared.allowInviteExernalUsers) + { + self.contactsTableView.tableHeaderView = nil; + return; + } + InviteFriendsHeaderView *inviteFriendsHeaderView = [InviteFriendsHeaderView instantiate]; inviteFriendsHeaderView.delegate = self; self.contactsTableView.tableHeaderView = inviteFriendsHeaderView; diff --git a/Riot/SupportingFiles/Riot-Bridging-Header.h b/Riot/SupportingFiles/Riot-Bridging-Header.h index fbf30cb3a..7daa40759 100644 --- a/Riot/SupportingFiles/Riot-Bridging-Header.h +++ b/Riot/SupportingFiles/Riot-Bridging-Header.h @@ -23,9 +23,12 @@ #import "DirectoryServerPickerViewController.h" #import "MXSession+Riot.h" #import "RoomFilesViewController.h" +#import "RoomSearchViewController.h" +#import "IntegrationManagerViewController.h" #import "RoomSettingsViewController.h" #import "JitsiWidgetData.h" #import "InviteRecentTableViewCell.h" #import "AuthFallBackViewController.h" #import "CallViewController.h" #import "MatrixContactsDataSource.h" +#import "TypingUserInfo.h"