diff --git a/Config/AppConfiguration.swift b/Config/AppConfiguration.swift
index 597876852..52b041656 100644
--- a/Config/AppConfiguration.swift
+++ b/Config/AppConfiguration.swift
@@ -15,7 +15,6 @@
//
import Foundation
-import MatrixKit
/// AppConfiguration is CommonConfiguration plus configurations dedicated to the app
class AppConfiguration: CommonConfiguration {
diff --git a/Config/BuildSettings.swift b/Config/BuildSettings.swift
index 44c32742c..0919c08fd 100644
--- a/Config/BuildSettings.swift
+++ b/Config/BuildSettings.swift
@@ -16,8 +16,6 @@
import Foundation
-import MatrixKit
-
/// BuildSettings provides settings computed at build time.
/// In future, it may be automatically generated from xcconfig files
@objcMembers
diff --git a/Config/CommonConfiguration.swift b/Config/CommonConfiguration.swift
index f2e4b68dc..5edcacc37 100644
--- a/Config/CommonConfiguration.swift
+++ b/Config/CommonConfiguration.swift
@@ -15,7 +15,7 @@
//
import Foundation
-import MatrixKit
+import MatrixSDK
/// CommonConfiguration is the central point to setup settings for MatrixSDK, MatrixKit and common configurations for all targets.
class CommonConfiguration: NSObject, Configurable {
diff --git a/Config/Configurable.swift b/Config/Configurable.swift
index a5a6d980e..acfb97605 100644
--- a/Config/Configurable.swift
+++ b/Config/Configurable.swift
@@ -15,9 +15,7 @@
//
import Foundation
-
-import Foundation
-import MatrixKit
+import MatrixSDK
/// Configurable expose settings app and its entensions must use.
@objc protocol Configurable {
diff --git a/Podfile b/Podfile
index 2fed586f1..298eddf8c 100644
--- a/Podfile
+++ b/Podfile
@@ -6,43 +6,55 @@ platform :ios, '12.1'
# Use frameforks to allow usage of pod written in Swift (like PiwikTracker)
use_frameworks!
-# Different flavours of pods to MatrixKit. Can be one of:
-# - a String indicating an official MatrixKit released version number
+# Different flavours of pods to MatrixSDK. Can be one of:
+# - a String indicating an official MatrixSDK released version number
# - `:local` (to use Development Pods)
-# - `{'kit branch name' => 'sdk branch name'}` to depend on specific branches of each repo
-# - `{ {kit spec hash} => {sdk spec hash}` to depend on specific pod options (:git => …, :podspec => …) for each repo. Used by Fastfile during CI
+# - `{ :branch => 'sdk branch name'}` to depend on specific branch of MatrixSDK repo
+# - `{ :specHash => {sdk spec hash}` to depend on specific pod options (:git => …, :podspec => …) for MatrixSDK 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.16.10'
-# $matrixKitVersion = :local
-# $matrixKitVersion = {'develop' => 'develop'}
+$matrixSDKVersion = '= 0.20.10'
+# $matrixSDKVersion = :local
+# $matrixSDKVersion = { :branch => 'develop'}
########################################
-case $matrixKitVersion
+case $matrixSDKVersion
when :local
-$matrixKitVersionSpec = { :path => '../matrix-ios-kit/MatrixKit.podspec' }
$matrixSDKVersionSpec = { :path => '../matrix-ios-sdk/MatrixSDK.podspec' }
-when Hash # kit branch name => sdk branch name – or {kit spec Hash} => {sdk spec Hash}
-kit_spec, sdk_spec = $matrixKitVersion.first # extract first and only key/value pair; key is kit_spec, value is sdk_spec
-kit_spec = { :git => 'https://github.com/matrix-org/matrix-ios-kit.git', :branch => kit_spec.to_s } unless kit_spec.is_a?(Hash)
-sdk_spec = { :git => 'https://github.com/matrix-org/matrix-ios-sdk.git', :branch => sdk_spec.to_s } unless sdk_spec.is_a?(Hash)
-$matrixKitVersionSpec = kit_spec
+when Hash
+spec_mode, sdk_spec = $matrixSDKVersion.first # extract first and only key/value pair; key is spec_mode, value is sdk_spec
+
+ case spec_mode
+ when :branch
+ # :branch => sdk branch name
+ sdk_spec = { :git => 'https://github.com/matrix-org/matrix-ios-sdk.git', :branch => sdk_spec.to_s } unless sdk_spec.is_a?(Hash)
+ when :specHash
+ # :specHash => {sdk spec Hash}
+ sdk_spec = sdk_spec
+ end
+
$matrixSDKVersionSpec = sdk_spec
-when String # specific MatrixKit released version
-$matrixKitVersionSpec = $matrixKitVersion
-$matrixSDKVersionSpec = {}
+when String # specific MatrixSDK released version
+$matrixSDKVersionSpec = $matrixSDKVersion
end
-# Method to import the MatrixKit
-def import_MatrixKit
+# Method to import the MatrixSDK
+def import_MatrixSDK
pod 'MatrixSDK', $matrixSDKVersionSpec
pod 'MatrixSDK/JingleCallStack', $matrixSDKVersionSpec
- pod 'MatrixKit', $matrixKitVersionSpec
end
########################################
+def import_MatrixKit_pods
+ pod 'HPGrowingTextView', '~> 1.1'
+ pod 'libPhoneNumber-iOS', '~> 0.9.13'
+ pod 'DTCoreText', '~> 1.6.25'
+ #pod 'DTCoreText/Extension', '~> 1.6.25'
+ pod 'Down', '~> 0.11.0'
+end
+
def import_SwiftUI_pods
pod 'Introspect', '~> 0.1'
end
@@ -66,7 +78,8 @@ abstract_target 'RiotPods' do
pod 'SwiftLint', '~> 0.44.0'
target "Riot" do
- import_MatrixKit
+ import_MatrixSDK
+ import_MatrixKit_pods
import_SwiftUI_pods
@@ -89,7 +102,8 @@ abstract_target 'RiotPods' do
end
target "RiotShareExtension" do
- import_MatrixKit
+ import_MatrixSDK
+ import_MatrixKit_pods
end
target "RiotSwiftUI" do
@@ -101,11 +115,13 @@ abstract_target 'RiotPods' do
end
target "SiriIntents" do
- import_MatrixKit
+ import_MatrixSDK
+ import_MatrixKit_pods
end
target "RiotNSE" do
- import_MatrixKit
+ import_MatrixSDK
+ import_MatrixKit_pods
end
end
diff --git a/Riot/Assets/third_party_licenses.html b/Riot/Assets/third_party_licenses.html
index fc4b54a43..b615810b2 100644
--- a/Riot/Assets/third_party_licenses.html
+++ b/Riot/Assets/third_party_licenses.html
@@ -15,21 +15,7 @@
This application is making use of the following third party softwares:
The Matrix reusable UI library for iOS based on MatrixSDK.
-
Copyright (c) 2014-2016 OpenMarket Ltd.
- Copyright (c) 2016-2017 Vector Creations Ltd
- Copyright (c) 2018-2019 New Vector Ltd
- Copyright (c) 2019 The Matrix.org Foundation C.I.C
-
Licensed under the Apache License, Version 2.0 (the "License"); you may not use this work except in compliance with the License. You may obtain a copy of the License at:
-
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.
-
-
+
MatrixSDK (https://github.com/matrix-org/matrix-ios-sdk.git)
diff --git a/Riot/Categories/MXGroup+Riot.h b/Riot/Categories/MXGroup+Riot.h
index 0d1beb543..76b1e76e5 100644
--- a/Riot/Categories/MXGroup+Riot.h
+++ b/Riot/Categories/MXGroup+Riot.h
@@ -14,7 +14,8 @@
limitations under the License.
*/
-#import
+#import
+#import "MatrixKit.h"
/**
Define a `MXGroup` category at Riot level.
diff --git a/Riot/Categories/MXKRoomBubbleTableViewCell+Riot.h b/Riot/Categories/MXKRoomBubbleTableViewCell+Riot.h
index 91b69a123..24a0ff136 100644
--- a/Riot/Categories/MXKRoomBubbleTableViewCell+Riot.h
+++ b/Riot/Categories/MXKRoomBubbleTableViewCell+Riot.h
@@ -14,7 +14,7 @@
limitations under the License.
*/
-#import
+#import "MatrixKit.h"
@class CircleProgressView;
@@ -39,11 +39,6 @@ extern NSString *const kMXKRoomBubbleCellTapOnReceiptsContainer;
*/
extern NSString *const kMXKRoomBubbleCellLongPressOnReactionView;
-/**
- 'userInfo' dictionary key 'kMXKRoomBubbleCellEventIdKey' is associated to a 'NSString' object representing an event id.
- */
-extern NSString *const kMXKRoomBubbleCellEventIdKey;
-
/**
Action identifier used when the user pressed accept button for an incoming key verification request.
diff --git a/Riot/Categories/MXKRoomBubbleTableViewCell+Riot.m b/Riot/Categories/MXKRoomBubbleTableViewCell+Riot.m
index d5b58eb11..2d2a0dbfc 100644
--- a/Riot/Categories/MXKRoomBubbleTableViewCell+Riot.m
+++ b/Riot/Categories/MXKRoomBubbleTableViewCell+Riot.m
@@ -17,20 +17,19 @@
#import "MXKRoomBubbleTableViewCell+Riot.h"
-#import "RoomBubbleCellData.h"
-
-#import "ThemeService.h"
-#import "GeneratedInterface-Swift.h"
-
#import
+#import "RoomBubbleCellData.h"
+#import "ThemeService.h"
+
+#import "GeneratedInterface-Swift.h"
+
#define VECTOR_ROOMBUBBLETABLEVIEWCELL_MARK_X 48
#define VECTOR_ROOMBUBBLETABLEVIEWCELL_MARK_WIDTH 4
NSString *const kMXKRoomBubbleCellRiotEditButtonPressed = @"kMXKRoomBubbleCellRiotEditButtonPressed";
NSString *const kMXKRoomBubbleCellTapOnReceiptsContainer = @"kMXKRoomBubbleCellTapOnReceiptsContainer";
NSString *const kMXKRoomBubbleCellLongPressOnReactionView = @"kMXKRoomBubbleCellLongPressOnReactionView";
-NSString *const kMXKRoomBubbleCellEventIdKey = @"kMXKRoomBubbleCellEventIdKey";
NSString *const kMXKRoomBubbleCellKeyVerificationIncomingRequestAcceptPressed = @"kMXKRoomBubbleCellKeyVerificationAcceptPressed";
NSString *const kMXKRoomBubbleCellKeyVerificationIncomingRequestDeclinePressed = @"kMXKRoomBubbleCellKeyVerificationDeclinePressed";
diff --git a/Riot/Categories/MXRoom+Riot.h b/Riot/Categories/MXRoom+Riot.h
index 42a6a6c01..e57cd21a2 100644
--- a/Riot/Categories/MXRoom+Riot.h
+++ b/Riot/Categories/MXRoom+Riot.h
@@ -15,9 +15,8 @@
limitations under the License.
*/
-#import
-
#import "UserEncryptionTrustLevel.h"
+#import
/**
Define a `MXRoom` category at Riot level.
diff --git a/Riot/Categories/MXRoom+Riot.m b/Riot/Categories/MXRoom+Riot.m
index 5bef645be..801221597 100644
--- a/Riot/Categories/MXRoom+Riot.m
+++ b/Riot/Categories/MXRoom+Riot.m
@@ -19,6 +19,7 @@
#import "MXRoom+Riot.h"
#import "AvatarGenerator.h"
+#import "MatrixKit.h"
#import
diff --git a/Riot/Categories/MXRoomSummary+Riot.h b/Riot/Categories/MXRoomSummary+Riot.h
index 0ae7c7359..d25cdee5f 100644
--- a/Riot/Categories/MXRoomSummary+Riot.h
+++ b/Riot/Categories/MXRoomSummary+Riot.h
@@ -14,7 +14,7 @@
limitations under the License.
*/
-#import
+#import "MatrixKit.h"
/**
RoomEncryptionTrustLevel represents the trust level in an encrypted room.
diff --git a/Riot/Categories/UINavigationController+Riot.h b/Riot/Categories/UINavigationController+Riot.h
index e5632e7d7..6e82c2b3e 100644
--- a/Riot/Categories/UINavigationController+Riot.h
+++ b/Riot/Categories/UINavigationController+Riot.h
@@ -15,12 +15,12 @@
limitations under the License.
*/
-#import
-
/**
The `UINavigationController` category overrides the default auto rotation handling.
*/
+#import
+
@interface UINavigationController (Riot)
@end
diff --git a/Riot/Categories/UIViewController+RiotSearch.h b/Riot/Categories/UIViewController+RiotSearch.h
index fd077f18d..db29e0183 100644
--- a/Riot/Categories/UIViewController+RiotSearch.h
+++ b/Riot/Categories/UIViewController+RiotSearch.h
@@ -15,12 +15,12 @@
limitations under the License.
*/
-#import
-
/**
The `RiotSearch` category adds the management of the search bar in Riot screens.
*/
+#import
+
@interface UIViewController (RiotSearch)
/**
diff --git a/Riot/Generated/MatrixKitStrings.swift b/Riot/Generated/MatrixKitStrings.swift
new file mode 100644
index 000000000..23a0802bc
--- /dev/null
+++ b/Riot/Generated/MatrixKitStrings.swift
@@ -0,0 +1,1797 @@
+// swiftlint:disable all
+// Generated using SwiftGen, by O.Halligon — https://github.com/SwiftGen/SwiftGen
+
+import Foundation
+
+// swiftlint:disable superfluous_disable_command
+// swiftlint:disable file_length
+
+// MARK: - Strings
+
+// swiftlint:disable function_parameter_count identifier_name line_length type_body_length
+@objcMembers
+public class MatrixKitL10n: NSObject {
+ /// Abort
+ public static var abort: String {
+ return MatrixKitL10n.tr("MatrixKit", "abort")
+ }
+ /// Unable to verify email address. Please check your email and click on the link it contains. Once this is done, click continue
+ public static var accountEmailValidationError: String {
+ return MatrixKitL10n.tr("MatrixKit", "account_email_validation_error")
+ }
+ /// Please check your email and click on the link it contains. Once this is done, click continue.
+ public static var accountEmailValidationMessage: String {
+ return MatrixKitL10n.tr("MatrixKit", "account_email_validation_message")
+ }
+ /// Verification Pending
+ public static var accountEmailValidationTitle: String {
+ return MatrixKitL10n.tr("MatrixKit", "account_email_validation_title")
+ }
+ /// Display name change failed
+ public static var accountErrorDisplayNameChangeFailed: String {
+ return MatrixKitL10n.tr("MatrixKit", "account_error_display_name_change_failed")
+ }
+ /// This doesn't appear to be a valid email address
+ public static var accountErrorEmailWrongDescription: String {
+ return MatrixKitL10n.tr("MatrixKit", "account_error_email_wrong_description")
+ }
+ /// Invalid Email Address
+ public static var accountErrorEmailWrongTitle: String {
+ return MatrixKitL10n.tr("MatrixKit", "account_error_email_wrong_title")
+ }
+ /// Matrix session is not opened
+ public static var accountErrorMatrixSessionIsNotOpened: String {
+ return MatrixKitL10n.tr("MatrixKit", "account_error_matrix_session_is_not_opened")
+ }
+ /// This doesn't appear to be a valid phone number
+ public static var accountErrorMsisdnWrongDescription: String {
+ return MatrixKitL10n.tr("MatrixKit", "account_error_msisdn_wrong_description")
+ }
+ /// Invalid Phone Number
+ public static var accountErrorMsisdnWrongTitle: String {
+ return MatrixKitL10n.tr("MatrixKit", "account_error_msisdn_wrong_title")
+ }
+ /// Picture change failed
+ public static var accountErrorPictureChangeFailed: String {
+ return MatrixKitL10n.tr("MatrixKit", "account_error_picture_change_failed")
+ }
+ /// Notifications not allowed
+ public static var accountErrorPushNotAllowed: String {
+ return MatrixKitL10n.tr("MatrixKit", "account_error_push_not_allowed")
+ }
+ /// Link Email
+ public static var accountLinkEmail: String {
+ return MatrixKitL10n.tr("MatrixKit", "account_link_email")
+ }
+ /// Linked emails
+ public static var accountLinkedEmails: String {
+ return MatrixKitL10n.tr("MatrixKit", "account_linked_emails")
+ }
+ /// Unable to verify phone number.
+ public static var accountMsisdnValidationError: String {
+ return MatrixKitL10n.tr("MatrixKit", "account_msisdn_validation_error")
+ }
+ /// We've sent an SMS with an activation code. Please enter this code below.
+ public static var accountMsisdnValidationMessage: String {
+ return MatrixKitL10n.tr("MatrixKit", "account_msisdn_validation_message")
+ }
+ /// Verification Pending
+ public static var accountMsisdnValidationTitle: String {
+ return MatrixKitL10n.tr("MatrixKit", "account_msisdn_validation_title")
+ }
+ /// Save changes
+ public static var accountSaveChanges: String {
+ return MatrixKitL10n.tr("MatrixKit", "account_save_changes")
+ }
+ /// Logout
+ public static var actionLogout: String {
+ return MatrixKitL10n.tr("MatrixKit", "action_logout")
+ }
+ /// Answer Call
+ public static var answerCall: String {
+ return MatrixKitL10n.tr("MatrixKit", "answer_call")
+ }
+ /// Attach Media from Library
+ public static var attachMedia: String {
+ return MatrixKitL10n.tr("MatrixKit", "attach_media")
+ }
+ /// Cancel the download?
+ public static var attachmentCancelDownload: String {
+ return MatrixKitL10n.tr("MatrixKit", "attachment_cancel_download")
+ }
+ /// Cancel the upload?
+ public static var attachmentCancelUpload: String {
+ return MatrixKitL10n.tr("MatrixKit", "attachment_cancel_upload")
+ }
+ /// This file contains encryption keys exported from a Matrix client.\nDo you want to view the file content or import the keys it contains?
+ public static var attachmentE2eKeysFilePrompt: String {
+ return MatrixKitL10n.tr("MatrixKit", "attachment_e2e_keys_file_prompt")
+ }
+ /// Import...
+ public static var attachmentE2eKeysImport: String {
+ return MatrixKitL10n.tr("MatrixKit", "attachment_e2e_keys_import")
+ }
+ /// Large (~%@)
+ public static func attachmentLarge(_ p1: String) -> String {
+ return MatrixKitL10n.tr("MatrixKit", "attachment_large", p1)
+ }
+ /// Large %@ (~%@)
+ public static func attachmentLargeWithResolution(_ p1: String, _ p2: String) -> String {
+ return MatrixKitL10n.tr("MatrixKit", "attachment_large_with_resolution", p1, p2)
+ }
+ /// Medium (~%@)
+ public static func attachmentMedium(_ p1: String) -> String {
+ return MatrixKitL10n.tr("MatrixKit", "attachment_medium", p1)
+ }
+ /// Medium %@ (~%@)
+ public static func attachmentMediumWithResolution(_ p1: String, _ p2: String) -> String {
+ return MatrixKitL10n.tr("MatrixKit", "attachment_medium_with_resolution", p1, p2)
+ }
+ /// Actual Size
+ public static var attachmentMultiselectionOriginal: String {
+ return MatrixKitL10n.tr("MatrixKit", "attachment_multiselection_original")
+ }
+ /// Do you want to send images as:
+ public static var attachmentMultiselectionSizePrompt: String {
+ return MatrixKitL10n.tr("MatrixKit", "attachment_multiselection_size_prompt")
+ }
+ /// Actual Size (%@)
+ public static func attachmentOriginal(_ p1: String) -> String {
+ return MatrixKitL10n.tr("MatrixKit", "attachment_original", p1)
+ }
+ /// Do you want to send as:
+ public static var attachmentSizePrompt: String {
+ return MatrixKitL10n.tr("MatrixKit", "attachment_size_prompt")
+ }
+ /// You can turn this off in settings.
+ public static var attachmentSizePromptMessage: String {
+ return MatrixKitL10n.tr("MatrixKit", "attachment_size_prompt_message")
+ }
+ /// Confirm size to send
+ public static var attachmentSizePromptTitle: String {
+ return MatrixKitL10n.tr("MatrixKit", "attachment_size_prompt_title")
+ }
+ /// Small (~%@)
+ public static func attachmentSmall(_ p1: String) -> String {
+ return MatrixKitL10n.tr("MatrixKit", "attachment_small", p1)
+ }
+ /// Small %@ (~%@)
+ public static func attachmentSmallWithResolution(_ p1: String, _ p2: String) -> String {
+ return MatrixKitL10n.tr("MatrixKit", "attachment_small_with_resolution", p1, p2)
+ }
+ /// This file type is not supported.
+ public static var attachmentUnsupportedPreviewMessage: String {
+ return MatrixKitL10n.tr("MatrixKit", "attachment_unsupported_preview_message")
+ }
+ /// Unable to preview
+ public static var attachmentUnsupportedPreviewTitle: String {
+ return MatrixKitL10n.tr("MatrixKit", "attachment_unsupported_preview_title")
+ }
+ /// Invalid username
+ public static var authInvalidUserName: String {
+ return MatrixKitL10n.tr("MatrixKit", "auth_invalid_user_name")
+ }
+ /// Not found
+ public static var authResetPasswordErrorNotFound: String {
+ return MatrixKitL10n.tr("MatrixKit", "auth_reset_password_error_not_found")
+ }
+ /// Unauthorized
+ public static var authResetPasswordErrorUnauthorized: String {
+ return MatrixKitL10n.tr("MatrixKit", "auth_reset_password_error_unauthorized")
+ }
+ /// Username in use
+ public static var authUsernameInUse: String {
+ return MatrixKitL10n.tr("MatrixKit", "auth_username_in_use")
+ }
+ /// Back
+ public static var back: String {
+ return MatrixKitL10n.tr("MatrixKit", "back")
+ }
+ /// Ban
+ public static var ban: String {
+ return MatrixKitL10n.tr("MatrixKit", "ban")
+ }
+ /// Connecting…
+ public static var callConnecting: String {
+ return MatrixKitL10n.tr("MatrixKit", "call_connecting")
+ }
+ /// Consulting with %@
+ public static func callConsultingWithUser(_ p1: String) -> String {
+ return MatrixKitL10n.tr("MatrixKit", "call_consulting_with_user", p1)
+ }
+ /// Call ended
+ public static var callEnded: String {
+ return MatrixKitL10n.tr("MatrixKit", "call_ended")
+ }
+ /// You held the call
+ public static var callHolded: String {
+ return MatrixKitL10n.tr("MatrixKit", "call_holded")
+ }
+ /// Call Invite Expired
+ public static var callInviteExpired: String {
+ return MatrixKitL10n.tr("MatrixKit", "call_invite_expired")
+ }
+ /// Device Speaker
+ public static var callMoreActionsAudioUseDevice: String {
+ return MatrixKitL10n.tr("MatrixKit", "call_more_actions_audio_use_device")
+ }
+ /// Change Audio Device
+ public static var callMoreActionsChangeAudioDevice: String {
+ return MatrixKitL10n.tr("MatrixKit", "call_more_actions_change_audio_device")
+ }
+ /// Dial pad
+ public static var callMoreActionsDialpad: String {
+ return MatrixKitL10n.tr("MatrixKit", "call_more_actions_dialpad")
+ }
+ /// Hold
+ public static var callMoreActionsHold: String {
+ return MatrixKitL10n.tr("MatrixKit", "call_more_actions_hold")
+ }
+ /// Transfer
+ public static var callMoreActionsTransfer: String {
+ return MatrixKitL10n.tr("MatrixKit", "call_more_actions_transfer")
+ }
+ /// Resume
+ public static var callMoreActionsUnhold: String {
+ return MatrixKitL10n.tr("MatrixKit", "call_more_actions_unhold")
+ }
+ /// %@ held the call
+ public static func callRemoteHolded(_ p1: String) -> String {
+ return MatrixKitL10n.tr("MatrixKit", "call_remote_holded", p1)
+ }
+ /// Ringing…
+ public static var callRinging: String {
+ return MatrixKitL10n.tr("MatrixKit", "call_ringing")
+ }
+ /// Transfer to %@
+ public static func callTransferToUser(_ p1: String) -> String {
+ return MatrixKitL10n.tr("MatrixKit", "call_transfer_to_user", p1)
+ }
+ /// Video call with %@
+ public static func callVideoWithUser(_ p1: String) -> String {
+ return MatrixKitL10n.tr("MatrixKit", "call_video_with_user", p1)
+ }
+ /// Voice call with %@
+ public static func callVoiceWithUser(_ p1: String) -> String {
+ return MatrixKitL10n.tr("MatrixKit", "call_voice_with_user", p1)
+ }
+ /// Video calls require access to the Camera but %@ doesn't have permission to use it
+ public static func cameraAccessNotGrantedForCall(_ p1: String) -> String {
+ return MatrixKitL10n.tr("MatrixKit", "camera_access_not_granted_for_call", p1)
+ }
+ /// Cancel
+ public static var cancel: String {
+ return MatrixKitL10n.tr("MatrixKit", "cancel")
+ }
+ /// Cancel Download
+ public static var cancelDownload: String {
+ return MatrixKitL10n.tr("MatrixKit", "cancel_download")
+ }
+ /// Cancel Upload
+ public static var cancelUpload: String {
+ return MatrixKitL10n.tr("MatrixKit", "cancel_upload")
+ }
+ /// Take Photo/Video
+ public static var captureMedia: String {
+ return MatrixKitL10n.tr("MatrixKit", "capture_media")
+ }
+ /// Close
+ public static var close: String {
+ return MatrixKitL10n.tr("MatrixKit", "close")
+ }
+ /// Local Contacts
+ public static var contactLocalContacts: String {
+ return MatrixKitL10n.tr("MatrixKit", "contact_local_contacts")
+ }
+ /// Matrix Users
+ public static var contactMxUsers: String {
+ return MatrixKitL10n.tr("MatrixKit", "contact_mx_users")
+ }
+ /// Continue
+ public static var `continue`: String {
+ return MatrixKitL10n.tr("MatrixKit", "continue")
+ }
+ /// Copy
+ public static var copyButtonName: String {
+ return MatrixKitL10n.tr("MatrixKit", "copy_button_name")
+ }
+ /// Choose a country
+ public static var countryPickerTitle: String {
+ return MatrixKitL10n.tr("MatrixKit", "country_picker_title")
+ }
+ /// Create Account
+ public static var createAccount: String {
+ return MatrixKitL10n.tr("MatrixKit", "create_account")
+ }
+ /// Create Room
+ public static var createRoom: String {
+ return MatrixKitL10n.tr("MatrixKit", "create_room")
+ }
+ /// default
+ public static var `default`: String {
+ return MatrixKitL10n.tr("MatrixKit", "default")
+ }
+ /// Delete
+ public static var delete: String {
+ return MatrixKitL10n.tr("MatrixKit", "delete")
+ }
+ /// This operation requires additional authentication.\nTo continue, please enter your password.
+ public static var deviceDetailsDeletePromptMessage: String {
+ return MatrixKitL10n.tr("MatrixKit", "device_details_delete_prompt_message")
+ }
+ /// Authentication
+ public static var deviceDetailsDeletePromptTitle: String {
+ return MatrixKitL10n.tr("MatrixKit", "device_details_delete_prompt_title")
+ }
+ /// ID\n
+ public static var deviceDetailsIdentifier: String {
+ return MatrixKitL10n.tr("MatrixKit", "device_details_identifier")
+ }
+ /// Last seen\n
+ public static var deviceDetailsLastSeen: String {
+ return MatrixKitL10n.tr("MatrixKit", "device_details_last_seen")
+ }
+ /// %@ @ %@\n
+ public static func deviceDetailsLastSeenFormat(_ p1: String, _ p2: String) -> String {
+ return MatrixKitL10n.tr("MatrixKit", "device_details_last_seen_format", p1, p2)
+ }
+ /// Public Name\n
+ public static var deviceDetailsName: String {
+ return MatrixKitL10n.tr("MatrixKit", "device_details_name")
+ }
+ /// A session's public name is visible to people you communicate with
+ public static var deviceDetailsRenamePromptMessage: String {
+ return MatrixKitL10n.tr("MatrixKit", "device_details_rename_prompt_message")
+ }
+ /// Session Name
+ public static var deviceDetailsRenamePromptTitle: String {
+ return MatrixKitL10n.tr("MatrixKit", "device_details_rename_prompt_title")
+ }
+ /// Session information\n
+ public static var deviceDetailsTitle: String {
+ return MatrixKitL10n.tr("MatrixKit", "device_details_title")
+ }
+ /// Discard
+ public static var discard: String {
+ return MatrixKitL10n.tr("MatrixKit", "discard")
+ }
+ /// Dismiss
+ public static var dismiss: String {
+ return MatrixKitL10n.tr("MatrixKit", "dismiss")
+ }
+ /// Export
+ public static var e2eExport: String {
+ return MatrixKitL10n.tr("MatrixKit", "e2e_export")
+ }
+ /// This process allows you to export the keys for messages you have received in encrypted rooms to a local file. You will then be able to import the file into another Matrix client in the future, so that client will also be able to decrypt these messages.\nThe exported file will allow anyone who can read it to decrypt any encrypted messages that you can see, so you should be careful to keep it secure.
+ public static var e2eExportPrompt: String {
+ return MatrixKitL10n.tr("MatrixKit", "e2e_export_prompt")
+ }
+ /// Export room keys
+ public static var e2eExportRoomKeys: String {
+ return MatrixKitL10n.tr("MatrixKit", "e2e_export_room_keys")
+ }
+ /// Import
+ public static var e2eImport: String {
+ return MatrixKitL10n.tr("MatrixKit", "e2e_import")
+ }
+ /// This process allows you to import encryption keys that you had previously exported from another Matrix client. You will then be able to decrypt any messages that the other client could decrypt.\nThe export file is protected with a passphrase. You should enter the passphrase here, to decrypt the file.
+ public static var e2eImportPrompt: String {
+ return MatrixKitL10n.tr("MatrixKit", "e2e_import_prompt")
+ }
+ /// Import room keys
+ public static var e2eImportRoomKeys: String {
+ return MatrixKitL10n.tr("MatrixKit", "e2e_import_room_keys")
+ }
+ /// Confirm passphrase
+ public static var e2ePassphraseConfirm: String {
+ return MatrixKitL10n.tr("MatrixKit", "e2e_passphrase_confirm")
+ }
+ /// Create passphrase
+ public static var e2ePassphraseCreate: String {
+ return MatrixKitL10n.tr("MatrixKit", "e2e_passphrase_create")
+ }
+ /// Passphrase must not be empty
+ public static var e2ePassphraseEmpty: String {
+ return MatrixKitL10n.tr("MatrixKit", "e2e_passphrase_empty")
+ }
+ /// Enter passphrase
+ public static var e2ePassphraseEnter: String {
+ return MatrixKitL10n.tr("MatrixKit", "e2e_passphrase_enter")
+ }
+ /// Passphrases must match
+ public static var e2ePassphraseNotMatch: String {
+ return MatrixKitL10n.tr("MatrixKit", "e2e_passphrase_not_match")
+ }
+ /// Passphrase too short (It must be at a minimum %d characters in length)
+ public static func e2ePassphraseTooShort(_ p1: Int) -> String {
+ return MatrixKitL10n.tr("MatrixKit", "e2e_passphrase_too_short", p1)
+ }
+ /// End Call
+ public static var endCall: String {
+ return MatrixKitL10n.tr("MatrixKit", "end_call")
+ }
+ /// Error
+ public static var error: String {
+ return MatrixKitL10n.tr("MatrixKit", "error")
+ }
+ /// An error occured. Please try again later.
+ public static var errorCommonMessage: String {
+ return MatrixKitL10n.tr("MatrixKit", "error_common_message")
+ }
+ /// d
+ public static var formatTimeD: String {
+ return MatrixKitL10n.tr("MatrixKit", "format_time_d")
+ }
+ /// h
+ public static var formatTimeH: String {
+ return MatrixKitL10n.tr("MatrixKit", "format_time_h")
+ }
+ /// m
+ public static var formatTimeM: String {
+ return MatrixKitL10n.tr("MatrixKit", "format_time_m")
+ }
+ /// s
+ public static var formatTimeS: String {
+ return MatrixKitL10n.tr("MatrixKit", "format_time_s")
+ }
+ /// Invites
+ public static var groupInviteSection: String {
+ return MatrixKitL10n.tr("MatrixKit", "group_invite_section")
+ }
+ /// Groups
+ public static var groupSection: String {
+ return MatrixKitL10n.tr("MatrixKit", "group_section")
+ }
+ /// Ignore
+ public static var ignore: String {
+ return MatrixKitL10n.tr("MatrixKit", "ignore")
+ }
+ /// Incoming Video Call
+ public static var incomingVideoCall: String {
+ return MatrixKitL10n.tr("MatrixKit", "incoming_video_call")
+ }
+ /// Incoming Voice Call
+ public static var incomingVoiceCall: String {
+ return MatrixKitL10n.tr("MatrixKit", "incoming_voice_call")
+ }
+ /// I'd like to chat with you with matrix. Please, visit the website http://matrix.org to have more information.
+ public static var invitationMessage: String {
+ return MatrixKitL10n.tr("MatrixKit", "invitation_message")
+ }
+ /// Invite
+ public static var invite: String {
+ return MatrixKitL10n.tr("MatrixKit", "invite")
+ }
+ /// Invite matrix User
+ public static var inviteUser: String {
+ return MatrixKitL10n.tr("MatrixKit", "invite_user")
+ }
+ /// Kick
+ public static var kick: String {
+ return MatrixKitL10n.tr("MatrixKit", "kick")
+ }
+ /// Default (%@)
+ public static func languagePickerDefaultLanguage(_ p1: String) -> String {
+ return MatrixKitL10n.tr("MatrixKit", "language_picker_default_language", p1)
+ }
+ /// Choose a language
+ public static var languagePickerTitle: String {
+ return MatrixKitL10n.tr("MatrixKit", "language_picker_title")
+ }
+ /// Leave
+ public static var leave: String {
+ return MatrixKitL10n.tr("MatrixKit", "leave")
+ }
+ /// To discover contacts already using Matrix, %@ can send email addresses and phone numbers in your address book to your chosen Matrix identity server. Where supported, personal data is hashed before sending - please check your identity server's privacy policy for more details.
+ public static func localContactsAccessDiscoveryWarning(_ p1: String) -> String {
+ return MatrixKitL10n.tr("MatrixKit", "local_contacts_access_discovery_warning", p1)
+ }
+ /// Users discovery
+ public static var localContactsAccessDiscoveryWarningTitle: String {
+ return MatrixKitL10n.tr("MatrixKit", "local_contacts_access_discovery_warning_title")
+ }
+ /// Users discovery from local contacts requires access to you contacts but %@ doesn't have permission to use it
+ public static func localContactsAccessNotGranted(_ p1: String) -> String {
+ return MatrixKitL10n.tr("MatrixKit", "local_contacts_access_not_granted", p1)
+ }
+ /// Login
+ public static var login: String {
+ return MatrixKitL10n.tr("MatrixKit", "login")
+ }
+ /// Create account:
+ public static var loginCreateAccount: String {
+ return MatrixKitL10n.tr("MatrixKit", "login_create_account")
+ }
+ /// Desktop
+ public static var loginDesktopDevice: String {
+ return MatrixKitL10n.tr("MatrixKit", "login_desktop_device")
+ }
+ /// Display name (e.g. Bob Obson)
+ public static var loginDisplayNamePlaceholder: String {
+ return MatrixKitL10n.tr("MatrixKit", "login_display_name_placeholder")
+ }
+ /// Specify an email address lets other users find you on Matrix more easily, and will give you a way to reset your password in the future.
+ public static var loginEmailInfo: String {
+ return MatrixKitL10n.tr("MatrixKit", "login_email_info")
+ }
+ /// Email address
+ public static var loginEmailPlaceholder: String {
+ return MatrixKitL10n.tr("MatrixKit", "login_email_placeholder")
+ }
+ /// Already logged in
+ public static var loginErrorAlreadyLoggedIn: String {
+ return MatrixKitL10n.tr("MatrixKit", "login_error_already_logged_in")
+ }
+ /// Malformed JSON
+ public static var loginErrorBadJson: String {
+ return MatrixKitL10n.tr("MatrixKit", "login_error_bad_json")
+ }
+ /// Currently we do not support any or all login flows defined by this homeserver
+ public static var loginErrorDoNotSupportLoginFlows: String {
+ return MatrixKitL10n.tr("MatrixKit", "login_error_do_not_support_login_flows")
+ }
+ /// Invalid username/password
+ public static var loginErrorForbidden: String {
+ return MatrixKitL10n.tr("MatrixKit", "login_error_forbidden")
+ }
+ /// Forgot password is not currently supported
+ public static var loginErrorForgotPasswordIsNotSupported: String {
+ return MatrixKitL10n.tr("MatrixKit", "login_error_forgot_password_is_not_supported")
+ }
+ /// Too many requests have been sent
+ public static var loginErrorLimitExceeded: String {
+ return MatrixKitL10n.tr("MatrixKit", "login_error_limit_exceeded")
+ }
+ /// The email link which has not been clicked yet
+ public static var loginErrorLoginEmailNotYet: String {
+ return MatrixKitL10n.tr("MatrixKit", "login_error_login_email_not_yet")
+ }
+ /// URL must start with http[s]://
+ public static var loginErrorMustStartHttp: String {
+ return MatrixKitL10n.tr("MatrixKit", "login_error_must_start_http")
+ }
+ /// We failed to retrieve authentication information from this homeserver
+ public static var loginErrorNoLoginFlow: String {
+ return MatrixKitL10n.tr("MatrixKit", "login_error_no_login_flow")
+ }
+ /// Did not contain valid JSON
+ public static var loginErrorNotJson: String {
+ return MatrixKitL10n.tr("MatrixKit", "login_error_not_json")
+ }
+ /// Registration is not currently supported
+ public static var loginErrorRegistrationIsNotSupported: String {
+ return MatrixKitL10n.tr("MatrixKit", "login_error_registration_is_not_supported")
+ }
+ /// Contact Administrator
+ public static var loginErrorResourceLimitExceededContactButton: String {
+ return MatrixKitL10n.tr("MatrixKit", "login_error_resource_limit_exceeded_contact_button")
+ }
+ /// \n\nPlease contact your service administrator to continue using this service.
+ public static var loginErrorResourceLimitExceededMessageContact: String {
+ return MatrixKitL10n.tr("MatrixKit", "login_error_resource_limit_exceeded_message_contact")
+ }
+ /// This homeserver has exceeded one of its resource limits.
+ public static var loginErrorResourceLimitExceededMessageDefault: String {
+ return MatrixKitL10n.tr("MatrixKit", "login_error_resource_limit_exceeded_message_default")
+ }
+ /// This homeserver has hit its Monthly Active User limit.
+ public static var loginErrorResourceLimitExceededMessageMonthlyActiveUser: String {
+ return MatrixKitL10n.tr("MatrixKit", "login_error_resource_limit_exceeded_message_monthly_active_user")
+ }
+ /// Resource Limit Exceeded
+ public static var loginErrorResourceLimitExceededTitle: String {
+ return MatrixKitL10n.tr("MatrixKit", "login_error_resource_limit_exceeded_title")
+ }
+ /// Login Failed
+ public static var loginErrorTitle: String {
+ return MatrixKitL10n.tr("MatrixKit", "login_error_title")
+ }
+ /// The access token specified was not recognised
+ public static var loginErrorUnknownToken: String {
+ return MatrixKitL10n.tr("MatrixKit", "login_error_unknown_token")
+ }
+ /// This user name is already used
+ public static var loginErrorUserInUse: String {
+ return MatrixKitL10n.tr("MatrixKit", "login_error_user_in_use")
+ }
+ /// Your homeserver stores all your conversations and account data
+ public static var loginHomeServerInfo: String {
+ return MatrixKitL10n.tr("MatrixKit", "login_home_server_info")
+ }
+ /// Homeserver URL:
+ public static var loginHomeServerTitle: String {
+ return MatrixKitL10n.tr("MatrixKit", "login_home_server_title")
+ }
+ /// Matrix provides identity servers to track which emails etc. belong to which Matrix IDs. Only https://matrix.org currently exists.
+ public static var loginIdentityServerInfo: String {
+ return MatrixKitL10n.tr("MatrixKit", "login_identity_server_info")
+ }
+ /// Identity server URL:
+ public static var loginIdentityServerTitle: String {
+ return MatrixKitL10n.tr("MatrixKit", "login_identity_server_title")
+ }
+ /// Invalid parameter
+ public static var loginInvalidParam: String {
+ return MatrixKitL10n.tr("MatrixKit", "login_invalid_param")
+ }
+ /// Cancel
+ public static var loginLeaveFallback: String {
+ return MatrixKitL10n.tr("MatrixKit", "login_leave_fallback")
+ }
+ /// Mobile
+ public static var loginMobileDevice: String {
+ return MatrixKitL10n.tr("MatrixKit", "login_mobile_device")
+ }
+ /// optional
+ public static var loginOptionalField: String {
+ return MatrixKitL10n.tr("MatrixKit", "login_optional_field")
+ }
+ /// Password
+ public static var loginPasswordPlaceholder: String {
+ return MatrixKitL10n.tr("MatrixKit", "login_password_placeholder")
+ }
+ /// Please enter your email validation token:
+ public static var loginPromptEmailToken: String {
+ return MatrixKitL10n.tr("MatrixKit", "login_prompt_email_token")
+ }
+ /// URL (e.g. https://matrix.org)
+ public static var loginServerUrlPlaceholder: String {
+ return MatrixKitL10n.tr("MatrixKit", "login_server_url_placeholder")
+ }
+ /// Tablet
+ public static var loginTabletDevice: String {
+ return MatrixKitL10n.tr("MatrixKit", "login_tablet_device")
+ }
+ /// Use fallback page
+ public static var loginUseFallback: String {
+ return MatrixKitL10n.tr("MatrixKit", "login_use_fallback")
+ }
+ /// Matrix ID (e.g. @bob:matrix.org or bob)
+ public static var loginUserIdPlaceholder: String {
+ return MatrixKitL10n.tr("MatrixKit", "login_user_id_placeholder")
+ }
+ /// Matrix
+ public static var matrix: String {
+ return MatrixKitL10n.tr("MatrixKit", "matrix")
+ }
+ /// Banned
+ public static var membershipBan: String {
+ return MatrixKitL10n.tr("MatrixKit", "membership_ban")
+ }
+ /// Invited
+ public static var membershipInvite: String {
+ return MatrixKitL10n.tr("MatrixKit", "membership_invite")
+ }
+ /// Left
+ public static var membershipLeave: String {
+ return MatrixKitL10n.tr("MatrixKit", "membership_leave")
+ }
+ /// Mention
+ public static var mention: String {
+ return MatrixKitL10n.tr("MatrixKit", "mention")
+ }
+ /// In reply to
+ public static var messageReplyToMessageToReplyToPrefix: String {
+ return MatrixKitL10n.tr("MatrixKit", "message_reply_to_message_to_reply_to_prefix")
+ }
+ /// sent a file.
+ public static var messageReplyToSenderSentAFile: String {
+ return MatrixKitL10n.tr("MatrixKit", "message_reply_to_sender_sent_a_file")
+ }
+ /// sent a video.
+ public static var messageReplyToSenderSentAVideo: String {
+ return MatrixKitL10n.tr("MatrixKit", "message_reply_to_sender_sent_a_video")
+ }
+ /// sent a voice message.
+ public static var messageReplyToSenderSentAVoiceMessage: String {
+ return MatrixKitL10n.tr("MatrixKit", "message_reply_to_sender_sent_a_voice_message")
+ }
+ /// sent an audio file.
+ public static var messageReplyToSenderSentAnAudioFile: String {
+ return MatrixKitL10n.tr("MatrixKit", "message_reply_to_sender_sent_an_audio_file")
+ }
+ /// sent an image.
+ public static var messageReplyToSenderSentAnImage: String {
+ return MatrixKitL10n.tr("MatrixKit", "message_reply_to_sender_sent_an_image")
+ }
+ /// There are unsaved changes. Leaving will discard them.
+ public static var messageUnsavedChanges: String {
+ return MatrixKitL10n.tr("MatrixKit", "message_unsaved_changes")
+ }
+ /// Calls require access to the Microphone but %@ doesn't have permission to use it
+ public static func microphoneAccessNotGrantedForCall(_ p1: String) -> String {
+ return MatrixKitL10n.tr("MatrixKit", "microphone_access_not_granted_for_call", p1)
+ }
+ /// Voice messages require access to the Microphone but %@ doesn't have permission to use it
+ public static func microphoneAccessNotGrantedForVoiceMessage(_ p1: String) -> String {
+ return MatrixKitL10n.tr("MatrixKit", "microphone_access_not_granted_for_voice_message", p1)
+ }
+ /// Please check your network connectivity
+ public static var networkErrorNotReachable: String {
+ return MatrixKitL10n.tr("MatrixKit", "network_error_not_reachable")
+ }
+ /// No
+ public static var no: String {
+ return MatrixKitL10n.tr("MatrixKit", "no")
+ }
+ /// Not supported yet
+ public static var notSupportedYet: String {
+ return MatrixKitL10n.tr("MatrixKit", "not_supported_yet")
+ }
+ /// %@ answered the call
+ public static func noticeAnsweredVideoCall(_ p1: String) -> String {
+ return MatrixKitL10n.tr("MatrixKit", "notice_answered_video_call", p1)
+ }
+ /// You answered the call
+ public static var noticeAnsweredVideoCallByYou: String {
+ return MatrixKitL10n.tr("MatrixKit", "notice_answered_video_call_by_you")
+ }
+ /// audio attachment
+ public static var noticeAudioAttachment: String {
+ return MatrixKitL10n.tr("MatrixKit", "notice_audio_attachment")
+ }
+ /// (avatar was changed too)
+ public static var noticeAvatarChangedToo: String {
+ return MatrixKitL10n.tr("MatrixKit", "notice_avatar_changed_too")
+ }
+ /// %@ changed their avatar
+ public static func noticeAvatarUrlChanged(_ p1: String) -> String {
+ return MatrixKitL10n.tr("MatrixKit", "notice_avatar_url_changed", p1)
+ }
+ /// You changed your avatar
+ public static var noticeAvatarUrlChangedByYou: String {
+ return MatrixKitL10n.tr("MatrixKit", "notice_avatar_url_changed_by_you")
+ }
+ /// VoIP conference finished
+ public static var noticeConferenceCallFinished: String {
+ return MatrixKitL10n.tr("MatrixKit", "notice_conference_call_finished")
+ }
+ /// %@ requested a VoIP conference
+ public static func noticeConferenceCallRequest(_ p1: String) -> String {
+ return MatrixKitL10n.tr("MatrixKit", "notice_conference_call_request", p1)
+ }
+ /// You requested a VoIP conference
+ public static var noticeConferenceCallRequestByYou: String {
+ return MatrixKitL10n.tr("MatrixKit", "notice_conference_call_request_by_you")
+ }
+ /// VoIP conference started
+ public static var noticeConferenceCallStarted: String {
+ return MatrixKitL10n.tr("MatrixKit", "notice_conference_call_started")
+ }
+ /// The sender's session has not sent us the keys for this message.
+ public static var noticeCryptoErrorUnknownInboundSessionId: String {
+ return MatrixKitL10n.tr("MatrixKit", "notice_crypto_error_unknown_inbound_session_id")
+ }
+ /// ** Unable to decrypt: %@ **
+ public static func noticeCryptoUnableToDecrypt(_ p1: String) -> String {
+ return MatrixKitL10n.tr("MatrixKit", "notice_crypto_unable_to_decrypt", p1)
+ }
+ /// %@ declined the call
+ public static func noticeDeclinedVideoCall(_ p1: String) -> String {
+ return MatrixKitL10n.tr("MatrixKit", "notice_declined_video_call", p1)
+ }
+ /// You declined the call
+ public static var noticeDeclinedVideoCallByYou: String {
+ return MatrixKitL10n.tr("MatrixKit", "notice_declined_video_call_by_you")
+ }
+ /// %@ changed their display name from %@ to %@
+ public static func noticeDisplayNameChangedFrom(_ p1: String, _ p2: String, _ p3: String) -> String {
+ return MatrixKitL10n.tr("MatrixKit", "notice_display_name_changed_from", p1, p2, p3)
+ }
+ /// You changed your display name from %@ to %@
+ public static func noticeDisplayNameChangedFromByYou(_ p1: String, _ p2: String) -> String {
+ return MatrixKitL10n.tr("MatrixKit", "notice_display_name_changed_from_by_you", p1, p2)
+ }
+ /// %@ removed their display name
+ public static func noticeDisplayNameRemoved(_ p1: String) -> String {
+ return MatrixKitL10n.tr("MatrixKit", "notice_display_name_removed", p1)
+ }
+ /// You removed your display name
+ public static var noticeDisplayNameRemovedByYou: String {
+ return MatrixKitL10n.tr("MatrixKit", "notice_display_name_removed_by_you")
+ }
+ /// %@ set their display name to %@
+ public static func noticeDisplayNameSet(_ p1: String, _ p2: String) -> String {
+ return MatrixKitL10n.tr("MatrixKit", "notice_display_name_set", p1, p2)
+ }
+ /// You set your display name to %@
+ public static func noticeDisplayNameSetByYou(_ p1: String) -> String {
+ return MatrixKitL10n.tr("MatrixKit", "notice_display_name_set_by_you", p1)
+ }
+ /// Encrypted message
+ public static var noticeEncryptedMessage: String {
+ return MatrixKitL10n.tr("MatrixKit", "notice_encrypted_message")
+ }
+ /// %@ turned on end-to-end encryption.
+ public static func noticeEncryptionEnabledOk(_ p1: String) -> String {
+ return MatrixKitL10n.tr("MatrixKit", "notice_encryption_enabled_ok", p1)
+ }
+ /// You turned on end-to-end encryption.
+ public static var noticeEncryptionEnabledOkByYou: String {
+ return MatrixKitL10n.tr("MatrixKit", "notice_encryption_enabled_ok_by_you")
+ }
+ /// %1$@ turned on end-to-end encryption (unrecognised algorithm %2$@).
+ public static func noticeEncryptionEnabledUnknownAlgorithm(_ p1: String, _ p2: String) -> String {
+ return MatrixKitL10n.tr("MatrixKit", "notice_encryption_enabled_unknown_algorithm", p1, p2)
+ }
+ /// You turned on end-to-end encryption (unrecognised algorithm %@).
+ public static func noticeEncryptionEnabledUnknownAlgorithmByYou(_ p1: String) -> String {
+ return MatrixKitL10n.tr("MatrixKit", "notice_encryption_enabled_unknown_algorithm_by_you", p1)
+ }
+ /// %@ ended the call
+ public static func noticeEndedVideoCall(_ p1: String) -> String {
+ return MatrixKitL10n.tr("MatrixKit", "notice_ended_video_call", p1)
+ }
+ /// You ended the call
+ public static var noticeEndedVideoCallByYou: String {
+ return MatrixKitL10n.tr("MatrixKit", "notice_ended_video_call_by_you")
+ }
+ /// Unexpected event
+ public static var noticeErrorUnexpectedEvent: String {
+ return MatrixKitL10n.tr("MatrixKit", "notice_error_unexpected_event")
+ }
+ /// Unknown event type
+ public static var noticeErrorUnknownEventType: String {
+ return MatrixKitL10n.tr("MatrixKit", "notice_error_unknown_event_type")
+ }
+ /// Unsupported event
+ public static var noticeErrorUnsupportedEvent: String {
+ return MatrixKitL10n.tr("MatrixKit", "notice_error_unsupported_event")
+ }
+ ///
+ public static func noticeEventRedacted(_ p1: String) -> String {
+ return MatrixKitL10n.tr("MatrixKit", "notice_event_redacted", p1)
+ }
+ /// by %@
+ public static func noticeEventRedactedBy(_ p1: String) -> String {
+ return MatrixKitL10n.tr("MatrixKit", "notice_event_redacted_by", p1)
+ }
+ /// by you
+ public static var noticeEventRedactedByYou: String {
+ return MatrixKitL10n.tr("MatrixKit", "notice_event_redacted_by_you")
+ }
+ /// [reason: %@]
+ public static func noticeEventRedactedReason(_ p1: String) -> String {
+ return MatrixKitL10n.tr("MatrixKit", "notice_event_redacted_reason", p1)
+ }
+ /// Feedback event (id: %@): %@
+ public static func noticeFeedback(_ p1: String, _ p2: String) -> String {
+ return MatrixKitL10n.tr("MatrixKit", "notice_feedback", p1, p2)
+ }
+ /// file attachment
+ public static var noticeFileAttachment: String {
+ return MatrixKitL10n.tr("MatrixKit", "notice_file_attachment")
+ }
+ /// image attachment
+ public static var noticeImageAttachment: String {
+ return MatrixKitL10n.tr("MatrixKit", "notice_image_attachment")
+ }
+ /// In reply to
+ public static var noticeInReplyTo: String {
+ return MatrixKitL10n.tr("MatrixKit", "notice_in_reply_to")
+ }
+ /// invalid attachment
+ public static var noticeInvalidAttachment: String {
+ return MatrixKitL10n.tr("MatrixKit", "notice_invalid_attachment")
+ }
+ /// location attachment
+ public static var noticeLocationAttachment: String {
+ return MatrixKitL10n.tr("MatrixKit", "notice_location_attachment")
+ }
+ /// %@ placed a video call
+ public static func noticePlacedVideoCall(_ p1: String) -> String {
+ return MatrixKitL10n.tr("MatrixKit", "notice_placed_video_call", p1)
+ }
+ /// You placed a video call
+ public static var noticePlacedVideoCallByYou: String {
+ return MatrixKitL10n.tr("MatrixKit", "notice_placed_video_call_by_you")
+ }
+ /// %@ placed a voice call
+ public static func noticePlacedVoiceCall(_ p1: String) -> String {
+ return MatrixKitL10n.tr("MatrixKit", "notice_placed_voice_call", p1)
+ }
+ /// You placed a voice call
+ public static var noticePlacedVoiceCallByYou: String {
+ return MatrixKitL10n.tr("MatrixKit", "notice_placed_voice_call_by_you")
+ }
+ /// %@ updated their profile %@
+ public static func noticeProfileChangeRedacted(_ p1: String, _ p2: String) -> String {
+ return MatrixKitL10n.tr("MatrixKit", "notice_profile_change_redacted", p1, p2)
+ }
+ /// You updated your profile %@
+ public static func noticeProfileChangeRedactedByYou(_ p1: String) -> String {
+ return MatrixKitL10n.tr("MatrixKit", "notice_profile_change_redacted_by_you", p1)
+ }
+ /// %@ redacted an event (id: %@)
+ public static func noticeRedaction(_ p1: String, _ p2: String) -> String {
+ return MatrixKitL10n.tr("MatrixKit", "notice_redaction", p1, p2)
+ }
+ /// You redacted an event (id: %@)
+ public static func noticeRedactionByYou(_ p1: String) -> String {
+ return MatrixKitL10n.tr("MatrixKit", "notice_redaction_by_you", p1)
+ }
+ /// The room aliases are: %@
+ public static func noticeRoomAliases(_ p1: String) -> String {
+ return MatrixKitL10n.tr("MatrixKit", "notice_room_aliases", p1)
+ }
+ /// The aliases are: %@
+ public static func noticeRoomAliasesForDm(_ p1: String) -> String {
+ return MatrixKitL10n.tr("MatrixKit", "notice_room_aliases_for_dm", p1)
+ }
+ /// %@ banned %@
+ public static func noticeRoomBan(_ p1: String, _ p2: String) -> String {
+ return MatrixKitL10n.tr("MatrixKit", "notice_room_ban", p1, p2)
+ }
+ /// You banned %@
+ public static func noticeRoomBanByYou(_ p1: String) -> String {
+ return MatrixKitL10n.tr("MatrixKit", "notice_room_ban_by_you", p1)
+ }
+ /// %@ created and configured the room.
+ public static func noticeRoomCreated(_ p1: String) -> String {
+ return MatrixKitL10n.tr("MatrixKit", "notice_room_created", p1)
+ }
+ /// You created and configured the room.
+ public static var noticeRoomCreatedByYou: String {
+ return MatrixKitL10n.tr("MatrixKit", "notice_room_created_by_you")
+ }
+ /// You joined.
+ public static var noticeRoomCreatedByYouForDm: String {
+ return MatrixKitL10n.tr("MatrixKit", "notice_room_created_by_you_for_dm")
+ }
+ /// %@ joined.
+ public static func noticeRoomCreatedForDm(_ p1: String) -> String {
+ return MatrixKitL10n.tr("MatrixKit", "notice_room_created_for_dm", p1)
+ }
+ /// %@ made future room history visible to anyone.
+ public static func noticeRoomHistoryVisibleToAnyone(_ p1: String) -> String {
+ return MatrixKitL10n.tr("MatrixKit", "notice_room_history_visible_to_anyone", p1)
+ }
+ /// You made future room history visible to anyone.
+ public static var noticeRoomHistoryVisibleToAnyoneByYou: String {
+ return MatrixKitL10n.tr("MatrixKit", "notice_room_history_visible_to_anyone_by_you")
+ }
+ /// %@ made future room history visible to all room members.
+ public static func noticeRoomHistoryVisibleToMembers(_ p1: String) -> String {
+ return MatrixKitL10n.tr("MatrixKit", "notice_room_history_visible_to_members", p1)
+ }
+ /// You made future room history visible to all room members.
+ public static var noticeRoomHistoryVisibleToMembersByYou: String {
+ return MatrixKitL10n.tr("MatrixKit", "notice_room_history_visible_to_members_by_you")
+ }
+ /// You made future messages visible to all room members.
+ public static var noticeRoomHistoryVisibleToMembersByYouForDm: String {
+ return MatrixKitL10n.tr("MatrixKit", "notice_room_history_visible_to_members_by_you_for_dm")
+ }
+ /// %@ made future messages visible to all room members.
+ public static func noticeRoomHistoryVisibleToMembersForDm(_ p1: String) -> String {
+ return MatrixKitL10n.tr("MatrixKit", "notice_room_history_visible_to_members_for_dm", p1)
+ }
+ /// %@ made future room history visible to all room members, from the point they are invited.
+ public static func noticeRoomHistoryVisibleToMembersFromInvitedPoint(_ p1: String) -> String {
+ return MatrixKitL10n.tr("MatrixKit", "notice_room_history_visible_to_members_from_invited_point", p1)
+ }
+ /// You made future room history visible to all room members, from the point they are invited.
+ public static var noticeRoomHistoryVisibleToMembersFromInvitedPointByYou: String {
+ return MatrixKitL10n.tr("MatrixKit", "notice_room_history_visible_to_members_from_invited_point_by_you")
+ }
+ /// You made future messages visible to everyone, from when they get invited.
+ public static var noticeRoomHistoryVisibleToMembersFromInvitedPointByYouForDm: String {
+ return MatrixKitL10n.tr("MatrixKit", "notice_room_history_visible_to_members_from_invited_point_by_you_for_dm")
+ }
+ /// %@ made future messages visible to everyone, from when they get invited.
+ public static func noticeRoomHistoryVisibleToMembersFromInvitedPointForDm(_ p1: String) -> String {
+ return MatrixKitL10n.tr("MatrixKit", "notice_room_history_visible_to_members_from_invited_point_for_dm", p1)
+ }
+ /// %@ made future room history visible to all room members, from the point they joined.
+ public static func noticeRoomHistoryVisibleToMembersFromJoinedPoint(_ p1: String) -> String {
+ return MatrixKitL10n.tr("MatrixKit", "notice_room_history_visible_to_members_from_joined_point", p1)
+ }
+ /// You made future room history visible to all room members, from the point they joined.
+ public static var noticeRoomHistoryVisibleToMembersFromJoinedPointByYou: String {
+ return MatrixKitL10n.tr("MatrixKit", "notice_room_history_visible_to_members_from_joined_point_by_you")
+ }
+ /// You made future messages visible to everyone, from when they joined.
+ public static var noticeRoomHistoryVisibleToMembersFromJoinedPointByYouForDm: String {
+ return MatrixKitL10n.tr("MatrixKit", "notice_room_history_visible_to_members_from_joined_point_by_you_for_dm")
+ }
+ /// %@ made future messages visible to everyone, from when they joined.
+ public static func noticeRoomHistoryVisibleToMembersFromJoinedPointForDm(_ p1: String) -> String {
+ return MatrixKitL10n.tr("MatrixKit", "notice_room_history_visible_to_members_from_joined_point_for_dm", p1)
+ }
+ /// %@ invited %@
+ public static func noticeRoomInvite(_ p1: String, _ p2: String) -> String {
+ return MatrixKitL10n.tr("MatrixKit", "notice_room_invite", p1, p2)
+ }
+ /// You invited %@
+ public static func noticeRoomInviteByYou(_ p1: String) -> String {
+ return MatrixKitL10n.tr("MatrixKit", "notice_room_invite_by_you", p1)
+ }
+ /// %@ invited you
+ public static func noticeRoomInviteYou(_ p1: String) -> String {
+ return MatrixKitL10n.tr("MatrixKit", "notice_room_invite_you", p1)
+ }
+ /// %@ joined
+ public static func noticeRoomJoin(_ p1: String) -> String {
+ return MatrixKitL10n.tr("MatrixKit", "notice_room_join", p1)
+ }
+ /// You joined
+ public static var noticeRoomJoinByYou: String {
+ return MatrixKitL10n.tr("MatrixKit", "notice_room_join_by_you")
+ }
+ /// The join rule is: %@
+ public static func noticeRoomJoinRule(_ p1: String) -> String {
+ return MatrixKitL10n.tr("MatrixKit", "notice_room_join_rule", p1)
+ }
+ /// %@ made the room invite only.
+ public static func noticeRoomJoinRuleInvite(_ p1: String) -> String {
+ return MatrixKitL10n.tr("MatrixKit", "notice_room_join_rule_invite", p1)
+ }
+ /// You made the room invite only.
+ public static var noticeRoomJoinRuleInviteByYou: String {
+ return MatrixKitL10n.tr("MatrixKit", "notice_room_join_rule_invite_by_you")
+ }
+ /// You made this invite only.
+ public static var noticeRoomJoinRuleInviteByYouForDm: String {
+ return MatrixKitL10n.tr("MatrixKit", "notice_room_join_rule_invite_by_you_for_dm")
+ }
+ /// %@ made this invite only.
+ public static func noticeRoomJoinRuleInviteForDm(_ p1: String) -> String {
+ return MatrixKitL10n.tr("MatrixKit", "notice_room_join_rule_invite_for_dm", p1)
+ }
+ /// %@ made the room public.
+ public static func noticeRoomJoinRulePublic(_ p1: String) -> String {
+ return MatrixKitL10n.tr("MatrixKit", "notice_room_join_rule_public", p1)
+ }
+ /// You made the room public.
+ public static var noticeRoomJoinRulePublicByYou: String {
+ return MatrixKitL10n.tr("MatrixKit", "notice_room_join_rule_public_by_you")
+ }
+ /// You made this public.
+ public static var noticeRoomJoinRulePublicByYouForDm: String {
+ return MatrixKitL10n.tr("MatrixKit", "notice_room_join_rule_public_by_you_for_dm")
+ }
+ /// %@ made this public.
+ public static func noticeRoomJoinRulePublicForDm(_ p1: String) -> String {
+ return MatrixKitL10n.tr("MatrixKit", "notice_room_join_rule_public_for_dm", p1)
+ }
+ /// %@ kicked %@
+ public static func noticeRoomKick(_ p1: String, _ p2: String) -> String {
+ return MatrixKitL10n.tr("MatrixKit", "notice_room_kick", p1, p2)
+ }
+ /// You kicked %@
+ public static func noticeRoomKickByYou(_ p1: String) -> String {
+ return MatrixKitL10n.tr("MatrixKit", "notice_room_kick_by_you", p1)
+ }
+ /// %@ left
+ public static func noticeRoomLeave(_ p1: String) -> String {
+ return MatrixKitL10n.tr("MatrixKit", "notice_room_leave", p1)
+ }
+ /// You left
+ public static var noticeRoomLeaveByYou: String {
+ return MatrixKitL10n.tr("MatrixKit", "notice_room_leave_by_you")
+ }
+ /// %@ changed the room name to %@.
+ public static func noticeRoomNameChanged(_ p1: String, _ p2: String) -> String {
+ return MatrixKitL10n.tr("MatrixKit", "notice_room_name_changed", p1, p2)
+ }
+ /// You changed the room name to %@.
+ public static func noticeRoomNameChangedByYou(_ p1: String) -> String {
+ return MatrixKitL10n.tr("MatrixKit", "notice_room_name_changed_by_you", p1)
+ }
+ /// You changed the name to %@.
+ public static func noticeRoomNameChangedByYouForDm(_ p1: String) -> String {
+ return MatrixKitL10n.tr("MatrixKit", "notice_room_name_changed_by_you_for_dm", p1)
+ }
+ /// %@ changed the name to %@.
+ public static func noticeRoomNameChangedForDm(_ p1: String, _ p2: String) -> String {
+ return MatrixKitL10n.tr("MatrixKit", "notice_room_name_changed_for_dm", p1, p2)
+ }
+ /// %@ removed the room name
+ public static func noticeRoomNameRemoved(_ p1: String) -> String {
+ return MatrixKitL10n.tr("MatrixKit", "notice_room_name_removed", p1)
+ }
+ /// You removed the room name
+ public static var noticeRoomNameRemovedByYou: String {
+ return MatrixKitL10n.tr("MatrixKit", "notice_room_name_removed_by_you")
+ }
+ /// You removed the name
+ public static var noticeRoomNameRemovedByYouForDm: String {
+ return MatrixKitL10n.tr("MatrixKit", "notice_room_name_removed_by_you_for_dm")
+ }
+ /// %@ removed the name
+ public static func noticeRoomNameRemovedForDm(_ p1: String) -> String {
+ return MatrixKitL10n.tr("MatrixKit", "notice_room_name_removed_for_dm", p1)
+ }
+ /// The minimum power levels that a user must have before acting are:
+ public static var noticeRoomPowerLevelActingRequirement: String {
+ return MatrixKitL10n.tr("MatrixKit", "notice_room_power_level_acting_requirement")
+ }
+ /// The minimum power levels related to events are:
+ public static var noticeRoomPowerLevelEventRequirement: String {
+ return MatrixKitL10n.tr("MatrixKit", "notice_room_power_level_event_requirement")
+ }
+ /// The power level of room members are:
+ public static var noticeRoomPowerLevelIntro: String {
+ return MatrixKitL10n.tr("MatrixKit", "notice_room_power_level_intro")
+ }
+ /// The power level of members are:
+ public static var noticeRoomPowerLevelIntroForDm: String {
+ return MatrixKitL10n.tr("MatrixKit", "notice_room_power_level_intro_for_dm")
+ }
+ /// . Reason: %@
+ public static func noticeRoomReason(_ p1: String) -> String {
+ return MatrixKitL10n.tr("MatrixKit", "notice_room_reason", p1)
+ }
+ /// %@ rejected the invitation
+ public static func noticeRoomReject(_ p1: String) -> String {
+ return MatrixKitL10n.tr("MatrixKit", "notice_room_reject", p1)
+ }
+ /// You rejected the invitation
+ public static var noticeRoomRejectByYou: String {
+ return MatrixKitL10n.tr("MatrixKit", "notice_room_reject_by_you")
+ }
+ /// The groups associated with this room are: %@
+ public static func noticeRoomRelatedGroups(_ p1: String) -> String {
+ return MatrixKitL10n.tr("MatrixKit", "notice_room_related_groups", p1)
+ }
+ /// %@ sent an invitation to %@ to join the room
+ public static func noticeRoomThirdPartyInvite(_ p1: String, _ p2: String) -> String {
+ return MatrixKitL10n.tr("MatrixKit", "notice_room_third_party_invite", p1, p2)
+ }
+ /// You sent an invitation to %@ to join the room
+ public static func noticeRoomThirdPartyInviteByYou(_ p1: String) -> String {
+ return MatrixKitL10n.tr("MatrixKit", "notice_room_third_party_invite_by_you", p1)
+ }
+ /// You invited %@
+ public static func noticeRoomThirdPartyInviteByYouForDm(_ p1: String) -> String {
+ return MatrixKitL10n.tr("MatrixKit", "notice_room_third_party_invite_by_you_for_dm", p1)
+ }
+ /// %@ invited %@
+ public static func noticeRoomThirdPartyInviteForDm(_ p1: String, _ p2: String) -> String {
+ return MatrixKitL10n.tr("MatrixKit", "notice_room_third_party_invite_for_dm", p1, p2)
+ }
+ /// %@ accepted the invitation for %@
+ public static func noticeRoomThirdPartyRegisteredInvite(_ p1: String, _ p2: String) -> String {
+ return MatrixKitL10n.tr("MatrixKit", "notice_room_third_party_registered_invite", p1, p2)
+ }
+ /// You accepted the invitation for %@
+ public static func noticeRoomThirdPartyRegisteredInviteByYou(_ p1: String) -> String {
+ return MatrixKitL10n.tr("MatrixKit", "notice_room_third_party_registered_invite_by_you", p1)
+ }
+ /// %@ revoked the invitation for %@ to join the room
+ public static func noticeRoomThirdPartyRevokedInvite(_ p1: String, _ p2: String) -> String {
+ return MatrixKitL10n.tr("MatrixKit", "notice_room_third_party_revoked_invite", p1, p2)
+ }
+ /// You revoked the invitation for %@ to join the room
+ public static func noticeRoomThirdPartyRevokedInviteByYou(_ p1: String) -> String {
+ return MatrixKitL10n.tr("MatrixKit", "notice_room_third_party_revoked_invite_by_you", p1)
+ }
+ /// You revoked %@'s invitation
+ public static func noticeRoomThirdPartyRevokedInviteByYouForDm(_ p1: String) -> String {
+ return MatrixKitL10n.tr("MatrixKit", "notice_room_third_party_revoked_invite_by_you_for_dm", p1)
+ }
+ /// %@ revoked %@'s invitation
+ public static func noticeRoomThirdPartyRevokedInviteForDm(_ p1: String, _ p2: String) -> String {
+ return MatrixKitL10n.tr("MatrixKit", "notice_room_third_party_revoked_invite_for_dm", p1, p2)
+ }
+ /// %@ removed the topic
+ public static func noticeRoomTopicRemoved(_ p1: String) -> String {
+ return MatrixKitL10n.tr("MatrixKit", "notice_room_topic_removed", p1)
+ }
+ /// You removed the topic
+ public static var noticeRoomTopicRemovedByYou: String {
+ return MatrixKitL10n.tr("MatrixKit", "notice_room_topic_removed_by_you")
+ }
+ /// %@ unbanned %@
+ public static func noticeRoomUnban(_ p1: String, _ p2: String) -> String {
+ return MatrixKitL10n.tr("MatrixKit", "notice_room_unban", p1, p2)
+ }
+ /// You unbanned %@
+ public static func noticeRoomUnbanByYou(_ p1: String) -> String {
+ return MatrixKitL10n.tr("MatrixKit", "notice_room_unban_by_you", p1)
+ }
+ /// %@ withdrew %@'s invitation
+ public static func noticeRoomWithdraw(_ p1: String, _ p2: String) -> String {
+ return MatrixKitL10n.tr("MatrixKit", "notice_room_withdraw", p1, p2)
+ }
+ /// You withdrew %@'s invitation
+ public static func noticeRoomWithdrawByYou(_ p1: String) -> String {
+ return MatrixKitL10n.tr("MatrixKit", "notice_room_withdraw_by_you", p1)
+ }
+ /// sticker
+ public static var noticeSticker: String {
+ return MatrixKitL10n.tr("MatrixKit", "notice_sticker")
+ }
+ /// %@ changed the topic to "%@".
+ public static func noticeTopicChanged(_ p1: String, _ p2: String) -> String {
+ return MatrixKitL10n.tr("MatrixKit", "notice_topic_changed", p1, p2)
+ }
+ /// You changed the topic to "%@".
+ public static func noticeTopicChangedByYou(_ p1: String) -> String {
+ return MatrixKitL10n.tr("MatrixKit", "notice_topic_changed_by_you", p1)
+ }
+ /// Unsupported attachment: %@
+ public static func noticeUnsupportedAttachment(_ p1: String) -> String {
+ return MatrixKitL10n.tr("MatrixKit", "notice_unsupported_attachment", p1)
+ }
+ /// video attachment
+ public static var noticeVideoAttachment: String {
+ return MatrixKitL10n.tr("MatrixKit", "notice_video_attachment")
+ }
+ /// Always notify
+ public static var notificationSettingsAlwaysNotify: String {
+ return MatrixKitL10n.tr("MatrixKit", "notification_settings_always_notify")
+ }
+ /// By default...
+ public static var notificationSettingsByDefault: String {
+ return MatrixKitL10n.tr("MatrixKit", "notification_settings_by_default")
+ }
+ /// Notify me with sound about messages that contain my display name
+ public static var notificationSettingsContainMyDisplayName: String {
+ return MatrixKitL10n.tr("MatrixKit", "notification_settings_contain_my_display_name")
+ }
+ /// Notify me with sound about messages that contain my user name
+ public static var notificationSettingsContainMyUserName: String {
+ return MatrixKitL10n.tr("MatrixKit", "notification_settings_contain_my_user_name")
+ }
+ /// Custom sound
+ public static var notificationSettingsCustomSound: String {
+ return MatrixKitL10n.tr("MatrixKit", "notification_settings_custom_sound")
+ }
+ /// Disable all notifications
+ public static var notificationSettingsDisableAll: String {
+ return MatrixKitL10n.tr("MatrixKit", "notification_settings_disable_all")
+ }
+ /// Enable notifications
+ public static var notificationSettingsEnableNotifications: String {
+ return MatrixKitL10n.tr("MatrixKit", "notification_settings_enable_notifications")
+ }
+ /// All notifications are currently disabled for all devices.
+ public static var notificationSettingsEnableNotificationsWarning: String {
+ return MatrixKitL10n.tr("MatrixKit", "notification_settings_enable_notifications_warning")
+ }
+ /// Notification settings are saved to your user account and are shared between all clients which support them (including desktop notifications).\n\nRules are applied in order; the first rule which matches defines the outcome for the message.\nSo: Per-word notifications are more important than per-room notifications which are more important than per-sender notifications.\nFor multiple rules of the same kind, the first one in the list that matches takes priority.
+ public static var notificationSettingsGlobalInfo: String {
+ return MatrixKitL10n.tr("MatrixKit", "notification_settings_global_info")
+ }
+ /// Highlight
+ public static var notificationSettingsHighlight: String {
+ return MatrixKitL10n.tr("MatrixKit", "notification_settings_highlight")
+ }
+ /// Notify me when I am invited to a new room
+ public static var notificationSettingsInviteToANewRoom: String {
+ return MatrixKitL10n.tr("MatrixKit", "notification_settings_invite_to_a_new_room")
+ }
+ /// Notify me with sound about messages sent just to me
+ public static var notificationSettingsJustSentToMe: String {
+ return MatrixKitL10n.tr("MatrixKit", "notification_settings_just_sent_to_me")
+ }
+ /// Never notify
+ public static var notificationSettingsNeverNotify: String {
+ return MatrixKitL10n.tr("MatrixKit", "notification_settings_never_notify")
+ }
+ /// Notify for all other messages/rooms
+ public static var notificationSettingsNotifyAllOther: String {
+ return MatrixKitL10n.tr("MatrixKit", "notification_settings_notify_all_other")
+ }
+ /// Other Alerts
+ public static var notificationSettingsOtherAlerts: String {
+ return MatrixKitL10n.tr("MatrixKit", "notification_settings_other_alerts")
+ }
+ /// Notify me when people join or leave rooms
+ public static var notificationSettingsPeopleJoinLeaveRooms: String {
+ return MatrixKitL10n.tr("MatrixKit", "notification_settings_people_join_leave_rooms")
+ }
+ /// Per-room notifications
+ public static var notificationSettingsPerRoomNotifications: String {
+ return MatrixKitL10n.tr("MatrixKit", "notification_settings_per_room_notifications")
+ }
+ /// Per-sender notifications
+ public static var notificationSettingsPerSenderNotifications: String {
+ return MatrixKitL10n.tr("MatrixKit", "notification_settings_per_sender_notifications")
+ }
+ /// Words match case insensitively, and may include a * wildcard. So:\nfoo matches the string foo surrounded by word delimiters (e.g. punctuation and whitespace or start/end of line).\nfoo* matches any such word that begins foo.\n*foo* matches any such word which includes the 3 letters foo.
+ public static var notificationSettingsPerWordInfo: String {
+ return MatrixKitL10n.tr("MatrixKit", "notification_settings_per_word_info")
+ }
+ /// Per-word notifications
+ public static var notificationSettingsPerWordNotifications: String {
+ return MatrixKitL10n.tr("MatrixKit", "notification_settings_per_word_notifications")
+ }
+ /// Notify me when I receive a call
+ public static var notificationSettingsReceiveACall: String {
+ return MatrixKitL10n.tr("MatrixKit", "notification_settings_receive_a_call")
+ }
+ /// Room: '%@'
+ public static func notificationSettingsRoomRuleTitle(_ p1: String) -> String {
+ return MatrixKitL10n.tr("MatrixKit", "notification_settings_room_rule_title", p1)
+ }
+ /// Select a room
+ public static var notificationSettingsSelectRoom: String {
+ return MatrixKitL10n.tr("MatrixKit", "notification_settings_select_room")
+ }
+ /// @user:domain.com
+ public static var notificationSettingsSenderHint: String {
+ return MatrixKitL10n.tr("MatrixKit", "notification_settings_sender_hint")
+ }
+ /// Suppress notifications from bots
+ public static var notificationSettingsSuppressFromBots: String {
+ return MatrixKitL10n.tr("MatrixKit", "notification_settings_suppress_from_bots")
+ }
+ /// word to match
+ public static var notificationSettingsWordToMatch: String {
+ return MatrixKitL10n.tr("MatrixKit", "notification_settings_word_to_match")
+ }
+ /// %@ user
+ public static func numMembersOne(_ p1: String) -> String {
+ return MatrixKitL10n.tr("MatrixKit", "num_members_one", p1)
+ }
+ /// %@ users
+ public static func numMembersOther(_ p1: String) -> String {
+ return MatrixKitL10n.tr("MatrixKit", "num_members_other", p1)
+ }
+ /// offline
+ public static var offline: String {
+ return MatrixKitL10n.tr("MatrixKit", "offline")
+ }
+ /// OK
+ public static var ok: String {
+ return MatrixKitL10n.tr("MatrixKit", "ok")
+ }
+ /// Power Level
+ public static var powerLevel: String {
+ return MatrixKitL10n.tr("MatrixKit", "power_level")
+ }
+ /// Private
+ public static var `private`: String {
+ return MatrixKitL10n.tr("MatrixKit", "private")
+ }
+ /// Public
+ public static var `public`: String {
+ return MatrixKitL10n.tr("MatrixKit", "public")
+ }
+ /// Remove
+ public static var redact: String {
+ return MatrixKitL10n.tr("MatrixKit", "redact")
+ }
+ /// Registration Failed
+ public static var registerErrorTitle: String {
+ return MatrixKitL10n.tr("MatrixKit", "register_error_title")
+ }
+ /// Reject Call
+ public static var rejectCall: String {
+ return MatrixKitL10n.tr("MatrixKit", "reject_call")
+ }
+ /// Rename
+ public static var rename: String {
+ return MatrixKitL10n.tr("MatrixKit", "rename")
+ }
+ /// Resend
+ public static var resend: String {
+ return MatrixKitL10n.tr("MatrixKit", "resend")
+ }
+ /// Resend the message
+ public static var resendMessage: String {
+ return MatrixKitL10n.tr("MatrixKit", "resend_message")
+ }
+ /// Reset to default
+ public static var resetToDefault: String {
+ return MatrixKitL10n.tr("MatrixKit", "reset_to_default")
+ }
+ /// Resume
+ public static var resumeCall: String {
+ return MatrixKitL10n.tr("MatrixKit", "resume_call")
+ }
+ /// Retry
+ public static var retry: String {
+ return MatrixKitL10n.tr("MatrixKit", "retry")
+ }
+ /// (e.g. #foo:example.org)
+ public static var roomCreationAliasPlaceholder: String {
+ return MatrixKitL10n.tr("MatrixKit", "room_creation_alias_placeholder")
+ }
+ /// (e.g. #foo%@)
+ public static func roomCreationAliasPlaceholderWithHomeserver(_ p1: String) -> String {
+ return MatrixKitL10n.tr("MatrixKit", "room_creation_alias_placeholder_with_homeserver", p1)
+ }
+ /// Room alias:
+ public static var roomCreationAliasTitle: String {
+ return MatrixKitL10n.tr("MatrixKit", "room_creation_alias_title")
+ }
+ /// (e.g. lunchGroup)
+ public static var roomCreationNamePlaceholder: String {
+ return MatrixKitL10n.tr("MatrixKit", "room_creation_name_placeholder")
+ }
+ /// Room name:
+ public static var roomCreationNameTitle: String {
+ return MatrixKitL10n.tr("MatrixKit", "room_creation_name_title")
+ }
+ /// (e.g. @bob:homeserver1; @john:homeserver2...)
+ public static var roomCreationParticipantsPlaceholder: String {
+ return MatrixKitL10n.tr("MatrixKit", "room_creation_participants_placeholder")
+ }
+ /// Participants:
+ public static var roomCreationParticipantsTitle: String {
+ return MatrixKitL10n.tr("MatrixKit", "room_creation_participants_title")
+ }
+ /// Room Details
+ public static var roomDetailsTitle: String {
+ return MatrixKitL10n.tr("MatrixKit", "room_details_title")
+ }
+ /// %@ (Left)
+ public static func roomDisplaynameAllOtherMembersLeft(_ p1: String) -> String {
+ return MatrixKitL10n.tr("MatrixKit", "room_displayname_all_other_members_left", p1)
+ }
+ /// Empty room
+ public static var roomDisplaynameEmptyRoom: String {
+ return MatrixKitL10n.tr("MatrixKit", "room_displayname_empty_room")
+ }
+ /// %@ and %@ others
+ public static func roomDisplaynameMoreThanTwoMembers(_ p1: String, _ p2: String) -> String {
+ return MatrixKitL10n.tr("MatrixKit", "room_displayname_more_than_two_members", p1, p2)
+ }
+ /// %@ and %@
+ public static func roomDisplaynameTwoMembers(_ p1: String, _ p2: String) -> String {
+ return MatrixKitL10n.tr("MatrixKit", "room_displayname_two_members", p1, p2)
+ }
+ /// Failed to load timeline
+ public static var roomErrorCannotLoadTimeline: String {
+ return MatrixKitL10n.tr("MatrixKit", "room_error_cannot_load_timeline")
+ }
+ /// It is not currently possible to join an empty room.
+ public static var roomErrorJoinFailedEmptyRoom: String {
+ return MatrixKitL10n.tr("MatrixKit", "room_error_join_failed_empty_room")
+ }
+ /// Failed to join room
+ public static var roomErrorJoinFailedTitle: String {
+ return MatrixKitL10n.tr("MatrixKit", "room_error_join_failed_title")
+ }
+ /// You are not authorized to edit this room name
+ public static var roomErrorNameEditionNotAuthorized: String {
+ return MatrixKitL10n.tr("MatrixKit", "room_error_name_edition_not_authorized")
+ }
+ /// The application was trying to load a specific point in this room's timeline but was unable to find it
+ public static var roomErrorTimelineEventNotFound: String {
+ return MatrixKitL10n.tr("MatrixKit", "room_error_timeline_event_not_found")
+ }
+ /// Failed to load timeline position
+ public static var roomErrorTimelineEventNotFoundTitle: String {
+ return MatrixKitL10n.tr("MatrixKit", "room_error_timeline_event_not_found_title")
+ }
+ /// You are not authorized to edit this room topic
+ public static var roomErrorTopicEditionNotAuthorized: String {
+ return MatrixKitL10n.tr("MatrixKit", "room_error_topic_edition_not_authorized")
+ }
+ /// Blacklist
+ public static var roomEventEncryptionInfoBlock: String {
+ return MatrixKitL10n.tr("MatrixKit", "room_event_encryption_info_block")
+ }
+ /// \nSender session information\n
+ public static var roomEventEncryptionInfoDevice: String {
+ return MatrixKitL10n.tr("MatrixKit", "room_event_encryption_info_device")
+ }
+ /// Blacklisted
+ public static var roomEventEncryptionInfoDeviceBlocked: String {
+ return MatrixKitL10n.tr("MatrixKit", "room_event_encryption_info_device_blocked")
+ }
+ /// Ed25519 fingerprint\n
+ public static var roomEventEncryptionInfoDeviceFingerprint: String {
+ return MatrixKitL10n.tr("MatrixKit", "room_event_encryption_info_device_fingerprint")
+ }
+ /// ID\n
+ public static var roomEventEncryptionInfoDeviceId: String {
+ return MatrixKitL10n.tr("MatrixKit", "room_event_encryption_info_device_id")
+ }
+ /// Public Name\n
+ public static var roomEventEncryptionInfoDeviceName: String {
+ return MatrixKitL10n.tr("MatrixKit", "room_event_encryption_info_device_name")
+ }
+ /// NOT verified
+ public static var roomEventEncryptionInfoDeviceNotVerified: String {
+ return MatrixKitL10n.tr("MatrixKit", "room_event_encryption_info_device_not_verified")
+ }
+ /// unknown session\n
+ public static var roomEventEncryptionInfoDeviceUnknown: String {
+ return MatrixKitL10n.tr("MatrixKit", "room_event_encryption_info_device_unknown")
+ }
+ /// Verification\n
+ public static var roomEventEncryptionInfoDeviceVerification: String {
+ return MatrixKitL10n.tr("MatrixKit", "room_event_encryption_info_device_verification")
+ }
+ /// Verified
+ public static var roomEventEncryptionInfoDeviceVerified: String {
+ return MatrixKitL10n.tr("MatrixKit", "room_event_encryption_info_device_verified")
+ }
+ /// Event information\n
+ public static var roomEventEncryptionInfoEvent: String {
+ return MatrixKitL10n.tr("MatrixKit", "room_event_encryption_info_event")
+ }
+ /// Algorithm\n
+ public static var roomEventEncryptionInfoEventAlgorithm: String {
+ return MatrixKitL10n.tr("MatrixKit", "room_event_encryption_info_event_algorithm")
+ }
+ /// Decryption error\n
+ public static var roomEventEncryptionInfoEventDecryptionError: String {
+ return MatrixKitL10n.tr("MatrixKit", "room_event_encryption_info_event_decryption_error")
+ }
+ /// Claimed Ed25519 fingerprint key\n
+ public static var roomEventEncryptionInfoEventFingerprintKey: String {
+ return MatrixKitL10n.tr("MatrixKit", "room_event_encryption_info_event_fingerprint_key")
+ }
+ /// Curve25519 identity key\n
+ public static var roomEventEncryptionInfoEventIdentityKey: String {
+ return MatrixKitL10n.tr("MatrixKit", "room_event_encryption_info_event_identity_key")
+ }
+ /// none
+ public static var roomEventEncryptionInfoEventNone: String {
+ return MatrixKitL10n.tr("MatrixKit", "room_event_encryption_info_event_none")
+ }
+ /// Session ID\n
+ public static var roomEventEncryptionInfoEventSessionId: String {
+ return MatrixKitL10n.tr("MatrixKit", "room_event_encryption_info_event_session_id")
+ }
+ /// unencrypted
+ public static var roomEventEncryptionInfoEventUnencrypted: String {
+ return MatrixKitL10n.tr("MatrixKit", "room_event_encryption_info_event_unencrypted")
+ }
+ /// User ID\n
+ public static var roomEventEncryptionInfoEventUserId: String {
+ return MatrixKitL10n.tr("MatrixKit", "room_event_encryption_info_event_user_id")
+ }
+ /// End-to-end encryption information\n\n
+ public static var roomEventEncryptionInfoTitle: String {
+ return MatrixKitL10n.tr("MatrixKit", "room_event_encryption_info_title")
+ }
+ /// Unblacklist
+ public static var roomEventEncryptionInfoUnblock: String {
+ return MatrixKitL10n.tr("MatrixKit", "room_event_encryption_info_unblock")
+ }
+ /// Unverify
+ public static var roomEventEncryptionInfoUnverify: String {
+ return MatrixKitL10n.tr("MatrixKit", "room_event_encryption_info_unverify")
+ }
+ /// Verify...
+ public static var roomEventEncryptionInfoVerify: String {
+ return MatrixKitL10n.tr("MatrixKit", "room_event_encryption_info_verify")
+ }
+ /// To verify that this session can be trusted, please contact its owner using some other means (e.g. in person or a phone call) and ask them whether the key they see in their User Settings for this session matches the key below:\n\n Session name: %@\n Session ID: %@\n Session key: %@\n\nIf it matches, press the verify button below. If it doesnt, then someone else is intercepting this session and you probably want to press the blacklist button instead.\n\nIn future this verification process will be more sophisticated.
+ public static func roomEventEncryptionVerifyMessage(_ p1: String, _ p2: String, _ p3: String) -> String {
+ return MatrixKitL10n.tr("MatrixKit", "room_event_encryption_verify_message", p1, p2, p3)
+ }
+ /// Verify
+ public static var roomEventEncryptionVerifyOk: String {
+ return MatrixKitL10n.tr("MatrixKit", "room_event_encryption_verify_ok")
+ }
+ /// Verify session\n\n
+ public static var roomEventEncryptionVerifyTitle: String {
+ return MatrixKitL10n.tr("MatrixKit", "room_event_encryption_verify_title")
+ }
+ /// You left the room
+ public static var roomLeft: String {
+ return MatrixKitL10n.tr("MatrixKit", "room_left")
+ }
+ /// You left
+ public static var roomLeftForDm: String {
+ return MatrixKitL10n.tr("MatrixKit", "room_left_for_dm")
+ }
+ /// Are you sure you want to hide all messages from this user?
+ public static var roomMemberIgnorePrompt: String {
+ return MatrixKitL10n.tr("MatrixKit", "room_member_ignore_prompt")
+ }
+ /// You will not be able to undo this change as you are promoting the user to have the same power level as yourself.\nAre you sure?
+ public static var roomMemberPowerLevelPrompt: String {
+ return MatrixKitL10n.tr("MatrixKit", "room_member_power_level_prompt")
+ }
+ /// Conference calls are not supported in encrypted rooms
+ public static var roomNoConferenceCallInEncryptedRooms: String {
+ return MatrixKitL10n.tr("MatrixKit", "room_no_conference_call_in_encrypted_rooms")
+ }
+ /// You need permission to invite to start a conference in this room
+ public static var roomNoPowerToCreateConferenceCall: String {
+ return MatrixKitL10n.tr("MatrixKit", "room_no_power_to_create_conference_call")
+ }
+ /// Please select a room
+ public static var roomPleaseSelect: String {
+ return MatrixKitL10n.tr("MatrixKit", "room_please_select")
+ }
+ /// Save
+ public static var save: String {
+ return MatrixKitL10n.tr("MatrixKit", "save")
+ }
+ /// No Results
+ public static var searchNoResults: String {
+ return MatrixKitL10n.tr("MatrixKit", "search_no_results")
+ }
+ /// Search in progress...
+ public static var searchSearching: String {
+ return MatrixKitL10n.tr("MatrixKit", "search_searching")
+ }
+ /// Select an account
+ public static var selectAccount: String {
+ return MatrixKitL10n.tr("MatrixKit", "select_account")
+ }
+ /// Select All
+ public static var selectAll: String {
+ return MatrixKitL10n.tr("MatrixKit", "select_all")
+ }
+ /// Send
+ public static var send: String {
+ return MatrixKitL10n.tr("MatrixKit", "send")
+ }
+ /// Set Admin
+ public static var setAdmin: String {
+ return MatrixKitL10n.tr("MatrixKit", "set_admin")
+ }
+ /// Reset Power Level
+ public static var setDefaultPowerLevel: String {
+ return MatrixKitL10n.tr("MatrixKit", "set_default_power_level")
+ }
+ /// Set Moderator
+ public static var setModerator: String {
+ return MatrixKitL10n.tr("MatrixKit", "set_moderator")
+ }
+ /// Set Power Level
+ public static var setPowerLevel: String {
+ return MatrixKitL10n.tr("MatrixKit", "set_power_level")
+ }
+ /// Settings
+ public static var settings: String {
+ return MatrixKitL10n.tr("MatrixKit", "settings")
+ }
+ /// Homeserver: %@
+ public static func settingsConfigHomeServer(_ p1: String) -> String {
+ return MatrixKitL10n.tr("MatrixKit", "settings_config_home_server", p1)
+ }
+ /// Identity server: %@
+ public static func settingsConfigIdentityServer(_ p1: String) -> String {
+ return MatrixKitL10n.tr("MatrixKit", "settings_config_identity_server", p1)
+ }
+ /// User ID: %@
+ public static func settingsConfigUserId(_ p1: String) -> String {
+ return MatrixKitL10n.tr("MatrixKit", "settings_config_user_id", p1)
+ }
+ /// Enable In-App notifications
+ public static var settingsEnableInappNotifications: String {
+ return MatrixKitL10n.tr("MatrixKit", "settings_enable_inapp_notifications")
+ }
+ /// Enable push notifications
+ public static var settingsEnablePushNotifications: String {
+ return MatrixKitL10n.tr("MatrixKit", "settings_enable_push_notifications")
+ }
+ /// Enter validation token for %@:
+ public static func settingsEnterValidationTokenFor(_ p1: String) -> String {
+ return MatrixKitL10n.tr("MatrixKit", "settings_enter_validation_token_for", p1)
+ }
+ /// Configuration
+ public static var settingsTitleConfig: String {
+ return MatrixKitL10n.tr("MatrixKit", "settings_title_config")
+ }
+ /// Notifications
+ public static var settingsTitleNotifications: String {
+ return MatrixKitL10n.tr("MatrixKit", "settings_title_notifications")
+ }
+ /// Share
+ public static var share: String {
+ return MatrixKitL10n.tr("MatrixKit", "share")
+ }
+ /// Show Details
+ public static var showDetails: String {
+ return MatrixKitL10n.tr("MatrixKit", "show_details")
+ }
+ /// Sign up
+ public static var signUp: String {
+ return MatrixKitL10n.tr("MatrixKit", "sign_up")
+ }
+ /// If the server administrator has said that this is expected, ensure that the fingerprint below matches the fingerprint provided by them.
+ public static var sslCertNewAccountExpl: String {
+ return MatrixKitL10n.tr("MatrixKit", "ssl_cert_new_account_expl")
+ }
+ /// This could mean that someone is maliciously intercepting your traffic, or that your phone does not trust the certificate provided by the remote server.
+ public static var sslCertNotTrust: String {
+ return MatrixKitL10n.tr("MatrixKit", "ssl_cert_not_trust")
+ }
+ /// Could not verify identity of remote server.
+ public static var sslCouldNotVerify: String {
+ return MatrixKitL10n.tr("MatrixKit", "ssl_could_not_verify")
+ }
+ /// The certificate has changed from a previously trusted one to one that is not trusted. The server may have renewed its certificate. Contact the server administrator for the expected fingerprint.
+ public static var sslExpectedExistingExpl: String {
+ return MatrixKitL10n.tr("MatrixKit", "ssl_expected_existing_expl")
+ }
+ /// Fingerprint (%@):
+ public static func sslFingerprintHash(_ p1: String) -> String {
+ return MatrixKitL10n.tr("MatrixKit", "ssl_fingerprint_hash", p1)
+ }
+ /// Homeserver URL: %@
+ public static func sslHomeserverUrl(_ p1: String) -> String {
+ return MatrixKitL10n.tr("MatrixKit", "ssl_homeserver_url", p1)
+ }
+ /// Logout
+ public static var sslLogoutAccount: String {
+ return MatrixKitL10n.tr("MatrixKit", "ssl_logout_account")
+ }
+ /// ONLY accept the certificate if the server administrator has published a fingerprint that matches the one above.
+ public static var sslOnlyAccept: String {
+ return MatrixKitL10n.tr("MatrixKit", "ssl_only_accept")
+ }
+ /// Ignore
+ public static var sslRemainOffline: String {
+ return MatrixKitL10n.tr("MatrixKit", "ssl_remain_offline")
+ }
+ /// Trust
+ public static var sslTrust: String {
+ return MatrixKitL10n.tr("MatrixKit", "ssl_trust")
+ }
+ /// The certificate has changed from one that was trusted by your phone. This is HIGHLY UNUSUAL. It is recommended that you DO NOT ACCEPT this new certificate.
+ public static var sslUnexpectedExistingExpl: String {
+ return MatrixKitL10n.tr("MatrixKit", "ssl_unexpected_existing_expl")
+ }
+ /// Start Chat
+ public static var startChat: String {
+ return MatrixKitL10n.tr("MatrixKit", "start_chat")
+ }
+ /// Start Video Call
+ public static var startVideoCall: String {
+ return MatrixKitL10n.tr("MatrixKit", "start_video_call")
+ }
+ /// Start Voice Call
+ public static var startVoiceCall: String {
+ return MatrixKitL10n.tr("MatrixKit", "start_voice_call")
+ }
+ /// Submit
+ public static var submit: String {
+ return MatrixKitL10n.tr("MatrixKit", "submit")
+ }
+ /// Submit code
+ public static var submitCode: String {
+ return MatrixKitL10n.tr("MatrixKit", "submit_code")
+ }
+ /// Un-ban
+ public static var unban: String {
+ return MatrixKitL10n.tr("MatrixKit", "unban")
+ }
+ /// Unignore
+ public static var unignore: String {
+ return MatrixKitL10n.tr("MatrixKit", "unignore")
+ }
+ /// Unsent
+ public static var unsent: String {
+ return MatrixKitL10n.tr("MatrixKit", "unsent")
+ }
+ /// ex: @bob:homeserver
+ public static var userIdPlaceholder: String {
+ return MatrixKitL10n.tr("MatrixKit", "user_id_placeholder")
+ }
+ /// User ID:
+ public static var userIdTitle: String {
+ return MatrixKitL10n.tr("MatrixKit", "user_id_title")
+ }
+ /// View
+ public static var view: String {
+ return MatrixKitL10n.tr("MatrixKit", "view")
+ }
+ /// Yes
+ public static var yes: String {
+ return MatrixKitL10n.tr("MatrixKit", "yes")
+ }
+}
+// swiftlint:enable function_parameter_count identifier_name line_length type_body_length
+
+// MARK: - Implementation Details
+
+extension MatrixKitL10n {
+ static func tr(_ table: String, _ key: String, _ args: CVarArg...) -> String {
+ let format = NSLocalizedString(key, tableName: table, bundle: Bundle(for: BundleToken.self), comment: "")
+ let locale: Locale
+ if let providedLocale = LocaleProvider.locale {
+ locale = providedLocale
+ } else {
+ locale = Locale.current
+ }
+
+ return String(format: format, locale: locale, arguments: args)
+ }
+}
+
+private final class BundleToken {}
diff --git a/Riot/Managers/BugReport/RageShakeManager.h b/Riot/Managers/BugReport/RageShakeManager.h
index a220800b7..2caff3e88 100644
--- a/Riot/Managers/BugReport/RageShakeManager.h
+++ b/Riot/Managers/BugReport/RageShakeManager.h
@@ -17,7 +17,7 @@
#import
#import
-#import
+#import "MatrixKit.h"
@interface RageShakeManager : NSObject
diff --git a/Riot/Managers/BugReport/RageShakeManager.m b/Riot/Managers/BugReport/RageShakeManager.m
index a0357d046..4ce848240 100644
--- a/Riot/Managers/BugReport/RageShakeManager.m
+++ b/Riot/Managers/BugReport/RageShakeManager.m
@@ -21,8 +21,6 @@
#import "BugReportViewController.h"
-#import
-
#import "GeneratedInterface-Swift.h"
static RageShakeManager* sharedInstance = nil;
diff --git a/Riot/Managers/Call/CallPresenter.swift b/Riot/Managers/Call/CallPresenter.swift
index c0d286ab9..4a08f0e45 100644
--- a/Riot/Managers/Call/CallPresenter.swift
+++ b/Riot/Managers/Call/CallPresenter.swift
@@ -15,7 +15,6 @@
//
import Foundation
-import MatrixKit
// swiftlint:disable file_length
diff --git a/Riot/Managers/EncryptionKeyManager/EncryptionKeyManager.swift b/Riot/Managers/EncryptionKeyManager/EncryptionKeyManager.swift
index 3203fdf11..5085e9efb 100644
--- a/Riot/Managers/EncryptionKeyManager/EncryptionKeyManager.swift
+++ b/Riot/Managers/EncryptionKeyManager/EncryptionKeyManager.swift
@@ -16,8 +16,8 @@
import UIKit
import KeychainAccess
-import MatrixKit
import CommonCrypto
+import MatrixSDK
@objcMembers
class EncryptionKeyManager: NSObject, MXKeyProviderDelegate {
diff --git a/Riot/Managers/LocalAuthentication/LocalAuthenticationService.swift b/Riot/Managers/LocalAuthentication/LocalAuthenticationService.swift
index 14e97094b..83b9ec265 100644
--- a/Riot/Managers/LocalAuthentication/LocalAuthenticationService.swift
+++ b/Riot/Managers/LocalAuthentication/LocalAuthenticationService.swift
@@ -15,7 +15,6 @@
//
import Foundation
-import MatrixKit
@objcMembers
class LocalAuthenticationService: NSObject {
diff --git a/Riot/Managers/PushNotification/PushNotificationService.m b/Riot/Managers/PushNotification/PushNotificationService.m
index 13db25148..59fb9604a 100644
--- a/Riot/Managers/PushNotification/PushNotificationService.m
+++ b/Riot/Managers/PushNotification/PushNotificationService.m
@@ -17,8 +17,6 @@
#import "PushNotificationService.h"
-#import
-
#import
#import "GeneratedInterface-Swift.h"
diff --git a/Riot/Managers/Theme/ThemeService.h b/Riot/Managers/Theme/ThemeService.h
index 7fcaff121..24e01f82f 100644
--- a/Riot/Managers/Theme/ThemeService.h
+++ b/Riot/Managers/Theme/ThemeService.h
@@ -16,7 +16,7 @@
limitations under the License.
*/
-#import
+#import
@protocol Theme;
diff --git a/Riot/Managers/Widgets/WidgetManager.m b/Riot/Managers/Widgets/WidgetManager.m
index 1e6fbaa7c..2b25d2e13 100644
--- a/Riot/Managers/Widgets/WidgetManager.m
+++ b/Riot/Managers/Widgets/WidgetManager.m
@@ -21,8 +21,6 @@
#import "JitsiWidgetData.h"
#import "MXSession+Riot.h"
-#import
-
#pragma mark - Contants
NSString *const kWidgetManagerDidUpdateWidgetNotification = @"kWidgetManagerDidUpdateWidgetNotification";
diff --git a/Riot/Model/Contact/Contact.h b/Riot/Model/Contact/Contact.h
index 31036dedd..93a243609 100644
--- a/Riot/Model/Contact/Contact.h
+++ b/Riot/Model/Contact/Contact.h
@@ -14,7 +14,8 @@
limitations under the License.
*/
-#import
+#import "MatrixKit.h"
+#import
@interface Contact : MXKContact
diff --git a/Riot/Model/Room/RoomPreviewData.h b/Riot/Model/Room/RoomPreviewData.h
index f6f51bdca..f67712ccc 100644
--- a/Riot/Model/Room/RoomPreviewData.h
+++ b/Riot/Model/Room/RoomPreviewData.h
@@ -16,7 +16,7 @@
#import
-#import
+#import "MatrixKit.h"
#import "RoomEmailInvitation.h"
#import "RoomDataSource.h"
diff --git a/Riot/Modules/Application/LegacyAppDelegate.h b/Riot/Modules/Application/LegacyAppDelegate.h
index 6a3e2c3be..72dc4f61e 100644
--- a/Riot/Modules/Application/LegacyAppDelegate.h
+++ b/Riot/Modules/Application/LegacyAppDelegate.h
@@ -16,7 +16,7 @@
*/
#import
-#import
+#import "MatrixKit.h"
#import "MasterTabBarController.h"
#import "JitsiViewController.h"
diff --git a/Riot/Modules/Application/LegacyAppDelegate.m b/Riot/Modules/Application/LegacyAppDelegate.m
index 0a2aaed0e..180cfe27a 100644
--- a/Riot/Modules/Application/LegacyAppDelegate.m
+++ b/Riot/Modules/Application/LegacyAppDelegate.m
@@ -36,8 +36,6 @@
#import "RoomKeyRequestViewController.h"
#import "DecryptionFailureTracker.h"
-#import
-
#import "Tools.h"
#import "WidgetManager.h"
@@ -393,7 +391,6 @@ NSString *const AppDelegateUniversalLinkDidChangeNotification = @"AppDelegateUni
MXLogDebug(@"------------------------------");
MXLogDebug(@"Application info:");
MXLogDebug(@"%@ version: %@", appDisplayName, appVersion);
- MXLogDebug(@"MatrixKit version: %@", MatrixKitVersion);
MXLogDebug(@"MatrixSDK version: %@", MatrixSDKVersion);
MXLogDebug(@"Build: %@\n", build);
MXLogDebug(@"------------------------------\n");
diff --git a/Riot/Modules/Authentication/AuthenticationViewController.h b/Riot/Modules/Authentication/AuthenticationViewController.h
index a689fd5ae..133f0a867 100644
--- a/Riot/Modules/Authentication/AuthenticationViewController.h
+++ b/Riot/Modules/Authentication/AuthenticationViewController.h
@@ -16,7 +16,7 @@
limitations under the License.
*/
-#import
+#import "MatrixKit.h"
@protocol AuthenticationViewControllerDelegate;
diff --git a/Riot/Modules/Authentication/AuthenticationViewController.m b/Riot/Modules/Authentication/AuthenticationViewController.m
index de5eadc32..188e33268 100644
--- a/Riot/Modules/Authentication/AuthenticationViewController.m
+++ b/Riot/Modules/Authentication/AuthenticationViewController.m
@@ -18,13 +18,14 @@
#import "AuthenticationViewController.h"
-#import "GeneratedInterface-Swift.h"
#import "MXSession+Riot.h"
#import "AuthInputsView.h"
#import "ForgotPasswordInputsView.h"
#import "AuthFallBackViewController.h"
+#import "GeneratedInterface-Swift.h"
+
static const CGFloat kAuthInputContainerViewMinHeightConstraintConstant = 150.0;
@interface AuthenticationViewController ()
+#import "MatrixKit.h"
@class TermsView;
-@interface AuthInputsView : MXKAuthInputsView
-
+@interface AuthInputsView : MXKAuthInputsView
@property (weak, nonatomic) IBOutlet UITextField *userLoginTextField;
@property (weak, nonatomic) IBOutlet UITextField *passWordTextField;
@property (weak, nonatomic) IBOutlet UITextField *repeatPasswordTextField;
diff --git a/Riot/Modules/Authentication/Views/AuthInputsView.m b/Riot/Modules/Authentication/Views/AuthInputsView.m
index d3b9c65b4..43efbce8b 100644
--- a/Riot/Modules/Authentication/Views/AuthInputsView.m
+++ b/Riot/Modules/Authentication/Views/AuthInputsView.m
@@ -28,7 +28,7 @@
#import "GeneratedInterface-Swift.h"
-@interface AuthInputsView ()
+@interface AuthInputsView ()
{
/**
The current email validation
diff --git a/Riot/Modules/Authentication/Views/ForgotPasswordInputsView.h b/Riot/Modules/Authentication/Views/ForgotPasswordInputsView.h
index c5f088545..7aead4034 100644
--- a/Riot/Modules/Authentication/Views/ForgotPasswordInputsView.h
+++ b/Riot/Modules/Authentication/Views/ForgotPasswordInputsView.h
@@ -14,7 +14,7 @@
limitations under the License.
*/
-#import
+#import "MatrixKit.h"
@interface ForgotPasswordInputsView : MXKAuthInputsView
diff --git a/Riot/Modules/BugReport/BugReportViewController.h b/Riot/Modules/BugReport/BugReportViewController.h
index 972e02d24..715639661 100644
--- a/Riot/Modules/BugReport/BugReportViewController.h
+++ b/Riot/Modules/BugReport/BugReportViewController.h
@@ -16,7 +16,7 @@
#import
-#import
+#import "MatrixKit.h"
@interface BugReportViewController : MXKViewController
diff --git a/Riot/Modules/Call/CallViewController.h b/Riot/Modules/Call/CallViewController.h
index 98bedce60..1fe7837da 100644
--- a/Riot/Modules/Call/CallViewController.h
+++ b/Riot/Modules/Call/CallViewController.h
@@ -14,7 +14,7 @@
limitations under the License.
*/
-#import
+#import "MatrixKit.h"
/**
'CallViewController' instance displays a call. Only one matrix session is supported by this view controller.
diff --git a/Riot/Modules/Call/Views/IncomingCallView.m b/Riot/Modules/Call/Views/IncomingCallView.m
index 9da231175..35500cf4e 100644
--- a/Riot/Modules/Call/Views/IncomingCallView.m
+++ b/Riot/Modules/Call/Views/IncomingCallView.m
@@ -17,7 +17,7 @@
#import "IncomingCallView.h"
-#import
+#import "MatrixKit.h"
#import
#import "CircleButton.h"
diff --git a/Riot/Modules/Common/Cells/TableViewCellWithButton.h b/Riot/Modules/Common/Cells/TableViewCellWithButton.h
index 78c703ba1..3a8a172a6 100644
--- a/Riot/Modules/Common/Cells/TableViewCellWithButton.h
+++ b/Riot/Modules/Common/Cells/TableViewCellWithButton.h
@@ -14,7 +14,7 @@
limitations under the License.
*/
-#import
+#import "MatrixKit.h"
@interface TableViewCellWithButton : MXKTableViewCellWithButton
diff --git a/Riot/Modules/Common/Recents/DataSources/RecentsDataSource.h b/Riot/Modules/Common/Recents/DataSources/RecentsDataSource.h
index 585453317..2dbf20bc5 100644
--- a/Riot/Modules/Common/Recents/DataSources/RecentsDataSource.h
+++ b/Riot/Modules/Common/Recents/DataSources/RecentsDataSource.h
@@ -15,7 +15,7 @@
limitations under the License.
*/
-#import
+#import "MatrixKit.h"
#import "PublicRoomsDirectoryDataSource.h"
diff --git a/Riot/Modules/Common/Recents/Model/RecentCellData.h b/Riot/Modules/Common/Recents/Model/RecentCellData.h
index 53bc182bd..43efda4ef 100644
--- a/Riot/Modules/Common/Recents/Model/RecentCellData.h
+++ b/Riot/Modules/Common/Recents/Model/RecentCellData.h
@@ -17,7 +17,7 @@
#import
#import
-#import
+#import "MatrixKit.h"
/**
`RecentCellData` is Vector cuustomized MXKRecentCellData` cell.
diff --git a/Riot/Modules/Common/Recents/RecentsViewController.h b/Riot/Modules/Common/Recents/RecentsViewController.h
index 6a475073a..a9e9c4863 100644
--- a/Riot/Modules/Common/Recents/RecentsViewController.h
+++ b/Riot/Modules/Common/Recents/RecentsViewController.h
@@ -15,7 +15,7 @@
limitations under the License.
*/
-#import
+#import "MatrixKit.h"
@class RootTabEmptyView;
diff --git a/Riot/Modules/Common/Recents/RecentsViewController.m b/Riot/Modules/Common/Recents/RecentsViewController.m
index c9b9bb3c0..6e8adb76e 100644
--- a/Riot/Modules/Common/Recents/RecentsViewController.m
+++ b/Riot/Modules/Common/Recents/RecentsViewController.m
@@ -24,8 +24,6 @@
#import "MXRoom+Riot.h"
-#import
-
#import "RoomViewController.h"
#import "InviteRecentTableViewCell.h"
diff --git a/Riot/Modules/Common/Recents/Views/RecentTableViewCell.h b/Riot/Modules/Common/Recents/Views/RecentTableViewCell.h
index e3feee4e4..347a92f76 100644
--- a/Riot/Modules/Common/Recents/Views/RecentTableViewCell.h
+++ b/Riot/Modules/Common/Recents/Views/RecentTableViewCell.h
@@ -14,7 +14,7 @@
limitations under the License.
*/
-#import
+#import "MatrixKit.h"
/**
`RecentTableViewCell` instances display a room in the context of the recents list.
diff --git a/Riot/Modules/Common/SegmentedViewController/SegmentedViewController.h b/Riot/Modules/Common/SegmentedViewController/SegmentedViewController.h
index f68d498ae..fb0c3b583 100644
--- a/Riot/Modules/Common/SegmentedViewController/SegmentedViewController.h
+++ b/Riot/Modules/Common/SegmentedViewController/SegmentedViewController.h
@@ -18,6 +18,7 @@ limitations under the License.
#import
#import "UIViewController+RiotSearch.h"
+#import "MatrixKit.h"
/**
This view controller manages several uiviewcontrollers like UISegmentedController manages uiTableView
diff --git a/Riot/Modules/Common/WebViewController/WebViewViewController.h b/Riot/Modules/Common/WebViewController/WebViewViewController.h
index 8426ae613..24c95c42c 100644
--- a/Riot/Modules/Common/WebViewController/WebViewViewController.h
+++ b/Riot/Modules/Common/WebViewController/WebViewViewController.h
@@ -14,7 +14,7 @@
limitations under the License.
*/
-#import
+#import "MatrixKit.h"
/**
WebViewViewController extends MXKWebViewViewController to handle customization.
diff --git a/Riot/Modules/Communities/DataSources/GroupsDataSource.h b/Riot/Modules/Communities/DataSources/GroupsDataSource.h
index 7a8a4f9d6..836f6fdaa 100644
--- a/Riot/Modules/Communities/DataSources/GroupsDataSource.h
+++ b/Riot/Modules/Communities/DataSources/GroupsDataSource.h
@@ -14,7 +14,7 @@
limitations under the License.
*/
-#import
+#import "MatrixKit.h"
/**
'GroupsDataSource' class inherits from 'MXKSessionGroupsDataSource' to define the Riot groups source.
diff --git a/Riot/Modules/Communities/GroupsViewController.h b/Riot/Modules/Communities/GroupsViewController.h
index 08ab81101..ef2a4c486 100644
--- a/Riot/Modules/Communities/GroupsViewController.h
+++ b/Riot/Modules/Communities/GroupsViewController.h
@@ -14,7 +14,7 @@
limitations under the License.
*/
-#import
+#import "MatrixKit.h"
/**
The `GroupsViewController` screen is the view controller displayed when `Groups` tab is selected.
diff --git a/Riot/Modules/Communities/Home/GroupHomeViewController.h b/Riot/Modules/Communities/Home/GroupHomeViewController.h
index d07b59471..69b2300da 100644
--- a/Riot/Modules/Communities/Home/GroupHomeViewController.h
+++ b/Riot/Modules/Communities/Home/GroupHomeViewController.h
@@ -14,7 +14,7 @@
limitations under the License.
*/
-#import
+#import "MatrixKit.h"
@interface GroupHomeViewController : MXKViewController
diff --git a/Riot/Modules/Communities/Members/GroupParticipantsViewController.h b/Riot/Modules/Communities/Members/GroupParticipantsViewController.h
index 012b148c1..83b862664 100644
--- a/Riot/Modules/Communities/Members/GroupParticipantsViewController.h
+++ b/Riot/Modules/Communities/Members/GroupParticipantsViewController.h
@@ -15,6 +15,7 @@
*/
#import "ContactsTableViewController.h"
+#import "MatrixKit.h"
@class Contact;
diff --git a/Riot/Modules/Communities/Rooms/GroupRoomsViewController.h b/Riot/Modules/Communities/Rooms/GroupRoomsViewController.h
index 34e0bbe9a..792865b96 100644
--- a/Riot/Modules/Communities/Rooms/GroupRoomsViewController.h
+++ b/Riot/Modules/Communities/Rooms/GroupRoomsViewController.h
@@ -14,7 +14,7 @@
limitations under the License.
*/
-#import
+#import "MatrixKit.h"
/**
'GroupRoomsViewController' instance is used to list the rooms of the group defined by the property 'mxGroup'.
diff --git a/Riot/Modules/Communities/Rooms/Views/GroupRoomTableViewCell.h b/Riot/Modules/Communities/Rooms/Views/GroupRoomTableViewCell.h
index be8dd1ef9..e140b3585 100644
--- a/Riot/Modules/Communities/Rooms/Views/GroupRoomTableViewCell.h
+++ b/Riot/Modules/Communities/Rooms/Views/GroupRoomTableViewCell.h
@@ -14,7 +14,7 @@
limitations under the License.
*/
-#import
+#import "MatrixKit.h"
@interface GroupRoomTableViewCell : MXKTableViewCell
diff --git a/Riot/Modules/Communities/Views/GroupInviteTableViewCell.h b/Riot/Modules/Communities/Views/GroupInviteTableViewCell.h
index c640d95f9..b746d6ba1 100644
--- a/Riot/Modules/Communities/Views/GroupInviteTableViewCell.h
+++ b/Riot/Modules/Communities/Views/GroupInviteTableViewCell.h
@@ -14,8 +14,6 @@
limitations under the License.
*/
-#import
-
#import "GroupTableViewCell.h"
/**
diff --git a/Riot/Modules/Communities/Views/GroupTableViewCell.h b/Riot/Modules/Communities/Views/GroupTableViewCell.h
index 867ee5183..61f8f6849 100644
--- a/Riot/Modules/Communities/Views/GroupTableViewCell.h
+++ b/Riot/Modules/Communities/Views/GroupTableViewCell.h
@@ -14,7 +14,7 @@
limitations under the License.
*/
-#import
+#import "MatrixKit.h"
/**
`GroupTableViewCell` instances display a group in the context of the groups list.
diff --git a/Riot/Modules/Contacts/DataSources/ContactsDataSource.h b/Riot/Modules/Contacts/DataSources/ContactsDataSource.h
index ea6db9c2e..5b665377e 100644
--- a/Riot/Modules/Contacts/DataSources/ContactsDataSource.h
+++ b/Riot/Modules/Contacts/DataSources/ContactsDataSource.h
@@ -14,7 +14,7 @@
limitations under the License.
*/
-#import
+#import "MatrixKit.h"
/**
The state of the users search from the homeserver user directory.
diff --git a/Riot/Modules/Contacts/Details/ContactDetailsViewController.h b/Riot/Modules/Contacts/Details/ContactDetailsViewController.h
index a41a8df84..737dd6532 100644
--- a/Riot/Modules/Contacts/Details/ContactDetailsViewController.h
+++ b/Riot/Modules/Contacts/Details/ContactDetailsViewController.h
@@ -14,7 +14,7 @@
limitations under the License.
*/
-#import
+#import "MatrixKit.h"
/**
Available actions on contact
diff --git a/Riot/Modules/Contacts/Details/Views/RoomTableViewCell.h b/Riot/Modules/Contacts/Details/Views/RoomTableViewCell.h
index db8a7590c..a99bd2df5 100644
--- a/Riot/Modules/Contacts/Details/Views/RoomTableViewCell.h
+++ b/Riot/Modules/Contacts/Details/Views/RoomTableViewCell.h
@@ -14,7 +14,7 @@
limitations under the License.
*/
-#import
+#import "MatrixKit.h"
/**
The `RoomTableViewCell` cell displays a room (avatar and displayname).
diff --git a/Riot/Modules/Contacts/Views/ContactTableViewCell.h b/Riot/Modules/Contacts/Views/ContactTableViewCell.h
index eadf78150..acb9e0097 100644
--- a/Riot/Modules/Contacts/Views/ContactTableViewCell.h
+++ b/Riot/Modules/Contacts/Views/ContactTableViewCell.h
@@ -14,7 +14,7 @@
limitations under the License.
*/
-#import
+#import "MatrixKit.h"
/**
'ContactTableCell' extends MXKTableViewCell.
diff --git a/Riot/Modules/Contacts/Views/ContactTableViewCell.m b/Riot/Modules/Contacts/Views/ContactTableViewCell.m
index c07d2f33e..a1c3b12a2 100644
--- a/Riot/Modules/Contacts/Views/ContactTableViewCell.m
+++ b/Riot/Modules/Contacts/Views/ContactTableViewCell.m
@@ -17,8 +17,6 @@
#import "ContactTableViewCell.h"
-#import
-
#import "ThemeService.h"
#import "GeneratedInterface-Swift.h"
diff --git a/Riot/Modules/EncryptionInfo/EncryptionInfoView.h b/Riot/Modules/EncryptionInfo/EncryptionInfoView.h
index 3c772b423..ce61e04c4 100644
--- a/Riot/Modules/EncryptionInfo/EncryptionInfoView.h
+++ b/Riot/Modules/EncryptionInfo/EncryptionInfoView.h
@@ -14,7 +14,7 @@
limitations under the License.
*/
-#import
+#import "MatrixKit.h"
/**
TODO: This view as it is implemented in this class must disappear.
diff --git a/Riot/Modules/GlobalSearch/DataSources/UnifiedSearchRecentsDataSource.h b/Riot/Modules/GlobalSearch/DataSources/UnifiedSearchRecentsDataSource.h
index 6102584de..7c89ea8b1 100644
--- a/Riot/Modules/GlobalSearch/DataSources/UnifiedSearchRecentsDataSource.h
+++ b/Riot/Modules/GlobalSearch/DataSources/UnifiedSearchRecentsDataSource.h
@@ -14,8 +14,6 @@
limitations under the License.
*/
-#import
-
#import "RecentsDataSource.h"
/**
diff --git a/Riot/Modules/GlobalSearch/Files/CellData/FilesSearchCellData.h b/Riot/Modules/GlobalSearch/Files/CellData/FilesSearchCellData.h
index b8f6d3955..15832ca55 100644
--- a/Riot/Modules/GlobalSearch/Files/CellData/FilesSearchCellData.h
+++ b/Riot/Modules/GlobalSearch/Files/CellData/FilesSearchCellData.h
@@ -14,7 +14,7 @@
limitations under the License.
*/
-#import
+#import "MatrixKit.h"
/**
`FilesSearchCellData` prepares the data for the Vector cell used to display the files search result.
diff --git a/Riot/Modules/GlobalSearch/Files/HomeFilesSearchViewController.h b/Riot/Modules/GlobalSearch/Files/HomeFilesSearchViewController.h
index bf64d5bf2..bf9826858 100644
--- a/Riot/Modules/GlobalSearch/Files/HomeFilesSearchViewController.h
+++ b/Riot/Modules/GlobalSearch/Files/HomeFilesSearchViewController.h
@@ -15,7 +15,7 @@
*/
-#import
+#import "MatrixKit.h"
/**
`HomeFilesSearchViewController` displays the files search in user's rooms under a `HomeViewController` segment.
diff --git a/Riot/Modules/GlobalSearch/Files/Views/FilesSearchTableViewCell.h b/Riot/Modules/GlobalSearch/Files/Views/FilesSearchTableViewCell.h
index 46f6e97e0..01ebbbd03 100644
--- a/Riot/Modules/GlobalSearch/Files/Views/FilesSearchTableViewCell.h
+++ b/Riot/Modules/GlobalSearch/Files/Views/FilesSearchTableViewCell.h
@@ -14,7 +14,7 @@
limitations under the License.
*/
-#import
+#import "MatrixKit.h"
/**
Each `FilesSearchTableViewCell` instance displays a files search result.
diff --git a/Riot/Modules/GlobalSearch/Messages/DataSources/HomeMessagesSearchDataSource.h b/Riot/Modules/GlobalSearch/Messages/DataSources/HomeMessagesSearchDataSource.h
index 511d9e540..1f1136942 100644
--- a/Riot/Modules/GlobalSearch/Messages/DataSources/HomeMessagesSearchDataSource.h
+++ b/Riot/Modules/GlobalSearch/Messages/DataSources/HomeMessagesSearchDataSource.h
@@ -14,7 +14,7 @@
limitations under the License.
*/
-#import
+#import "MatrixKit.h"
/**
`HomeMessagesSearchDataSource` overrides `MXKSearchDataSource` to render search results
diff --git a/Riot/Modules/GlobalSearch/Messages/DataSources/HomeMessagesSearchDataSource.m b/Riot/Modules/GlobalSearch/Messages/DataSources/HomeMessagesSearchDataSource.m
index 8a60808cf..950715c17 100644
--- a/Riot/Modules/GlobalSearch/Messages/DataSources/HomeMessagesSearchDataSource.m
+++ b/Riot/Modules/GlobalSearch/Messages/DataSources/HomeMessagesSearchDataSource.m
@@ -16,7 +16,7 @@
limitations under the License.
*/
-#import
+#import "MatrixKit.h"
#import "HomeMessagesSearchDataSource.h"
diff --git a/Riot/Modules/GlobalSearch/Messages/HomeMessagesSearchViewController.h b/Riot/Modules/GlobalSearch/Messages/HomeMessagesSearchViewController.h
index 1c4468201..8a3553771 100644
--- a/Riot/Modules/GlobalSearch/Messages/HomeMessagesSearchViewController.h
+++ b/Riot/Modules/GlobalSearch/Messages/HomeMessagesSearchViewController.h
@@ -15,7 +15,7 @@
*/
-#import
+#import "MatrixKit.h"
/**
`HomeMessagesSearchViewController` displays messages search in user's rooms under a `HomeViewController` segment.
diff --git a/Riot/Modules/GlobalSearch/Messages/Views/MessagesSearchResultAttachmentBubbleCell.h b/Riot/Modules/GlobalSearch/Messages/Views/MessagesSearchResultAttachmentBubbleCell.h
index 7c01be438..709b25cfd 100644
--- a/Riot/Modules/GlobalSearch/Messages/Views/MessagesSearchResultAttachmentBubbleCell.h
+++ b/Riot/Modules/GlobalSearch/Messages/Views/MessagesSearchResultAttachmentBubbleCell.h
@@ -14,7 +14,7 @@
limitations under the License.
*/
-#import
+#import "MatrixKit.h"
/**
`MessagesSearchResultAttachmentBubbleCell` displays an attachment with the information of the room and the sender.
diff --git a/Riot/Modules/GlobalSearch/Messages/Views/MessagesSearchResultTextMsgBubbleCell.h b/Riot/Modules/GlobalSearch/Messages/Views/MessagesSearchResultTextMsgBubbleCell.h
index 04228a453..4c12d1d17 100644
--- a/Riot/Modules/GlobalSearch/Messages/Views/MessagesSearchResultTextMsgBubbleCell.h
+++ b/Riot/Modules/GlobalSearch/Messages/Views/MessagesSearchResultTextMsgBubbleCell.h
@@ -14,7 +14,7 @@
limitations under the License.
*/
-#import
+#import "MatrixKit.h"
/**
`MessagesSearchResultTextMsgBubbleCell` displays a message text with the information of the room and the sender.
diff --git a/Riot/Modules/GlobalSearch/Rooms/DirectoryViewController.h b/Riot/Modules/GlobalSearch/Rooms/DirectoryViewController.h
index ada83aca8..c4cf2dc9a 100644
--- a/Riot/Modules/GlobalSearch/Rooms/DirectoryViewController.h
+++ b/Riot/Modules/GlobalSearch/Rooms/DirectoryViewController.h
@@ -14,7 +14,7 @@
limitations under the License.
*/
-#import
+#import "MatrixKit.h"
@class PublicRoomsDirectoryDataSource;
diff --git a/Riot/Modules/GlobalSearch/UnifiedSearchViewController.h b/Riot/Modules/GlobalSearch/UnifiedSearchViewController.h
index 3a22f76e4..72fddaef6 100644
--- a/Riot/Modules/GlobalSearch/UnifiedSearchViewController.h
+++ b/Riot/Modules/GlobalSearch/UnifiedSearchViewController.h
@@ -14,8 +14,6 @@
limitations under the License.
*/
-#import
-
#import "SegmentedViewController.h"
#import "ContactsTableViewController.h"
diff --git a/Riot/Modules/GlobalSearch/Views/DirectoryRecentTableViewCell.h b/Riot/Modules/GlobalSearch/Views/DirectoryRecentTableViewCell.h
index 15be3d4dd..04266edb6 100644
--- a/Riot/Modules/GlobalSearch/Views/DirectoryRecentTableViewCell.h
+++ b/Riot/Modules/GlobalSearch/Views/DirectoryRecentTableViewCell.h
@@ -14,7 +14,7 @@
limitations under the License.
*/
-#import
+#import "MatrixKit.h"
@class PublicRoomsDirectoryDataSource;
diff --git a/Riot/Modules/GlobalSearch/Views/RoomIdOrAliasTableViewCell.h b/Riot/Modules/GlobalSearch/Views/RoomIdOrAliasTableViewCell.h
index 1f3ab2398..36d082255 100644
--- a/Riot/Modules/GlobalSearch/Views/RoomIdOrAliasTableViewCell.h
+++ b/Riot/Modules/GlobalSearch/Views/RoomIdOrAliasTableViewCell.h
@@ -14,7 +14,7 @@
limitations under the License.
*/
-#import
+#import "MatrixKit.h"
/**
The `RoomIdOrAliasTableViewCell` cell displays a room identifier or a room alias.
diff --git a/Riot/Modules/Home/Views/RoomCollectionViewCell.h b/Riot/Modules/Home/Views/RoomCollectionViewCell.h
index 439cf0527..f08116c11 100644
--- a/Riot/Modules/Home/Views/RoomCollectionViewCell.h
+++ b/Riot/Modules/Home/Views/RoomCollectionViewCell.h
@@ -14,7 +14,7 @@
limitations under the License.
*/
-#import
+#import "MatrixKit.h"
@class BadgeLabel;
diff --git a/Riot/Modules/Home/Views/TableViewCellWithCollectionView.h b/Riot/Modules/Home/Views/TableViewCellWithCollectionView.h
index aa71ab9d4..832037a88 100644
--- a/Riot/Modules/Home/Views/TableViewCellWithCollectionView.h
+++ b/Riot/Modules/Home/Views/TableViewCellWithCollectionView.h
@@ -14,7 +14,7 @@
limitations under the License.
*/
-#import
+#import "MatrixKit.h"
@interface TableViewCellWithCollectionView : MXKTableViewCell
diff --git a/Riot/Modules/Integrations/WidgetPicker/WidgetPickerViewController.h b/Riot/Modules/Integrations/WidgetPicker/WidgetPickerViewController.h
index 8293c1029..0396a55f0 100644
--- a/Riot/Modules/Integrations/WidgetPicker/WidgetPickerViewController.h
+++ b/Riot/Modules/Integrations/WidgetPicker/WidgetPickerViewController.h
@@ -17,7 +17,7 @@
#import
#import
-#import
+#import "MatrixKit.h"
/**
`WidgetPickerViewController` displays the list of widgets within a room plus a
diff --git a/Riot/Modules/Integrations/Widgets/Jitsi/JitsiViewController.h b/Riot/Modules/Integrations/Widgets/Jitsi/JitsiViewController.h
index 2fb2b368f..c79a07b2a 100644
--- a/Riot/Modules/Integrations/Widgets/Jitsi/JitsiViewController.h
+++ b/Riot/Modules/Integrations/Widgets/Jitsi/JitsiViewController.h
@@ -14,7 +14,7 @@
limitations under the License.
*/
-#import
+#import "MatrixKit.h"
#import "WidgetManager.h"
diff --git a/Riot/Modules/Integrations/Widgets/WidgetViewController.h b/Riot/Modules/Integrations/Widgets/WidgetViewController.h
index 49b6a6ac2..895f229c5 100644
--- a/Riot/Modules/Integrations/Widgets/WidgetViewController.h
+++ b/Riot/Modules/Integrations/Widgets/WidgetViewController.h
@@ -17,7 +17,7 @@
#import "WebViewViewController.h"
#import "WidgetManager.h"
-#import "MatrixKit/MatrixKit.h"
+#import "MatrixKit.h"
/**
`WidgetViewController` displays widget within a webview.
diff --git a/Riot/Modules/MatrixKit/Animators/MXKAttachmentAnimator.h b/Riot/Modules/MatrixKit/Animators/MXKAttachmentAnimator.h
new file mode 100644
index 000000000..31588cfad
--- /dev/null
+++ b/Riot/Modules/MatrixKit/Animators/MXKAttachmentAnimator.h
@@ -0,0 +1,45 @@
+/*
+ Copyright 2017 Aram Sargsyan
+
+ 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
+
+typedef NS_ENUM(NSInteger, PhotoBrowserAnimationType) {
+ PhotoBrowserZoomInAnimation,
+ PhotoBrowserZoomOutAnimation
+};
+
+@protocol MXKSourceAttachmentAnimatorDelegate
+
+- (UIImageView *)originalImageView;
+
+- (CGRect)convertedFrameForOriginalImageView;
+
+@end
+
+@protocol MXKDestinationAttachmentAnimatorDelegate
+
+- (UIImageView *)finalImageView;
+
+@end
+
+@interface MXKAttachmentAnimator : NSObject
+
+- (instancetype)initWithAnimationType:(PhotoBrowserAnimationType)animationType sourceViewController:(UIViewController *)viewController;
+
++ (CGRect)aspectFitImage:(UIImage *)image inFrame:(CGRect)targetFrame;
+
+@end
diff --git a/Riot/Modules/MatrixKit/Animators/MXKAttachmentAnimator.m b/Riot/Modules/MatrixKit/Animators/MXKAttachmentAnimator.m
new file mode 100644
index 000000000..d4ca8393f
--- /dev/null
+++ b/Riot/Modules/MatrixKit/Animators/MXKAttachmentAnimator.m
@@ -0,0 +1,161 @@
+/*
+ Copyright 2017 Aram Sargsyan
+
+ 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 "MXKAttachmentAnimator.h"
+#import "MXLog.h"
+
+@interface MXKAttachmentAnimator ()
+
+@property (nonatomic) PhotoBrowserAnimationType animationType;
+@property (nonatomic, weak) UIViewController *sourceViewController;
+
+@end
+
+@implementation MXKAttachmentAnimator
+
+#pragma mark - Lifecycle
+
+- (instancetype)initWithAnimationType:(PhotoBrowserAnimationType)animationType sourceViewController:(UIViewController *)viewController
+{
+ self = [self init];
+ if (self) {
+ self.animationType = animationType;
+ self.sourceViewController = viewController;
+ }
+ return self;
+}
+
+#pragma mark - Public
+
++ (CGRect)aspectFitImage:(UIImage *)image inFrame:(CGRect)targetFrame
+{
+ // Sanity check
+ if (!image)
+ {
+ MXLogDebug(@"[MXKAttachmentAnimator] aspectFitImage failed: image is nil");
+ return CGRectZero;
+ }
+
+ if (CGSizeEqualToSize(image.size, targetFrame.size))
+ {
+ return targetFrame;
+ }
+
+ CGFloat targetWidth = CGRectGetWidth(targetFrame);
+ CGFloat targetHeight = CGRectGetHeight(targetFrame);
+ CGFloat imageWidth = image.size.width;
+ CGFloat imageHeight = image.size.height;
+
+ CGFloat factor = MIN(targetWidth/imageWidth, targetHeight/imageHeight);
+
+ CGSize finalSize = CGSizeMake(imageWidth * factor, imageHeight * factor);
+ CGRect finalFrame = CGRectMake((targetWidth - finalSize.width)/2 + targetFrame.origin.x, (targetHeight - finalSize.height)/2 + targetFrame.origin.y, finalSize.width, finalSize.height);
+
+ return finalFrame;
+}
+
+#pragma mark - Animations
+
+- (void)animateZoomInAnimation:(id)transitionContext
+{
+ //originalImageView
+ UIImageView *originalImageView = [self.sourceViewController originalImageView];
+ originalImageView.hidden = YES;
+ CGRect convertedFrame = [self.sourceViewController convertedFrameForOriginalImageView];
+
+ //toViewController
+ UIViewController *toViewController = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
+ toViewController.view.frame = [transitionContext finalFrameForViewController:toViewController];
+ [[transitionContext containerView] addSubview:toViewController.view];
+ toViewController.view.alpha = 0.0;
+
+ //destinationImageView
+ UIImageView *destinationImageView = [toViewController finalImageView];
+ destinationImageView.hidden = YES;
+
+ //transitioningImageView
+ UIImageView *transitioningImageView = [[UIImageView alloc] initWithImage:originalImageView.image];
+ transitioningImageView.frame = convertedFrame;
+ [[transitionContext containerView] addSubview:transitioningImageView];
+ CGRect finalFrameForTransitioningView = [[self class] aspectFitImage:originalImageView.image inFrame:toViewController.view.frame];
+
+
+ //animation
+ [UIView animateWithDuration:[self transitionDuration:transitionContext] animations:^{
+ toViewController.view.alpha = 1.0;
+ transitioningImageView.frame = finalFrameForTransitioningView;
+ } completion:^(BOOL finished) {
+ [transitioningImageView removeFromSuperview];
+ destinationImageView.hidden = NO;
+ originalImageView.hidden = NO;
+ [transitionContext completeTransition:![transitionContext transitionWasCancelled]];
+ }];
+}
+
+- (void)animateZoomOutAnimation:(id)transitionContext
+{
+ //fromViewController
+ UIViewController *fromViewController = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];
+ UIImageView *destinationImageView = [fromViewController finalImageView];
+ destinationImageView.hidden = YES;
+
+ //toViewController
+ UIViewController *toViewController = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
+ toViewController.view.frame = [transitionContext finalFrameForViewController:toViewController];
+ [[transitionContext containerView] insertSubview:toViewController.view belowSubview:fromViewController.view];
+ UIImageView *originalImageView = [self.sourceViewController originalImageView];
+ originalImageView.hidden = YES;
+ CGRect convertedFrame = [self.sourceViewController convertedFrameForOriginalImageView];
+
+ //transitioningImageView
+ UIImageView *transitioningImageView = [[UIImageView alloc] initWithImage:destinationImageView.image];
+ transitioningImageView.frame = [[self class] aspectFitImage:destinationImageView.image inFrame:destinationImageView.frame];
+ [[transitionContext containerView] addSubview:transitioningImageView];
+
+ //animation
+ [UIView animateWithDuration:[self transitionDuration:transitionContext] animations:^{
+ fromViewController.view.alpha = 0.0;
+ transitioningImageView.frame = convertedFrame;
+ } completion:^(BOOL finished) {
+ [transitioningImageView removeFromSuperview];
+ destinationImageView.hidden = NO;
+ originalImageView.hidden = NO;
+ [transitionContext completeTransition:![transitionContext transitionWasCancelled]];
+ }];
+}
+
+#pragma mark - UIViewControllerAnimatedTransitioning
+
+- (NSTimeInterval)transitionDuration:(id)transitionContext
+{
+ return 0.3;
+}
+
+- (void)animateTransition:(id)transitionContext
+{
+ switch (self.animationType) {
+ case PhotoBrowserZoomInAnimation:
+ [self animateZoomInAnimation:transitionContext];
+ break;
+
+ case PhotoBrowserZoomOutAnimation:
+ [self animateZoomOutAnimation:transitionContext];
+ break;
+ }
+}
+
+
+@end
diff --git a/Riot/Modules/MatrixKit/Animators/MXKAttachmentInteractionController.h b/Riot/Modules/MatrixKit/Animators/MXKAttachmentInteractionController.h
new file mode 100644
index 000000000..64b55cc22
--- /dev/null
+++ b/Riot/Modules/MatrixKit/Animators/MXKAttachmentInteractionController.h
@@ -0,0 +1,26 @@
+/*
+ Copyright 2017 Aram Sargsyan
+
+ 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 "MXKAttachmentAnimator.h"
+
+@interface MXKAttachmentInteractionController : UIPercentDrivenInteractiveTransition
+
+@property (nonatomic) BOOL interactionInProgress;
+
+- (instancetype)initWithDestinationViewController:(UIViewController *)viewController sourceViewController:(UIViewController *)sourceViewController;
+
+@end
diff --git a/Riot/Modules/MatrixKit/Animators/MXKAttachmentInteractionController.m b/Riot/Modules/MatrixKit/Animators/MXKAttachmentInteractionController.m
new file mode 100644
index 000000000..ba3d952fc
--- /dev/null
+++ b/Riot/Modules/MatrixKit/Animators/MXKAttachmentInteractionController.m
@@ -0,0 +1,204 @@
+/*
+ Copyright 2017 Aram Sargsyan
+
+ 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 "MXKAttachmentInteractionController.h"
+#import "MXLog.h"
+
+@interface MXKAttachmentInteractionController ()
+
+@property (nonatomic, weak) UIViewController *destinationViewController;
+@property (nonatomic, weak) UIViewController *sourceViewController;
+
+@property (nonatomic) UIImageView *transitioningImageView;
+@property (nonatomic, weak) id transitionContext;
+
+@property (nonatomic) CGPoint translation;
+@property (nonatomic) CGPoint delta;
+
+@end
+
+@implementation MXKAttachmentInteractionController
+
+#pragma mark - Lifecycle
+
+- (instancetype)initWithDestinationViewController:(UIViewController *)viewController sourceViewController:(UIViewController *)sourceViewController
+{
+ self = [super init];
+ if (self) {
+ self.destinationViewController = viewController;
+ self.sourceViewController = sourceViewController;
+ self.interactionInProgress = NO;
+
+ [self preparePanGestureRecognizerInView:viewController.view];
+ }
+ return self;
+}
+
+#pragma mark - Gesture recognizer
+
+- (void)preparePanGestureRecognizerInView:(UIView *)view
+{
+ UIPanGestureRecognizer *recognizer = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(handleGesture:)];
+ recognizer.minimumNumberOfTouches = 1;
+ recognizer.maximumNumberOfTouches = 3;
+ [view addGestureRecognizer:recognizer];
+}
+
+- (void)handleGesture:(UIPanGestureRecognizer *)recognizer
+{
+ CGPoint translation = [recognizer translationInView:self.destinationViewController.view];
+ self.delta = CGPointMake(translation.x - self.translation.x, translation.y - self.translation.y);
+ self.translation = translation;
+
+ switch (recognizer.state) {
+ case UIGestureRecognizerStateBegan:
+
+ self.interactionInProgress = YES;
+
+ if (self.destinationViewController.navigationController) {
+ [self.destinationViewController.navigationController popViewControllerAnimated:YES];
+ } else {
+ [self.destinationViewController dismissViewControllerAnimated:YES completion:nil];
+ }
+
+ break;
+
+ case UIGestureRecognizerStateChanged:
+
+ [self updateInteractiveTransition:(ABS(translation.y) / (CGRectGetHeight(self.destinationViewController.view.frame) / 2))];
+
+ break;
+
+ case UIGestureRecognizerStateCancelled:
+
+ self.interactionInProgress = NO;
+ [self cancelInteractiveTransition];
+
+ break;
+
+ case UIGestureRecognizerStateEnded:
+
+ self.interactionInProgress = NO;
+ if (ABS(self.translation.y) < CGRectGetHeight(self.destinationViewController.view.frame)/6) {
+ [self cancelInteractiveTransition];
+ } else {
+ [self finishInteractiveTransition];
+ }
+
+ break;
+
+ default:
+ MXLogDebug(@"UIGestureRecognizerState not handled");
+ break;
+ }
+}
+
+#pragma mark - UIPercentDrivenInteractiveTransition
+
+- (void)startInteractiveTransition:(id )transitionContext
+{
+ self.transitionContext = transitionContext;
+
+ UIViewController *fromViewController = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];
+ UIImageView *destinationImageView = [self.destinationViewController finalImageView];
+ destinationImageView.hidden = YES;
+
+ UIViewController *toViewController = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
+ toViewController.view.frame = [transitionContext finalFrameForViewController:toViewController];
+ [[transitionContext containerView] insertSubview:toViewController.view belowSubview:fromViewController.view];
+ UIImageView *originalImageView = [self.sourceViewController originalImageView];
+ originalImageView.hidden = YES;
+
+ self.transitioningImageView = [[UIImageView alloc] initWithImage:destinationImageView.image];
+ self.transitioningImageView.frame = [MXKAttachmentAnimator aspectFitImage:destinationImageView.image inFrame:destinationImageView.frame];
+ [[transitionContext containerView] addSubview:self.transitioningImageView];
+}
+
+- (void)updateInteractiveTransition:(CGFloat)percentComplete {
+ self.destinationViewController.view.alpha = MAX(0, (1 - percentComplete));
+
+ CGRect newFrame = CGRectMake(self.transitioningImageView.frame.origin.x, self.transitioningImageView.frame.origin.y + self.delta.y, CGRectGetWidth(self.transitioningImageView.frame), CGRectGetHeight(self.transitioningImageView.frame));
+ self.transitioningImageView.frame = newFrame;
+}
+
+- (void)cancelInteractiveTransition {
+ UIViewController *fromViewController = [self.transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];
+ UIImageView *destinationImageView = [self.destinationViewController finalImageView];
+ UIImageView *originalImageView = [self.sourceViewController originalImageView];
+
+ __weak typeof(self) weakSelf = self;
+
+ [UIView animateWithDuration:([self transitionDuration:self.transitionContext]/2) animations:^{
+ if (weakSelf)
+ {
+ typeof(self) self = weakSelf;
+ fromViewController.view.alpha = 1;
+ self.transitioningImageView.frame = [MXKAttachmentAnimator aspectFitImage:destinationImageView.image inFrame:destinationImageView.frame];
+ }
+ } completion:^(BOOL finished) {
+ if (weakSelf)
+ {
+ typeof(self) self = weakSelf;
+ destinationImageView.hidden = NO;
+ originalImageView.hidden = NO;
+ [self.transitioningImageView removeFromSuperview];
+
+ [self.transitionContext cancelInteractiveTransition];
+ [self.transitionContext completeTransition:NO];
+ }
+ }];
+}
+
+- (void)finishInteractiveTransition
+{
+ UIViewController *fromViewController = [self.transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];
+ UIImageView *destinationImageView = [self.destinationViewController finalImageView];
+
+ UIImageView *originalImageView = [self.sourceViewController originalImageView];
+ CGRect originalImageViewFrame = [self.sourceViewController convertedFrameForOriginalImageView];
+
+ __weak typeof(self) weakSelf = self;
+
+ [UIView animateWithDuration:[self transitionDuration:self.transitionContext] animations:^{
+ if (weakSelf)
+ {
+ typeof(self) self = weakSelf;
+ fromViewController.view.alpha = 0.0;
+ self.transitioningImageView.frame = originalImageViewFrame;
+ }
+ } completion:^(BOOL finished) {
+ if (weakSelf)
+ {
+ typeof(self) self = weakSelf;
+ [self.transitioningImageView removeFromSuperview];
+ destinationImageView.hidden = NO;
+ originalImageView.hidden = NO;
+
+ [self.transitionContext finishInteractiveTransition];
+ [self.transitionContext completeTransition:YES];
+ }
+ }];
+}
+
+#pragma mark - UIViewControllerAnimatedTransitioning
+
+- (NSTimeInterval)transitionDuration:(id)transitionContext
+{
+ return 0.3;
+}
+
+
+@end
diff --git a/Riot/Modules/MatrixKit/Assets/MatrixKitAssets.bundle/Images/back_icon.png b/Riot/Modules/MatrixKit/Assets/MatrixKitAssets.bundle/Images/back_icon.png
new file mode 100644
index 000000000..dfef9b68d
Binary files /dev/null and b/Riot/Modules/MatrixKit/Assets/MatrixKitAssets.bundle/Images/back_icon.png differ
diff --git a/Riot/Modules/MatrixKit/Assets/MatrixKitAssets.bundle/Images/back_icon@2x.png b/Riot/Modules/MatrixKit/Assets/MatrixKitAssets.bundle/Images/back_icon@2x.png
new file mode 100644
index 000000000..96d24fa95
Binary files /dev/null and b/Riot/Modules/MatrixKit/Assets/MatrixKitAssets.bundle/Images/back_icon@2x.png differ
diff --git a/Riot/Modules/MatrixKit/Assets/MatrixKitAssets.bundle/Images/back_icon@3x.png b/Riot/Modules/MatrixKit/Assets/MatrixKitAssets.bundle/Images/back_icon@3x.png
new file mode 100644
index 000000000..c5186c770
Binary files /dev/null and b/Riot/Modules/MatrixKit/Assets/MatrixKitAssets.bundle/Images/back_icon@3x.png differ
diff --git a/Riot/Modules/MatrixKit/Assets/MatrixKitAssets.bundle/Images/back_icon@4x.png b/Riot/Modules/MatrixKit/Assets/MatrixKitAssets.bundle/Images/back_icon@4x.png
new file mode 100644
index 000000000..c6e227a11
Binary files /dev/null and b/Riot/Modules/MatrixKit/Assets/MatrixKitAssets.bundle/Images/back_icon@4x.png differ
diff --git a/Riot/Modules/MatrixKit/Assets/MatrixKitAssets.bundle/Images/bubble_ios_messages_right.png b/Riot/Modules/MatrixKit/Assets/MatrixKitAssets.bundle/Images/bubble_ios_messages_right.png
new file mode 100644
index 000000000..50d90254c
Binary files /dev/null and b/Riot/Modules/MatrixKit/Assets/MatrixKitAssets.bundle/Images/bubble_ios_messages_right.png differ
diff --git a/Riot/Modules/MatrixKit/Assets/MatrixKitAssets.bundle/Images/bubble_ios_messages_right@2x.png b/Riot/Modules/MatrixKit/Assets/MatrixKitAssets.bundle/Images/bubble_ios_messages_right@2x.png
new file mode 100644
index 000000000..d0bec7c32
Binary files /dev/null and b/Riot/Modules/MatrixKit/Assets/MatrixKitAssets.bundle/Images/bubble_ios_messages_right@2x.png differ
diff --git a/Riot/Modules/MatrixKit/Assets/MatrixKitAssets.bundle/Images/default-profile.png b/Riot/Modules/MatrixKit/Assets/MatrixKitAssets.bundle/Images/default-profile.png
new file mode 100644
index 000000000..b48dd4098
Binary files /dev/null and b/Riot/Modules/MatrixKit/Assets/MatrixKitAssets.bundle/Images/default-profile.png differ
diff --git a/Riot/Modules/MatrixKit/Assets/MatrixKitAssets.bundle/Images/default-profile@2x.png b/Riot/Modules/MatrixKit/Assets/MatrixKitAssets.bundle/Images/default-profile@2x.png
new file mode 100644
index 000000000..6f81a3c41
Binary files /dev/null and b/Riot/Modules/MatrixKit/Assets/MatrixKitAssets.bundle/Images/default-profile@2x.png differ
diff --git a/Riot/Modules/MatrixKit/Assets/MatrixKitAssets.bundle/Images/disclosure.png b/Riot/Modules/MatrixKit/Assets/MatrixKitAssets.bundle/Images/disclosure.png
new file mode 100755
index 000000000..501026d49
Binary files /dev/null and b/Riot/Modules/MatrixKit/Assets/MatrixKitAssets.bundle/Images/disclosure.png differ
diff --git a/Riot/Modules/MatrixKit/Assets/MatrixKitAssets.bundle/Images/disclosure@2x.png b/Riot/Modules/MatrixKit/Assets/MatrixKitAssets.bundle/Images/disclosure@2x.png
new file mode 100644
index 000000000..3920aba23
Binary files /dev/null and b/Riot/Modules/MatrixKit/Assets/MatrixKitAssets.bundle/Images/disclosure@2x.png differ
diff --git a/Riot/Modules/MatrixKit/Assets/MatrixKitAssets.bundle/Images/filetype-gif.png b/Riot/Modules/MatrixKit/Assets/MatrixKitAssets.bundle/Images/filetype-gif.png
new file mode 100644
index 000000000..a4f048f7b
Binary files /dev/null and b/Riot/Modules/MatrixKit/Assets/MatrixKitAssets.bundle/Images/filetype-gif.png differ
diff --git a/Riot/Modules/MatrixKit/Assets/MatrixKitAssets.bundle/Images/filetype-gif@2x.png b/Riot/Modules/MatrixKit/Assets/MatrixKitAssets.bundle/Images/filetype-gif@2x.png
new file mode 100644
index 000000000..415769083
Binary files /dev/null and b/Riot/Modules/MatrixKit/Assets/MatrixKitAssets.bundle/Images/filetype-gif@2x.png differ
diff --git a/Riot/Modules/MatrixKit/Assets/MatrixKitAssets.bundle/Images/icon_audio_mute.png b/Riot/Modules/MatrixKit/Assets/MatrixKitAssets.bundle/Images/icon_audio_mute.png
new file mode 100755
index 000000000..020c67583
Binary files /dev/null and b/Riot/Modules/MatrixKit/Assets/MatrixKitAssets.bundle/Images/icon_audio_mute.png differ
diff --git a/Riot/Modules/MatrixKit/Assets/MatrixKitAssets.bundle/Images/icon_audio_mute@2x.png b/Riot/Modules/MatrixKit/Assets/MatrixKitAssets.bundle/Images/icon_audio_mute@2x.png
new file mode 100755
index 000000000..2804524e1
Binary files /dev/null and b/Riot/Modules/MatrixKit/Assets/MatrixKitAssets.bundle/Images/icon_audio_mute@2x.png differ
diff --git a/Riot/Modules/MatrixKit/Assets/MatrixKitAssets.bundle/Images/icon_audio_unmute.png b/Riot/Modules/MatrixKit/Assets/MatrixKitAssets.bundle/Images/icon_audio_unmute.png
new file mode 100755
index 000000000..1eb9a831b
Binary files /dev/null and b/Riot/Modules/MatrixKit/Assets/MatrixKitAssets.bundle/Images/icon_audio_unmute.png differ
diff --git a/Riot/Modules/MatrixKit/Assets/MatrixKitAssets.bundle/Images/icon_audio_unmute@2x.png b/Riot/Modules/MatrixKit/Assets/MatrixKitAssets.bundle/Images/icon_audio_unmute@2x.png
new file mode 100644
index 000000000..87095ec67
Binary files /dev/null and b/Riot/Modules/MatrixKit/Assets/MatrixKitAssets.bundle/Images/icon_audio_unmute@2x.png differ
diff --git a/Riot/Modules/MatrixKit/Assets/MatrixKitAssets.bundle/Images/icon_backtoapp.png b/Riot/Modules/MatrixKit/Assets/MatrixKitAssets.bundle/Images/icon_backtoapp.png
new file mode 100644
index 000000000..0c0db3a59
Binary files /dev/null and b/Riot/Modules/MatrixKit/Assets/MatrixKitAssets.bundle/Images/icon_backtoapp.png differ
diff --git a/Riot/Modules/MatrixKit/Assets/MatrixKitAssets.bundle/Images/icon_backtoapp@2x.png b/Riot/Modules/MatrixKit/Assets/MatrixKitAssets.bundle/Images/icon_backtoapp@2x.png
new file mode 100644
index 000000000..ece39112e
Binary files /dev/null and b/Riot/Modules/MatrixKit/Assets/MatrixKitAssets.bundle/Images/icon_backtoapp@2x.png differ
diff --git a/Riot/Modules/MatrixKit/Assets/MatrixKitAssets.bundle/Images/icon_keyboard.png b/Riot/Modules/MatrixKit/Assets/MatrixKitAssets.bundle/Images/icon_keyboard.png
new file mode 100644
index 000000000..8fbefd852
Binary files /dev/null and b/Riot/Modules/MatrixKit/Assets/MatrixKitAssets.bundle/Images/icon_keyboard.png differ
diff --git a/Riot/Modules/MatrixKit/Assets/MatrixKitAssets.bundle/Images/icon_keyboard@2x.png b/Riot/Modules/MatrixKit/Assets/MatrixKitAssets.bundle/Images/icon_keyboard@2x.png
new file mode 100644
index 000000000..f6a01a8f6
Binary files /dev/null and b/Riot/Modules/MatrixKit/Assets/MatrixKitAssets.bundle/Images/icon_keyboard@2x.png differ
diff --git a/Riot/Modules/MatrixKit/Assets/MatrixKitAssets.bundle/Images/icon_minus.png b/Riot/Modules/MatrixKit/Assets/MatrixKitAssets.bundle/Images/icon_minus.png
new file mode 100644
index 000000000..93e8cc466
Binary files /dev/null and b/Riot/Modules/MatrixKit/Assets/MatrixKitAssets.bundle/Images/icon_minus.png differ
diff --git a/Riot/Modules/MatrixKit/Assets/MatrixKitAssets.bundle/Images/icon_minus@2x.png b/Riot/Modules/MatrixKit/Assets/MatrixKitAssets.bundle/Images/icon_minus@2x.png
new file mode 100644
index 000000000..4239229fc
Binary files /dev/null and b/Riot/Modules/MatrixKit/Assets/MatrixKitAssets.bundle/Images/icon_minus@2x.png differ
diff --git a/Riot/Modules/MatrixKit/Assets/MatrixKitAssets.bundle/Images/icon_pause.png b/Riot/Modules/MatrixKit/Assets/MatrixKitAssets.bundle/Images/icon_pause.png
new file mode 100644
index 000000000..4120c3c32
Binary files /dev/null and b/Riot/Modules/MatrixKit/Assets/MatrixKitAssets.bundle/Images/icon_pause.png differ
diff --git a/Riot/Modules/MatrixKit/Assets/MatrixKitAssets.bundle/Images/icon_pause@2x.png b/Riot/Modules/MatrixKit/Assets/MatrixKitAssets.bundle/Images/icon_pause@2x.png
new file mode 100644
index 000000000..93e8d149b
Binary files /dev/null and b/Riot/Modules/MatrixKit/Assets/MatrixKitAssets.bundle/Images/icon_pause@2x.png differ
diff --git a/Riot/Modules/MatrixKit/Assets/MatrixKitAssets.bundle/Images/icon_play.png b/Riot/Modules/MatrixKit/Assets/MatrixKitAssets.bundle/Images/icon_play.png
new file mode 100644
index 000000000..82f93f7e5
Binary files /dev/null and b/Riot/Modules/MatrixKit/Assets/MatrixKitAssets.bundle/Images/icon_play.png differ
diff --git a/Riot/Modules/MatrixKit/Assets/MatrixKitAssets.bundle/Images/icon_play@2x.png b/Riot/Modules/MatrixKit/Assets/MatrixKitAssets.bundle/Images/icon_play@2x.png
new file mode 100644
index 000000000..a7151e5e8
Binary files /dev/null and b/Riot/Modules/MatrixKit/Assets/MatrixKitAssets.bundle/Images/icon_play@2x.png differ
diff --git a/Riot/Modules/MatrixKit/Assets/MatrixKitAssets.bundle/Images/icon_speaker_off.png b/Riot/Modules/MatrixKit/Assets/MatrixKitAssets.bundle/Images/icon_speaker_off.png
new file mode 100755
index 000000000..dbfdc83cc
Binary files /dev/null and b/Riot/Modules/MatrixKit/Assets/MatrixKitAssets.bundle/Images/icon_speaker_off.png differ
diff --git a/Riot/Modules/MatrixKit/Assets/MatrixKitAssets.bundle/Images/icon_speaker_off@2x.png b/Riot/Modules/MatrixKit/Assets/MatrixKitAssets.bundle/Images/icon_speaker_off@2x.png
new file mode 100644
index 000000000..acc6eba4d
Binary files /dev/null and b/Riot/Modules/MatrixKit/Assets/MatrixKitAssets.bundle/Images/icon_speaker_off@2x.png differ
diff --git a/Riot/Modules/MatrixKit/Assets/MatrixKitAssets.bundle/Images/icon_speaker_on.png b/Riot/Modules/MatrixKit/Assets/MatrixKitAssets.bundle/Images/icon_speaker_on.png
new file mode 100755
index 000000000..1022e8d98
Binary files /dev/null and b/Riot/Modules/MatrixKit/Assets/MatrixKitAssets.bundle/Images/icon_speaker_on.png differ
diff --git a/Riot/Modules/MatrixKit/Assets/MatrixKitAssets.bundle/Images/icon_speaker_on@2x.png b/Riot/Modules/MatrixKit/Assets/MatrixKitAssets.bundle/Images/icon_speaker_on@2x.png
new file mode 100755
index 000000000..198c12d90
Binary files /dev/null and b/Riot/Modules/MatrixKit/Assets/MatrixKitAssets.bundle/Images/icon_speaker_on@2x.png differ
diff --git a/Riot/Modules/MatrixKit/Assets/MatrixKitAssets.bundle/Images/icon_video.png b/Riot/Modules/MatrixKit/Assets/MatrixKitAssets.bundle/Images/icon_video.png
new file mode 100755
index 000000000..8ee589aaa
Binary files /dev/null and b/Riot/Modules/MatrixKit/Assets/MatrixKitAssets.bundle/Images/icon_video.png differ
diff --git a/Riot/Modules/MatrixKit/Assets/MatrixKitAssets.bundle/Images/icon_video@2x.png b/Riot/Modules/MatrixKit/Assets/MatrixKitAssets.bundle/Images/icon_video@2x.png
new file mode 100755
index 000000000..7b68c34c4
Binary files /dev/null and b/Riot/Modules/MatrixKit/Assets/MatrixKitAssets.bundle/Images/icon_video@2x.png differ
diff --git a/Riot/Modules/MatrixKit/Assets/MatrixKitAssets.bundle/Images/icon_video_mute.png b/Riot/Modules/MatrixKit/Assets/MatrixKitAssets.bundle/Images/icon_video_mute.png
new file mode 100755
index 000000000..efdb97478
Binary files /dev/null and b/Riot/Modules/MatrixKit/Assets/MatrixKitAssets.bundle/Images/icon_video_mute.png differ
diff --git a/Riot/Modules/MatrixKit/Assets/MatrixKitAssets.bundle/Images/icon_video_mute@2x.png b/Riot/Modules/MatrixKit/Assets/MatrixKitAssets.bundle/Images/icon_video_mute@2x.png
new file mode 100755
index 000000000..1c041f16a
Binary files /dev/null and b/Riot/Modules/MatrixKit/Assets/MatrixKitAssets.bundle/Images/icon_video_mute@2x.png differ
diff --git a/Riot/Modules/MatrixKit/Assets/MatrixKitAssets.bundle/Images/icon_video_unmute.png b/Riot/Modules/MatrixKit/Assets/MatrixKitAssets.bundle/Images/icon_video_unmute.png
new file mode 100755
index 000000000..ff47d6256
Binary files /dev/null and b/Riot/Modules/MatrixKit/Assets/MatrixKitAssets.bundle/Images/icon_video_unmute.png differ
diff --git a/Riot/Modules/MatrixKit/Assets/MatrixKitAssets.bundle/Images/icon_video_unmute@2x.png b/Riot/Modules/MatrixKit/Assets/MatrixKitAssets.bundle/Images/icon_video_unmute@2x.png
new file mode 100755
index 000000000..b196c5052
Binary files /dev/null and b/Riot/Modules/MatrixKit/Assets/MatrixKitAssets.bundle/Images/icon_video_unmute@2x.png differ
diff --git a/Riot/Modules/MatrixKit/Assets/MatrixKitAssets.bundle/Images/logoHighRes.png b/Riot/Modules/MatrixKit/Assets/MatrixKitAssets.bundle/Images/logoHighRes.png
new file mode 100644
index 000000000..7187eada0
Binary files /dev/null and b/Riot/Modules/MatrixKit/Assets/MatrixKitAssets.bundle/Images/logoHighRes.png differ
diff --git a/Riot/Modules/MatrixKit/Assets/MatrixKitAssets.bundle/Images/logoHighRes@2x.png b/Riot/Modules/MatrixKit/Assets/MatrixKitAssets.bundle/Images/logoHighRes@2x.png
new file mode 100644
index 000000000..c4b53a848
Binary files /dev/null and b/Riot/Modules/MatrixKit/Assets/MatrixKitAssets.bundle/Images/logoHighRes@2x.png differ
diff --git a/Riot/Modules/MatrixKit/Assets/MatrixKitAssets.bundle/Images/matrixUser.png b/Riot/Modules/MatrixKit/Assets/MatrixKitAssets.bundle/Images/matrixUser.png
new file mode 100755
index 000000000..09496113e
Binary files /dev/null and b/Riot/Modules/MatrixKit/Assets/MatrixKitAssets.bundle/Images/matrixUser.png differ
diff --git a/Riot/Modules/MatrixKit/Assets/MatrixKitAssets.bundle/Images/matrixUser@2x.png b/Riot/Modules/MatrixKit/Assets/MatrixKitAssets.bundle/Images/matrixUser@2x.png
new file mode 100755
index 000000000..9282f3d60
Binary files /dev/null and b/Riot/Modules/MatrixKit/Assets/MatrixKitAssets.bundle/Images/matrixUser@2x.png differ
diff --git a/Riot/Modules/MatrixKit/Assets/MatrixKitAssets.bundle/Images/network_matrix.png b/Riot/Modules/MatrixKit/Assets/MatrixKitAssets.bundle/Images/network_matrix.png
new file mode 100644
index 000000000..cf21452bc
Binary files /dev/null and b/Riot/Modules/MatrixKit/Assets/MatrixKitAssets.bundle/Images/network_matrix.png differ
diff --git a/Riot/Modules/MatrixKit/Assets/MatrixKitAssets.bundle/Images/network_matrix@2x.png b/Riot/Modules/MatrixKit/Assets/MatrixKitAssets.bundle/Images/network_matrix@2x.png
new file mode 100644
index 000000000..64d6f4891
Binary files /dev/null and b/Riot/Modules/MatrixKit/Assets/MatrixKitAssets.bundle/Images/network_matrix@2x.png differ
diff --git a/Riot/Modules/MatrixKit/Assets/MatrixKitAssets.bundle/Images/play.png b/Riot/Modules/MatrixKit/Assets/MatrixKitAssets.bundle/Images/play.png
new file mode 100755
index 000000000..e4da01208
Binary files /dev/null and b/Riot/Modules/MatrixKit/Assets/MatrixKitAssets.bundle/Images/play.png differ
diff --git a/Riot/Modules/MatrixKit/Assets/MatrixKitAssets.bundle/Images/play@2x.png b/Riot/Modules/MatrixKit/Assets/MatrixKitAssets.bundle/Images/play@2x.png
new file mode 100755
index 000000000..50e34bc24
Binary files /dev/null and b/Riot/Modules/MatrixKit/Assets/MatrixKitAssets.bundle/Images/play@2x.png differ
diff --git a/Riot/Modules/MatrixKit/Assets/MatrixKitAssets.bundle/Images/shrink.png b/Riot/Modules/MatrixKit/Assets/MatrixKitAssets.bundle/Images/shrink.png
new file mode 100755
index 000000000..ab04eedc0
Binary files /dev/null and b/Riot/Modules/MatrixKit/Assets/MatrixKitAssets.bundle/Images/shrink.png differ
diff --git a/Riot/Modules/MatrixKit/Assets/MatrixKitAssets.bundle/Images/shrink@2x.png b/Riot/Modules/MatrixKit/Assets/MatrixKitAssets.bundle/Images/shrink@2x.png
new file mode 100644
index 000000000..43021be82
Binary files /dev/null and b/Riot/Modules/MatrixKit/Assets/MatrixKitAssets.bundle/Images/shrink@2x.png differ
diff --git a/Riot/Modules/MatrixKit/Assets/MatrixKitAssets.bundle/Sounds/busy.mp3 b/Riot/Modules/MatrixKit/Assets/MatrixKitAssets.bundle/Sounds/busy.mp3
new file mode 100644
index 000000000..fec27ba4c
Binary files /dev/null and b/Riot/Modules/MatrixKit/Assets/MatrixKitAssets.bundle/Sounds/busy.mp3 differ
diff --git a/Riot/Modules/MatrixKit/Assets/MatrixKitAssets.bundle/Sounds/callend.mp3 b/Riot/Modules/MatrixKit/Assets/MatrixKitAssets.bundle/Sounds/callend.mp3
new file mode 100644
index 000000000..50c34e564
Binary files /dev/null and b/Riot/Modules/MatrixKit/Assets/MatrixKitAssets.bundle/Sounds/callend.mp3 differ
diff --git a/Riot/Modules/MatrixKit/Assets/MatrixKitAssets.bundle/Sounds/message.mp3 b/Riot/Modules/MatrixKit/Assets/MatrixKitAssets.bundle/Sounds/message.mp3
new file mode 100644
index 000000000..942adbe85
Binary files /dev/null and b/Riot/Modules/MatrixKit/Assets/MatrixKitAssets.bundle/Sounds/message.mp3 differ
diff --git a/Riot/Modules/MatrixKit/Assets/MatrixKitAssets.bundle/Sounds/ring.mp3 b/Riot/Modules/MatrixKit/Assets/MatrixKitAssets.bundle/Sounds/ring.mp3
new file mode 100644
index 000000000..3c3cdde3f
Binary files /dev/null and b/Riot/Modules/MatrixKit/Assets/MatrixKitAssets.bundle/Sounds/ring.mp3 differ
diff --git a/Riot/Modules/MatrixKit/Assets/MatrixKitAssets.bundle/Sounds/ringback.mp3 b/Riot/Modules/MatrixKit/Assets/MatrixKitAssets.bundle/Sounds/ringback.mp3
new file mode 100644
index 000000000..6ee34bf39
Binary files /dev/null and b/Riot/Modules/MatrixKit/Assets/MatrixKitAssets.bundle/Sounds/ringback.mp3 differ
diff --git a/Riot/Modules/MatrixKit/Assets/MatrixKitAssets.bundle/ar.lproj/MatrixKit.strings b/Riot/Modules/MatrixKit/Assets/MatrixKitAssets.bundle/ar.lproj/MatrixKit.strings
new file mode 100644
index 000000000..fd6b76f33
--- /dev/null
+++ b/Riot/Modules/MatrixKit/Assets/MatrixKitAssets.bundle/ar.lproj/MatrixKit.strings
@@ -0,0 +1,549 @@
+
+
+"show_details" = "إظهَارُ التَّفاصيل";
+"cancel_download" = "إلغَاءُ التَّنزيل";
+"cancel_upload" = "إلغَاءُ الرَّفع";
+"select_all" = "تَحدِيدُ الكُل";
+"resend_message" = "إعادَةُ إرسَالِ الرِّسالَة";
+"reset_to_default" = "إعادَةُ الضَّبط إلى الاِفتِراضي";
+"invite_user" = "دَعوَة مُستَخدِمِ matrix";
+"capture_media" = "اِلتِقَاطُ صُّورة/مَقطَعَ مَرئيّ";
+"attach_media" = "إرفاقُ وسائطٍ مِنَ المَكتَبَة";
+"select_account" = "حَدِّد حِسَابًا";
+"mention" = "الذِّكْر";
+"start_video_call" = "بَدءُ مُكالَمَةٍ مَرئيَّة";
+"start_voice_call" = "بَدءُ مُكالَمَةٍ صَوتيَّة";
+"start_chat" = "بَدءُ مُحادَثَة";
+"login_error_resource_limit_exceeded_contact_button" = "التَّواصُل مع المُدير";
+"login_error_resource_limit_exceeded_message_contact" = "\n\nيُرجَى التَّواصُل مَعَ مُدير خِدمَتك لِمُواصَلَة اِستِخدام هَذِهِ الخِدمَة.";
+"set_admin" = "تَعيِينُ مُدير";
+"set_moderator" = "تَعيِينُ مُشرِف";
+"set_default_power_level" = "إعادَة ضَبط مُستَوى القُوَّة";
+"set_power_level" = "ضَبط مُستَوى القُوَّة";
+"submit_code" = "تَسلِيم الرَّمز";
+"submit" = "التَّسلِيم";
+"sign_up" = "الاِشتِراك";
+"retry" = "إعادَةُ المُحاوَلة";
+"dismiss" = "إبعَاد";
+"discard" = "اِستِبعاد";
+"continue" = "الاِستِمرار";
+"close" = "إغلاق";
+"back" = "الرُّجُوع";
+"abort" = "إِجهَاض";
+"yes" = "نَعَم";
+
+// Action
+"no" = "لَا";
+"login_error_resource_limit_exceeded_message_monthly_active_user" = "هَذَا الخادِم الرَّئيس قَد وَصَلَ إلى حَدّه الشَّهري للمُستخدِم النَّشِط.";
+"login_error_resource_limit_exceeded_message_default" = "هَذَا الخادِم الرَّئيس قَد تَجاوزَ أحَد حُدُود مَوارِده.";
+"login_error_resource_limit_exceeded_title" = "تَمَّ تَجاوُز حَدّ المَورِد";
+"login_email_placeholder" = "عُنوان بَريد إلِكتُرونيّ";
+"login_email_info" = "إنَّ تَعيِين عُنوان بَريد إلِكتُرونيّ يُتيحُ لِلمُستَخدِمين الآخَرين العُثُور عَلَيك في Matrix بِشَكل أسهَل، وَيَمنَحُكَ طَريقَة لِإعادَة تَعيِين كَلِمة المُرُور الخاصَّة بِك في المُستَقبَل.";
+"login_desktop_device" = "سَطح مَكتَب";
+"login_tablet_device" = "لَوحي";
+"login_mobile_device" = "هَاتِف";
+"login_error_forgot_password_is_not_supported" = "نِسيان كَلِمَة المُرُور غَير مَدعُوم حاليًا";
+"register_error_title" = "فَشَلَ التَّسجِيل";
+"login_invalid_param" = "مُعامِل غَير صَالِح";
+"login_leave_fallback" = "إلغَاء";
+"login_use_fallback" = "اِستِخدام صَفحَة اِحتياطيَّة بَديلَة";
+"login_error_user_in_use" = "إنَّ اِسم المُستَخدِم هَذَا مُستخدَمٌ بِالفِعل";
+"login_error_limit_exceeded" = "لَقَد أُرسِلَت العَديد مِنَ الطَّلَبات";
+"login_error_not_json" = "لَم يَحتَوي عَلَى JSON صالِح";
+"login_error_bad_json" = "إنَّ JSON مُشَوَّه";
+"login_error_unknown_token" = "لَم يَتِمّ التَّعرُّف عَلَى رَمز الوُصُول المَذكُور";
+"login_error_forbidden" = "اِسم مُستَخدِم/كَلِمَةُ مُرُور غَير صالِحَة";
+"login_error_registration_is_not_supported" = "إنَّ التَّسجِيل غَير مَدعوم حاليًا";
+"login_error_do_not_support_login_flows" = "حاليًا، نَحنُ لا نَدعَم أيًا مِن تَدَفُقات تَسجيل الدُّخُول المُعَرَّفة بِواسِطَة هَذَا الخادِم الرَّئيس";
+"login_error_no_login_flow" = "لَقَد فَشلنَا في اِستِرداد بَيَانات المُصادَقَة مِن هَذَا الخادِم الرَّئيس";
+"login_error_title" = "فَشَلَ تَسجيلُ الدُّخُول";
+"login_prompt_email_token" = "يُرجى إدخال رَمز التَّحقُّق مِنَ البَريد الإلِكتُرونيّ الخاص بِك:";
+"login_user_id_placeholder" = "مُعَرِّف Matrix (مِثل bob:matrix.org@ أو bob)";
+"login_display_name_placeholder" = "الاِسم الظّاهِر (مِثل. محمد عبدالله)";
+"login_optional_field" = "اِختياري";
+"login_password_placeholder" = "كَلِمَةُ المُرُور";
+"login_identity_server_info" = "تُوَفِّرُ Matrix خَوادِم هُويَّة لتَتَبُع عَناوين البَريد الإلِكتُرونيّ وَما إلى ذَلِكَ مِنَ الَّتي تَنتَمي إلى مُعَرِّفات Matrix. فَقَط https://matrix.org مُتَوَفِّر حاليًا.";
+"login_identity_server_title" = "عُنوان URL لِخادِم الهُويَّة:";
+"login_home_server_info" = "الخادِم الرَّئيس الخاصّ بِك يُخَزِّنُ جَميع مُحادَثاتك وَمَعلُومات حِسابك";
+"login_home_server_title" = "عُنوان URL لِلخادِم الرَّئيس:";
+"login_server_url_placeholder" = "عُنوان URL (مِثل https://matrix.org)";
+
+// Login Screen
+"login_create_account" = "إنشاءُ حِساب:";
+/* *********************** */
+/* iOS specific */
+/* *********************** */
+
+"matrix" = "Matrix";
+"notice_encrypted_message" = "رِسَالَةٌ مُعمّاة";
+"notice_room_related_groups" = "المَجمُوعاتُ المُرتَبِطَةُ بِهَذِهِ الغُرفَةِ هِيَ: %@";
+"notice_room_aliases_for_dm" = "الاَسماءُ البَديلَة هِيَ: %@";
+"notice_room_aliases" = "الاَسماءُ البَديلَة لِلغُرفَة هِيَ: %@";
+"notice_room_power_level_event_requirement" = "مُستَوى القُوَّة الأدنَى المُتَعلِّقُ بِالأحداث هُوَ:";
+"notice_room_power_level_acting_requirement" = "مُستَوى القُوَّة الأدنَى الَّذي يَجِبُ عَلَى المُستَخدِم اِمتِلاكُه قَبلَ التَّفاعُل هُوَ:";
+"notice_room_power_level_intro_for_dm" = "مُستَوى القُوَّة لِلأعضاءِ هُوَ:";
+"notice_room_power_level_intro" = "مُستَوى القُوَّة لِأعضاءِ الغُرفَة هُوَ:";
+"notice_room_join_rule_public_by_you_for_dm" = "أنتَ قَد جَعلتَ هَذِهِ عَامَّة.";
+"notice_room_join_rule_public_by_you" = "أنتَ قَد جَعلتَ الغُرفَةَ عَامَّة.";
+"notice_room_join_rule_public_for_dm" = "لَقَد جَعَلَ %@ هَذِهِ عَامَّة.";
+"notice_room_join_rule_public" = "لَقَد جَعَلَ %@ الغُرفَةَ عَامَّة.";
+"notice_room_join_rule_invite_by_you_for_dm" = "أنتَ قَد جَعلتَ هَذِهِ بالدَّعوَةِ فَقَط.";
+"notice_room_join_rule_invite_by_you" = "أنتَ قَد جَعلتَ الغُرفَةَ بالدَّعوَةِ فَقَط.";
+"notice_room_join_rule_invite_for_dm" = "لَقَد جَعَلَ %@ هَذِهِ بِالدَّعوَةِ فَقَط.";
+// New
+"notice_room_join_rule_invite" = "لَقَد جَعَلَ %@ الغُرفَةَ بِالدَّعوَةِ فَقَط.";
+// Old
+"notice_room_join_rule" = "قاعِدَة الاِنضِمام هِيَ: %@";
+"notice_room_created_for_dm" = "لَقَد اِنضَمَّ %@.";
+"notice_room_created" = "لَقَد أنشأ %@ الغُرفَة وَهَيَّأَها.";
+"notice_profile_change_redacted" = "لَقَد حدَّثَ %@ مَلفَّهُ الشَّخصي %@";
+"notice_event_redacted_reason" = " [السَّبَب: %@]";
+"notice_event_redacted_by" = " بِواسِطَة %@";
+"notice_event_redacted" = "<مُنَقَّح%@>";
+"notice_room_topic_removed" = "لَقَد أزالَ %@ المَوضُوع";
+"notice_room_name_removed_for_dm" = "لَقَد أزالَ %@ الاِسم";
+"notice_room_name_removed" = "لَقَد أزالَ %@ اِسمَ الغُرفَة";
+
+// Events formatter
+"notice_avatar_changed_too" = "(غُيِّرَت الصُّورَة الرَّمزية أيضًا)";
+"unignore" = "إلغَاءُ التَّجاهُل";
+"ignore" = "تَجاهُل";
+"end_call" = "إنهَاءُ المُكالَمَة";
+"reject_call" = "رَفضُ المُكالَمَة";
+"answer_call" = "الإجابَةُ عَلَى المُكالَمَة";
+"attachment_multiselection_original" = "الحَجمُ الفِعلي";
+"attachment_multiselection_size_prompt" = "هَل تُريدُ إِرسَالَ الصُّوَرِ كَـ :";
+"attachment_cancel_upload" = "إلغَاء الرَّفع؟";
+"attachment_cancel_download" = "إلغَاء التَّنزيل؟";
+"attachment_large" = "كَبير: %@";
+"attachment_medium" = "مُتَوَسِّط: %@";
+"attachment_small" = "صَغير: %@";
+"attachment_original" = "الحَجمُ الفِعلي: %@";
+
+// Attachment
+"attachment_size_prompt" = "هَل تُريدُ الإِرسَالَ كَـ :";
+"room_member_power_level_prompt" = "لَن تَكُونَ قَادِرًا عَلَى التَّراجُع عَن هَذا التَّغيِير فَأنتَ تُرَقِّي المُستَخدِم لِيَكُونَ لَهُ نَفس مُستَوى القُوَّة الَّذِي لدَيك.\nهَل أَنتَ مُتَأكِّد؟";
+
+// Room members
+"room_member_ignore_prompt" = "هَل أنتَ مُتَأكِّدٌ مِن رَغبَتِكَ فِي إخفَاءِ جَميعِ الرَّسَائِل عَن هَذَا المُستَخدِم؟";
+"message_reply_to_message_to_reply_to_prefix" = "رَدًّا عَلَى";
+"message_reply_to_sender_sent_a_file" = "أرسَلَ مَلَفّ.";
+"message_reply_to_sender_sent_a_video" = "أرسَلَ مَقطَعًا مَرئيًا.";
+"message_reply_to_sender_sent_an_audio_file" = "أرسَلَ مَلَفًّا صَوتيًا.";
+
+// Reply to message
+"message_reply_to_sender_sent_an_image" = "أرسَلَ صُورَةَ.";
+"room_no_conference_call_in_encrypted_rooms" = "إنَّ مُكالَمَاتُ الاِجتِمَاع غَيرُ مَدعُومَةٍ فِي الغُرَفِ المُعمّاة";
+"room_no_power_to_create_conference_call" = "أنتَ بِحاجَةٍ إلَى إذن لِلدَّعوَة حَتَّى تَبدَأَ اِجتِمَاع فِي هَذِهِ الغُرفَة";
+"room_left_for_dm" = "أنتَ قَد غادَرت";
+"room_left" = "أنتَ قَد غادَرتَ الغُرفَة";
+"room_error_timeline_event_not_found" = "التَّطبيقُ كَانَ يُحاوِلُ تَحميلَ نُقطَةٍ مُعَيَّنَةٍ مِنَ الخَطِّ الزَّمَنيِ لِهَذِهِ الغُرفَة لَكِن تَعَذَّرَ عَليهِ العُثُور عَلَيهَا";
+"room_error_cannot_load_timeline" = "فَشَلَ تَحمِيلُ الخَطِّ الزَّمَنِي";
+"room_error_timeline_event_not_found_title" = "فَشَلَ تَحمِيلُ مَوضِعِ الخَطِّ الزَّمَنِي";
+"room_error_topic_edition_not_authorized" = "أنتَ غَيرُ مُخَوَّلٍ لِتَحرِيرِ مَوضُوعِ هَذِهِ الغُرفَة";
+"room_error_name_edition_not_authorized" = "أنتَ غَيرُ مُخَوَّلٍ لِتَحرِيرِ اِسمِ هَذِهِ الغُرفَة";
+"room_error_join_failed_empty_room" = "حاليًّا مِن غَيرِ المُمكِنِ إعادَةُ الاِنضِمامِ إلى غُرفَةٍ فارِغَة.";
+"room_error_join_failed_title" = "فَشَلَ الاِنضِمام إلى الغُرفَة";
+
+// Room
+"room_please_select" = "يُرجَى تَحديدُ غُرفَة";
+"room_creation_participants_placeholder" = "(مِثل. (@bob:homeserver1; @john:homeserver2...";
+"room_creation_participants_title" = "المُشَارِكُون:";
+"room_creation_alias_placeholder_with_homeserver" = "(مِثل. %@foo#)";
+"room_creation_alias_placeholder" = "(مِثل. foo:example.org#)";
+"room_creation_alias_title" = "اِسمُ الغُرفَةِ البَديل:";
+"room_creation_name_placeholder" = "(مِثل. مجموعة الغداء)";
+
+// Room creation
+"room_creation_name_title" = "اِسمُ الغُرفَة:";
+"account_error_push_not_allowed" = "الإِشعَاراتُ غَيرُ مَسمُوحَة";
+"account_error_msisdn_wrong_description" = "يَبدو أنَّ هَذَا لَيسَ رَقمُ هَاتِفٍ صَالِح";
+"account_error_msisdn_wrong_title" = "رَقمُ هَاتِفٍ غَيرُ صَالِح";
+"account_error_email_wrong_description" = "يَبدو أنَّ هَذَا لَيسَ عُنوان بَريد إلِكتُرونيّ صَالِح";
+"account_error_email_wrong_title" = "عُنوانُ بَريدٍ إلِكتُرونيّ غَيرَ صَالِح";
+"account_error_matrix_session_is_not_opened" = "جَلسَةُ Matrix غَيرَ مَفتُوحَة";
+"account_error_display_name_change_failed" = "فَشَلَ تَغيِيرُ الاِسم الظّاهِر";
+"account_error_picture_change_failed" = "فَشَلَ تَغيِيرُ الصُّورَة";
+"account_msisdn_validation_error" = "يَتَعَذَّرُ التَّحَقُّق مِن رَقمِ الهَاتِف.";
+"account_msisdn_validation_message" = "لَقَد أرسَلنا رِسَالَة SMS تَحوِي رَمزًا لِلتَفعِيل. يُرجَى إدخَالُ هَذَا الرَّمز أَدناه.";
+"account_msisdn_validation_title" = "قَيدُ التَّحَقُّق";
+"account_email_validation_title" = "قَيدُ التَّحَقُّق";
+"account_email_validation_error" = "يَتَعَذَّر التَّحَقُّق مِن عُنوان البَريد إلِكتُرونيّ. يُرجَى الاِطِّلاع عَلَى البَريد إلِكتُرونيّ الخاصّ بِك ثُمَّ النَّقر عَلَى الرَّابِط الّذي يَحوِيه. بِمُجَرَّد الاِنتِهاء مِن ذَلِك، اُنقُر عَلَى الاِستِمرار";
+"account_email_validation_message" = "يُرجَى الاِطِّلاعُ عَلَى البَريدِ إلِكتُرونيّ الخاصِّ بِك ثُمَّ النَّقرَ عَلَى الرَّابِط الَّذي يَحوِيه. بِمُجَرَّدِ الاِنتِهاءُ مِن ذَلِك، اُنقُر عَلَى الاِستِمرار.";
+"account_linked_emails" = "عَناوينُ البَريدِ الإلِكتُرونيّ المُرتَبِطَة";
+"account_link_email" = "رَبطُ بَريدٍ إلِكتُرونيّ";
+
+// Account
+"account_save_changes" = "حِفظُ التَّغَيُّرات";
+"room_event_encryption_verify_ok" = "تَأكِيدُ التَّحَقُّق";
+"room_event_encryption_verify_message" = "لِلتَحَقُّق مِن إِمكانيَّة الوُثُوق بِهَذِه الجَلسَة، يُرجَى التَّوَاصُل مَعَ المَالِك بِاِستِخدام بَعض الوَسَائِل الأُخرَى (عَلَى سَبِيلِ المِثَال شَخصيًّا أَو عَن طَرِيق مُكَالَمَة هَاتِفيَّة) وَاِسأَلهُ عَمَّا إِذَا كَانَ المِفتَاح الَّذِي يَرَاه فِي إعدادَات المُستَخدِم لِهَذِهِ الجَلسَة يَتَطَابَقُ مَعَ المِفتَاح أَدناه:\n\nاِسم الجَلسَة: %@\nمُعَرِّف الجَلسَة: %@\nمِفتَاح الجَلسَة: %@\n\nإِذَا تَطَابق، اِضغَط عَلَى زِرِّ التَّحَقُّق أَدناه. إِذَا لَم يَحدُث ذَلِك، فَهَذَا يَعنِي أَنَّ شَخصًا آخَر يَعتَرَضُ هَذِهِ الجَلسَة وَرُبَّما تُوَدُّ الضَّغطَ عَلَى زِرُّ الإضافَةِ لِلقَائِمَة السَّوداء بَدَلَا مِن ذَلِك.\n\n سَوفَ تَكُونُ عَمَلِيَّةُ التَّحَقُّقِ هَذِهِ أَكثَرُ تَطَوُّرًا في المُستَقبَل.";
+"room_event_encryption_verify_title" = "التَّحَقُّقُ مِنَ الجَلسَة\n\n";
+"room_event_encryption_info_unblock" = "الإزالَة مِنَ القائِمَةِ السَّوداء";
+"room_event_encryption_info_block" = "الإضافَةُ إلى القائِمَةِ السَّوداء";
+"room_event_encryption_info_unverify" = "إلغَاءُ التَّحَقُّق";
+"room_event_encryption_info_verify" = "يَجري التَّحَقُّق...";
+"room_event_encryption_info_device_blocked" = "ضِمنُ القائِمَةِ السَّوداء";
+"room_event_encryption_info_device_not_verified" = "غَيرُ مُتَحَقَّقٍ مِنه";
+"room_event_encryption_info_device_verified" = "مُتَحَقَّقٌ مِنه";
+"room_event_encryption_info_device_fingerprint" = "بَصمَة Ed25519\n";
+"room_event_encryption_info_device_verification" = "التَّحَقُّق\n";
+"room_event_encryption_info_device_id" = "المُعَرِّف\n";
+"room_event_encryption_info_device_name" = "الاِسمُ العَامّ\n";
+"room_event_encryption_info_device_unknown" = "جَلسَةٌ غَيرَ مَعرُوفة\n";
+"room_event_encryption_info_device" = "\nمَعلُومَاتُ جَلسَةِ المُرسِل\n";
+"room_event_encryption_info_event_none" = "لَا شَيء";
+"room_event_encryption_info_event_unencrypted" = "غَيرُ مُعَمَى";
+"room_event_encryption_info_event_decryption_error" = "خَطَأٌ فِي فَكِّ التَّعميَة\n";
+"room_event_encryption_info_event_session_id" = "مُعَرِّفُ الجَلسَة\n";
+"room_event_encryption_info_event_algorithm" = "خَوارِزميَّة\n";
+"room_event_encryption_info_event_fingerprint_key" = "مُطَالَبَةُ مِفتَاحِ بَصمَة Ed25519\n";
+"room_event_encryption_info_event_identity_key" = "مِفتَاحُ هُويَّة Curve25519\n";
+"room_event_encryption_info_event_user_id" = "مُعَرِّفُ المُستَخدِم\n";
+"room_event_encryption_info_event" = "مَعلُومَاتُ الحَدَث\n";
+
+// Encryption information
+"room_event_encryption_info_title" = "مَعلُومَاتُ تَعمِيَةِ النِّهايَة-إلى-النِّهايَة\n\n";
+"device_details_delete_prompt_message" = "هَذِهِ العَمَلِيَّة تَتَطَلَّبُ مُصادَقةً إضافيَة.\nللاِستِمرار، يُرجَى إدخَالُ كَلِمَةُ المُرُورِ الخاصَّةِ بِك.";
+"device_details_delete_prompt_title" = "المُصادَقَة";
+"device_details_rename_prompt_message" = "إنَّ اِسمَ الجَلسَةِ العَامّ مَرئيٌّ لِلأشخَاصِ الَّذِينَ تَتَواصَلُ مَعَهُم";
+"device_details_rename_prompt_title" = "اِسمُ الجَلسَة";
+"device_details_last_seen_format" = "%@ @ %@\n";
+"device_details_last_seen" = "آخِرُ ظُهور\n";
+"device_details_identifier" = "المُعَرِّف\n";
+"device_details_name" = "الاِسمُ العَامّ\n";
+
+// Devices
+"device_details_title" = "مَعلُومَاتُ الجَلسَة\n";
+"notification_settings_room_rule_title" = "الغُرفَة: '%@'";
+"settings_enter_validation_token_for" = "أدخِل رَمزَ المُصادَقَة لِـ %@:";
+"settings_enable_push_notifications" = "تَفعِيلُ دَفعِ الإِشعَارات";
+"settings_enable_inapp_notifications" = "تَفعِيلُ الإِشعَاراتِ دَاخِلَ التَّطبِيق";
+
+// Settings
+"settings" = "الإعدَادَات";
+"room_displayname_more_than_two_members" = "العُضو %@ وَعَدَد %@ آخَرُون";
+"room_displayname_two_members" = "العُضو %@ وَ %@";
+
+// room display name
+"room_displayname_empty_room" = "غُرفَةٌ فَارِغَة";
+"notice_in_reply_to" = "رَدًّا عَلَى";
+"notice_sticker" = "مُلصَق";
+"notice_crypto_error_unknown_inbound_session_id" = "إنَّ جَلسَةَ المُرسِل لَم تُرسِل إلَينا المَفاتيح لِهَذِهِ الرِّسَالَة.";
+"notice_crypto_unable_to_decrypt" = "** يَتَعَذَّرُ فَكَّ التَّعميَة: %@ **";
+"notice_room_history_visible_to_members_from_joined_point_for_dm" = "لَقَد جَعَلَ %@ الرَّسائِلَ المُستَقبَليَّة مَرئيَّةٌ لِلجَميع، مُنذُ أنِ اِنضَمُّوا.";
+"notice_room_history_visible_to_members_from_joined_point" = "لَقَد جَعَلَ %@ تَأريخَ الغُرفَةِ المُستَقبَليّ مَرئيٌّ لِجَميعِ أعضاءِ الغُرفَة، مِنَ النُّقطَةِ الَّتي اِنضَمُّوا فِيهَا.";
+"notice_room_history_visible_to_members_from_invited_point_for_dm" = "لَقَد جَعَلَ %@ الرَّسائِلَ المُستَقبَليَّة مَرئيَّةٌ لِلجَميع، مُنذُ أن تَمَّت دَعوَتُهُم.";
+"notice_room_history_visible_to_members_from_invited_point" = "لَقَد جَعَلَ %@ تَأريخَ الغُرفَةِ المُستَقبَليّ مَرئيٌّ لِجَميعِ أعضاءِ الغُرفَة، مِنَ النُّقطَةِ الَّتي تَمَّت دَعوَتُهُم فِيهَا.";
+"notice_room_history_visible_to_members_for_dm" = "لَقَد جَعَلَ %@ الرَّسائِلَ المُستَقبَليَّة مَرئيَّةٌ لِجَميعِ أعضاءِ الغُرفَة.";
+"notice_room_history_visible_to_members" = "لَقَد جَعَلَ %@ تَأريخَ الغُرفَةِ المُستَقبَليّ مَرئيٌّ لِجَميعِ أعضاءِ الغُرفَة.";
+"notice_room_history_visible_to_anyone" = "لَقَد جَعَلَ %@ تَأريخَ الغُرفَةِ المُستَقبَليّ مَرئيٌّ لِأَيّ شَخص.";
+"notice_error_unknown_event_type" = "حَدثٌ غَيرُ مَعرُوفِ النَّوع";
+"notice_error_unexpected_event" = "حَدَثٌ غَيرُ مُتَوَقَّع";
+"notice_error_unsupported_event" = "حَدَثٌ غَيرُ مَدعُوم";
+"notice_redaction" = "لَقَد نَقَّحَ %@ حَدَث (المُعَرِّف: %@)";
+"notice_feedback" = "حَدَثُ اِنطِباع (المُعَرِّف: %@): %@";
+"notice_unsupported_attachment" = "مُرفَقٌ غَيرُ مَدعُوم: %@";
+"notice_invalid_attachment" = "مُرفَقٌ غَيرُ صَالِح";
+"notice_file_attachment" = "مُرفَقُ مَلَفّ";
+"notice_location_attachment" = "مُرفَقُ مَوقِعٍ جُغرَافِيّ";
+"notice_video_attachment" = "مُرفَق مَقطَع مَرئي";
+"notice_audio_attachment" = "مُرفَق صَوت";
+"notice_image_attachment" = "مُرفَق صُّورَة";
+"notice_encryption_enabled_unknown_algorithm" = "لَقَد شَغَّلَ %1$@ تَعميَة النِّهايَة-إلى-النِّهايَة (خَوارِزميَّة غَير مُتَعَرَّف عَليها %2$@).";
+"notice_encryption_enabled_ok" = "لَقَد شَغَّلَ %@ تَعميَة النِّهايَة-إلى-النِّهايَة.";
+"power_level" = "مُستَوى القُوَّة";
+"public" = "عَامّ";
+"private" = "خاصّ";
+"default" = "الاِفتراضي";
+"not_supported_yet" = "غَيرُ مَدعُومَةٍ حَتَّى الآن";
+"error_common_message" = "لَقَد حَدَثَ خَطَأ. يُرجَى المُحاوَلَة مَرَّةً أُخرَى.";
+"error" = "خَطَأ";
+"unsent" = "غَيرُ مُرسَلَة";
+"offline" = "غَيرُ مُتَّصِل";
+
+// Others
+"user_id_title" = "مُعَرِّف المُستَخدِم:";
+"e2e_passphrase_not_match" = "عِبارَاتُ المُرُورِ يَجِبُ أن تَكُونَ مُتَطابَقة";
+"e2e_passphrase_create" = "إنشاءُ عِبارَةِ مُرُور";
+"e2e_passphrase_empty" = "عِبارَةُ المُرُورِ يَجِبُ ألَا تَكونَ خَالِيَة";
+"e2e_passphrase_confirm" = "تَأكيدُ عِبارَةِ المُرُور";
+"e2e_export_prompt" = "تُتيحُ لَكَ هَذِهِ العَمَلِيَّة تَصدير مَفاتيح الرَّسائِل الَّتي قَد تَلَقَّيتَها فِي الغُرَف المُعَمّاة إلى مَلَفّ مَحَلِّيّ. يُمكِنُكَ بَعدَ ذَلِكَ أن تَستَورِدَ المَلَفّ إلى عَميل Matrix آخر في المُستَقبَل، لِكَي يَتَمَكَّن هَذَا العَميل أيضًا مِن فَكّ تَعمِيَة هَذِهِ الرَّسائِل.\nالمَلَفّ الَّذِي قَد تَمَّ تَصديرُه سَيَسمَحُ لِأيّ شَخص يُمكِنَهُ قِرَاءته أن يَفُكّ تَعمِيَة أيّ رَسائِل مُعمّاة يُمكِنُكَ رُؤيَتها، لِذَلِك يَجِبُ أن تَكُونَ حَريصًا عَلَى إبقائه آمِن.";
+"e2e_export" = "تَصدير";
+
+// E2E export
+"e2e_export_room_keys" = "تَصديرُ مَفاتيحِ الغُرفَة";
+"e2e_passphrase_enter" = "أدخِل عِبارَةَ المُرُور";
+"e2e_import" = "اِستيراد";
+"e2e_import_prompt" = "تُتِيحُ لَكَ هَذِهِ العَمَلِيَّة اِستيراد المَفاتيح الَّتي قَد صَدَّرتَهَا مُسبقًا مِن عَميل Matrix آخر. أنتَ سَتَتَمَكَّن بَعدَ ذَلِك مِن فَكّ تَّعميَة جَميع رَسائِل الَّتي يُمكِن لِلعَميل الآخر فَكّ تَعمِيَتها.\nإنَّ المَلَفّ الْمُصَدَّر مَحميّ بِعِبارَة مُرُور. يَجِبُ إدخال عِبارَة المُرُور هُنا لِفَكّ تَعمِيَة المَلَفّ.";
+
+// E2E import
+"e2e_import_room_keys" = "اِستيرادُ مَفاتيحِ الغُرفَة";
+"format_time_d" = "ي";
+"format_time_h" = "س";
+"format_time_m" = "د";
+
+// Time
+"format_time_s" = "ث";
+"search_searching" = "يَجري البَحث...";
+
+// Search
+"search_no_results" = "لَا تُوجَدُ نَتائِج";
+"group_section" = "المَجمُوعات";
+
+// Groups
+"group_invite_section" = "الدَّعَوات";
+"contact_local_contacts" = "جِهاتُ الاِتِّصالِ المَحَلِّيَّة";
+
+// Contacts
+"contact_mx_users" = "مُستَخدِمُو Matrix";
+"attachment_e2e_keys_import" = "يَجري الاِستيراد...";
+"attachment_e2e_keys_file_prompt" = "يَحتَوي هَذَا المَلَفّ عَلَى مَفاتيح تَعمِيَة قَد تمَّ تَصديرُها مِن عَميلِ Matrix.\nهَل تُريدُ عَرضَ مُحتَوى المَلفّ أمِ اِستيرادُ المَفاتيحِ الَّتي يَحويها؟";
+
+// Settings screen
+"settings_title_config" = "الإعداد";
+"notice_room_third_party_registered_invite_by_you" = "أنتَ قَد قَبلتَ دَعوَة %@";
+"notice_room_third_party_revoked_invite" = "لَقَد ألغَى %@ دَعوَة %@ لِلاِنضِمام إلى الغُرفَة";
+"notice_room_third_party_registered_invite" = "لَقَد قَبَلَ %@ دَعوَةَ %@";
+"ssl_only_accept" = "فَقَطّ اِقبَل الشَّهادَة إذا نَشَرَ مُدير الخادِم بَصمَة تُطابِق البَصمَة أعلَاه.";
+"ssl_expected_existing_expl" = "لَقَد تَغَيَّرَت الشَّهادَة الَّتي قَد كانَت مَوثُوقَة مُسبَقًا إلى أُخرَى غَير مَوثُوقَة. مِنَ المُمكِن أنَّ الخادِم قَد جَدَّدَ شَهادَته. تَواصَل مَعَ المُدير لِلحُصُول عَلَى البَصمَة المُتَوَقَّعَة.";
+"ssl_unexpected_existing_expl" = "تَمَّ تَغيِير الشَّهادَة الَّتي قَد كانَت مَوثُوقة مِن قِبَل هاتِفك. هَذَا غَيرُ طَبِيعِيّ لِلغَايَة. يُوصَى بِعَدَم قُبُول هَذِهِ الشَّهادَة الجَديدَة.";
+"ssl_cert_new_account_expl" = "إذَا قَالَ مُديرُ الخادِم أنَّ هَذَا مُتَوَقَّع، فَتَأكَّد مِن تَطابُق البَصمَة أدناه مَعَ البَصمَة الَّتي قَد قَدَّمَها.";
+"ssl_cert_not_trust" = "هَذَا قَد يَعني أنَّ شَخصًا ما يَعتَرِضُ حَرَكَة المُرُور الخَاصَّةِ بِك بِخُبث، أو أنَّ هَاتِفَكَ لا يَثِقُ في الشَّهادَة المُقَدَّمَة مِنَ الخادِمِ البَعيد.";
+"notification_settings_per_word_info" = "تُطابَق الكَلِمات مَعَ حالَة الأحرُف بِشَكل غَير حَساس، وَقَد تَتَضَمَّن حَرف البَدَل *. وَبِالتَّالي:\nإنَّ foo تَتَطابَق مَعَ السِّلسِلَة foo المُحاطَة بِمُحَدِّدِات الكَلِمَة (مِثل عَلامات التَّرقيم وَالمَسافَة البَيضاء أو بِدايَة/نِهايَة السَّطر).\nإنَّ *foo تَتَطابَق مَعَ أي كَلِمَة تَبدَأُ بِـ foo.\nإنَّ *foo* تَتَطابَق مع أي كَلِمَة مِن هَذَا القَبيل وَتَتَضَمَّن الثَّلاث أحرُف foo.";
+"notification_settings_global_info" = "تَُحفَظُ إعدادات الإشعَارات في حِسابِ المُستَخدِم الخاصِّ بِك وَتَتِمُّ مُشارَكَتَها بَينَ جَمِيعِ العُمَلاء الَّذينَ يَدعَمُونَها (بِما في ذَلِكَ إشعَاراتُ سَطحِ المَكتَب).\n\nيَتِمُّ تَطبيق القَواعِد بِالتَرتيب؛ أوَّل قاعِدَة تَتَطَابَق تُحَدِّد النَّتيجَة لِلرِسالَة.\nإذًا: الإِشعَارات وَفقًا لِلكَلِمَة أكثَرُ أولَويَّة مِنَ الإِشعَارات وَفقًا لِلغُرفَة الَّتِي أيضًا أكثَرُ أولَويَّة مِنَ الإِشعَارات وَفقًا لِلمُرسِل.\nبِالنِّسبَةِ لِلقَواعِد المُتَعَدِّدَة مِن نَفسِ النَّوع، تَكُونُ الأَولَويَّة لِأوَّل قاعِدَة تَتَطابَق في القائِمَة.";
+"login_error_login_email_not_yet" = "لَم يُنقَر عَلَى الرَّابِط الَّذي في البَريد الإلِكتُرونيّ حَتَّى الآن";
+"ssl_could_not_verify" = "تَعَذَّرَ التَّحَقُّق مِن هُوِيَّة الخَادِم البَعيد.";
+"ssl_fingerprint_hash" = "بَصمَة (%@):";
+"ssl_remain_offline" = "تَجاهُل";
+"ssl_logout_account" = "تَسجِيلُ الخُرُوج";
+
+// unrecognized SSL certificate
+"ssl_trust" = "الوُثُوق";
+"call_invite_expired" = "لَقَد اِنتَهَت صَلاحيَّةُ دَعوَة المُكالَمَة";
+"incoming_voice_call" = "مُكالَمَةٌ صَوتيَّةٌ وَارِدَة";
+"incoming_video_call" = "مُكالَمَةٌ مَرئيَّةٌ وَارِدَة";
+"call_ring" = "تَجري المُكالَمَة…";
+"call_ended" = "اِنتَهَت المُكالَمَة";
+"call_connecting" = "الاِتِّصالُ جارٍ…";
+
+// Settings keys
+
+// call string
+"call_waiting" = "يَجري الاِنتِظار…";
+"settings_config_user_id" = "مُعَرِّفُ المُستَخدِم: %@";
+"settings_config_identity_server" = "خادِم الهُويَّة: %@";
+
+// gcm section
+"settings_config_home_server" = "الخادِم الرَّئيس: %@";
+"notification_settings_notify_all_other" = "أخطِرني لِكَافَّةِ الرَّسائِل/الغُرَف الأُخرَى";
+"notification_settings_by_default" = "بِشَكلٍ اِفتِراضي...";
+"notification_settings_suppress_from_bots" = "كَتْمُ الإشعَارات مِنَ الرُّوبُوتَات";
+"notification_settings_receive_a_call" = "أخطِرني عِندَما أَتَلَقَّى مُكالَمَة";
+"notification_settings_people_join_leave_rooms" = "أخطِرني عِندَما يَنضَمّ الأشخاص أو يُغادِرُوا الغُرَف";
+"notification_settings_invite_to_a_new_room" = "أخطِرني عِندَما أُدعَى إلى غُرفَة جَديدَة";
+"notification_settings_just_sent_to_me" = "أخطِرني بِالصَّوت حَولَ الرَّسَائِل الَّتي أُرسِلَت إليَّ فَقَط";
+"notification_settings_contain_my_display_name" = "أخطِرني بِالصَّوت حَولَ الرَّسَائِل الَّتي تَحتَوي عَلَى الاِسم الظّاهِر الخاصّ بِي";
+"notification_settings_contain_my_user_name" = "أخطِرني بِالصَّوت حَولَ الرَّسَائِل الَّتي تَحتَوي عَلَى اِسم المُستَخدِم الخاصّ بِي";
+"notification_settings_other_alerts" = "تَنبيهاتٌ أُخرَى";
+"notification_settings_select_room" = "حَّدِد غُرفَة";
+"notification_settings_sender_hint" = "user:domain.com@";
+"notification_settings_per_sender_notifications" = "الإِشعَاراتٌ وَفقًا لِلمُرسِل";
+"notification_settings_per_room_notifications" = "الإِشعَاراتُ وَفقًا لِلغُرفَة";
+"notification_settings_custom_sound" = "صَوتٌ مُخَصَّص";
+"notification_settings_highlight" = "إبرَاز";
+"notification_settings_word_to_match" = "كَلِمَةٌ لِلمُطابَقَة";
+"notification_settings_never_notify" = "لا تُخطِرني أبَدًا";
+"notification_settings_always_notify" = "أخطِرني دَومًا";
+"notification_settings_per_word_notifications" = "الإِشعَاراتُ وَفقًا لِلكَلِمَة";
+"notification_settings_enable_notifications_warning" = "جَميعُ الإِشعَاراتِ مُعَطَّلَةٌ حالِيًّا لِكُلِ الأجهِزَة.";
+"notification_settings_enable_notifications" = "تَفعِيلُ الإِشعَارات";
+
+// Notification settings screen
+"notification_settings_disable_all" = "تَعطيلُ كَافَّةِ الإِشعَارات";
+"settings_title_notifications" = "الإِشعَارات";
+
+// contacts list screen
+"invitation_message" = "أوَدُّ أن أتَحَدَّثَ مَعكَ بِاستِخدامِ matrix. يرُجَى زيارَةُ المَوقِع https://matrix.org لِلحُصُولِ عَلَى مَزيدٍ مِنَ المَعلُومات.";
+
+// members list Screen
+
+// accounts list Screen
+
+// image size selection
+
+// invitation members list Screen
+
+// room creation dialog Screen
+
+// room info dialog Screen
+
+// room details dialog screen
+"room_details_title" = "تَفاصيلُ الغُرفَة";
+"login_error_must_start_http" = "عُنوانُ URL يَجِبُ أن يَبدَأ بِـ //:[s]http";
+
+// Login Screen
+"login_error_already_logged_in" = "مُسَجِّل الدُّخُولِ بِالفِعل";
+"message_unsaved_changes" = "تُوجَدُ تَغيِيرات غَير مَحفُوظَة. المُغادَرَة سَوفَ تَستَبعِدَها.";
+"unban" = "رَفع-الحَظْر";
+"ban" = "حَظْر";
+"kick" = "طَرد";
+"invite" = "اُدعُ";
+"num_members_other" = "عَدَد %@ مُستَخدِم";
+"num_members_one" = "عَدَد %@ مُستَخدِم";
+"membership_ban" = "حَظْر";
+"membership_leave" = "غادَر";
+"membership_invite" = "مَدعُوّ";
+"create_account" = "إنشاءُ حِساب";
+"login" = "تَسجيلُ الدُّخُول";
+"create_room" = "إنشاءُ غُرفَة";
+
+// actions
+"action_logout" = "تَسجِيلُ الخُرُوج";
+"view" = "الاِطِّلاع";
+"delete" = "حَذف";
+"share" = "مُشارَكَة";
+"redact" = "إزالَة";
+"resend" = "إعادَة الإرسَال";
+"copy_button_name" = "نَسخ";
+"send" = "إرسَال";
+"leave" = "المُغادَرَة";
+"save" = "حِفظ";
+"cancel" = "إلغَاء";
+
+// Room Screen
+
+// general errors
+
+// Home Screen
+
+// Last seen time
+
+// call events
+
+/* -*-
+ Automatic localization for en
+
+ The following key/value pairs were extracted from the android i18n file:
+ /console/src/main/res/values/strings.xml.
+*/
+
+
+// titles
+
+// button names
+"ok" = "حَسَنًا";
+"notice_room_history_visible_to_members_from_joined_point_by_you_for_dm" = "أنتَ قَد جَعَلتَ الرَّسائِلَ المُستَقبَليَّة مَرئيَّة لِلجَميع، مُنذُ أنِ اِنضَمُّوا.";
+"notice_room_history_visible_to_members_from_joined_point_by_you" = "أنتَ قَد جَعَلتَ تَأريخَ الغُرفَةِ المُستَقبَليّ مَرئيٌّ لِجَميعِ أعضاءِ الغُرفَة، مِن النُّقطَة الَّتي اِنضَمُّوا فِيهَا.";
+"notice_room_history_visible_to_members_from_invited_point_by_you_for_dm" = "أنتَ قَد جَعَلتَ الرَّسائِلَ المُستَقبَليَّة مَرئيَّة لِلجَميع، مُنذُ أن تَمَّت دَعوَتُهُم.";
+"notice_room_history_visible_to_members_from_invited_point_by_you" = "أنتَ قَد جَعَلتَ تَأريخ الغُرفَة المُستَقبَليّ مَرئيٌّ لِجَميعِ أعضاءِ الغُرفَة، مِنَ النُّقطَةِ الَّتي تَمَّت دَعوَتُهُم فِيهَا.";
+"notice_room_history_visible_to_members_by_you_for_dm" = "أنتَ قَد جَعَلتَ الرَّسائِل المُستَقبَليَّة مَرئيَّة لِجَميع أعضاء الغُرفَة.";
+"notice_room_history_visible_to_members_by_you" = "أنتَ قَد جَعَلتَ تَأريخَ الغُرفَةِ المُستَقبَليّ مَرئيٌّ لِجَميعِ أعضاءِ الغُرفَة.";
+"notice_room_history_visible_to_anyone_by_you" = "أنتَ قَد جَعَلتَ تَأريخ الغُرفَة المُستَقبَليّ مَرئي لِأَيّ شَخص.";
+"notice_redaction_by_you" = "أنتَ قَد نَقَّحتَ حَدَث (المُعَرِّف: %@))";
+"notice_encryption_enabled_unknown_algorithm_by_you" = "أنتَ قَد شَغَّلتَ تَعمِيَة النِّهايَة-إلى-النِّهايَة. (خَوارِزميَّة غَير مُتَعَرَّف عَليها %@).";
+"notice_encryption_enabled_ok_by_you" = "أنتَ قَد فعَّلتَ تَعمِيَة النِّهايَة-إلى-النِّهايَة.";
+"notice_room_created_by_you_for_dm" = "أنتَ قَد اِنضَمَمت.";
+"notice_room_created_by_you" = "أنتَ قَد أنشأتَ الغُرفَة وَهَيَّأَتَها.";
+"notice_profile_change_redacted_by_you" = "أنتَ قَد حدَّثتَ مَلفَّكَ الشَّخصي %@";
+"notice_event_redacted_by_you" = " بِواسِطَتِك";
+"notice_room_topic_removed_by_you" = "أنتَ قَد أزلتَ المَوضُوع";
+"notice_room_name_removed_by_you_for_dm" = "أنتَ قَد أزَلتَ الاِسم";
+"notice_room_name_removed_by_you" = "أنتَ قَد أزَلتَ اِسم الغُرفَة";
+"notice_conference_call_request_by_you" = "أنتَ قَد طَلبتَ عَقد اِجتِمَاع VoIP";
+"notice_ended_video_call_by_you" = "أنتَ قَد أنهيتَ المُكالَمَة";
+"notice_answered_video_call_by_you" = "أنتَ قَد أجَبتَ عَلَى المُكالَمَة";
+"notice_placed_video_call_by_you" = "أنتَ قَد أجرَيتَ مُكالَمَةً مَرئيَّة";
+"notice_placed_voice_call_by_you" = "أنتَ قَد أجرَيتَ مُكالَمَةً صَوتيَّة";
+"notice_room_name_changed_by_you_for_dm" = "أنتَ قَد غيَّرتَ الاِسم إلى %@.";
+"notice_room_name_changed_by_you" = "أنتَ قَد غيَّرتَ اِسمَ الغُرفَةِ إلى %@.";
+"notice_topic_changed_by_you" = "أنتَ قَد غيَّرتَ المَوضُوع إلى \"%@\".";
+"notice_display_name_removed_by_you" = "أنتَ قَد أزَلتَ اِسمكَ الظّاهِر";
+"notice_display_name_changed_from_by_you" = "أنتَ قَد غيَّرتَ اِسمكَ الظّاهِر مِن %@ إلى %@";
+"notice_display_name_set_by_you" = "أنتَ قَد عيَّنتَ اِسمكَ الظّاهِر إلى %@";
+"notice_avatar_url_changed_by_you" = "أنتَ قَد غيَّرتَ صُّورَتكَ الرَّمزية";
+"notice_room_withdraw_by_you" = "أنتَ قَد سَحبتَ دَعوَة %@";
+"notice_room_ban_by_you" = "أنتَ قَد حَظرتَ %@";
+"notice_room_unban_by_you" = "أنتَ قَد رَفعتَ الحَظرَ عَن %@";
+"notice_room_kick_by_you" = "أنتَ قَد طَردتَ %@";
+"notice_room_reject_by_you" = "أنتَ قَد رَفضتَ الدَعوَة";
+"notice_room_join_by_you" = "أنتَ قَد اِنضَمَمت";
+"notice_room_leave_by_you" = "أنتَ قَد غادَرت";
+"notice_room_third_party_revoked_invite_by_you_for_dm" = "أنتَ قد ألغيتَ دَعوَة %@";
+"notice_room_third_party_revoked_invite_by_you" = "أنتَ قَد ألغيتَ دَعوَة %@ لِلاِنضِمام إلى الغُرفَة";
+"notice_room_third_party_invite_by_you_for_dm" = "أنتَ قَد دَعوتَ %@";
+"notice_room_third_party_invite_by_you" = "أنتَ قَد أرسَلتَ دَعوَةً إلى %@ لِلاِنضِمامِ إلى الغُرفَة";
+"notice_room_invite_you" = "لَقَد دَعاكَ %@";
+
+// Notice Events with "You"
+"notice_room_invite_by_you" = "أنتَ قد دَعوتَ %@";
+"notice_conference_call_finished" = "لَقَد اِنتَهَى اِجتِمَاع VoIP";
+"notice_conference_call_started" = "لَقَد بَدأ اِجتِمَاع VoIP";
+"notice_conference_call_request" = "لَقَد طَلَبَ %@ عَقد اِجتِمَاع VoIP";
+"notice_ended_video_call" = "لَقَد أنهَى %@ المُكالَمَة";
+"notice_answered_video_call" = "لَقَد أجابَ %@ عَلَى المُكالَمَة";
+"notice_placed_video_call" = "لَقَد أجرَى %@ مُكالَمَة مَرئيَّة";
+"notice_placed_voice_call" = "لَقَد أجرَى %@ مُكالَمَة صَوتيَّة";
+"notice_room_name_changed_for_dm" = "لَقَد غيَّرَ %@ الاِسم إلى %@.";
+"notice_room_name_changed" = "لَقَد غيَّرَ %@ اِسم الغُرفَة إلى %@.";
+"notice_topic_changed" = "لَقَد غيَّرَ %@ المَوضُوع إلى \"%@\".";
+"notice_display_name_removed" = "لَقَد أزالَ %@ اِسمهُ الظّاهِر";
+"notice_display_name_changed_from" = "لَقَد غيَّرَ %@ اِسمُهُ الظّاهِر مِن %@ إلى %@";
+"notice_display_name_set" = "لَقَد عيَّنَ %@ اِسمُهُ الظّاهِر إلى %@";
+"notice_avatar_url_changed" = "لَقَد غيَّرَ %@ صُّورَتَهُ الرَّمزية";
+"notice_room_reason" = ". السَّبَب: %@";
+"notice_room_withdraw" = "لَقَد سَحَبَ %@ دَعوَة %@";
+"notice_room_ban" = "إنَّ %@ قَد حَظَرَ %@";
+"notice_room_unban" = "لَقَد رَفَعَ %@ الحَظرَ عَن %@";
+"notice_room_kick" = "إنَّ %@ قَد طَرَدَ %@";
+"notice_room_reject" = "لَقَد رَفَضَ %@ الدَّعوَة";
+"notice_room_leave" = "لَقَد غَادَرَ %@";
+"notice_room_third_party_invite" = "لَقَد أرسَلَ %@ دَعوَةً إلى %@ لِلاِنضِمامِ إلى الغُرفَة";
+"notice_room_join" = "لَقَد اِنضَمَّ %@";
+"notice_room_third_party_revoked_invite_for_dm" = "لَقَد ألغَى %@ دَعوَة %@";
+"notice_room_third_party_invite_for_dm" = "إنَّ %@ قَد أضافَ %@";
+
+/* -*-
+ Automatic localization for en
+
+ The following key/value pairs were extracted from the android i18n file:
+ /matrix-sdk/src/main/res/values/strings.xml.
+*/
+
+"notice_room_invite" = "إنَّ %@ قَد أضافَ %@";
+"language_picker_default_language" = "الاِفتراضي (%@)";
+
+// Language picker
+"language_picker_title" = "اِختَر لُغَةً";
+
+// Country picker
+"country_picker_title" = "اِختَر بَلَدًا";
+"local_contacts_access_discovery_warning" = "لاِكتِشاف جِهات الاِتِّصال الَّتي تَستَخدِمُ Matrix بِالفِعل، يُمكِنُ لِـ%@ إرسَال عَناوين البَريد الإلِكتُرونيّ وأرقام الهَواتِف الَّتي في دَفتَرِ العَناوين الخاصِّ بِك إلى خادِمِ هُويَّة Matrix المُختار. يَتِّمُ تَجزِئة البَياناتِ الشَّخصيَّة قَبلَ إرسالِها حَيثُما كانَت مَدعُومَة - يُرجى مُراجَعَة سياسَة الخُصُوصيَّة الخاصَّة بِخادِم الهُويَّة لِلحُصُولِ عَلَى المَزيدِ مِنَ التَّفاصيل.";
+"local_contacts_access_discovery_warning_title" = "اِكتِشافُ المُستَخدِمين";
+"local_contacts_access_not_granted" = "اِكتِشافُ المُستَخدِمين مِن جِهاتِ الاِتِّصالِ المَحَلِّيَّة يَتَطَلَّب الوُصُول إلَى جِهاتِ الاِتِّصالِ الخاصّةِ بِك لَكِنّ %@ لَيسَ لَدَيهِ الاِذن لاِستِخدامَها";
+"microphone_access_not_granted_for_call" = "المُكالَمَاتُ المَرئيَّة تَتَطَلَّب الوُصُول إلَى المِيكرُوفُون لَكِنّ %@ لَيسَ لَدَيهِ الاِذن لاِستِخدامه";
+
+// Permissions
+"camera_access_not_granted_for_call" = "المُكالَمَاتُ المَرئيَّة تَتَطَلَّب الوُصُول إلَى الكاميرة لَكِنّ %@ لَيسَ لَدَيهِ الاِذن لاِستِخدامها";
+"ssl_homeserver_url" = "عُنوانُ URL لِلخادِم الرَّئيس: %@";
+"user_id_placeholder" = "مِثال: bob:homeserver@";
+"network_error_not_reachable" = "يُرجَى التَّحَقُّق مِن اِتِّصالَكَ بِالشَبَكَة";
+"call_more_actions_dialpad" = "لَوحَةُ الاِتِّصَال";
+"call_more_actions_transfer" = "النَّقل";
+"call_more_actions_audio_use_device" = "مُكَبِّر صَوت الجِّهَاز";
+"call_more_actions_audio_use_headset" = "اِستِخدامُ صَوتَ سمَّاعَةِ الرَّأس";
+"call_more_actions_change_audio_device" = "تَغيِيرُ جِهَازِ الصَوت";
+"call_more_actions_unhold" = "الاِستِئنَاف";
+"call_more_actions_hold" = "التَّمَسُّك";
+"call_remote_holded" = "لقد عَلَّقَ %@ المكالمة";
+"call_holded" = "أنتَ قَد عَلَّقتَ المُكالَمَة";
+"notice_declined_video_call_by_you" = "أنتَ قَد رَفَضتَ المُكالَمَة";
+"notice_declined_video_call" = "لَقَد رَفضَ %@ المُكالَمَة";
+"resume_call" = "اِستِئنَاف";
+"call_transfer_to_user" = "التَّحويلُ إلَى %@";
+"call_consulting_with_user" = "اِستِشارَة مَعَ %@";
+"call_video_with_user" = "مُكالَمَةٌ مَرئيَّةٌ مَع %@";
+"call_voice_with_user" = "مُكالَمَةٌ صَوتيَّةٌ مَع %@";
+"call_ringing" = "الرَّنِينُ جَارٍ…";
+"e2e_passphrase_too_short" = "عِبارَة المُرُور قَصِيرَةٌ جِدًا (يَجِبُ ألَّا يَقِلَّ طُولهَا عَن %d أحرف)";
+"microphone_access_not_granted_for_voice_message" = "الرَسائِلُ الصَوتيَّةُ تتطَلَبُ الوصُولَ إلَى المِيكرُوفُون لَكِنَ %@ ليسَ لَديهِ إذنٌ لِاستِخدَامِه";
+"message_reply_to_sender_sent_a_voice_message" = "أرسَلَ رِسَالَةً صَوتيَّة.";
diff --git a/Riot/Modules/MatrixKit/Assets/MatrixKitAssets.bundle/bg.lproj/MatrixKit.strings b/Riot/Modules/MatrixKit/Assets/MatrixKitAssets.bundle/bg.lproj/MatrixKit.strings
new file mode 100644
index 000000000..0cb86edfd
--- /dev/null
+++ b/Riot/Modules/MatrixKit/Assets/MatrixKitAssets.bundle/bg.lproj/MatrixKit.strings
@@ -0,0 +1,466 @@
+"view" = "Виж";
+"back" = "Назад";
+"continue" = "Продължи";
+"leave" = "Напусни";
+"invite" = "Покани";
+"retry" = "Опитай отново";
+"cancel" = "Отказ";
+"save" = "Запази";
+// room details dialog screen
+"room_details_title" = "Информация";
+"matrix" = "Matrix";
+// Login Screen
+"login_create_account" = "Регистрация:";
+"login_server_url_placeholder" = "URL (напр. https://matrix.org)";
+"login_home_server_title" = "Home сървър адрес:";
+"login_home_server_info" = "Сървърът Ви съхранява цялата кореспонденция и профилни данни";
+"login_identity_server_title" = "Адрес на сървър за самоличност:";
+"login_identity_server_info" = "Matrix предоставя сървъри за самоличност, които проследяват кои имейли и т.н. на кои Matrix идентификатори принадлежат. В момента съществува само https://matrix.org.";
+"login_user_id_placeholder" = "Matrix ID (напр. @ivan:matrix.org или ivan)";
+"login_password_placeholder" = "Парола";
+"login_optional_field" = "по избор";
+"login_display_name_placeholder" = "Име (напр. Иван Георгиев)";
+"login_email_info" = "Задайте имейл адрес, за да позволите на другите потребители да Ви намерят по-лесно в Matrix и да можете да възстановите Вашата парола в бъдеще.";
+"login_email_placeholder" = "Имейл адрес";
+"login_prompt_email_token" = "Моля, въведете кода за потвърждение от имейл адреса:";
+"login_error_title" = "Неуспешно влизане в профила";
+"login_error_no_login_flow" = "Не успяхме да извлечем информация за автентикация от този Home сървър";
+"login_error_do_not_support_login_flows" = "В момента не поддържаме методите за влизане, определени от този Home сървър";
+"login_error_registration_is_not_supported" = "В момента не се поддържа регистрация";
+"login_error_forbidden" = "Невалидно потребителско име или парола";
+"login_error_unknown_token" = "Не е разпознат указаният тоукън за достъп";
+"login_error_bad_json" = "Грешно формиран JSON";
+"login_error_not_json" = "Не съдържаше валиден JSON";
+"login_error_limit_exceeded" = "Бяха изпратени твърде много заявки";
+"login_error_user_in_use" = "Това потребителско име е вече заето";
+"login_error_login_email_not_yet" = "Все още не сте кликнали върху връзката в имейла";
+"login_use_fallback" = "Използване на страница заместител";
+"login_leave_fallback" = "Откажи";
+"login_invalid_param" = "Невалиден параметър";
+"register_error_title" = "Неуспешна регистрация";
+"login_error_forgot_password_is_not_supported" = "В момента не се поддържа \"Забравена парола\"";
+"login_mobile_device" = "Мобилно устройство";
+"login_tablet_device" = "Таблет";
+"login_desktop_device" = "Работен плот";
+// Action
+"no" = "Не";
+"yes" = "Да";
+"abort" = "Прекрати";
+"close" = "Затвори";
+"discard" = "Откажи";
+"dismiss" = "Затвори";
+"sign_up" = "Регистрация";
+"submit" = "Изпрати";
+"submit_code" = "Изпрати код";
+"set_default_power_level" = "Възвръщане нивото на достъп";
+"set_moderator" = "Направи модератор";
+"set_admin" = "Направи администратор";
+"start_chat" = "Започни чат";
+"start_voice_call" = "Започни гласов разговор";
+"start_video_call" = "Започни видео разговор";
+"mention" = "Спомени";
+"select_account" = "Изберете профил";
+"attach_media" = "Прикачи файл от галерията";
+"capture_media" = "Направи снимка/видео";
+"invite_user" = "Покани matrix потребител";
+"resend_message" = "Изпрати съобщението отново";
+"reset_to_default" = "Връщане към ниво по подразбиране";
+"select_all" = "Избери всички";
+"cancel_upload" = "Откажи качването";
+"cancel_download" = "Откажи свалянето";
+"show_details" = "Покажи детайлите";
+"answer_call" = "Отговори на повикването";
+"reject_call" = "Откажи повикването";
+"ignore" = "Игнорирай";
+"unignore" = "Премахни игнорирането";
+// Events formatter
+"notice_avatar_changed_too" = "(аватарът също беше променен)";
+"notice_room_name_removed" = "%@ премахна името на стаята";
+"notice_room_topic_removed" = "%@ премахна темата";
+"notice_event_redacted" = "<изтрито%@>";
+"notice_event_redacted_by" = " от %@";
+"notice_event_redacted_reason" = " [причина: %@]";
+"notice_profile_change_redacted" = "%@ обнови своя профил %@";
+"notice_room_created" = "%@ създаде и конфигурира стаята.";
+"notice_room_join_rule" = "Правилото за присъединяване е: %@";
+"notice_room_power_level_intro" = "Нивата на достъп на членовете в стаята са:";
+"end_call" = "Прекрати разговора";
+"notice_room_power_level_acting_requirement" = "Минималните нива на достъп, които потребител трябва да има за следните действия са:";
+"notice_room_power_level_event_requirement" = "Минималните нива на достъп отнасящи се към събития са:";
+"notice_room_aliases" = "Адресите на стаята са: %@";
+"notice_room_related_groups" = "Групите, асоциирани с тази стая, са: %@";
+"notice_encrypted_message" = "Шифровано съобщение";
+"notice_encryption_enabled" = "%@ включи шифроването от край до край (алгоритъм %@)";
+"notice_image_attachment" = "прикачена снимка";
+"notice_audio_attachment" = "прикачено аудио";
+"notice_video_attachment" = "прикачено видео";
+"notice_location_attachment" = "прикачено местоположение";
+"notice_file_attachment" = "прикачен файл";
+"notice_invalid_attachment" = "невалидно прикачване";
+"notice_unsupported_attachment" = "Прикачен файл, който не се поддържа: %@";
+"notice_feedback" = "Събитие за обратна връзка (id: %@): %@";
+"notice_redaction" = "%@ изтри събитие (id: %@)";
+"notice_error_unsupported_event" = "Събитие, което не се поддържа";
+"notice_error_unexpected_event" = "Неочаквано събитие";
+"notice_error_unknown_event_type" = "Непознат вид на събитието";
+"notice_room_history_visible_to_anyone" = "%@ направи бъдещата история на стаята видима за всеки.";
+"notice_room_history_visible_to_members" = "%@ направи бъдещата история на стаята видима за всички членове.";
+"notice_room_history_visible_to_members_from_invited_point" = "%@ направи бъдещата история на стаята видима за всички членове, от момента на поканването им в нея.";
+"notice_room_history_visible_to_members_from_joined_point" = "%@ направи бъдещата история на стаята видима за всички членове, от момента на присъединяването им в нея.";
+"notice_crypto_unable_to_decrypt" = "** Неуспешно разшифроване: %@ **";
+"notice_crypto_error_unknown_inbound_session_id" = "Сесията на подателя не изпрати ключовете за това съобщение.";
+// room display name
+"room_displayname_empty_room" = "Празна стая";
+"room_displayname_two_members" = "%@ и %@";
+"room_displayname_more_than_two_members" = "%@ и %u други";
+// Settings
+"settings" = "Настройки";
+"settings_enable_inapp_notifications" = "Включване на известия в приложението";
+"settings_enable_push_notifications" = "Включване на известия";
+"settings_enter_validation_token_for" = "Въведете код за потвърждение за %@:";
+"notification_settings_room_rule_title" = "Стая: '%@'";
+// Devices
+"device_details_title" = "Информация за сесията\n";
+"device_details_name" = "Публично име\n";
+"device_details_identifier" = "ID\n";
+"device_details_last_seen" = "Последно видян\n";
+"device_details_last_seen_format" = "%@ @ %@\n";
+"device_details_rename_prompt_message" = "Публичното име на сесията е видимо за всеки, с който комуникирате";
+"device_details_delete_prompt_title" = "Автентикация";
+"device_details_delete_prompt_message" = "Тази операция изискра допълнителна автентикация.\nЗа да продължите, моля, въведете Вашата парола.";
+// Encryption information
+"room_event_encryption_info_title" = "Информация за шифроване от край до край\n\n";
+"room_event_encryption_info_event" = "Информация за събитие\n";
+"room_event_encryption_info_event_user_id" = "ID на потребител\n";
+"room_event_encryption_info_event_identity_key" = "Curve25519 ключ за самоличност\n";
+"room_event_encryption_info_event_fingerprint_key" = "Заявен ключов отпечатък Ed25519\n";
+"room_event_encryption_info_event_algorithm" = "Алгоритъм\n";
+"room_event_encryption_info_event_session_id" = "ID на сесия\n";
+"room_event_encryption_info_event_decryption_error" = "Грешка при разшифроване\n";
+"room_event_encryption_info_event_unencrypted" = "нешифрован";
+"room_event_encryption_info_event_none" = "няма";
+"room_event_encryption_info_device" = "\nИнформация за сесията на подателя\n";
+"room_event_encryption_info_device_unknown" = "неизвестна сесия\n";
+"room_event_encryption_info_device_name" = "Публично име\n";
+"room_event_encryption_info_device_id" = "ID\n";
+"room_event_encryption_info_device_verification" = "Потвърждение\n";
+"room_event_encryption_info_device_fingerprint" = "Ed25519 отпечатък\n";
+"room_event_encryption_info_device_verified" = "Потвърдено";
+"room_event_encryption_info_device_not_verified" = "НЕ е потвърдено";
+"room_event_encryption_info_device_blocked" = "В черния списък";
+"room_event_encryption_info_unblock" = "Отблокирай";
+"room_event_encryption_info_block" = "Блокирай";
+"room_event_encryption_info_verify" = "Потвърди...";
+"room_event_encryption_info_unverify" = "Махни потвържд.";
+"room_event_encryption_verify_title" = "Потвърди сесията\n\n";
+"room_event_encryption_verify_ok" = "Потвърди";
+// Account
+"account_save_changes" = "Запази промените";
+"account_link_email" = "Свържи имейл";
+"account_linked_emails" = "Свързани имейли";
+"account_email_validation_title" = "Очакване на потвърждение";
+"account_email_validation_message" = "Моля, проверете своя имейл адрес и натиснете връзката, която той съдържа. След като направите това, натиснете продължи.";
+"account_email_validation_error" = "Неуспешно потвърждение на имейл адрес. Моля, проверете своя имейл адрес и натиснете връзката, която той съдържа. След като направите това, натиснете продължи";
+"account_msisdn_validation_title" = "Очакване на потвърждение";
+"account_msisdn_validation_message" = "Изпратихме Ви SMS с код за активиране. Моля, въведете този код по-долу.";
+"account_msisdn_validation_error" = "Неуспешно потвърждение на телефонен номер.";
+"account_error_display_name_change_failed" = "Неуспешна смяна на име";
+"account_error_picture_change_failed" = "Неуспешна смяна на профилната снимка";
+"account_error_matrix_session_is_not_opened" = "Matrix сесията не е отворена";
+"account_error_email_wrong_title" = "Невалиден имейл адрес";
+"account_error_email_wrong_description" = "Това не изглежда да е валиден имейл адрес";
+"account_error_msisdn_wrong_title" = "Невалиден телефонен номер";
+"account_error_msisdn_wrong_description" = "Това не изглежда да е валиден телефонен номер";
+// Room creation
+"room_creation_name_title" = "Име на стая:";
+"room_creation_name_placeholder" = "(напр. lunchGroup)";
+"room_creation_alias_title" = "Псевдоним на стая:";
+"room_creation_alias_placeholder" = "(напр. #foo:example.org)";
+"room_creation_alias_placeholder_with_homeserver" = "(напр. #foo%@)";
+"room_creation_participants_title" = "Участници:";
+"room_creation_participants_placeholder" = "(напр. @ivan:homeserver1; @georgi:homeserver2...)";
+// Room
+"room_please_select" = "Моля, изберете стая";
+"room_error_join_failed_title" = "Неуспешно присъединяване към стаята";
+"room_error_join_failed_empty_room" = "В момента не е възможно да се присъедините отново към празна стая.";
+"room_error_name_edition_not_authorized" = "Нямате право да редактирате името на тази стая";
+"room_error_topic_edition_not_authorized" = "Нямате право да редактирате темата на тази стая";
+"room_error_cannot_load_timeline" = "Неуспешно зареждане на хронологията";
+"room_error_timeline_event_not_found_title" = "Неуспешно зареждане на позицията в хронологията";
+"room_error_timeline_event_not_found" = "Беше направен опит да се зареди конкретна точка в хронологията на тази стая, но не я намери";
+"room_left" = "Вие напуснахте стаята";
+"room_no_power_to_create_conference_call" = "Трябва да имате разрешение за изпращане на покани, за да може да започнете групов разговор в тази стая";
+"room_no_conference_call_in_encrypted_rooms" = "Не се поддържат групови разговори в шифровани стаи";
+// Room members
+"room_member_ignore_prompt" = "Сигурни ли сте, че искате да скриете всички съобщения от този потребител?";
+"room_member_power_level_prompt" = "Няма да можете да възвърнете тази промяна, тъй като повишавате този потребител до същото ниво на достъп като Вашето.\nСигурни ли сте?";
+// Attachment
+"attachment_size_prompt" = "Изпратете с размер:";
+"attachment_original" = "Оригинален: %@";
+"attachment_small" = "Малък: %@";
+"attachment_medium" = "Среден: %@";
+"attachment_large" = "Голям: %@";
+"attachment_cancel_download" = "Отказване на свалянето?";
+"attachment_cancel_upload" = "Отказване на качването?";
+"attachment_multiselection_size_prompt" = "Изпратете снимките с размер:";
+"attachment_multiselection_original" = "Оригинален";
+"attachment_e2e_keys_file_prompt" = "Файлът съдържа ключове за шифроване, експортирани от Matrix клиент.\nИскате ли да видите съдържанието на файла или да импортирате ключовете, които съдържа?";
+"attachment_e2e_keys_import" = "Импортиране...";
+// Contacts
+"contact_mx_users" = "Matrix потребители";
+"contact_local_contacts" = "Локални контакти";
+// Groups
+"group_invite_section" = "Покани";
+"group_section" = "Групи";
+// Search
+"search_no_results" = "Няма резултати";
+"search_searching" = "В процес на търсене...";
+// Time
+"format_time_s" = "сек";
+"format_time_m" = "мин";
+"format_time_h" = "ч";
+"format_time_d" = "д";
+// E2E import
+"e2e_import_room_keys" = "Импортиране на ключове за стая";
+"e2e_import_prompt" = "Този процес позволява да импортирате ключове за шифроване, които преди сте експортирали от друг Matrix клиент. Тогава ще можете да разшифровате всяко съобщение, което другият клиент може да разшифрова. Експортираният файл може да бъде предпазен с парола. Трябва да въведете парола тук, за да разшифровате файла.";
+"e2e_import" = "Импортирай";
+"e2e_passphrase_enter" = "Въведи парола";
+// E2E export
+"e2e_export_room_keys" = "Експортиране на ключове за стая";
+"e2e_export_prompt" = "Този процес Ви позволява да експортирате във файл ключовете за съобщения в шифровани стаи. Така ще можете да импортирате файла в друг Matrix клиент, така че той също да може да разшифрова такива съобщения.\nЕкспортираният файл ще позволи на всеки, който може да го прочете, да разшифрова всяко шифровано съобщение, което можете да видите. Трябва да го държите на сигурно място.";
+"e2e_export" = "Експортирай";
+"e2e_passphrase_confirm" = "Потвърди парола";
+"e2e_passphrase_empty" = "Паролата не трябва да е празна";
+"e2e_passphrase_not_match" = "Паролите трябва да съвпадат";
+// Others
+"user_id_title" = "ID на потребител:";
+"settings_config_user_id" = "ID на потребител: %@";
+"offline" = "офлайн";
+"unsent" = "Неизпратено";
+"error" = "Грешка";
+"not_supported_yet" = "Все още не се поддържа";
+"default" = "по подразбиране";
+"private" = "Лична";
+"public" = "Публична";
+"power_level" = "Ниво на достъп";
+"network_error_not_reachable" = "Моля, проверете интернет връзката си";
+"user_id_placeholder" = "напр.: @ivan:homeserver";
+"ssl_homeserver_url" = "Адрес на Home сървър: %@";
+// Permissions
+"camera_access_not_granted_for_call" = "Видео разговорите изискват достъп до камерата, но %@ няма разрешение да я използва";
+"microphone_access_not_granted_for_call" = "Разговорите изискват достъп до микрофона, но %@ няма разрешение да го използва";
+"local_contacts_access_not_granted" = "Откриване на потребители от локални контакти изисква достъп до контактите Ви, но %@ няма разрешение да ги използва";
+"local_contacts_access_discovery_warning_title" = "Откриване на потребители";
+"local_contacts_access_discovery_warning" = "За да открие контакти използващи Matrix, %@ може да изпрати имейл адресите и телефонните номера от телефонния указател към избрания от вас Matrix сървър за самоличност. Ако се поддържа, личните данни могат да бъдат хеширани преди изпращане - вижте политиката за поверителност на сървъра за самоличност за повече информация.";
+// Country picker
+"country_picker_title" = "Избор на държава";
+// Language picker
+"language_picker_title" = "Избор на език";
+"language_picker_default_language" = "По подразбиране (%@)";
+"notice_room_invite" = "%@ покани %@";
+"notice_room_third_party_invite" = "%@ изпрати покана на %@ да се присъедини към стаята";
+"notice_room_third_party_registered_invite" = "%@ прие поканата за %@";
+"notice_room_join" = "%@ се присъедини";
+"notice_room_leave" = "%@ напусна";
+"notice_room_reject" = "%@ отхвърли поканата";
+"notice_room_kick" = "%@ изгони %@";
+"notice_room_unban" = "%@ отблокира %@";
+"notice_room_ban" = "%@ блокира %@";
+"notice_room_withdraw" = "%@ оттегли поканата си за %@";
+"notice_room_reason" = ". Причина: %@";
+"notice_avatar_url_changed" = "%@ смени своята профилна снимка";
+"notice_display_name_set" = "%@ си сложи име %@";
+"notice_display_name_changed_from" = "%@ смени своето име от %@ на %@";
+"notice_display_name_removed" = "%@ премахна своето име";
+"notice_topic_changed" = "%@ промени темата на \"%@\".";
+"notice_room_name_changed" = "%@ промени името на стаята на %@.";
+"notice_placed_voice_call" = "%@ започна гласов разговор";
+"notice_placed_video_call" = "%@ започна видео разговор";
+"notice_answered_video_call" = "%@ отговори на повикването";
+"notice_ended_video_call" = "%@ прекрати разговора";
+"notice_conference_call_request" = "%@ заяви VoIP групов разговор";
+"notice_conference_call_started" = "Започна VoIP конференция";
+"notice_conference_call_finished" = "VoIP конференцията приключи";
+// button names
+"ok" = "ОК";
+"send" = "Изпрати";
+"copy_button_name" = "Копирай";
+"resend" = "Изпрати отново";
+"redact" = "Премахни";
+"share" = "Сподели";
+"set_power_level" = "Ниво на достъп";
+"delete" = "Изтрий";
+// actions
+"action_logout" = "Излез";
+"create_room" = "Създай стая";
+"login" = "Влез";
+"create_account" = "Създай профил";
+"membership_invite" = "Поканен";
+"membership_leave" = "Напуснал";
+"membership_ban" = "Блокиран";
+"num_members_one" = "%@ потребител";
+"num_members_other" = "%@ потребители";
+"kick" = "Изгони";
+"ban" = "Блокирай";
+"unban" = "Отблокирай";
+"message_unsaved_changes" = "Има незапазени промени. При напускане ще се загубят.";
+// Login Screen
+"login_error_already_logged_in" = "Вече сте в профила си";
+"login_error_must_start_http" = "URL адресът трябва да започва с http[s]://";
+// contacts list screen
+"invitation_message" = "Бих искал да си пиша с Вас в matrix. За повече информация, моля, посетете уебсайта http://matrix.org.";
+// Settings screen
+"settings_title_config" = "Конфигурация";
+"settings_title_notifications" = "Известия";
+// Notification settings screen
+"notification_settings_disable_all" = "Изключване на всички известия";
+"notification_settings_enable_notifications" = "Включване на известия";
+"notification_settings_enable_notifications_warning" = "В момента всички известия към всички устройства са изключени.";
+"notification_settings_always_notify" = "Известяване винаги";
+"notification_settings_never_notify" = "Известяване никога";
+"notification_settings_word_to_match" = "дума, която да потърсим";
+"notification_settings_highlight" = "Подчертаване";
+"notification_settings_custom_sound" = "Индивидуален звук";
+"notification_settings_per_room_notifications" = "Известия за конкретна стая";
+"notification_settings_per_sender_notifications" = "Известия за конкретен подател";
+"notification_settings_sender_hint" = "@потребител:domain.com";
+"notification_settings_select_room" = "Избиране на стая";
+"notification_settings_other_alerts" = "Други известия";
+"notification_settings_contain_my_user_name" = "Известявай ме със звук за съобщения, съдържащи потребителското ми име";
+"notification_settings_contain_my_display_name" = "Известявай ме със звук за съобщения, съдържащи името ми";
+"notification_settings_just_sent_to_me" = "Известявай ме със звук за съобщения изпратени само до мен";
+"notification_settings_invite_to_a_new_room" = "Известявай ме, когато съм поканен в нова стая";
+"notification_settings_people_join_leave_rooms" = "Известявай ме, когато хората се присъединяват или напускат стаи";
+"notification_settings_receive_a_call" = "Известявай ме, когато получавам обаждане";
+"notification_settings_suppress_from_bots" = "Блокирай известията от ботове";
+"notification_settings_by_default" = "По подразбиране...";
+"notification_settings_notify_all_other" = "Известявай ме за всички други съобщения/стаи";
+"notification_settings_per_word_notifications" = "Известия за конкретна дума";
+// gcm section
+"settings_config_home_server" = "Home сървър: %@";
+"settings_config_identity_server" = "Сървър за самоличност: %@";
+// call string
+"call_waiting" = "Изчакване...";
+"call_connecting" = "Свързване…";
+"call_ended" = "Разговорът приключи";
+"call_ring" = "Позвъняване...";
+"incoming_video_call" = "Входящо видео повикване";
+"incoming_voice_call" = "Входящо гласово повикване";
+"call_invite_expired" = "Поканата за разговор изтече";
+// unrecognized SSL certificate
+"ssl_trust" = "Довери се";
+"ssl_logout_account" = "Излез";
+"ssl_remain_offline" = "Игнорирай";
+"ssl_fingerprint_hash" = "Отпечатък (%@):";
+"ssl_could_not_verify" = "Неуспешно потвърждаване на самоличността на отдалечения сървър.";
+"ssl_cert_not_trust" = "Това може да означава, че някой злонамерено прихваща Вашата връзка, или че телефонът Ви не се доверява на сертификата, предоставен от отдалечения сървър.";
+"ssl_cert_new_account_expl" = "Ако администраторът на сървъра е обявил, че това е нормално, уверете се, че отпечатъкът по-долу съвпада с този, предоставен от него.";
+"ssl_unexpected_existing_expl" = "Сертификатът е различен от този, на който телефонът Ви се доверява. Това е МНОГО НЕОБИЧАЙНО. Препоръчваме да НЕ ПРИЕМАТЕ този нов серфитикат.";
+"ssl_expected_existing_expl" = "Сертификатът се промени от такъв, който е бил доверен, на такъв който вече не е. Сървърът може да е подновил своя сертификат. Свържете се с администратора за правилния отпечатък.";
+"ssl_only_accept" = "Приемайте сертификата САМО ако администратора на сървъра е публикувал отпечатък, който съвпада с този по-горе.";
+"room_event_encryption_verify_message" = "За да потвърдите, че на това устройство може да се вярва, моля свържете се със собственика му по друг начин (напр. на живо или чрез телефонен разговор) и го попитайте дали ключът, който той вижда в неговите настройки на потребителя за това устройство, съвпада с ключа по-долу:\n\n\tИме на сесията: %@\n\tID на сесията: %@\n\tКлюч на сесията: %@\n\nАко съвпада, моля натиснете бутона за потвърждение по-долу. Ако не, то тогава някой друг имитира тази сесия и вероятно искате вместо това да натиснете бутона за черен списък.\n\nВ бъдеще този процес на потвърждение ще бъде по-лесен.";
+"notification_settings_global_info" = "Настройки на известията се пазят в потребителския Ви профил и се споделят измежду всички клиенти, които ги поддържат (включително и известия на работния плот).\n\nПравилата се проверяват по ред; първото съвпадащо правило дефинира резултата за съобщението.\nТака че: Известията за конкретна дума са по-важни от известията за конкретна стая, които пък са по-важни от известията за конкретен потребител.\nАко има няколко еднотипни правила, с приоритет е първото съвпадащо.";
+"notification_settings_per_word_info" = "Съвпаденията за думите се правят без взимане под внимание на малка/главна буква. Могат да съдържат и * wildcard символ. Така че:\nfoo намира съвпадения за низа foo, ограден от разделители за дума (напр. пунктуация и празно място или начало/край на ред).\nfoo* намира съвпадения във всяка дума започваща с foo.\n*foo* намира съвпадения във всяка дума включваща някъде в себе си низа foo.";
+"notice_sticker" = "стикер";
+"notice_in_reply_to" = "В отговор на";
+"error_common_message" = "Възникна грешка. Моля опитайте пак по-късно.";
+// Reply to message
+"message_reply_to_sender_sent_an_image" = "изпрати снимка.";
+"message_reply_to_sender_sent_a_video" = "изпрати видео.";
+"message_reply_to_sender_sent_an_audio_file" = "изпрати аудио файл.";
+"message_reply_to_sender_sent_a_file" = "изпрати файл.";
+"message_reply_to_message_to_reply_to_prefix" = "В отговор на";
+"login_error_resource_limit_exceeded_title" = "Надхвърлен лимит за ресурс";
+"login_error_resource_limit_exceeded_message_default" = "Този сървър е надхвърлил някой свой лимит.";
+"login_error_resource_limit_exceeded_message_monthly_active_user" = "Този сървър е достигнал своя лимит за потребители на месец.";
+"login_error_resource_limit_exceeded_message_contact" = "\n\nМоля, свържете се с администратора на услугата за да продължите да я използвате.";
+"login_error_resource_limit_exceeded_contact_button" = "Свържи се с администратора";
+"e2e_passphrase_create" = "Създай парола";
+"account_error_push_not_allowed" = "Уведомленията не са разрешени";
+"notice_room_third_party_revoked_invite" = "%@ оттегли поканата за присъединяването на %@ към стаята";
+"device_details_rename_prompt_title" = "Име на сесията";
+"notice_encryption_enabled_ok" = "%@ включи шифроване от-край-до-край.";
+"notice_encryption_enabled_unknown_algorithm" = "%1$@ включи шифроване от-край-до-край (неразпознат алгоритъм %2$@).";
+"notice_room_history_visible_to_members_from_joined_point_by_you_for_dm" = "Направихте бъдещите съобщения видими за всички, от момента на присъединяването им.";
+"notice_room_history_visible_to_members_from_joined_point_by_you" = "Направихте бъдещата история на стаята видима за всички нейни членове, от момента на присъединяването им.";
+"notice_room_history_visible_to_members_from_invited_point_by_you_for_dm" = "Направихте бъдещите съобщения видими за всички, от момента на поканването им.";
+"notice_room_history_visible_to_members_from_invited_point_by_you" = "Направихте бъдещата история на стаята видима за всички нейни членове, от момента на поканването им.";
+"notice_room_history_visible_to_members_by_you_for_dm" = "Направихте бъдещите съобщения видими за всички членове на стаята.";
+"notice_room_history_visible_to_members_by_you" = "Направихте бъдещата история на стаята видима за всички нейни членове.";
+"notice_room_history_visible_to_anyone_by_you" = "Направихте бъдещата история на стаята видима за всички.";
+"notice_redaction_by_you" = "Редактирахте събитие (идентификатор: %@)";
+"notice_encryption_enabled_unknown_algorithm_by_you" = "Включихте шифроване от-край-до-край (непознат алгоритъм %@).";
+"notice_encryption_enabled_ok_by_you" = "Включихте шифроване от-край-до-край.";
+"notice_room_created_by_you_for_dm" = "Присъединихте се.";
+"notice_room_created_by_you" = "Създадохте и конфигурирахте стаята.";
+"notice_profile_change_redacted_by_you" = "Обновихте %@ профила си";
+"notice_event_redacted_by_you" = " от вас";
+"notice_room_topic_removed_by_you" = "Премахнахте темата";
+"notice_room_name_removed_by_you_for_dm" = "Премахнахте името";
+"notice_room_name_removed_by_you" = "Премахнахте името на стаята";
+"notice_conference_call_request_by_you" = "Направихте заявка за VoIP конференция";
+"notice_ended_video_call_by_you" = "Прекратихте разговора";
+"notice_answered_video_call_by_you" = "Отговорихте на разговора";
+"notice_placed_video_call_by_you" = "Започнахте видео разговор";
+"notice_placed_voice_call_by_you" = "Започнахте гласов разговор";
+"notice_room_name_changed_by_you_for_dm" = "Променихте името на %@.";
+"notice_room_name_changed_by_you" = "Променихте името на стаята на %@.";
+"notice_topic_changed_by_you" = "Променихте темата на \"%@\".";
+"notice_display_name_removed_by_you" = "Премахнахте името си";
+"notice_display_name_changed_from_by_you" = "Променихте името си от %@ на %@";
+"notice_display_name_set_by_you" = "Променихте името си на %@";
+"notice_avatar_url_changed_by_you" = "Променихте снимката си";
+"notice_room_withdraw_by_you" = "Оттеглихте поканата на %@";
+"notice_room_ban_by_you" = "Блокирахте %@";
+"notice_room_unban_by_you" = "Отблокирахте %@";
+"notice_room_kick_by_you" = "Изгонихте %@";
+"notice_room_reject_by_you" = "Отхвърлихте поканата";
+"notice_room_leave_by_you" = "Напуснахте";
+"notice_room_join_by_you" = "Присъединихте се";
+"notice_room_third_party_revoked_invite_by_you_for_dm" = "Оттеглихте поканата на %@";
+"notice_room_third_party_revoked_invite_by_you" = "Оттеглихте поканата от %@ за присъединяване към стаята";
+"notice_room_third_party_registered_invite_by_you" = "Приехте поканата за %@";
+"notice_room_third_party_invite_by_you_for_dm" = "Поканихте %@";
+"notice_room_third_party_invite_by_you" = "Изпратихте покана към %@ за присъединяване в стаята";
+"notice_room_invite_you" = "%@ ви покани";
+
+// Notice Events with "You"
+"notice_room_invite_by_you" = "Поканихте %@";
+"notice_room_name_changed_for_dm" = "%@ промени името на %@.";
+"notice_room_third_party_revoked_invite_for_dm" = "%@ оттегли поканата от %@";
+"notice_room_third_party_invite_for_dm" = "%@ покани %@";
+"room_left_for_dm" = "Напуснахте";
+"notice_room_history_visible_to_members_from_joined_point_for_dm" = "%@ направи бъдещите съобщения видими за всички, от момента на присъединяването им в стаята.";
+"notice_room_history_visible_to_members_from_invited_point_for_dm" = "%@ направи бъдещите съобщения видими за всички, от момента на поканването им в стаята.";
+"notice_room_history_visible_to_members_for_dm" = "%@ направи бъдещите съобщения видими за всички членове в стаята.";
+"notice_room_aliases_for_dm" = "Псевдонимите са: %@";
+"notice_room_power_level_intro_for_dm" = "Нивата на достъп на членовете са:";
+"notice_room_join_rule_public_by_you_for_dm" = "Направихте стаята публична.";
+"notice_room_join_rule_public_by_you" = "Направихте тази стая публична.";
+"notice_room_join_rule_public_for_dm" = "%@ направи стаята публична.";
+"notice_room_join_rule_public" = "%@ направи стаята публична.";
+"notice_room_join_rule_invite_by_you_for_dm" = "Направихте достъпа да е само за поканени.";
+"notice_room_join_rule_invite_by_you" = "Направихте тази стая достъпна само за поканени.";
+"notice_room_join_rule_invite_for_dm" = "%@ направи достъпа да е само за поканени.";
+// New
+"notice_room_join_rule_invite" = "%@ направи стаята достъпна само за поканени.";
+"notice_room_created_for_dm" = "%@ се присъедини.";
+"notice_room_name_removed_for_dm" = "%@ премахна името";
+"call_transfer_to_user" = "Прехвърли към %@";
+"call_consulting_with_user" = "Консултация с %@";
+"call_video_with_user" = "Видео разговор с %@";
+"call_voice_with_user" = "Гласов разговор с %@";
+"call_more_actions_dialpad" = "Панел за набиране";
+"call_more_actions_transfer" = "Прехвърляне";
+"call_more_actions_audio_use_headset" = "Използвай звук от слушалките";
+"call_more_actions_audio_use_device" = "Използвай звук от устройството";
+"call_more_actions_change_audio_device" = "Смени аудио устройството";
+"call_more_actions_unhold" = "Възобнови";
+"call_more_actions_hold" = "Задръж";
+"call_holded" = "Задържахте разговора";
+"call_remote_holded" = "%@ задържа разговора";
+"call_ringing" = "Звънене…";
+"notice_declined_video_call_by_you" = "Отказахте разговора";
+"notice_declined_video_call" = "%@ отказа разговора";
+"e2e_passphrase_too_short" = "Паролата е прекалено кратка (трябва да е дълга поне %d символа)";
+"resume_call" = "Възобнови";
diff --git a/Riot/Modules/MatrixKit/Assets/MatrixKitAssets.bundle/ca.lproj/MatrixKit.strings b/Riot/Modules/MatrixKit/Assets/MatrixKitAssets.bundle/ca.lproj/MatrixKit.strings
new file mode 100644
index 000000000..7c376a636
--- /dev/null
+++ b/Riot/Modules/MatrixKit/Assets/MatrixKitAssets.bundle/ca.lproj/MatrixKit.strings
@@ -0,0 +1,363 @@
+"matrix" = "Matrix";
+"login_password_placeholder" = "Contrasenya";
+"login_optional_field" = "opcional";
+"login_email_placeholder" = "Adreça de correu electrònic";
+"view" = "Veure";
+"back" = "Torna";
+"continue" = "Continua";
+"leave" = "Sortir";
+"invite" = "Convida";
+"retry" = "Torna a provar";
+"cancel" = "Cancel·lar";
+"save" = "Desar";
+// Login Screen
+"login_create_account" = "Crear compte:";
+"login_server_url_placeholder" = "URL (ex. https://matrix.org)";
+"login_home_server_title" = "Servidor hoste:";
+"login_home_server_info" = "El teu servidor hoste desa totes les teves converses i dades del compte";
+"login_identity_server_title" = "Servidor d'identitat:";
+"login_identity_server_info" = "Matrix proporciona servidors d'identitat per fer un seguiment dels correus electrònics etc. que pertanyen als identificadors Matrix. Actualment només existeix https://matrix.org.";
+"login_user_id_placeholder" = "ID a Matrix (ex @bob:matrix.org o bob)";
+"login_display_name_placeholder" = "Mostra nom (ex. Bob Obson)";
+// room details dialog screen
+"room_details_title" = "Detalls de la sala";
+"login_email_info" = "Especificar una adreça de correu electrònic permet que altres usuaris et trobin més fàcilment a Matrix i et proporcionarà una manera de restablir la teva contrasenya en el futur.";
+"login_prompt_email_token" = "Introduïu el teu codi de validació de correu electrònic:";
+"login_error_title" = "Error d'inici de sessió";
+"login_error_no_login_flow" = "No hem pogut recuperar la informació d'autenticació d'aquest servidor hoste";
+"login_error_do_not_support_login_flows" = "Actualment no admetem cap o tots els fluxos d'inici de sessió definits per aquest servidor hoste";
+"login_error_registration_is_not_supported" = "El registre no és suportat per ara";
+"login_error_forbidden" = "Nom d'usuari/contrasenya invàlid";
+"login_error_unknown_token" = "No s'ha reconegut el token d'accés especificat";
+"login_error_bad_json" = "JSON incorrecte";
+"login_error_not_json" = "No contenia JSON vàlid";
+"login_error_limit_exceeded" = "S'han enviat massa sol·licituds";
+"login_error_user_in_use" = "Aquest nom d'usuari ja està en ús";
+"login_error_login_email_not_yet" = "L'enllaç del correu electrònic que encara no heu fet clic";
+"login_use_fallback" = "Usa la pàgina de devolució";
+"login_leave_fallback" = "Cancel·lar";
+"login_invalid_param" = "Paràmetre invàlid";
+"register_error_title" = "Error de registre";
+"login_error_forgot_password_is_not_supported" = "Contrasenya oblidada encara no es suportat";
+"login_mobile_device" = "Mòbil";
+"login_tablet_device" = "Tableta";
+"login_desktop_device" = "Escriptori";
+// Action
+"no" = "No";
+"yes" = "Sí";
+"abort" = "Avortar";
+"close" = "Tanca";
+"discard" = "Descarta";
+"dismiss" = "Omet";
+"sign_up" = "Registre";
+"submit" = "Presentar";
+"submit_code" = "Presentar codi";
+"set_default_power_level" = "Restablir el nivell de potència";
+"set_moderator" = "Establir el Moderador";
+"set_admin" = "Establir l'Administrador";
+"start_chat" = "Iniciar xat";
+"start_voice_call" = "Iniciar trucada de veu";
+"start_video_call" = "Iniciar vídeo trucada";
+"mention" = "Mencionar";
+"select_account" = "Selecciona un compte";
+"attach_media" = "Adjunta mitjans de la biblioteca";
+"capture_media" = "Fes una foto o un vídeo";
+"invite_user" = "Convida usuari Matrix";
+"reset_to_default" = "Estableix per defecte";
+"resend_message" = "Reenvia el missatge";
+"select_all" = "Selecciona-ho tot";
+"cancel_upload" = "Cancel·la la pujada";
+"cancel_download" = "Cancel·la la descàrrega";
+"show_details" = "Mostra els detalls";
+"answer_call" = "Respon la trucada";
+"reject_call" = "Rebutja la trucada";
+"end_call" = "Penja";
+"ignore" = "Ignora";
+"unignore" = "Deixa de ignorar";
+// Events formatter
+"notice_avatar_changed_too" = "(també s'ha canviat l'avatar)";
+"notice_room_name_removed" = "%@ ha eliminat el nom de la sala";
+"notice_room_topic_removed" = "%@ ha eliminat el tema";
+"notice_event_redacted" = "";
+"notice_event_redacted_by" = " per %@";
+"notice_event_redacted_reason" = " [motiu: %@]";
+"notice_profile_change_redacted" = "%@ ha actualitzat el seu perfil %@";
+"notice_room_created" = "%@ ha creat la sala";
+"notice_room_join_rule" = "La norma per entrar és: %@";
+"notice_room_power_level_intro" = "El nivell de potència dels participants és:";
+"notice_room_power_level_acting_requirement" = "Els nivells de potència mínims que un usuari ha de tenir abans d'actuar són:";
+"notice_room_power_level_event_requirement" = "Els nivells mínims de potència relacionats als esdeveniments són:";
+"notice_room_aliases" = "Els àlies de la sala són: %@";
+"notice_room_related_groups" = "Els grups associats amb aquesta sala són: %@";
+"notice_encrypted_message" = "Missatge xifrat";
+"notice_encryption_enabled" = "%@ ha activat el xifrat punt a punt (algoritme %@)";
+"notice_image_attachment" = "adjunt d'imatge";
+"notice_audio_attachment" = "adjunt d'àudio";
+"notice_video_attachment" = "adjunt de vídeo";
+"notice_location_attachment" = "adjunt de localització";
+"notice_file_attachment" = "adjunt de fitxer";
+"notice_invalid_attachment" = "adjunt invàlid";
+"notice_unsupported_attachment" = "Adjunt no suportat: %@";
+"notice_feedback" = "Esdeveniment de resposta (id: %@): %@";
+"notice_redaction" = "%@ ha redactat un esdeveniment (id: %@)";
+"notice_error_unsupported_event" = "Esdeveniment no suportat";
+"notice_error_unexpected_event" = "Esdeveniment inesperat";
+"notice_error_unknown_event_type" = "Tipus desconegut d'esdeveniment";
+"notice_room_history_visible_to_anyone" = "%@ ha fet visible per qualsevol l'històric futur de la sala.";
+"notice_room_history_visible_to_members" = "%@ ha fet visible per tots els participants l'històric futur de la sala.";
+"notice_room_history_visible_to_members_from_invited_point" = "%@ ha fet visible l'històric futur de la sala per a tots els membres, a partir de que hi són convidats.";
+"notice_room_history_visible_to_members_from_joined_point" = "%@ ha fet visible l'històric futur de la sala a tots els membres, des de que entren a la sala.";
+"notice_crypto_unable_to_decrypt" = "** No es pot desxifrar: %@ **";
+"notice_crypto_error_unknown_inbound_session_id" = "El dispositiu del remitent no ha enviat les claus per aquest missatge.";
+// room display name
+"room_displayname_empty_room" = "Sala buida";
+"room_displayname_two_members" = "%@ i %@";
+"room_displayname_more_than_two_members" = "%@ i %u més";
+// Settings
+"settings" = "Configuració";
+"settings_enable_inapp_notifications" = "Habilitar les notificacions de les App integrades";
+"settings_enable_push_notifications" = "Activar notificacions push";
+"settings_enter_validation_token_for" = "Introduir el codi de validació per %@:";
+"notification_settings_room_rule_title" = "Sala: '%@'";
+// Devices
+"device_details_title" = "Informació del dispositiu\n";
+"device_details_name" = "Nom\n";
+"device_details_identifier" = "ID del dispositiu\n";
+"device_details_last_seen" = "Vist per últim cop\n";
+"device_details_last_seen_format" = "%@ @ %@\n";
+"device_details_rename_prompt_message" = "Nom del dispositiu:";
+"device_details_delete_prompt_title" = "Autenticació";
+"device_details_delete_prompt_message" = "Aquesta operació necessita que t'autentiquis.\nPer continuar, introdueix la teva contrasenya.";
+// Encryption information
+"room_event_encryption_info_title" = "Informació del xifrat punt a punt\n\n";
+"room_event_encryption_info_event" = "Informació del esdeveniment\n";
+"room_event_encryption_info_event_user_id" = "ID d'usuari\n";
+"room_event_encryption_info_event_identity_key" = "Clau de la identitat Curve25519\n";
+"room_event_encryption_info_event_fingerprint_key" = "Empremta digital Ed25519 reclamada\n";
+"room_event_encryption_info_event_algorithm" = "Algoritme\n";
+"room_event_encryption_info_event_session_id" = "ID de la sessió\n";
+"room_event_encryption_info_event_decryption_error" = "Error de desxifrat\n";
+"room_event_encryption_info_event_unencrypted" = "no xifrat";
+"room_event_encryption_info_event_none" = "cap";
+"room_event_encryption_info_device" = "\nInformació del dispositiu del remitent\n";
+"room_event_encryption_info_device_unknown" = "dispositiu desconegut\n";
+"room_event_encryption_info_device_name" = "Nom\n";
+"room_event_encryption_info_device_id" = "ID del dispositiu\n";
+"room_event_encryption_info_device_verification" = "Verificació\n";
+"room_event_encryption_info_device_fingerprint" = "Empremta digital Ed25519\n";
+"room_event_encryption_info_device_verified" = "Verificat";
+"room_event_encryption_info_device_not_verified" = "NO verificat";
+"room_event_encryption_info_device_blocked" = "Bloquejat";
+"room_event_encryption_info_verify" = "Verifica...";
+"room_event_encryption_info_unverify" = "No verificar";
+"room_event_encryption_info_block" = "Llista negre";
+"room_event_encryption_info_unblock" = "Desbloquejar";
+"room_event_encryption_verify_title" = "Verifica el dispositiu\n\n";
+"room_event_encryption_verify_message" = "Per a verificar que aquest dispositiu pot ser confiable si us plau contacta el seu propietari per altres mijans (ex. trucant-lo al telèfon) i pregunta-li si la clau que veu a la configuració d'usuari d'aquest dispositiu coincideix amb la clau següent:\n\n\tNom del dispositiu: %@\n\tID del dispositiu: %@\n\tClau del dispositiu: %@\n\nSi coincideix, prem el botó verificar de sota. Si no coincideix es que algú altre està interceptant aquest dispositiu i probablement vols prema el botó de bloquejar a canvi.\n\nEn el futur aquest procés de verificació serà més sofisticat.";
+"room_event_encryption_verify_ok" = "Verifica";
+// Account
+"account_save_changes" = "Desar canvis";
+"account_link_email" = "Vincular correu electrònic";
+"account_linked_emails" = "Correus electrònics vinculats";
+"account_email_validation_title" = "Verificació pendent";
+"account_email_validation_message" = "Revisa el teu correu electrònic i fes clic a l'enllaç que conté. Un cop fet això, fes clic a continua.";
+"account_email_validation_error" = "No ha estat possible verificar l'adreça de correu electrònic. Mira el correu electrònic i fes clic en l'enllaç que conté. Un cop fet això, fes clic per continuar";
+"account_msisdn_validation_title" = "Verificació pendent";
+"account_msisdn_validation_message" = "Hem enviat un SMS amb un codi d'activació. Introdueix aquest codi a continuació.";
+"account_msisdn_validation_error" = "No es pot verificar el número de telèfon.";
+"account_error_display_name_change_failed" = "Ha fallat el canvi del nom a mostrar";
+"account_error_picture_change_failed" = "Ha fallat el canvi de foto";
+"account_error_matrix_session_is_not_opened" = "No està oberta la sessió Matrix";
+"account_error_email_wrong_title" = "Adreça de correu electrònic no valida";
+"account_error_email_wrong_description" = "Aquest no sembla ser un correu electrònic vàlid";
+"account_error_msisdn_wrong_title" = "Número de telèfon invalid";
+"account_error_msisdn_wrong_description" = "Aquest no sembla ser un número de telèfon vàlid";
+// Room creation
+"room_creation_name_title" = "Nom de la sala:";
+"room_creation_name_placeholder" = "(ex. collaesmorzar)";
+"room_creation_alias_title" = "Àlies de sala:";
+"room_creation_alias_placeholder" = "(ex. #foo:exemple.org)";
+"room_creation_alias_placeholder_with_homeserver" = "(ex. #foo%@)";
+"room_creation_participants_title" = "Participants:";
+"room_creation_participants_placeholder" = "(ex. @jordi:servidorhoste1; @juan:servidorhoste2...)";
+// Room
+"room_please_select" = "Si us plau tria una sala";
+"room_error_join_failed_title" = "No s'ha pogut entrar a la sala";
+"room_error_join_failed_empty_room" = "Actualment no es pot tornar a entrar a una sala buida.";
+"room_error_name_edition_not_authorized" = "No tens permís per editar el nom d'aquesta sala";
+"room_error_topic_edition_not_authorized" = "No tens permís per editar el tema d'aquesta sala";
+"room_error_cannot_load_timeline" = "No s'ha pogut carregar la línia de temps";
+"room_error_timeline_event_not_found_title" = "No s'ha pogut carregar la posició de la línia de temps";
+"room_error_timeline_event_not_found" = "Aquesta aplicació estava intentant carregar un punt especific en la línia de temps d'aquesta sala però no l'ha trobat";
+"room_left" = "Has sortit de la sala";
+"room_no_power_to_create_conference_call" = "Necessites permís per a convidar a iniciar una conferència en aquesta sala";
+"room_no_conference_call_in_encrypted_rooms" = "No es poden fer conferències en sales xifrades";
+// Room members
+"room_member_ignore_prompt" = "Estàs segur que vols amagar tots els missatges d'aquest usuari?";
+"room_member_power_level_prompt" = "Si puges aquest usuari al mateix nivell de poder que el teu després no podràs desfer el canvi.\nEstàs segur?";
+// Attachment
+"attachment_size_prompt" = "Ho vols enviar com a:";
+"attachment_original" = "Mida actual: %@";
+"attachment_small" = "Petit: %@";
+"attachment_medium" = "Mitjà: %@";
+"attachment_large" = "Gran: %@";
+"attachment_cancel_download" = "Cancel·lar la descàrrega?";
+"attachment_cancel_upload" = "Cancel·lar la pujada?";
+"attachment_multiselection_size_prompt" = "Vols enviar imatges com a:";
+"attachment_multiselection_original" = "Mida actual";
+"attachment_e2e_keys_file_prompt" = "Aquest fitxer conté claus xifrades exportades des d'un client Matrix.\nVols veure el contingut del fitxer o importar les claus que conté?";
+"attachment_e2e_keys_import" = "Importa...";
+// Contacts
+"contact_mx_users" = "Usuaris de Matrix";
+"contact_local_contacts" = "Contactes locals";
+// Groups
+"group_invite_section" = "Convits";
+"group_section" = "Grups";
+// Search
+"search_no_results" = "Sense resultats";
+"search_searching" = "Cercant...";
+// Time
+"format_time_s" = "s";
+"format_time_m" = "m";
+"format_time_h" = "h";
+"format_time_d" = "d";
+// E2E import
+"e2e_import_room_keys" = "Importa les claus de la sala";
+"e2e_import_prompt" = "Aquest procés et pemet importar les claus de xifratge que previament has exportat des de un altre client Matrix. Després podràs desxifrar qualsevol missatge que l'altre client pugui xifrar.\nEl fitxer exportat està protegit amb una contrasenya. Hauries de introduir la contrasenya aquí per desxifrar-l'ho.";
+"e2e_import" = "Importa";
+"e2e_passphrase_enter" = "Introduir contrasenya";
+// E2E export
+"e2e_export_room_keys" = "Exporta les claus E2E de la sala";
+"e2e_export_prompt" = "Aquest procés et permet exportar a un fitxer local les claus dels missatges que has rebut de sales xifrades. A continuació, podràs importar el fitxer a un altre client Matrix en el futur, de manera que aquest client també podrà desxifrar aquests missatges.\nEl fitxer exportat permetrà que qualsevol que pugui llegir-lo per desxifrar qualsevol dels missatges xifrats que tu pots veure, així que has de tenir cura de mantenir-lo segur.";
+"e2e_export" = "Exporta les claus E2E";
+"e2e_passphrase_confirm" = "Confirma la contrasenya";
+"e2e_passphrase_empty" = "La contrasenya no ha de estar buida";
+"e2e_passphrase_not_match" = "Les contrasenyes han de coincidir";
+// Others
+"user_id_title" = "ID d'usuari:";
+"offline" = "fora de línia";
+"unsent" = "No enviats";
+"error" = "Error";
+"not_supported_yet" = "Encara no suportat";
+"default" = "per defecte";
+"private" = "Privat";
+"public" = "Públic";
+"power_level" = "Nivell de potència";
+"network_error_not_reachable" = "Si us plau verifica la teva connexió de xarxa";
+"user_id_placeholder" = "ex: @jordi:servidorhoste";
+"ssl_homeserver_url" = "URL del Servidor Hoste: %@";
+// Permissions
+"camera_access_not_granted_for_call" = "Les vídeo trucades necessiten permís per accedir a la Càmera però %@ no té permís per utilitzar-la";
+"microphone_access_not_granted_for_call" = "Les trucades necessiten accedir al Micròfon però %@ no té permís per utilitzar-lo";
+"local_contacts_access_not_granted" = "El descobriment dels usuaris en els contactes locals requereix l'accés als vostres contactes, però %@ no té permís per utilitzar-lo";
+"local_contacts_access_discovery_warning_title" = "Descobriment d'usuaris";
+"local_contacts_access_discovery_warning" = "%@ vol pujar les adreces de correu i els números de telèfon dels teus Contactes per a poder descobrir usuaris";
+// Country picker
+"country_picker_title" = "Escull un país";
+// Language picker
+"language_picker_title" = "Escull l'idioma";
+"language_picker_default_language" = "Per defecte (%@)";
+"notice_room_invite" = "%@ ha convidat a %@";
+"notice_room_third_party_invite" = "%@ ha enviat una invitació %@ per entrar a la sala";
+"notice_room_third_party_registered_invite" = "%@ ha acceptat la invitació per a %@";
+"notice_room_join" = "%@ s'ha unit a la sala";
+"notice_room_leave" = "%@ ha marxat";
+"notice_room_reject" = "%@ ha rebutjat la invitació";
+"notice_room_kick" = "%@ ha fet fora a %@";
+"notice_room_unban" = "%@ ha readmès a %@";
+"notice_room_ban" = "%@ ha expulsat a %@";
+"notice_room_withdraw" = "%@ ha anul·lat la invitació de %@";
+"notice_room_reason" = ". Motiu: %@";
+"notice_avatar_url_changed" = "%@ ha canviat el seu avatar";
+"notice_display_name_set" = "%@ ha canviat el seu nom a %@";
+"notice_display_name_changed_from" = "%@ ha canviat el seu nom de %@ a %@";
+"notice_display_name_removed" = "%@ ha eliminat el seu nom visible";
+"notice_topic_changed" = "%@ ha canviat el tema a: %@";
+"notice_room_name_changed" = "%@ ha canviat el nom de la sala a: %@";
+"notice_placed_voice_call" = "%@ ha iniciat una trucada de veu";
+"notice_placed_video_call" = "%@ ha iniciat una vídeo conferència";
+"notice_answered_video_call" = "%@ ha contestat la trucada";
+"notice_ended_video_call" = "%@ ha finalitzat la trucada";
+"notice_conference_call_request" = "%@ ha sol·licitat una conferència VoIP";
+"notice_conference_call_started" = "Conferència VoIP iniciada";
+"notice_conference_call_finished" = "Conferència VoIP finalitzada";
+// button names
+"ok" = "D'acord";
+"send" = "Envia";
+"copy_button_name" = "Copia";
+"resend" = "Reenvia";
+"redact" = "Elimina";
+"share" = "Comparteix";
+"set_power_level" = "Nivell de potència";
+"delete" = "Esborra";
+// actions
+"action_logout" = "Tancar sessió";
+"create_room" = "Crear sala";
+"login" = "Iniciar sessió";
+"create_account" = "Crear compte";
+"membership_invite" = "Convidat";
+"membership_leave" = "Ha sortit";
+"membership_ban" = "Expulsat";
+"num_members_one" = "usuari %@";
+"num_members_other" = "Usuaris de %@";
+"kick" = "Fer fora";
+"ban" = "Expulsa";
+"unban" = "Readmetre";
+"message_unsaved_changes" = "Hi ha canvis no desats. Si ho deixes es perdran.";
+// Login Screen
+"login_error_already_logged_in" = "Ja autenticat";
+"login_error_must_start_http" = "La URL ha de començar per http[s]://";
+// contacts list screen
+"invitation_message" = "M'agradaría xatejar amb tu amb matrix. Si us plau visita el lloc http://matrix.org per a més informació.";
+// Settings screen
+"settings_title_config" = "Configuració";
+"settings_title_notifications" = "Notificacions";
+// Notification settings screen
+"notification_settings_disable_all" = "Desactiva totes les notificacions";
+"notification_settings_enable_notifications" = "Activa les notificacions";
+"notification_settings_enable_notifications_warning" = "Totes les notificacions de tots els dispositius estan actualment desactivades.";
+"notification_settings_global_info" = "Els ajustos de les notificacions son desades en el teu compte i compartides amb tots els clients que les suporten (incloent les notificacions d'escriptori).\n\nLes normes son aplicades en ordre; la primera norma que coincideix defineix el resultat del missatge.\nPer tant: es notificacions per paraula són més importants que les notificacions per sales i aquestes són més importants que les notificacions per remitents.\nPer a diverses normes del mateix tipus, la primera de la llista que coincideix té prioritat.";
+"notification_settings_per_word_notifications" = "Notificacions per paraula";
+"notification_settings_per_word_info" = "Les paraules coincideixen amb el cas de forma insensible i poden incloure un comodí *. Per tant:\nfoo coincideix amb la cadena foo rodejada de delimitadors de paraules (p. ex. puntuació i espai en blanc o inici/final de línia).\nfoo* coincideix amb qualsevol paraula que començi per foo.\n* foo* coincideix amb qualsevol paraula que inclogui les 3 lletres foo.";
+"notification_settings_always_notify" = "Notifica sempre";
+"notification_settings_never_notify" = "Mai notifica";
+"notification_settings_word_to_match" = "paraula coincident";
+"notification_settings_highlight" = "Ressaltat";
+"notification_settings_custom_sound" = "So personaltizat";
+"notification_settings_per_room_notifications" = "Notificacions per sala";
+"notification_settings_per_sender_notifications" = "Notificacions per remitent";
+"notification_settings_sender_hint" = "@usuari:domini.com";
+"notification_settings_select_room" = "Escull una sala";
+"notification_settings_other_alerts" = "Altres avisos";
+"notification_settings_contain_my_user_name" = "Notificar-me amb un so els missatges que continguin el meu nom d'usuari";
+"notification_settings_contain_my_display_name" = "Notificar-me amb un so els missatges que continguin el meu nom visible";
+"notification_settings_just_sent_to_me" = "Notificar-me amb un so els missatges enviats per mi";
+"notification_settings_invite_to_a_new_room" = "Notificar-me quan sigui convidat a una nova sala";
+"notification_settings_people_join_leave_rooms" = "Notificar-me quan algú entri o marxi de les sales";
+"notification_settings_receive_a_call" = "Notificar-me quan rebi una trucada";
+"notification_settings_suppress_from_bots" = "Suprimir les notificacions de robots";
+"notification_settings_by_default" = "Per defecte...";
+"notification_settings_notify_all_other" = "Notifica tots els altres missatges/sales";
+// gcm section
+"settings_config_home_server" = "Servidor hoste: %@";
+"settings_config_identity_server" = "Servidor d'identitat: %@";
+"settings_config_user_id" = "ID d'usuari: %@";
+// call string
+"call_waiting" = "Esperant...";
+"call_connecting" = "Establint la trucada...";
+"call_ended" = "Trucada finalitzada";
+"call_ring" = "Trucant...";
+"incoming_video_call" = "Vídeo trucada entrant";
+"incoming_voice_call" = "Trucada de veu entrant";
+"call_invite_expired" = "Invitació de trucada ha caducat";
+// unrecognized SSL certificate
+"ssl_trust" = "Confia";
+"ssl_logout_account" = "Tancar sessió";
+"ssl_remain_offline" = "Ignora";
+"ssl_fingerprint_hash" = "Empremta digital (%@):";
+"ssl_could_not_verify" = "No s'ha pogut verificar la identitat del servidor remot.";
+"ssl_cert_not_trust" = "Això pot voler dir que algú està maliciosament interceptant el tràfic o que el teu telèfon no confia en el certificat proporcionat pel servidor remot.";
+"ssl_cert_new_account_expl" = "Si l'administrador del servidor ha dit que això és correcte, assegura't que la següent empremta digital coincideix amb la que t'ha donat.";
+"ssl_unexpected_existing_expl" = "El certificat ha canviat respecte al que el teu telèfon hi havia confiat. Això es MOLT INUSUAL. Es recomana que NO ACCEPTIS aquest nou certificat.";
+"ssl_expected_existing_expl" = "El certificat ha canviat del prèviament confiat a un que no es confiable. El servidor pot haver renovat el certificat. Posa't en contacte amb l'administrador del servidor per obtenir l'empremta digital desitjada.";
+"ssl_only_accept" = "NOMÉS accepteu el certificat si l'administrador del servidor ha publicat una empremta digital que coincideixi amb l'anterior.";
diff --git a/Riot/Modules/MatrixKit/Assets/MatrixKitAssets.bundle/cy.lproj/MatrixKit.strings b/Riot/Modules/MatrixKit/Assets/MatrixKitAssets.bundle/cy.lproj/MatrixKit.strings
new file mode 100644
index 000000000..6bed7d575
--- /dev/null
+++ b/Riot/Modules/MatrixKit/Assets/MatrixKitAssets.bundle/cy.lproj/MatrixKit.strings
@@ -0,0 +1,383 @@
+"matrix" = "Matrix";
+// Login Screen
+"login_create_account" = "Creu cyfrif:";
+"login_server_url_placeholder" = "URL (e.e. https://matrix.org)";
+"login_home_server_title" = "URL Hafanweinydd:";
+"login_home_server_info" = "Mae'ch hafanweinydd yn cadw'ch holl sgyrsiau a gwybodaeth cyfrif";
+"login_identity_server_title" = "URL Gweinydd Adnabod:";
+"login_identity_server_info" = "Mae Matrix yn darparu gweinyddwyr adnabod i olrhain pa e-byst ac ati sy'n perthyn i IDau Matrix. Dim ond https://matrix.org sy'n bodoli ar hyn o bryd.";
+"login_user_id_placeholder" = "ID Matrix (e.g. @bob:matrix.org neu bob)";
+"login_password_placeholder" = "Cyfrinair";
+"login_optional_field" = "dewisol";
+"login_display_name_placeholder" = "Enw arddangos (e.e. Jac y Jwc)";
+"login_email_info" = "Mae nodi cyfeiriad e-bost yn caniatáu i ddefnyddwyr eraill ddod o hyd i chi ar Matrix yn haws, a bydd yn rhoi ffordd i chi ailosod eich cyfrinair yn y dyfodol.";
+"login_email_placeholder" = "Cyfeiriad E-bost";
+"login_prompt_email_token" = "Rhowch eich tocyn gwirio e-bost:";
+"login_error_title" = "Methwyd Mewngofnodi";
+"login_error_no_login_flow" = "Methom ni â chasglu gwybodaeth gwirio gan y Hafanweinydd hwn";
+"login_error_do_not_support_login_flows" = "Ar hyn o bryd nid ydym yn cefnogi unrhyw lif mewngofnodi, neu'rrhan ohono, a ddiffinnir gan y Hafanweinydd hwn";
+"login_error_registration_is_not_supported" = "Ni chefnogir cofrestru ar hyn o bryd";
+"login_error_forbidden" = "Enw defnyddiwr/cyfrinair annilys";
+"login_error_unknown_token" = "Ni chydnabuwyd y tocyn mynediad a nodwyd";
+"login_error_bad_json" = "JSON camffurfiedig";
+"login_error_not_json" = "Nid oedd yn cynnwys JSON dilys";
+"login_error_limit_exceeded" = "Mae gormod o geisiadau wedi'u hanfon";
+"login_error_user_in_use" = "Defnyddir yr enw defnyddiwr hwn eisoes";
+"login_error_login_email_not_yet" = "Nid yw'r ddolen e-bost wedi'i chlicio eto";
+"login_use_fallback" = "Defnyddiwch dudalen wrth gefn";
+"login_leave_fallback" = "Canslo";
+"login_invalid_param" = "Paramadr Annilys";
+"register_error_title" = "Methwyd Cofrestri";
+"login_error_forgot_password_is_not_supported" = "Ni chefnogir cyfrinair anghofedig ar hyn o bryd";
+"login_mobile_device" = "Ffôn Symudol";
+"login_tablet_device" = "Llechen";
+"login_desktop_device" = "Cyfrifiadur";
+"login_error_resource_limit_exceeded_title" = "Ty hwnt i'r Terfyn Adnoddau";
+"login_error_resource_limit_exceeded_message_default" = "Mae'r hafanweinydd hwn wedi rhagori ar un o'i derfynau adnoddau.";
+"login_error_resource_limit_exceeded_message_monthly_active_user" = "Mae'r hafanweinydd hwn wedi cyrraedd ei derfyn Defnyddiwr Gweithredol Misol.";
+"login_error_resource_limit_exceeded_message_contact" = "\n\nCysylltwch â'ch gweinyddwr gwasanaeth i barhau i ddefnyddio'r gwasanaeth hwn.";
+"login_error_resource_limit_exceeded_contact_button" = "Cysylltu â Gweinyddwr";
+// Action
+"no" = "Na";
+"yes" = "Ie";
+"abort" = "Cefnu";
+"back" = "Yn ôl";
+"close" = "Cau";
+"continue" = "Parhau";
+"discard" = "Taflu";
+"dismiss" = "Wfftio";
+"retry" = "Ailgynnig";
+"sign_up" = "Cofrestri";
+"submit" = "Anfon";
+"submit_code" = "Anfon côd";
+"set_power_level" = "Gosod Lefel Pŵer";
+"set_default_power_level" = "Ail-osod Lefel Pŵer";
+"set_moderator" = "Gosod Cymredolwr";
+"set_admin" = "Gosod Gweinyddwr";
+"start_chat" = "Dechrau Sgwrs";
+"start_voice_call" = "Dechrau Galwad Llais";
+"start_video_call" = "Dechrau Galwad Fideo";
+"mention" = "Crybwyll";
+"select_account" = "Dewis cyfrif";
+"attach_media" = "Ategu Cyfryngau o'r Llyfrgell";
+"capture_media" = "Tynnu Llun/Fideo";
+"invite_user" = "Gwahodd Defnyddiwr Matrix";
+"reset_to_default" = "Ailosod i'r diofyn";
+"resend_message" = "Ail-anfon y neges";
+"select_all" = "Dewis y cyfan";
+"cancel_upload" = "Canslo Uwchlwytho";
+"cancel_download" = "Canslo Lawrlwytho";
+"show_details" = "Dangos Manylion";
+"answer_call" = "Ateb Galwad";
+"reject_call" = "Gwrthod Galwad";
+"end_call" = "Gorffen Galwad";
+"ignore" = "Anwybyddu";
+"unignore" = "Dad-anwybyddu";
+// Events formatter
+"notice_avatar_changed_too" = "(newidiwyd rhithffurf hefyd)";
+"notice_room_name_removed" = "Tynnodd %@ enw'r ystafell";
+"notice_room_topic_removed" = "Fe wnaeth %@ ddileu'r pwnc";
+"notice_event_redacted" = "";
+"notice_event_redacted_by" = " gan %@";
+"notice_event_redacted_reason" = " [rheswm: %@]";
+"notice_profile_change_redacted" = "Diweddarodd %@ eu proffil %@";
+"notice_room_created" = "Creodd %@ yr ystafell";
+"notice_room_join_rule" = "Y rheol ymuno yw: %@";
+"notice_room_power_level_intro" = "Lefel pŵer aelodau'r ystafell yw:";
+"notice_room_power_level_acting_requirement" = "Y lefelau pŵer lleiaf y mae'n rhaid i ddefnyddiwr eu cael cyn gweithredu yw:";
+"notice_room_power_level_event_requirement" = "Isafswm y lefelau pŵer sy'n gysylltiedig â digwyddiadau yw:";
+"notice_room_aliases" = "Arallenwau'r ystafell yw: %@";
+"notice_room_related_groups" = "Y grwpiau sy'n gysylltiedig â'r ystafell hon yw: %@";
+"notice_encrypted_message" = "Neges amgryptiedig";
+"notice_encryption_enabled" = "Trodd %@ ar amgryptio o'r dechrau i'r diwedd (algorithm %@)";
+"notice_image_attachment" = "atodiad llun";
+"notice_audio_attachment" = "atodiad sain";
+"notice_video_attachment" = "atodiad fideo";
+"notice_location_attachment" = "atodiad lleoliad";
+"notice_file_attachment" = "atodiad ffeil";
+"notice_invalid_attachment" = "atodiad annilys";
+"notice_unsupported_attachment" = "Atodiad heb gefnogaeth: %@";
+"notice_feedback" = "Digwyddiad adborth (id: %@): %@";
+"notice_redaction" = "Golygodd %@ ddigwyddiad (id: %@)";
+"notice_error_unsupported_event" = "Digwyddiad heb gefnogaeth";
+"notice_error_unexpected_event" = "Digwyddiad annisgwyl";
+"notice_error_unknown_event_type" = "Math digwyddiad anhysbys";
+"notice_room_history_visible_to_anyone" = "Gwnaeth %@ hanes ystafell y dyfodol yn weladwy i unrhyw un.";
+"notice_room_history_visible_to_members" = "Gwnaeth %@ hanes ystafell y dyfodol yn weladwy i holl aelodau'r ystafell.";
+"notice_room_history_visible_to_members_from_invited_point" = "Gwnaeth %@ hanes ystafell y dyfodol yn weladwy i holl aelodau'r ystafell, o'r pwynt y cawsant eu gwahodd.";
+"notice_room_history_visible_to_members_from_joined_point" = "Gwnaeth %@ hanes ystafell y dyfodol yn weladwy i holl aelodau'r ystafell, o'r pwynt yr ymunon nhw ag ef.";
+"notice_crypto_unable_to_decrypt" = "** Methu dadgryptio: %@ **";
+"notice_crypto_error_unknown_inbound_session_id" = "Nid yw sesiwn yr anfonwr wedi anfon yr allweddi ar gyfer y neges hon atom.";
+"notice_sticker" = "sticer";
+"notice_in_reply_to" = "Mewn ateb i";
+// room display name
+"room_displayname_empty_room" = "Ystafell gwag";
+"room_displayname_two_members" = "%@ a %@";
+"room_displayname_more_than_two_members" = "%@ a %@ eraill";
+// Settings
+"settings" = "Gosodiadau";
+"settings_enable_inapp_notifications" = "Galluogi hysbysiadau Mewn-App";
+"settings_enable_push_notifications" = "Galluogi gwth-hysbysiadau";
+"settings_enter_validation_token_for" = "Rhowch docyn dilysu ar gyfer %@:";
+"notification_settings_room_rule_title" = "Ystafell: '%@'";
+// Devices
+"device_details_title" = "Gwybodaeth sesiwn\n";
+"device_details_name" = "Enw Cyhoeddus\n";
+"device_details_identifier" = "ID\n";
+"device_details_last_seen" = "Gwelwyd ddiweddaf\n";
+"device_details_last_seen_format" = "%@ @ %@\n";
+"device_details_rename_prompt_title" = "Enw Sesiwn";
+"device_details_rename_prompt_message" = "Mae enw cyhoeddus sessiwn yn weladwy i'r bobl rydych chi'n cyfathrebu â nhw";
+"device_details_delete_prompt_title" = "Dilysu";
+"device_details_delete_prompt_message" = "Mae angen dilysu ychwanegol ar gyfer y gweithgaredd hon.\nI barhau, nodwch eich cyfrinair.";
+// Encryption information
+"room_event_encryption_info_title" = "Gwybodaeth amgryptio dechrau i'r diwedd\n\n";
+"room_event_encryption_info_event" = "Gwybodaeth digwyddiad\n";
+"room_event_encryption_info_event_user_id" = "ID Defnyddiwr\n";
+"room_event_encryption_info_event_identity_key" = "Allwedd adnabod Curve25519\n";
+"room_event_encryption_info_event_fingerprint_key" = "Allwedd llofnod Ed25519 â hawliedig\n";
+"room_event_encryption_info_event_algorithm" = "Algorithm\n";
+"room_event_encryption_info_event_session_id" = "ID Sesiwn\n";
+"room_event_encryption_info_event_decryption_error" = "Gwall Dadgryptio\n";
+"room_event_encryption_info_event_unencrypted" = "digryptiedig";
+"room_event_encryption_info_event_none" = "dim";
+"room_event_encryption_info_device" = "\nGwybodaeth am sesiwn yr anfonwr\n";
+"room_event_encryption_info_device_unknown" = "sesiwn anhysbys\n";
+"room_event_encryption_info_device_name" = "Enw Cyhoeddus\n";
+"room_event_encryption_info_device_id" = "ID\n";
+"room_event_encryption_info_device_verification" = "Gwirio\n";
+"room_event_encryption_info_device_fingerprint" = "Llofnod Ed25519\n";
+"room_event_encryption_info_device_verified" = "Wedi Gwirio";
+"room_event_encryption_info_device_not_verified" = "HEB ei wirio";
+"room_event_encryption_info_device_blocked" = "Gwaharddiedig";
+"room_event_encryption_info_verify" = "Gwirio…";
+"room_event_encryption_info_unverify" = "Dad-wirio";
+"room_event_encryption_info_block" = "Gwahardd";
+"room_event_encryption_info_unblock" = "Dad-wahardd";
+"room_event_encryption_verify_title" = "Gwirio sesiwn\n\n";
+"room_event_encryption_verify_message" = "I wirio y gellir ymddiried yn y sesiwn hon, cysylltwch â'i pherchennog gan ddefnyddio rhyw fodd arall (e.e. yn bersonol neu alwad ffôn) a gofynnwch iddynt a yw'r allwedd a welant yn eu Gosodiadau Defnyddiwr ar gyfer y sesiwn hon yn cyfateb i'r allwedd isod:\n\nEnw'r sesiwn: %@\nID y sesiwn: %@\nAllwedd y sesiwn: %@\n\nOs yw'n cyd-fynd, pwyswch y botwm gwirio isod. Os nad yw'n gwneud hynny, yna mae rhywun arall yn rhyng-gipio'r sesiwn hon ac mae'n debyg eich y dylech wasgu'r botwm gwahardd yn lle.\n\nYn y dyfodol bydd y broses gwirio hon yn fwy soffistigedig.";
+"room_event_encryption_verify_ok" = "Gwirio";
+// Account
+"account_save_changes" = "Cadw newidiadau";
+"account_link_email" = "Dolen E-bost";
+"account_linked_emails" = "E-byst cysylltiedig";
+"account_email_validation_title" = "Aros am wirio";
+"account_email_validation_message" = "Gwiriwch eich e-bost a chliciwch ar y ddolen sydd ynddo. Ar ôl gwneud hyn, cliciwch parhau.";
+"account_email_validation_error" = "Methu gwirio cyfeiriad e-bost. Gwiriwch eich e-bost a chliciwch ar y ddolen sydd ynddo. Ar ôl gwneud hyn, cliciwch parhau";
+"account_msisdn_validation_title" = "Aros am wirio";
+"account_msisdn_validation_message" = "Rydyn ni wedi anfon neges testyn gyda chod actifadu. Rhowch y cod hwn isod.";
+"account_msisdn_validation_error" = "Methu gwirio rhif ffôn.";
+"account_error_display_name_change_failed" = "Methwyd newid enw arddangos";
+"account_error_picture_change_failed" = "Methwyd newid llun";
+"account_error_matrix_session_is_not_opened" = "Nid yw 'r sesiwn Matrix yn agored";
+"account_error_email_wrong_title" = "Cyfeiriad E-bost Annilys";
+"account_error_email_wrong_description" = "Nid yw hwn yn edrych fel cyfeiriad e-bost dilys";
+"account_error_msisdn_wrong_title" = "Rhif Ffôn Annilys";
+"account_error_msisdn_wrong_description" = "Nid yw hwn yn edrych fel rhif ffôn dilys";
+"account_error_push_not_allowed" = "Ni chaniateir hysbysiadau";
+// Room creation
+"room_creation_name_title" = "Enw ystafell:";
+"room_creation_name_placeholder" = "(e.e. criwCinio)";
+"room_creation_alias_title" = "Arallenw ystafell:";
+"room_creation_alias_placeholder" = "(e.e. #foo:example.org)";
+"room_creation_alias_placeholder_with_homeserver" = "(e.e. #foo%@)";
+"room_creation_participants_title" = "Cyfranogwyr:";
+"room_creation_participants_placeholder" = "(e.e. @daf:hafanweinydd1; @gwil:hafanweinydd2...)";
+// Room
+"room_please_select" = "Dewisiwch ystafell";
+"room_error_join_failed_title" = "Methwyd ymuno â'r ystafell";
+"room_error_join_failed_empty_room" = "Ar hyn o bryd nid yw'n bosibl ail-ymuno ag ystafell wag.";
+"room_error_name_edition_not_authorized" = "Nid oes gennych awdurdod i olygu enw'r ystafell hon";
+"room_error_topic_edition_not_authorized" = "Nid oes gennych awdurdod i olygu pwnc yr ystafell hon";
+"room_error_cannot_load_timeline" = "Methwyd llwytho llinell amser";
+"room_error_timeline_event_not_found_title" = "Methwyd llwytho safle llinell amser";
+"room_error_timeline_event_not_found" = "Roedd y rhaglen yn ceisio llwytho pwynt penodol yn llinell amser yr ystafell hon ond nid oedd yn gallu dod o hyd iddo";
+"room_left" = "Gadawsoch yr ystafell";
+"room_no_power_to_create_conference_call" = "Mae angen caniatâd arnoch i wahodd i ddechrau cynhadledd yn yr ystafell hon";
+"room_no_conference_call_in_encrypted_rooms" = "Ni chefnogir galwadau cynhadledd mewn ystafelloedd wedi'u hamgryptio";
+// Reply to message
+"message_reply_to_sender_sent_an_image" = "anfonwyd llun.";
+"message_reply_to_sender_sent_a_video" = "anfonwyd fideo.";
+"message_reply_to_sender_sent_an_audio_file" = "anfonwyd ffeil sain.";
+"message_reply_to_sender_sent_a_file" = "anfonwyd ffeil.";
+"message_reply_to_message_to_reply_to_prefix" = "Mewn ateb i";
+// Room members
+"room_member_ignore_prompt" = "Ydych chi'n siŵr eich bod chi eisiau cuddio pob neges oddi wrth y defnyddiwr hwn?";
+"room_member_power_level_prompt" = "Ni fyddwch yn gallu dadwneud y newid hwn gan eich bod yn hyrwyddo'r defnyddiwr i gael yr un lefel pŵer â chi'ch hun.\nYdych chi'n siwr?";
+// Attachment
+"attachment_size_prompt" = "Ydych chi am anfon fel:";
+"attachment_original" = "Maint Gwirioneddol: %@";
+"attachment_small" = "Bach: %@";
+"attachment_medium" = "Canolig: %@";
+"attachment_large" = "Mawr: %@";
+"attachment_cancel_download" = "Canslo y lawrlwythiad?";
+"attachment_cancel_upload" = "Canslo yr uwchlwythiad?";
+"attachment_multiselection_size_prompt" = "Hoffech chi anfon llun fel:";
+"attachment_multiselection_original" = "Maint Gwirioneddol";
+"attachment_e2e_keys_file_prompt" = "Mae'r ffeil hon yn cynnwys allweddi amgryptio a allfudwyd o gleient Matrix.\nYdych chi eisiau gweld cynnwys y ffeil neu fewnfudo'r allweddi sydd ynddo?";
+"attachment_e2e_keys_import" = "Mewnfudo...";
+// Contacts
+"contact_mx_users" = "Defnyddwyr Matrix";
+"contact_local_contacts" = "Cysylltiadau Lleol";
+// Groups
+"group_invite_section" = "Gwahoddiadau";
+"group_section" = "Grwpiau";
+// Search
+"search_no_results" = "Dum Canluniadau";
+"search_searching" = "Chwilio ar y gweill...";
+// Time
+"format_time_s" = "e";
+"format_time_m" = "m";
+"format_time_h" = "a";
+"format_time_d" = "d";
+// E2E import
+"e2e_import_room_keys" = "Mewnfudo allweddi ystafell";
+"e2e_import_prompt" = "Mae'r broses hon yn caniatáu ichi fewnfudo allweddi amgryptio yr oeddech wedi'u hallfudo o'r blaen o gleient Matrix arall. Yna byddwch yn gallu dadgryptio unrhyw negeseuon y gallai'r cleient arall eu dadgryptio.\nMae'r ffeil allfudo wedi'i gwarchod gyda chyfrinair. Dylech nodi'r cyfrinair yma, i ddadgryptio'r ffeil.";
+"e2e_import" = "Mewnfudo";
+"e2e_passphrase_enter" = "Rhowch cyfrinair";
+// E2E export
+"e2e_export_room_keys" = "Allfudo allweddi ystafell";
+"e2e_export_prompt" = "Mae'r broses hon yn caniatáu ichi allfudo i ffeil leol yr allweddi ar gyfer negeseuon rydych wedi'u derbyn mewn ystafelloedd amgryptiedig. Yna byddwch chi'n gallu mewnfudo'r ffeil i gleient Matrix arall yn y dyfodol, fel y bydd y cleient hwnnw hefyd yn gallu dadgryptio'r negeseuon hyn.\nBydd y ffeil a allfudir yn caniatáu i unrhyw un sy'n gallu ei darllen ddadgryptio unrhyw negeseuon amgryptiedig y gallwch eu gweld, felly dylech fod yn ofalus i'w cadw'n ddiogel.";
+"e2e_export" = "Allfudo";
+"e2e_passphrase_confirm" = "Cadarnhau cyfrinair";
+"e2e_passphrase_empty" = "Ni chaniateir cyfrinair gwag";
+"e2e_passphrase_not_match" = "Rhaid i'r cyfrineiriau gyfateb";
+"e2e_passphrase_create" = "Creu cyfrinair";
+// Others
+"user_id_title" = "ID Defnyddiwr:";
+"offline" = "all-lein";
+"unsent" = "Heb eu danfon";
+"error" = "Gwall";
+"error_common_message" = "Digwyddodd gwall. Rhowch gynning eto nes ymlaen.";
+"not_supported_yet" = "Heb ei gefnogi eto";
+"default" = "diofyn";
+"private" = "Preifat";
+"public" = "Cyhoeddus";
+"power_level" = "Lefel Pŵer";
+"network_error_not_reachable" = "Gwiriwch eich cysylltedd rhwydwaith";
+"user_id_placeholder" = "eng: @gwil:hafanweinydd";
+"ssl_homeserver_url" = "URL Hafanweinydd: %@";
+// Permissions
+"camera_access_not_granted_for_call" = "Mae galwadau fideo angen mynediad i'r Camera ond nid oes gan %@ ganiatâd i'w ddefnyddio";
+"microphone_access_not_granted_for_call" = "Mae galwadau fideo angen mynediad i'r Meicroffon ond nid oes gan %@ ganiatâd i'w ddefnyddio";
+"local_contacts_access_not_granted" = "Mae darganfyddiad defnyddwyr o gysylltiadau lleol angen mynediad i'ch cysylltiadau ond nid oes gan %@ ganiatâd i'w ddefnyddio";
+"local_contacts_access_discovery_warning_title" = "Darganfod defnyddwyr";
+"local_contacts_access_discovery_warning" = "I ddarganfod cysylltiadau sydd eisoes yn defnyddio Matrix, gall %@ anfon cyfeiriadau e-bost a rhifau ffôn yn eich llyfr cyfeiriadau at y gweinydd adnabod Matrix o'ch dewis. Pan gânt eu cefnogi, mae data personol yn cael ei amgodio cyn ei anfon - gwiriwch bolisi preifatrwydd eich gweinydd adnabod i gael mwy o fanylion.";
+// Country picker
+"country_picker_title" = "Dewiswch wlad";
+// Language picker
+"language_picker_title" = "Dewiswch iaith";
+"language_picker_default_language" = "Diofyn (%@)";
+"notice_room_invite" = "Mae %@ wedi gwahodd %@";
+"notice_room_third_party_invite" = "Anfonodd %@ wahoddiad i %@ i ymuno â'r ystafell";
+"notice_room_third_party_registered_invite" = "Derbyniodd %@ y gwahoddiad am %@";
+"notice_room_third_party_revoked_invite" = "Tynnodd %@ y gwahoddiad i %@ ymuno â'r ystafell";
+"notice_room_join" = "Ymunodd %@";
+"notice_room_leave" = "Gadawodd %@";
+"notice_room_reject" = "Gwrthododd %@ y gwahoddiad";
+"notice_room_kick" = "Ciciodd %@ %@";
+"notice_room_unban" = "Dad-waharddodd %@ %@";
+"notice_room_ban" = "Gwaharddod %@ %@";
+"notice_room_withdraw" = "Tynnodd %@ wahoddiad %@ yn ôl";
+"notice_room_reason" = ". Rheswm: %@";
+"notice_avatar_url_changed" = "Newidiodd %@ eu rhithffurf";
+"notice_display_name_set" = "Gosododd %@ eu henw arddangos i %@";
+"notice_display_name_changed_from" = "Newidiodd %@ eu henw arddangos o %@ i %@";
+"notice_display_name_removed" = "Tynnodd %@ eu henw arddangos";
+"notice_topic_changed" = "Newidiodd %@ y pwnc i: %@";
+"notice_room_name_changed" = "Newidiodd %@ enw'r ystafell i: %@";
+"notice_placed_voice_call" = "Gosododd %@ alwad llais";
+"notice_placed_video_call" = "Gosododd %@ alwad fideo";
+"notice_answered_video_call" = "Atebodd %@ y galwad";
+"notice_ended_video_call" = "Gorffenodd %@ y galwad";
+"notice_conference_call_request" = "Gofynnodd %@ am gynhadledd VoIP";
+"notice_conference_call_started" = "Dechreuwyd cynhadledd VoIP";
+"notice_conference_call_finished" = "Gorffenwyd cynhadledd VoIP";
+// button names
+"ok" = "Iawn";
+"cancel" = "Canslo";
+"save" = "Cadw";
+"leave" = "Gadael";
+"send" = "Anfon";
+"copy_button_name" = "Copi";
+"resend" = "Ail-anfon";
+"redact" = "Tynnu";
+"share" = "Rhannu";
+"delete" = "Dileu";
+"view" = "Gweld";
+// actions
+"action_logout" = "Allgofnodi";
+"create_room" = "Creu Ystafell";
+"login" = "Mewngofnodi";
+"create_account" = "Creu Cyfrif";
+"membership_invite" = "Gwahoddwyd";
+"membership_leave" = "Gadawodd";
+"membership_ban" = "Gwaharddedig";
+"num_members_one" = "%@ defnyddiwr";
+"num_members_other" = "%@ defnyddiwr";
+"invite" = "Gwahodd";
+"kick" = "Cic";
+"ban" = "Gwahardd";
+"unban" = "Dad-wahardd";
+"message_unsaved_changes" = "Mae yna newidiadau heb eu cadw. Bydd gadael yn golygu eu colli.";
+// Login Screen
+"login_error_already_logged_in" = "Wedi mewngofnodi eisoes";
+"login_error_must_start_http" = "Rhaid i URL ddechrau â http[s]://";
+// room details dialog screen
+"room_details_title" = "Manylion Ystafell";
+// contacts list screen
+"invitation_message" = "Hoffwn sgwrsio â chi gyda Matrix. Os gwelwch yn dda, ewch i'r wefan https://matrix.org i gael mwy o wybodaeth.";
+// Settings screen
+"settings_title_config" = "Gosodiadau";
+"settings_title_notifications" = "Hysbysebiadau";
+// Notification settings screen
+"notification_settings_disable_all" = "Analluogi pob hysbysiad";
+"notification_settings_enable_notifications" = "Galluogi hysbysiadau";
+"notification_settings_enable_notifications_warning" = "Ar hyn o bryd mae pob hysbysiad wedi eu hanalluogi ar gyfer pob dyfais.";
+"notification_settings_global_info" = "Mae gosodiadau hysbysiadau yn cael eu cadw i'ch cyfrif defnyddiwr ac yn cael eu rhannu rhwng yr holl gleientiaid sy'n eu cefnogi (gan gynnwys hysbysiadau cyfrifiadur).\n\nCymhwysir rheolau mewn trefn; mae'r rheol gyntaf sy'n cyfateb yn diffinio canlyniad y neges.\nFelly: Mae hysbysiadau fesul gair yn bwysicach na hysbysiadau fesul ystafell sy'n bwysicach na hysbysiadau fesul anfonwr.\nAr gyfer rheolau lluosog o'r un math, mae'r un gyntaf yn y rhestr sy'n cyfateb yn cael blaenoriaeth.";
+"notification_settings_per_word_notifications" = "Hysbysiadau fesul gair";
+"notification_settings_per_word_info" = "Mae geiriau'n cyfateb priflythrennau a rhai bach, a gallant gynnwys * cerdyn gwyllt. Felly:\nmae foo yn cyd-fynd â'r testyn foo wedi'i amgylchynu gan amffinyddion geiriau (e.e. atalnodi a gofod gwyn neu ddechrau / diwedd llinell).\nmae foo* yn cyfateb i unrhyw air o'r fath sy'n dechrau foo.\nmae *foo* yn cyfateb i unrhyw air o'r fath sy'n cynnwys y 3 llythyren foo.";
+"notification_settings_always_notify" = "Rhowch wybod bob amser";
+"notification_settings_never_notify" = "Peidiwch byth â hysbysu";
+"notification_settings_word_to_match" = "gair yw gyfateb";
+"notification_settings_highlight" = "Amlygiad";
+"notification_settings_custom_sound" = "Sain addasol";
+"notification_settings_per_room_notifications" = "Hysbysiadau fesul ystafell";
+"notification_settings_per_sender_notifications" = "Hysbysiadau fesul anfonwr";
+"notification_settings_sender_hint" = "@defnyddiwr:parth.com";
+"notification_settings_select_room" = "Dewisiwch ystafell";
+"notification_settings_other_alerts" = "Rhybuddion Eraill";
+"notification_settings_contain_my_user_name" = "Rhowch wybod i mi gyda sain am negeseuon sy'n cynnwys fy enw defnyddiwr";
+"notification_settings_contain_my_display_name" = "Rhowch wybod i mi gyda sain am negeseuon sy'n cynnwys fy enw arddangos";
+"notification_settings_just_sent_to_me" = "Rhowch wybod i mi gyda sain am negeseuon â anfonwyd ataf yn unig";
+"notification_settings_invite_to_a_new_room" = "Rhowch wybod i mi pan gaf wahoddiad i ystafell newydd";
+"notification_settings_people_join_leave_rooms" = "Rhowch wybod i mi pan fydd pobl yn ymuno neu'n gadael ystafelloedd";
+"notification_settings_receive_a_call" = "Rhowch wybod i mi pan fyddaf yn derbyn galwad";
+"notification_settings_suppress_from_bots" = "Atal hysbysiadau o botiau";
+"notification_settings_by_default" = "Yn ddiofyn...";
+"notification_settings_notify_all_other" = "Hysbysu am yr holl negeseuon / ystafelloedd eraill";
+// gcm section
+"settings_config_home_server" = "Hafanweinydd: %@";
+"settings_config_identity_server" = "Gweinydd adnabod: %@";
+"settings_config_user_id" = "ID Defnyddiwr: %@";
+// call string
+"call_waiting" = "Aros...";
+"call_connecting" = "Cysylltu galwad...";
+"call_ended" = "Gorffenwyd y galwad";
+"call_ring" = "Yn galw...";
+"incoming_video_call" = "Galwad Fideo sy'n dod i mewn";
+"incoming_voice_call" = "Galwad Llais sy'n dod i mewn";
+"call_invite_expired" = "Gwahoddiad Galwad wedi dod i ben";
+// unrecognized SSL certificate
+"ssl_trust" = "Ymddiried";
+"ssl_logout_account" = "Allgofnodi";
+"ssl_remain_offline" = "Anwybyddu";
+"ssl_fingerprint_hash" = "Llofnod (%@):";
+"ssl_could_not_verify" = "Methwyd gwirio gweinyddwr adnabod pell.";
+"ssl_cert_not_trust" = "Gallai hyn olygu bod rhywun yn rhyng-gipio eich traffig yn faleisus, neu nad yw'ch ffôn yn ymddiried yn y dystysgrif a ddarperir gan y gweinydd pell.";
+"ssl_cert_new_account_expl" = "Os yw gweinyddwr y gweinydd wedi dweud bod disgwyl hyn, sicrhewch fod yr llofnod isod yn cyfateb i'r llofnod a ddarperir ganddynt.";
+"ssl_unexpected_existing_expl" = "Mae'r dystysgrif wedi newid o un yr oedd eich ffôn yn ymddiried ynddo. Mae hyn yn ANNISGWYL IAWN. Argymhellir i chi BEIDIO Â DERBYN y dystysgrif newydd hon.";
+"ssl_expected_existing_expl" = "Mae'r dystysgrif wedi newid o un yr ymddiriedwyd ynddo o'r blaen i un nad oes ymddiried ynddo. Efallai bod y gweinydd wedi adnewyddu ei dystysgrif. Cysylltwch â gweinyddwr y gweinydd i gael y llofnod disgwyliedig.";
+"ssl_only_accept" = "Peidiwch a derbyn y dystysgrif ONIBAI bod gweinyddwr y gweinydd wedi cyhoeddi llofnod sy'n cyfateb i'r un uchod.";
+"notice_encryption_enabled_ok" = "Trodd %@ amgryptio o'r dechrau i'r diwedd ymlaen.";
+"notice_encryption_enabled_unknown_algorithm" = "Trodd %@ amgryptio o'r dechrau i'r diwedd ymlaen (algorithm anghydnabyddedig %2$@).";
diff --git a/Riot/Modules/MatrixKit/Assets/MatrixKitAssets.bundle/da.lproj/MatrixKit.strings b/Riot/Modules/MatrixKit/Assets/MatrixKitAssets.bundle/da.lproj/MatrixKit.strings
new file mode 100644
index 000000000..8b1378917
--- /dev/null
+++ b/Riot/Modules/MatrixKit/Assets/MatrixKitAssets.bundle/da.lproj/MatrixKit.strings
@@ -0,0 +1 @@
+
diff --git a/Riot/Modules/MatrixKit/Assets/MatrixKitAssets.bundle/de.lproj/MatrixKit.strings b/Riot/Modules/MatrixKit/Assets/MatrixKitAssets.bundle/de.lproj/MatrixKit.strings
new file mode 100644
index 000000000..fd15a50c4
--- /dev/null
+++ b/Riot/Modules/MatrixKit/Assets/MatrixKitAssets.bundle/de.lproj/MatrixKit.strings
@@ -0,0 +1,499 @@
+"matrix" = "Matrix";
+// Login Screen
+"login_create_account" = "Konto erstellen:";
+"login_server_url_placeholder" = "URL (z.B. https://matrix.org)";
+"login_home_server_title" = "Heimserver-URL:";
+"login_home_server_info" = "Dein Heimserver speichert alle deine Gespräche und Benutzerkontodaten";
+"login_identity_server_title" = "Identitätsserver-URL:";
+"login_identity_server_info" = "Matrix stellt Identitätsserver bereit, um feststellen zu können, welche E-Mail-Adressen, etc. zu welchen Matrix-IDs gehören. Momentan existiert nur https://vector.im.";
+"login_user_id_placeholder" = "Matrix-ID (z.B. @bob:matrix.org oder bob)";
+"login_password_placeholder" = "Passwort";
+"login_optional_field" = "optional";
+"login_display_name_placeholder" = "Anzeigename (z.B. Peter Pan)";
+"login_email_placeholder" = "E-Mail-Adresse";
+"login_error_title" = "Anmeldung fehlgeschlagen";
+"login_email_info" = "Die Eingabe einer E-Mail-Adresse erleichtert es anderen Benutzern, dich auf Matrix zu finden. Außerdem kannst du mit der hinterlegten E-Mail-Adresse dein Passwort zurücksetzen.";
+"login_prompt_email_token" = "Gib das E-Mail-Validierungstoken ein:";
+"login_error_no_login_flow" = "Die Authentifizierungsinformation von diesem Heimserver konnte nicht abgerufen werden";
+"login_error_do_not_support_login_flows" = "Momentan werden einige oder alle der von diesem Heimserver definierten Authentifizierungspfade nicht unterstützt";
+"login_error_registration_is_not_supported" = "Registrierung wird momentan nicht unterstützt";
+"login_error_forbidden" = "Benutzername oder Passwort ungültig";
+"login_error_unknown_token" = "Das angegebene Zugriffstoken wurde nicht erkannt";
+"login_error_bad_json" = "Deformiertes JSON";
+"login_error_not_json" = "Enthielt kein valides JSON";
+"login_error_limit_exceeded" = "Zu viele Anfragen wurden gesendet";
+"login_error_user_in_use" = "Dieser Benutzername wird bereits verwendet";
+"login_error_login_email_not_yet" = "Der Email-Link wurde noch nicht angeklickt";
+"login_use_fallback" = "Benutze die Ersatzseite";
+"login_leave_fallback" = "Abbrechen";
+"login_invalid_param" = "Ungültiger Parameter";
+"register_error_title" = "Registrierung fehlgeschlagen";
+"login_error_forgot_password_is_not_supported" = "\"Passwort vergessen\" wird momentan nicht unterstützt";
+"login_mobile_device" = "Mobilgerät";
+"login_tablet_device" = "Tablet";
+"login_desktop_device" = "Desktop";
+"login_error_resource_limit_exceeded_title" = "Ressourcengrenzwert überschritten";
+"login_error_resource_limit_exceeded_message_default" = "Dieser Heimserver hat eine seiner Ressourcengrenzwerte überschritten.";
+"login_error_resource_limit_exceeded_message_monthly_active_user" = "Dieser Heimserver hat sein Grenzwert an monatlich aktiven Nutzern erreicht.";
+"login_error_resource_limit_exceeded_message_contact" = "\n\nBitte kontaktiere deinen Dienstadministrator um mit der Nutzung dieses Dienstes fortzufahren.";
+"login_error_resource_limit_exceeded_contact_button" = "Kontaktiere Administrator";
+// Action
+"no" = "Nein";
+"yes" = "Ja";
+"abort" = "Abbrechen";
+"back" = "Zurück";
+"close" = "Schließen";
+"continue" = "Fortsetzen";
+"discard" = "Verwerfen";
+"dismiss" = "Ablehnen";
+"retry" = "Wiederholen";
+"sign_up" = "Registrieren";
+"submit" = "Absenden";
+"submit_code" = "Code übermitteln";
+"set_power_level" = "Berechtigungsstufe setzen";
+"set_default_power_level" = "Berechtigungsstufe zurücksetzen";
+"set_moderator" = "Moderator setzen";
+"set_admin" = "Administrator setzen";
+"start_chat" = "Chat starten";
+"start_voice_call" = "Sprachanruf starten";
+"start_video_call" = "Videoanruf starten";
+"mention" = "Erwähnung";
+"select_account" = "Wähle ein Konto";
+"attach_media" = "Medien aus der Bibliothek anhängen";
+"capture_media" = "Foto/Video aufnehmen";
+"invite_user" = "Matrixbenutzer einladen";
+"reset_to_default" = "Auf Standardeinstellungen zurücksetzen";
+"resend_message" = "Nachricht erneut senden";
+"select_all" = "Alles auswählen";
+"cancel_upload" = "Hochladen abbrechen";
+"cancel_download" = "Herunterladen abbrechen";
+"show_details" = "Zeige Details";
+"answer_call" = "Anruf annehmen";
+"reject_call" = "Anruf abweisen";
+"end_call" = "Anruf beenden";
+"ignore" = "Ignorieren";
+"unignore" = "Ignorieren aufheben";
+// Events formatter
+"notice_avatar_changed_too" = "(Avatar wurde auch geändert)";
+"notice_room_name_removed" = "%@ hat den Raumnamen gelöscht";
+"notice_room_name_removed_for_dm" = "%@ hat den Namen gelöscht";
+"notice_room_topic_removed" = "%@ hat das Raumthema gelöscht";
+"notice_event_redacted" = "";
+"notice_event_redacted_by" = " durch %@";
+"notice_event_redacted_reason" = " [Grund: %@]";
+"notice_profile_change_redacted" = "%@ aktualisierte sein Profil %@";
+"notice_room_created" = "%@ hat den Raum erstellt und konfiguriert.";
+"notice_room_created_for_dm" = "%@ ist beigetreten.";
+// Old
+"notice_room_join_rule" = "Die Beitrittsregel ist: %@";
+// New
+"notice_room_join_rule_invite" = "%@ hat den Raum auf \"nur-einladen\" gestellt.";
+"notice_room_join_rule_invite_for_dm" = "%@ hat auf Einladungen beschränkt.";
+"notice_room_join_rule_invite_by_you" = "Du hast den Raumbeitritt auf Einladungen beschränkt.";
+"notice_room_join_rule_invite_by_you_for_dm" = "Du hast dies auf Einladungen beschränkt.";
+"notice_room_join_rule_public" = "%@ hat den Raum öffentlich gemacht.";
+"notice_room_join_rule_public_for_dm" = "%@ hat es öffentlich gemacht.";
+"notice_room_join_rule_public_by_you" = "Du hast den Raum öffentlich gemacht.";
+"notice_room_join_rule_public_by_you_for_dm" = "Du hast es öffentlich gemacht.";
+"notice_room_power_level_intro" = "Die Berechtigungsstufe der Teilnehmer ist:";
+"notice_room_power_level_intro_for_dm" = "Die Berechtigungsstufe der Teilnehmer ist:";
+"notice_room_power_level_acting_requirement" = "Die minimale Berechtigungsstufe, die ein Benutzer zum Handeln benötigt:";
+"notice_room_power_level_event_requirement" = "Die minimalen Berechtigungsstufen bezogen auf Ereignisse:";
+"notice_room_aliases" = "Die Raumnamenaliase sind: %@";
+"notice_room_aliases_for_dm" = "Die Namenaliase sind: %@";
+"notice_room_related_groups" = "Die Gruppen, die mit diesem Raum verknüpft sind: %@";
+"notice_encrypted_message" = "Verschlüsselte Nachricht";
+"notice_encryption_enabled_ok" = "%@ hat die Ende-zu-Ende-Verschlüsselung aktiviert.";
+"notice_encryption_enabled_unknown_algorithm" = "%1$@ hat die Ende-zu-Ende-Verschlüsselung aktiviert (unbekannter Algorithmus %2$@).";
+"notice_image_attachment" = "angehängtes Bild";
+"notice_audio_attachment" = "Audioanhang";
+"notice_video_attachment" = "Videoanhang";
+"notice_location_attachment" = "Standortanhang";
+"notice_file_attachment" = "Dateianhang";
+"notice_invalid_attachment" = "Ungültige Anhang";
+"notice_unsupported_attachment" = "Nicht unterstützter Anhang: %@";
+"notice_feedback" = "Rückmeldeereignis (id: %@): %@";
+"notice_redaction" = "%@ hat Ereignis geschwärzt (id: %@)";
+"notice_error_unsupported_event" = "Nicht unterstütztes Ereignis";
+"notice_error_unexpected_event" = "Unerwartetes Ereignis";
+"notice_error_unknown_event_type" = "Unbekannter Ereignistyp";
+"notice_room_history_visible_to_anyone" = "%@ hat den zukünftigen Raumverlauf für alle sichtbar gemacht.";
+"notice_room_history_visible_to_members" = "%@ hat den zukünftigen Raumverlauf für alle Raumteilnehmer sichtbar gemacht.";
+"notice_room_history_visible_to_members_for_dm" = "%@ hat die folgenden Nachrichten für alle Teilnehmer des Raumes sichtbar gemacht.";
+"notice_room_history_visible_to_members_from_invited_point" = "%@ hat den zukünftigen Raumverlauf für alle Raumteilnehmer ab deren Einladung sichtbar gemacht.";
+"notice_room_history_visible_to_members_from_invited_point_for_dm" = "%@ hat den zukünftigen Verlauf für alle Raumteilnehmer ab deren Einladung sichtbar gemacht.";
+"notice_room_history_visible_to_members_from_joined_point" = "%@ hat den zukünftigen Raumverlauf für alle Raumteilnehmer ab deren Einladung sichtbar gemacht.";
+"notice_room_history_visible_to_members_from_joined_point_for_dm" = "%@ hat den zukünftigen Verlauf für alle Raumteilnehmer ab deren Einladung sichtbar gemacht.";
+"notice_crypto_unable_to_decrypt" = "** Entschlüsselung nicht möglich: %@ **";
+"notice_crypto_error_unknown_inbound_session_id" = "Die Sitzung des Absenders hat uns keine Schlüssel für diese Nachricht gesendet.";
+"notice_sticker" = "Aufkleber";
+"notice_in_reply_to" = "Als Antwort auf";
+// room display name
+"room_displayname_empty_room" = "Leerer Raum";
+"room_displayname_two_members" = "%@ und %@";
+"room_displayname_more_than_two_members" = "%@ und %u andere";
+// Settings
+"settings" = "Einstellungen";
+"settings_enable_inapp_notifications" = "Benachrichtigungen innerhalb der App aktivieren";
+"settings_enable_push_notifications" = "Puschbenachrichtigungen aktivieren";
+"settings_enter_validation_token_for" = "Eingabe Validierungstoken für %@:";
+"notification_settings_room_rule_title" = "Raum: '%@'";
+// Devices
+"device_details_title" = "Sitzungsinformation\n";
+"device_details_name" = "Öffentlicher Name\n";
+"device_details_identifier" = "ID\n";
+"device_details_last_seen" = "Zuletzt gesehen:\n";
+"device_details_last_seen_format" = "%@ @ %@\n";
+"device_details_rename_prompt_title" = "Sitzungsname";
+"device_details_rename_prompt_message" = "Der öffentliche Name einer Sitzung ist für Personen sichtbar, mit denen Sie kommunizieren";
+"device_details_delete_prompt_title" = "Authentifizierung";
+"device_details_delete_prompt_message" = "Diese Aktion erfordert zusätzliche Authentifizierung.\nBitte gib dein Passwort ein.";
+// Encryption information
+"room_event_encryption_info_title" = "Ende-zu-Ende Verschlüsselungsinformation\n\n";
+"room_event_encryption_info_event" = "Ereignis Information\n";
+"room_event_encryption_info_event_user_id" = "Benutzer-ID\n";
+"room_event_encryption_info_event_identity_key" = "Curve25519 Identitätsschlüssel\n";
+"room_event_encryption_info_event_fingerprint_key" = "Verlangter Ed25519-Fingerabdruck\n";
+"room_event_encryption_info_event_algorithm" = "Algorithmus\n";
+"room_event_encryption_info_event_session_id" = "Sitzungs-ID\n";
+"room_event_encryption_info_event_decryption_error" = "Entschlüsselungsfehler\n";
+"room_event_encryption_info_event_unencrypted" = "nicht verschlüsselt";
+"room_event_encryption_info_event_none" = "keine";
+"room_event_encryption_info_device" = "\nAbsendersitzungsinformation\n";
+"room_event_encryption_info_device_unknown" = "Unbekannte Sitzung\n";
+"room_event_encryption_info_device_name" = "Öffentlicher Name\n";
+"room_event_encryption_info_device_id" = "ID\n";
+"room_event_encryption_info_device_verification" = "Überprüfung\n";
+"room_event_encryption_info_device_fingerprint" = "Ed25519-Fingerabdruck\n";
+"room_event_encryption_info_device_verified" = "Überprüft";
+"room_event_encryption_info_device_not_verified" = "NICHT verifiziert";
+"room_event_encryption_info_device_blocked" = "auf schwarzer Liste";
+"room_event_encryption_info_verify" = "Überprüfe...";
+"room_event_encryption_info_unverify" = "Verifizierung widerrufen";
+"room_event_encryption_info_block" = "Blockieren";
+"room_event_encryption_info_unblock" = "Blockierung aufheben";
+"room_event_encryption_verify_title" = "Überprüfe Sitzung\n\n";
+"room_event_encryption_verify_message" = "Um zu prüfen, dass dieser Sitzung vertraut werden kann, kontaktiere bitte den Eigentümer über einen anderen Weg (z.B. in Person oder mit einem Telefonanruf) und frage ihn, ob der Schlüssel, den er in seinen Benutzereinstellungen für diese Sitzung sieht, dem folgenden Schlüssel entspricht:\n\n\tSitzungs-Name: %@\n\tSitzungs-ID: %@\n\tSitzungsschlüssel: %@\n\nWenn es übereinstimmt, die \"Überprüfe\" Schaltfläche drücken. Wenn nicht, dann hört jemand anderes diese Sitzung ab und du willst stattdessen vermutlich die Schaltfläche \"Blockieren\" drücken.\n\nIn Zukunft wird dieser Überprüfungsprozess ausgefeilter sein.";
+"room_event_encryption_verify_ok" = "Überprüfe";
+// Account
+"account_save_changes" = "Änderungen speichern";
+"account_link_email" = "Verbinde E-Mail-Adresse";
+"account_linked_emails" = "Verbundene E-Mail-Adressen";
+"account_email_validation_title" = "Verifizierung ausstehend";
+"account_email_validation_message" = "Bitte prüfe deine E-Mails und klicke auf den enthaltenen Link. Wenn dies erledigt ist, klicke auf \"Fortsetzen\".";
+"account_email_validation_error" = "Kann E-Mail-Adresse nicht verifizieren. Bitte prüfe deine E-Mails und klicke auf den enthaltenen Link. Wenn das erledigt ist, Schaltfläche \"Fortfahren\" drücken";
+"account_msisdn_validation_title" = "Verifizierung ausstehend";
+"account_msisdn_validation_message" = "Wir haben eine SMS mit einem Aktivierungscode gesendet. Bitte den Code unten eingeben.";
+"account_msisdn_validation_error" = "Kann Telefonnummer nicht verifizieren.";
+"account_error_display_name_change_failed" = "Änderung des Anzeigenamens fehlgeschlagen";
+"account_error_picture_change_failed" = "Änderung des Bildes fehlgeschlagen";
+"account_error_matrix_session_is_not_opened" = "Matrixsitzung ist nicht geöffnet";
+"account_error_email_wrong_title" = "Ungültige E-Mail-Adresse";
+"account_error_email_wrong_description" = "Sieht nicht aus wie eine gültige E-Mail-Adresse";
+"account_error_msisdn_wrong_title" = "Ungültige Telefonnummer";
+"account_error_msisdn_wrong_description" = "Sieht nicht wie eine valide Telefonnummer aus";
+"account_error_push_not_allowed" = "Benachrichtigungen nicht erlaubt";
+// Room creation
+"room_creation_name_title" = "Raumname:";
+"room_creation_name_placeholder" = "(z.B. MittagessenGruppe)";
+"room_creation_alias_title" = "Raumalias:";
+"room_creation_alias_placeholder" = "(z.B. #foo:example.org)";
+"room_creation_alias_placeholder_with_homeserver" = "(z.B. #foo%@)";
+"room_creation_participants_title" = "Teilnehmer:";
+"room_creation_participants_placeholder" = "(z.B. @laura:heimserver1; @thomas:heimserver2...)";
+// Room
+"room_please_select" = "Bitte wähle einen Raum";
+"room_error_join_failed_title" = "Konnte Raum nicht betreten";
+"room_error_join_failed_empty_room" = "Es ist aktuell nicht möglich einen leeren Raum zu betreten.";
+"room_error_name_edition_not_authorized" = "Du bist nicht authorisiert den Raumnamen zu ändern";
+"room_error_topic_edition_not_authorized" = "Du bist nicht authorisiert das Raumthema zu ändern";
+"room_error_cannot_load_timeline" = "Konnte Verlauf nicht laden";
+"room_error_timeline_event_not_found_title" = "Konnte Position im Verlauf nicht laden";
+"room_error_timeline_event_not_found" = "Konnte spezifischen Punkt im Verlauf dieses Raumes nicht finden";
+"room_left" = "Du hast den Raum verlassen";
+"room_left_for_dm" = "Du hast die Unterhaltung verlassen";
+"room_no_power_to_create_conference_call" = "Einladungsberechtigung benötigt, um Konferenz in diesem Raum zu starten";
+"room_no_conference_call_in_encrypted_rooms" = "Konferenzgespräche sind in verschlüsselten Räumen nicht unterstützt";
+// Reply to message
+"message_reply_to_sender_sent_an_image" = "sandte ein Bild.";
+"message_reply_to_sender_sent_a_video" = "sandte ein Video.";
+"message_reply_to_sender_sent_an_audio_file" = "sandte eine Audiodatei.";
+"message_reply_to_sender_sent_a_file" = "sandte eine Datei.";
+"message_reply_to_message_to_reply_to_prefix" = "Als Antwort auf";
+// Room members
+"room_member_ignore_prompt" = "Sicher, dass alle Nachrichten von diesem Benutzer versteckt werden sollen?";
+"room_member_power_level_prompt" = "Du kannst diese Änderung nicht rückgangig machen, weil du dem Benutzer die gleiche Berechtigungsstufe gibst, die du selbst hast.\nBist du sicher?";
+// Attachment
+"attachment_size_prompt" = "Möchtest du senden als:";
+"attachment_original" = "Originalgröße (%@)";
+"attachment_small" = "Klein (~%@)";
+"attachment_medium" = "Mittel (~%@)";
+"attachment_large" = "Groß (~%@)";
+"attachment_cancel_download" = "Herunterladen abbrechen?";
+"attachment_cancel_upload" = "Hochladen abbrechen?";
+"attachment_multiselection_size_prompt" = "Bilder senden als:";
+"attachment_multiselection_original" = "Originalgröße";
+"attachment_e2e_keys_file_prompt" = "Diese Datei enthält von einem Matrixclient exportierte Schlüssel.\nMöchtest du den Dateiinhalt sehen oder die Schlüssel importieren?";
+"attachment_e2e_keys_import" = "Importiere...";
+// Contacts
+"contact_mx_users" = "Matrixbenutzer";
+"contact_local_contacts" = "Lokale Kontakte";
+// Groups
+"group_invite_section" = "Einladungen";
+"group_section" = "Gruppen";
+// Search
+"search_no_results" = "Nichts gefunden";
+"search_searching" = "Suche wird durchgeführt...";
+// Time
+"format_time_s" = "s";
+"format_time_m" = "m";
+"format_time_h" = "h";
+"format_time_d" = "t";
+// E2E import
+"e2e_import_room_keys" = "Importiere Raumschlüssel";
+"e2e_import_prompt" = "Dieser Prozess erlaubt es dir, Schlüssel zu importieren, die du vorher von einem anderen Matrixclient exportiert hast. Du kannst anschließend alle Nachrichten entschlüsseln, die auch bereits der andere Client entschlüsseln konnte.\nDie Exportdatei ist mit einer Passphrase geschützt. Gib die Passphrase hier ein, um die Datei zu importieren.";
+"e2e_import" = "Importieren";
+"e2e_passphrase_enter" = "Passphrase eingeben";
+// E2E export
+"e2e_export_room_keys" = "Exportiere Raumschlüssel";
+"e2e_export_prompt" = "Dieser Prozeß erlaubt den Export von Schlüsseln, die du in verschlüsselten Räumen empfangen hast, in eine lokale Datei. Du kannst dann die Datei in einem anderen Matrixclient in Zukunft importieren, so dass dieser Client die Nachrichten auch entschlüsseln kann.\nDie exportierte Datei wird jedem der sie lesen kann erlauben, alle verschlüsselten Nachrichten sehen können, also verwahre die Datei sicher.";
+"e2e_export" = "Exportiere";
+"e2e_passphrase_confirm" = "Passphrase bestätigen";
+"e2e_passphrase_empty" = "Die Passphrase darf nicht leer sein";
+"e2e_passphrase_not_match" = "Passphrasen stimmen nicht überein";
+"e2e_passphrase_create" = "Passphrase erzeugen";
+// Others
+"user_id_title" = "Benutzer-ID:";
+"offline" = "offline";
+"unsent" = "Nicht gesendet";
+"error" = "Fehler";
+"error_common_message" = "Ein Fehler trat auf. Bitte später erneut probieren.";
+"not_supported_yet" = "Noch nicht unterstützt";
+"default" = "Standard";
+"private" = "Privat";
+"public" = "Öffentlich";
+"power_level" = "Berechtigungsstufe";
+"network_error_not_reachable" = "Bitte Netzwerkverbindung prüfen";
+"user_id_placeholder" = "z. B.: @thomas:heimserver";
+"ssl_homeserver_url" = "Heimserver URL: %@";
+// Permissions
+"camera_access_not_granted_for_call" = "Video-Anrufe benötigen Zugriff auf die Kamera, aber %@ hat keine Berechtigung";
+"microphone_access_not_granted_for_call" = "Anrufe benötigen Zugriff auf das Mikrofon, aber %@ hat keine Berechtigung";
+"local_contacts_access_not_granted" = "Finden von Benutzern in lokalen Kontakten benötigt Zugriff auf die Kontakte, aber %@ hat keine Berechtigung";
+"local_contacts_access_discovery_warning_title" = "Benutzer finden";
+"local_contacts_access_discovery_warning" = "Um Kontakte zu erkennen, die Matrix bereits verwenden, kann %@ E-Mail-Adressen und Telefonnummern in Ihrem Adressbuch an den von Ihnen ausgewählten Matrix-Identitätsserver senden. Sofern dies unterstützt wird, werden personenbezogene Daten vor dem Senden gehasht. Weitere Informationen finden Sie in den Datenschutzrichtlinien Ihres Identitätsservers.";
+// Country picker
+"country_picker_title" = "Wähle ein Land";
+// Language picker
+"language_picker_title" = "Wähle eine Sprache";
+"language_picker_default_language" = "Standard (%@)";
+"notice_room_invite" = "%@ hat %@ eingeladen";
+"notice_room_third_party_invite" = "%@ sendete eine Einladung an %@ den Raum zu betreten";
+"notice_room_third_party_invite_for_dm" = "%@ hat %@ eingeladen";
+"notice_room_third_party_registered_invite" = "%@ akzeptierte die Einladung für %@";
+"notice_room_third_party_revoked_invite" = "%@ hat die Einladung für %@, dem Raum beizutreten, zurückgezogen";
+"notice_room_third_party_revoked_invite_for_dm" = "%@ hat %@'s Einladung zurückgezogen";
+"notice_room_join" = "%@ betrat den Raum";
+"notice_room_leave" = "%@ hat den Raum verlassen";
+"notice_room_reject" = "%@ lehnte die Einladung ab";
+"notice_room_kick" = "%@ hat %@ entfernt";
+"notice_room_unban" = "%@ entsperrte %@";
+"notice_room_ban" = "%@ sperrte %@";
+"notice_room_withdraw" = "%@ hat %@s Einladung zurückgezogen";
+"notice_room_reason" = ". Grund: %@";
+"notice_avatar_url_changed" = "%@ hat den eigenen Avatar geändert";
+"notice_display_name_set" = "%@ setzte den Anzeigenamen auf %@";
+"notice_display_name_changed_from" = "%@ änderte den Anzeigenamen von %@ auf %@";
+"notice_display_name_removed" = "%@ hat den Anzeigenamen entfernt";
+"notice_topic_changed" = "%@ wechselte das Thema zu %@.";
+"notice_room_name_changed" = "%@ änderte den Raumnamen zu %@.";
+"notice_room_name_changed_for_dm" = "%@ änderte den Raumnamen zu %@.";
+"notice_placed_voice_call" = "%@ tätigte einen Sprachanruf";
+"notice_placed_video_call" = "%@ tätigte einen Videoanruf";
+"notice_answered_video_call" = "%@ hat den Anruf angenommen";
+"notice_ended_video_call" = "%@ hat den Anruf beendet";
+"notice_conference_call_request" = "%@ hat eine VoIP-Konferenz angefragt";
+"notice_conference_call_started" = "VoIP-Konferenz gestartet";
+"notice_conference_call_finished" = "VoIP-Konferenz beendet";
+// Notice Events with "You"
+"notice_room_invite_by_you" = "Du hast %@ eingeladen";
+"notice_room_invite_you" = "%@ hat Dich eingeladen";
+"notice_room_third_party_invite_by_you" = "Du hast an %@ eine Einladung gesendet dem Raum beizutreten";
+"notice_room_third_party_invite_by_you_for_dm" = "Du hast %@ eingeladen";
+"notice_room_third_party_registered_invite_by_you" = "Du hast die Einladung für %@ angenommen";
+"notice_room_third_party_revoked_invite_by_you" = "Du hast die Einladung dem Raum %@ beizutreten abgelehnt";
+"notice_room_third_party_revoked_invite_by_you_for_dm" = "Du hast die Einladung %@'s zurückgezogen";
+"notice_room_join_by_you" = "Du bist beigetreten";
+"notice_room_leave_by_you" = "Du bist ausgetreten";
+"notice_room_reject_by_you" = "Du hast die Einladung abgelehnt";
+"notice_room_kick_by_you" = "Du hast %@ entfernt";
+"notice_room_unban_by_you" = "Du hast %@ entbannt";
+"notice_room_ban_by_you" = "Du hast %@ gebannt";
+"notice_room_withdraw_by_you" = "Du hast die Einladung von %@ zurückgenommen";
+"notice_avatar_url_changed_by_you" = "Du hast dein Profilbild geändert";
+"notice_display_name_set_by_you" = "Du hast deinen Anzeigenamen auf %@ geändert";
+"notice_display_name_changed_from_by_you" = "Du hast deinen Anzeigenamen von %@ zu %@ geändert";
+"notice_display_name_removed_by_you" = "Du hast deinen Anzeigenamen entfernt";
+"notice_topic_changed_by_you" = "Du hast Das Thema zu %@ geändert.";
+"notice_room_name_changed_by_you" = "Du hast den Raumnamen zu %@ geändert.";
+"notice_room_name_changed_by_you_for_dm" = "Du hast den Namen zu %@ geändert.";
+"notice_placed_voice_call_by_you" = "Du hast einen Audioanruf gestartet";
+"notice_placed_video_call_by_you" = "Du hast einen Videoanruf gestartet";
+"notice_answered_video_call_by_you" = "Du hast den Anruf angenommen";
+"notice_ended_video_call_by_you" = "Du hast den Anruf beendet";
+"notice_conference_call_request_by_you" = "Du hast eine VoIP-Konferenz angefordert";
+"notice_room_name_removed_by_you" = "Du hast den Raumnamen entfernt";
+"notice_room_name_removed_by_you_for_dm" = "Du hast den Namen entfernt";
+"notice_room_topic_removed_by_you" = "Du hast das Raumthema entfernt";
+"notice_event_redacted_by_you" = " von dir";
+"notice_profile_change_redacted_by_you" = "Du hast dein Profil %@ aktualisiert";
+"notice_room_created_by_you" = "Du hast den Raum erstellt und konfiguriert.";
+"notice_room_created_by_you_for_dm" = "Du bist beigetreten.";
+"notice_encryption_enabled_ok_by_you" = "Du hast Ende-zu-Ende-Verschlüsselung aktiviert.";
+"notice_encryption_enabled_unknown_algorithm_by_you" = "Du hast Ende-zu-Ende-Verschlüsselung aktiviert (unbekannter Algorithmus %@).";
+"notice_redaction_by_you" = "Du hast ein Ereignis geschwärzt (ID: %@)";
+"notice_room_history_visible_to_anyone_by_you" = "Du hast den zukünftigen Nachrichtenverlauf für jeden sichtbar gemacht.";
+"notice_room_history_visible_to_members_by_you" = "Du hast den zukünftigen Nachrichtenverlauf für alle Mitglieder des Raums sichtbar gemacht.";
+"notice_room_history_visible_to_members_by_you_for_dm" = "Du hast den zukünftigen Nachrichtenverlauf für alle Mitglieder des Raums sichtbar gemacht.";
+"notice_room_history_visible_to_members_from_invited_point_by_you" = "Du hast den zukünftigen Nachrichtenverlauf für alle Mitglieder des Raums ab deren Teilnahme sichtbar gemacht.";
+"notice_room_history_visible_to_members_from_invited_point_by_you_for_dm" = "Du hast den zukünftigen Nachrichtenverlauf für alle sichtbar gemacht, sobald sie eingeladen werden.";
+"notice_room_history_visible_to_members_from_joined_point_by_you" = "Du hast den zukünftigen Nachrichtenverlauf für alle Mitglieder des Raums ab deren Teilnahme sichtbar gemacht.";
+"notice_room_history_visible_to_members_from_joined_point_by_you_for_dm" = "Du hast den zukünftigen Nachrichtenverlauf für alle Mitglieder des Raums sichtbar gemacht, ab deren Teilnahme.";
+// Room Screen
+
+// general errors
+
+// Home Screen
+
+// Last seen time
+
+// call events
+
+// titles
+
+// button names
+"ok" = "OK";
+"cancel" = "Abbruch";
+"save" = "Speichern";
+"leave" = "Verlassen";
+"send" = "Senden";
+"copy_button_name" = "Kopieren";
+"resend" = "Erneut senden";
+"redact" = "Entfernen";
+"share" = "Teilen";
+"set_power_level" = "Berechtigungslevel";
+"delete" = "Löschen";
+"view" = "Ansehen";
+// actions
+"action_logout" = "Abmelden";
+"create_room" = "Erstelle Raum";
+"login" = "Anmelden";
+"create_account" = "Erstelle Konto";
+"membership_invite" = "Eingeladen";
+"membership_leave" = "Verlassen";
+"membership_ban" = "Gesperrt";
+"num_members_one" = "%@ Benutzer";
+"num_members_other" = "%@ Benutzer";
+"invite" = "Einladen";
+"kick" = "Entfernen";
+"ban" = "Sperren";
+"unban" = "Entsperren";
+"message_unsaved_changes" = "Es gibt nicht gespeicherte Änderungen. Verlassen wird diese verwerfen.";
+// Login Screen
+"login_error_already_logged_in" = "Bereits angemeldet";
+"login_error_must_start_http" = "URL muss mit http[s]:// anfangen";
+// members list Screen
+
+// accounts list Screen
+
+// image size selection
+
+// invitation members list Screen
+
+// room creation dialog Screen
+
+// room info dialog Screen
+
+// room details dialog screen
+"room_details_title" = "Raumdetails";
+// contacts list screen
+"invitation_message" = "Ich würde gerne über Matrix mit dir chatten. Du kannst dich auf https://matrix.org darüber informieren.";
+// Settings screen
+"settings_title_config" = "Konfiguration";
+"settings_title_notifications" = "Benachrichtigungen";
+// Notification settings screen
+"notification_settings_disable_all" = "Alle Benachrichtigungen ausschalten";
+"notification_settings_enable_notifications" = "Benachrichtigungen einschalten";
+"notification_settings_enable_notifications_warning" = "Alle Benachrichtigungen sind momentan für alle Geräte ausgeschaltet.";
+"notification_settings_global_info" = "Benachrichtigungseinstellungen werden in deinem Benutzerkonto gespeichert und zwischen allen Clients die das unterstützen geteilt (inklusive Desktop Benachrichtigungen). \n\nRegeln werden der Reihe nach angewandt; die erste Regel, die zutrifft, bestimmt das Resultat für die Nachricht.\nPro-Wort-Benachrichtigungen sind wichtiger als Pro-Raum-Benachrichtigungen, die wichtiger sind wie Pro-Absender-Benachrichtigungen.\nBei mehrfachen Regeln des gleichen Typs wird die erste in der Liste die zutrifft angewendet.";
+"notification_settings_per_word_notifications" = "Pro-Wort-Benachrichtigungen";
+"notification_settings_per_word_info" = "Suchwörter ignorieren Groß-/Kleinschreibung und können ein *-Platzhalter enthalten. Beispiele:\nfoo findet den String foo umgeben durch Trennzeichen (Satzzeichen, Leerzeichen, Zeilenanfang/ende).\nfoo* findet Worte die mit foo beginnen.\n*foo* findet jedes Wort das foo an beliebiger Stelle enthält.";
+"notification_settings_always_notify" = "Immer benachrichtigen";
+"notification_settings_never_notify" = "Nie benachrichtigen";
+"notification_settings_word_to_match" = "übereinstimmende Wörter";
+"notification_settings_highlight" = "Hervorheben";
+"notification_settings_custom_sound" = "Individueller Klang";
+"notification_settings_per_room_notifications" = "Pro-Raum-Benachrichtigungen";
+"notification_settings_per_sender_notifications" = "Pro-Absender-Benachrichtigungen";
+"notification_settings_sender_hint" = "@benutzer:domaene.com";
+"notification_settings_select_room" = "Wähle einen Raum";
+"notification_settings_other_alerts" = "Andere Alarme";
+"notification_settings_contain_my_user_name" = "Klänge bei Nachrichten die meinen Benutzernamen enthalten";
+"notification_settings_contain_my_display_name" = "Klänge bei Nachrichten die meinen Anzeigenamen enthalten";
+"notification_settings_just_sent_to_me" = "Mich über gerade empfangene Nachrichten mit einem Klang informieren";
+"notification_settings_invite_to_a_new_room" = "Benachrichtige, wenn ich zu einem neuen Raum eingeladen werde";
+"notification_settings_people_join_leave_rooms" = "Benachrichtige, wenn Benutzer einen Raum betreten oder verlassen";
+"notification_settings_receive_a_call" = "Benachrichtige, wenn ich einen Anruf erhalte";
+"notification_settings_suppress_from_bots" = "Unterdrücke Benachrichtigungen von Bots";
+"notification_settings_by_default" = "Als Standard...";
+"notification_settings_notify_all_other" = "Benachrichtige für alle andereren Nachrichten/Räume";
+// gcm section
+"settings_config_home_server" = "Heimserver: %@";
+"settings_config_identity_server" = "Identitätsserver: %@";
+"settings_config_user_id" = "Benutzer-ID: %@";
+// Settings keys
+
+// call string
+"call_waiting" = "Warte...";
+"call_connecting" = "Verbinden…";
+"call_ended" = "Anruf beendet";
+"call_ring" = "Rufe an...";
+"incoming_video_call" = "Eingehender Videoanruf";
+"incoming_voice_call" = "Eingehender Sprachanruf";
+"call_invite_expired" = "Anrufeinladung abgelaufen";
+// unrecognized SSL certificate
+"ssl_trust" = "Vertrauensstellung";
+"ssl_logout_account" = "Abmelden";
+"ssl_remain_offline" = "Ignorieren";
+"ssl_fingerprint_hash" = "Fingerabdruck (%@):";
+"ssl_could_not_verify" = "Konnte die Identität des Servers nicht verifizieren.";
+"ssl_cert_not_trust" = "Das kann bedeuten, dass jemand den Datenverkehr mitliest, oder dass dein Gerät dem Zertifikat des Servers nicht vertraut.";
+"ssl_cert_new_account_expl" = "Wenn der Server Administrator gesagt hat, dass dies erwartet wird, stelle sicher, dass der Fingerabdruck unten dem vom Administrator mitgeteilten Fingerabdruck entspricht.";
+"ssl_unexpected_existing_expl" = "Das Zertifikat des Servers hat sich geändert, es ist nicht mehr das vertraute Zertifikat. Das ist SEHR UNGEWÖHNLICH! Es wird empfohlen das neue Zertifikat NICHT ZU AKZEPTIEREN.";
+"ssl_expected_existing_expl" = "Das Zertifikat des Servers hat sich geändert, es ist nicht mehr das vertraute Zertifikat. Der Server könnte sein Zertifikat erneuert haben. Kontaktiere den Serveradministrator um den Fingerabdruck zu überprüfen.";
+"ssl_only_accept" = "Akzeptiere AUSSCHLIESSLICH Zertifikate für die der Serveradministrator einen Fingerprint veröffentlicht hat.";
+"call_more_actions_transfer" = "Übertragung";
+"call_more_actions_audio_use_headset" = "Kopfhörer verwenden";
+"call_more_actions_change_audio_device" = "Audiogerät ändern";
+"call_more_actions_unhold" = "Fortsetzen";
+"call_more_actions_hold" = "Halten";
+"call_holded" = "Du hast den Anurf pausiert";
+"call_remote_holded" = "%@ hat den Anruf pausiert";
+"notice_declined_video_call_by_you" = "Du hast den Anruf abgelehnt";
+"notice_declined_video_call" = "%@ hat den Anruf abgelehnt";
+"resume_call" = "Fortsetzen";
+"call_more_actions_dialpad" = "Ziffernblatt";
+"call_more_actions_audio_use_device" = "Lautsprecher";
+"call_transfer_to_user" = "Durchstellen zu %@";
+"call_video_with_user" = "Videoanruf mit %@";
+"call_voice_with_user" = "Sprachanruf mit %@";
+"call_ringing" = "Läuten…";
+"e2e_passphrase_too_short" = "Passphrase zu kurz (Minimum sind %d Zeichen)";
+"call_consulting_with_user" = "Bei %@ anfragen";
+"microphone_access_not_granted_for_voice_message" = "%@ fehlt die Berechtigung, für Sprachnachrichten auf das Mikrofon zuzugreifen";
+"message_reply_to_sender_sent_a_voice_message" = "hat eine Sprachnachricht gesendet.";
+"attachment_size_prompt_title" = "Größe zum Senden";
+"attachment_large_with_resolution" = "Groß %@ (~%@)";
+"attachment_medium_with_resolution" = "Mittel %@ (~%@)";
+"attachment_small_with_resolution" = "Klein %@ (~%@)";
+"attachment_size_prompt_message" = "Du kannst dies in den Einstellungen ausschalten.";
diff --git a/Riot/Modules/MatrixKit/Assets/MatrixKitAssets.bundle/en.lproj/MatrixKit.strings b/Riot/Modules/MatrixKit/Assets/MatrixKitAssets.bundle/en.lproj/MatrixKit.strings
new file mode 100644
index 000000000..f9e40406a
--- /dev/null
+++ b/Riot/Modules/MatrixKit/Assets/MatrixKitAssets.bundle/en.lproj/MatrixKit.strings
@@ -0,0 +1,581 @@
+/*
+ Copyright 2015 OpenMarket Ltd
+ Copyright 2017 Vector Creations 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.
+ */
+
+/* *********************** */
+/* iOS specific */
+/* *********************** */
+
+"matrix" = "Matrix";
+
+// Login Screen
+"login_create_account" = "Create account:";
+"login_server_url_placeholder" = "URL (e.g. https://matrix.org)";
+"login_home_server_title" = "Homeserver URL:";
+"login_home_server_info" = "Your homeserver stores all your conversations and account data";
+"login_identity_server_title" = "Identity server URL:";
+"login_identity_server_info" = "Matrix provides identity servers to track which emails etc. belong to which Matrix IDs. Only https://matrix.org currently exists.";
+"login_user_id_placeholder" = "Matrix ID (e.g. @bob:matrix.org or bob)";
+"login_password_placeholder" = "Password";
+"login_optional_field" = "optional";
+"login_display_name_placeholder" = "Display name (e.g. Bob Obson)";
+"login_email_info" = "Specify an email address lets other users find you on Matrix more easily, and will give you a way to reset your password in the future.";
+"login_email_placeholder" = "Email address";
+"login_prompt_email_token" = "Please enter your email validation token:";
+"login_error_title" = "Login Failed";
+"login_error_no_login_flow" = "We failed to retrieve authentication information from this homeserver";
+"login_error_do_not_support_login_flows" = "Currently we do not support any or all login flows defined by this homeserver";
+"login_error_registration_is_not_supported" = "Registration is not currently supported";
+"login_error_forbidden" = "Invalid username/password";
+"login_error_unknown_token" = "The access token specified was not recognised";
+"login_error_bad_json" = "Malformed JSON";
+"login_error_not_json" = "Did not contain valid JSON";
+"login_error_limit_exceeded" = "Too many requests have been sent";
+"login_error_user_in_use" = "This user name is already used";
+"login_error_login_email_not_yet" = "The email link which has not been clicked yet";
+"login_use_fallback" = "Use fallback page";
+"login_leave_fallback" = "Cancel";
+"login_invalid_param" = "Invalid parameter";
+"register_error_title" = "Registration Failed";
+"login_error_forgot_password_is_not_supported" = "Forgot password is not currently supported";
+"login_mobile_device"="Mobile";
+"login_tablet_device"="Tablet";
+"login_desktop_device"="Desktop";
+"login_error_resource_limit_exceeded_title" = "Resource Limit Exceeded";
+"login_error_resource_limit_exceeded_message_default" = "This homeserver has exceeded one of its resource limits.";
+"login_error_resource_limit_exceeded_message_monthly_active_user" = "This homeserver has hit its Monthly Active User limit.";
+"login_error_resource_limit_exceeded_message_contact" = "\n\nPlease contact your service administrator to continue using this service.";
+"login_error_resource_limit_exceeded_contact_button" = "Contact Administrator";
+
+// Action
+"no" = "No";
+"yes" = "Yes";
+"abort" = "Abort";
+"back" = "Back";
+"close" = "Close";
+"continue" = "Continue";
+"discard" = "Discard";
+"dismiss" = "Dismiss";
+"retry" = "Retry";
+"sign_up" = "Sign up";
+"submit" = "Submit";
+"submit_code" = "Submit code";
+"set_power_level" = "Set Power Level";
+"set_default_power_level" = "Reset Power Level";
+"set_moderator" = "Set Moderator";
+"set_admin" = "Set Admin";
+"start_chat" = "Start Chat";
+"start_voice_call" = "Start Voice Call";
+"start_video_call" = "Start Video Call";
+"mention" = "Mention";
+"select_account" = "Select an account";
+"attach_media" = "Attach Media from Library";
+"capture_media" = "Take Photo/Video";
+"invite_user" = "Invite matrix User";
+"reset_to_default" = "Reset to default";
+"resend_message" = "Resend the message";
+"select_all" = "Select All";
+"cancel_upload" = "Cancel Upload";
+"cancel_download" = "Cancel Download";
+"show_details" = "Show Details";
+"answer_call" = "Answer Call";
+"reject_call" = "Reject Call";
+"end_call" = "End Call";
+"resume_call" = "Resume";
+"ignore" = "Ignore";
+"unignore" = "Unignore";
+
+// Events formatter
+"notice_avatar_changed_too" = "(avatar was changed too)";
+"notice_room_name_removed" = "%@ removed the room name";
+"notice_room_name_removed_for_dm" = "%@ removed the name";
+"notice_room_topic_removed" = "%@ removed the topic";
+"notice_event_redacted" = "";
+"notice_event_redacted_by" = " by %@";
+"notice_event_redacted_reason" = " [reason: %@]";
+"notice_profile_change_redacted" = "%@ updated their profile %@";
+"notice_room_created" = "%@ created and configured the room.";
+"notice_room_created_for_dm" = "%@ joined.";
+// Old
+"notice_room_join_rule" = "The join rule is: %@";
+// New
+"notice_room_join_rule_invite" = "%@ made the room invite only.";
+"notice_room_join_rule_invite_for_dm" = "%@ made this invite only.";
+"notice_room_join_rule_invite_by_you" = "You made the room invite only.";
+"notice_room_join_rule_invite_by_you_for_dm" = "You made this invite only.";
+"notice_room_join_rule_public" = "%@ made the room public.";
+"notice_room_join_rule_public_for_dm" = "%@ made this public.";
+"notice_room_join_rule_public_by_you" = "You made the room public.";
+"notice_room_join_rule_public_by_you_for_dm" = "You made this public.";
+"notice_room_power_level_intro" = "The power level of room members are:";
+"notice_room_power_level_intro_for_dm" = "The power level of members are:";
+"notice_room_power_level_acting_requirement" = "The minimum power levels that a user must have before acting are:";
+"notice_room_power_level_event_requirement" = "The minimum power levels related to events are:";
+"notice_room_aliases" = "The room aliases are: %@";
+"notice_room_aliases_for_dm" = "The aliases are: %@";
+"notice_room_related_groups" = "The groups associated with this room are: %@";
+"notice_encrypted_message" = "Encrypted message";
+"notice_encryption_enabled_ok" = "%@ turned on end-to-end encryption.";
+"notice_encryption_enabled_unknown_algorithm" = "%1$@ turned on end-to-end encryption (unrecognised algorithm %2$@).";
+"notice_image_attachment" = "image attachment";
+"notice_audio_attachment" = "audio attachment";
+"notice_video_attachment" = "video attachment";
+"notice_location_attachment" = "location attachment";
+"notice_file_attachment" = "file attachment";
+"notice_invalid_attachment" = "invalid attachment";
+"notice_unsupported_attachment" = "Unsupported attachment: %@";
+"notice_feedback" = "Feedback event (id: %@): %@";
+"notice_redaction" = "%@ redacted an event (id: %@)";
+"notice_error_unsupported_event" = "Unsupported event";
+"notice_error_unexpected_event" = "Unexpected event";
+"notice_error_unknown_event_type" = "Unknown event type";
+"notice_room_history_visible_to_anyone" = "%@ made future room history visible to anyone.";
+"notice_room_history_visible_to_members" = "%@ made future room history visible to all room members.";
+"notice_room_history_visible_to_members_for_dm" = "%@ made future messages visible to all room members.";
+"notice_room_history_visible_to_members_from_invited_point" = "%@ made future room history visible to all room members, from the point they are invited.";
+"notice_room_history_visible_to_members_from_invited_point_for_dm" = "%@ made future messages visible to everyone, from when they get invited.";
+"notice_room_history_visible_to_members_from_joined_point" = "%@ made future room history visible to all room members, from the point they joined.";
+"notice_room_history_visible_to_members_from_joined_point_for_dm" = "%@ made future messages visible to everyone, from when they joined.";
+"notice_crypto_unable_to_decrypt" = "** Unable to decrypt: %@ **";
+"notice_crypto_error_unknown_inbound_session_id" = "The sender's session has not sent us the keys for this message.";
+"notice_sticker" = "sticker";
+"notice_in_reply_to" = "In reply to";
+
+// room display name
+"room_displayname_empty_room" = "Empty room";
+"room_displayname_two_members" = "%@ and %@";
+"room_displayname_more_than_two_members" = "%@ and %@ others";
+"room_displayname_all_other_members_left" = "%@ (Left)";
+
+// Settings
+"settings" = "Settings";
+"settings_enable_inapp_notifications" = "Enable In-App notifications";
+"settings_enable_push_notifications" = "Enable push notifications";
+"settings_enter_validation_token_for" = "Enter validation token for %@:";
+
+"notification_settings_room_rule_title" = "Room: '%@'";
+
+// Devices
+"device_details_title" = "Session information\n";
+"device_details_name" = "Public Name\n";
+"device_details_identifier" = "ID\n";
+"device_details_last_seen" = "Last seen\n";
+"device_details_last_seen_format" = "%@ @ %@\n";
+"device_details_rename_prompt_title" = "Session Name";
+"device_details_rename_prompt_message" = "A session's public name is visible to people you communicate with";
+"device_details_delete_prompt_title" = "Authentication";
+"device_details_delete_prompt_message" = "This operation requires additional authentication.\nTo continue, please enter your password.";
+
+// Encryption information
+"room_event_encryption_info_title" = "End-to-end encryption information\n\n";
+"room_event_encryption_info_event" = "Event information\n";
+"room_event_encryption_info_event_user_id" = "User ID\n";
+"room_event_encryption_info_event_identity_key" = "Curve25519 identity key\n";
+"room_event_encryption_info_event_fingerprint_key" = "Claimed Ed25519 fingerprint key\n";
+"room_event_encryption_info_event_algorithm" = "Algorithm\n";
+"room_event_encryption_info_event_session_id" = "Session ID\n";
+"room_event_encryption_info_event_decryption_error" = "Decryption error\n";
+"room_event_encryption_info_event_unencrypted" = "unencrypted";
+"room_event_encryption_info_event_none" = "none";
+"room_event_encryption_info_device" = "\nSender session information\n";
+"room_event_encryption_info_device_unknown" = "unknown session\n";
+"room_event_encryption_info_device_name" = "Public Name\n";
+"room_event_encryption_info_device_id" = "ID\n";
+"room_event_encryption_info_device_verification" = "Verification\n";
+"room_event_encryption_info_device_fingerprint" = "Ed25519 fingerprint\n";
+"room_event_encryption_info_device_verified" = "Verified";
+"room_event_encryption_info_device_not_verified" = "NOT verified";
+"room_event_encryption_info_device_blocked" = "Blacklisted";
+"room_event_encryption_info_verify" = "Verify...";
+"room_event_encryption_info_unverify" = "Unverify";
+"room_event_encryption_info_block" = "Blacklist";
+"room_event_encryption_info_unblock" = "Unblacklist";
+"room_event_encryption_verify_title" = "Verify session\n\n";
+"room_event_encryption_verify_message" = "To verify that this session can be trusted, please contact its owner using some other means (e.g. in person or a phone call) and ask them whether the key they see in their User Settings for this session matches the key below:\n\n\tSession name: %@\n\tSession ID: %@\n\tSession key: %@\n\nIf it matches, press the verify button below. If it doesnt, then someone else is intercepting this session and you probably want to press the blacklist button instead.\n\nIn future this verification process will be more sophisticated.";
+"room_event_encryption_verify_ok" = "Verify";
+
+// Account
+"account_save_changes" = "Save changes";
+"account_link_email" = "Link Email";
+"account_linked_emails" = "Linked emails";
+
+"account_email_validation_title" = "Verification Pending";
+"account_email_validation_message" = "Please check your email and click on the link it contains. Once this is done, click continue.";
+"account_email_validation_error" = "Unable to verify email address. Please check your email and click on the link it contains. Once this is done, click continue";
+
+"account_msisdn_validation_title" = "Verification Pending";
+"account_msisdn_validation_message" = "We\'ve sent an SMS with an activation code. Please enter this code below.";
+"account_msisdn_validation_error" = "Unable to verify phone number.";
+
+"account_error_display_name_change_failed" = "Display name change failed";
+"account_error_picture_change_failed" = "Picture change failed";
+"account_error_matrix_session_is_not_opened" = "Matrix session is not opened";
+"account_error_email_wrong_title" = "Invalid Email Address";
+"account_error_email_wrong_description" = "This doesn't appear to be a valid email address";
+"account_error_msisdn_wrong_title" = "Invalid Phone Number";
+"account_error_msisdn_wrong_description" = "This doesn't appear to be a valid phone number";
+"account_error_push_not_allowed" = "Notifications not allowed";
+
+// Room creation
+"room_creation_name_title" = "Room name:";
+"room_creation_name_placeholder" = "(e.g. lunchGroup)";
+"room_creation_alias_title" = "Room alias:";
+"room_creation_alias_placeholder" = "(e.g. #foo:example.org)";
+"room_creation_alias_placeholder_with_homeserver" = "(e.g. #foo%@)";
+"room_creation_participants_title" = "Participants:";
+"room_creation_participants_placeholder" = "(e.g. @bob:homeserver1; @john:homeserver2...)";
+
+// Room
+"room_please_select" = "Please select a room";
+"room_error_join_failed_title" = "Failed to join room";
+"room_error_join_failed_empty_room" = "It is not currently possible to join an empty room.";
+"room_error_name_edition_not_authorized" = "You are not authorized to edit this room name";
+"room_error_topic_edition_not_authorized" = "You are not authorized to edit this room topic";
+"room_error_cannot_load_timeline" = "Failed to load timeline";
+"room_error_timeline_event_not_found_title" = "Failed to load timeline position";
+"room_error_timeline_event_not_found" = "The application was trying to load a specific point in this room's timeline but was unable to find it";
+"room_left" = "You left the room";
+"room_left_for_dm" = "You left";
+"room_no_power_to_create_conference_call" = "You need permission to invite to start a conference in this room";
+"room_no_conference_call_in_encrypted_rooms" = "Conference calls are not supported in encrypted rooms";
+
+// Reply to message
+"message_reply_to_sender_sent_an_image" = "sent an image.";
+"message_reply_to_sender_sent_a_video" = "sent a video.";
+"message_reply_to_sender_sent_an_audio_file" = "sent an audio file.";
+"message_reply_to_sender_sent_a_voice_message" = "sent a voice message.";
+"message_reply_to_sender_sent_a_file" = "sent a file.";
+"message_reply_to_message_to_reply_to_prefix" = "In reply to";
+
+// Room members
+"room_member_ignore_prompt" = "Are you sure you want to hide all messages from this user?";
+"room_member_power_level_prompt" = "You will not be able to undo this change as you are promoting the user to have the same power level as yourself.\nAre you sure?";
+
+// Attachment
+"attachment_size_prompt" = "Do you want to send as:";
+"attachment_size_prompt_title" = "Confirm size to send";
+"attachment_size_prompt_message" = "You can turn this off in settings.";
+"attachment_original" = "Actual Size (%@)";
+"attachment_small" = "Small (~%@)";
+"attachment_medium" = "Medium (~%@)";
+"attachment_large" = "Large (~%@)";
+"attachment_small_with_resolution" = "Small %@ (~%@)";
+"attachment_medium_with_resolution" = "Medium %@ (~%@)";
+"attachment_large_with_resolution" = "Large %@ (~%@)";
+"attachment_cancel_download" = "Cancel the download?";
+"attachment_cancel_upload" = "Cancel the upload?";
+"attachment_multiselection_size_prompt" = "Do you want to send images as:";
+"attachment_multiselection_original" = "Actual Size";
+"attachment_e2e_keys_file_prompt" = "This file contains encryption keys exported from a Matrix client.\nDo you want to view the file content or import the keys it contains?";
+"attachment_e2e_keys_import" = "Import...";
+"attachment_unsupported_preview_title" = "Unable to preview";
+"attachment_unsupported_preview_message" = "This file type is not supported.";
+
+// Contacts
+"contact_mx_users" = "Matrix Users";
+"contact_local_contacts" = "Local Contacts";
+
+// Groups
+"group_invite_section" = "Invites";
+"group_section" = "Groups";
+
+// Search
+"search_no_results" = "No Results";
+"search_searching" = "Search in progress...";
+
+// Time
+"format_time_s" = "s";
+"format_time_m" = "m";
+"format_time_h" = "h";
+"format_time_d" = "d";
+
+// E2E import
+"e2e_import_room_keys" = "Import room keys";
+"e2e_import_prompt" = "This process allows you to import encryption keys that you had previously exported from another Matrix client. You will then be able to decrypt any messages that the other client could decrypt.\nThe export file is protected with a passphrase. You should enter the passphrase here, to decrypt the file.";
+"e2e_import" = "Import";
+"e2e_passphrase_enter" = "Enter passphrase";
+
+// E2E export
+"e2e_export_room_keys" = "Export room keys";
+"e2e_export_prompt" = "This process allows you to export the keys for messages you have received in encrypted rooms to a local file. You will then be able to import the file into another Matrix client in the future, so that client will also be able to decrypt these messages.\nThe exported file will allow anyone who can read it to decrypt any encrypted messages that you can see, so you should be careful to keep it secure.";
+"e2e_export" = "Export";
+"e2e_passphrase_confirm" = "Confirm passphrase";
+"e2e_passphrase_empty" = "Passphrase must not be empty";
+"e2e_passphrase_too_short" = "Passphrase too short (It must be at a minimum %d characters in length)";
+"e2e_passphrase_not_match" = "Passphrases must match";
+"e2e_passphrase_create" = "Create passphrase";
+
+// Others
+"user_id_title" = "User ID:";
+"offline" = "offline";
+"unsent" = "Unsent";
+"error" = "Error";
+"error_common_message" = "An error occured. Please try again later.";
+"not_supported_yet" = "Not supported yet";
+"default" = "default";
+"private" = "Private";
+"public" = "Public";
+"power_level" = "Power Level";
+"network_error_not_reachable" = "Please check your network connectivity";
+"user_id_placeholder" = "ex: @bob:homeserver";
+"ssl_homeserver_url" = "Homeserver URL: %@";
+
+// Permissions
+"camera_access_not_granted_for_call" = "Video calls require access to the Camera but %@ doesn't have permission to use it";
+"microphone_access_not_granted_for_call" = "Calls require access to the Microphone but %@ doesn't have permission to use it";
+"local_contacts_access_not_granted" = "Users discovery from local contacts requires access to you contacts but %@ doesn't have permission to use it";
+
+"local_contacts_access_discovery_warning_title" = "Users discovery";
+"local_contacts_access_discovery_warning" = "To discover contacts already using Matrix, %@ can send email addresses and phone numbers in your address book to your chosen Matrix identity server. Where supported, personal data is hashed before sending - please check your identity server's privacy policy for more details.";
+
+"microphone_access_not_granted_for_voice_message" = "Voice messages require access to the Microphone but %@ doesn't have permission to use it";
+
+// Country picker
+"country_picker_title" = "Choose a country";
+
+// Language picker
+"language_picker_title" = "Choose a language";
+"language_picker_default_language" = "Default (%@)";
+
+/* -*-
+ Automatic localization for en
+
+ The following key/value pairs were extracted from the android i18n file:
+ /matrix-sdk/src/main/res/values/strings.xml.
+*/
+
+"notice_room_invite" = "%@ invited %@";
+"notice_room_third_party_invite" = "%@ sent an invitation to %@ to join the room";
+"notice_room_third_party_invite_for_dm" = "%@ invited %@";
+"notice_room_third_party_registered_invite" = "%@ accepted the invitation for %@";
+"notice_room_third_party_revoked_invite" = "%@ revoked the invitation for %@ to join the room";
+"notice_room_third_party_revoked_invite_for_dm" = "%@ revoked %@'s invitation";
+"notice_room_join" = "%@ joined";
+"notice_room_leave" = "%@ left";
+"notice_room_reject" = "%@ rejected the invitation";
+"notice_room_kick" = "%@ kicked %@";
+"notice_room_unban" = "%@ unbanned %@";
+"notice_room_ban" = "%@ banned %@";
+"notice_room_withdraw" = "%@ withdrew %@'s invitation";
+"notice_room_reason" = ". Reason: %@";
+"notice_avatar_url_changed" = "%@ changed their avatar";
+"notice_display_name_set" = "%@ set their display name to %@";
+"notice_display_name_changed_from" = "%@ changed their display name from %@ to %@";
+"notice_display_name_removed" = "%@ removed their display name";
+"notice_topic_changed" = "%@ changed the topic to \"%@\".";
+"notice_room_name_changed" = "%@ changed the room name to %@.";
+"notice_room_name_changed_for_dm" = "%@ changed the name to %@.";
+"notice_placed_voice_call" = "%@ placed a voice call";
+"notice_placed_video_call" = "%@ placed a video call";
+"notice_answered_video_call" = "%@ answered the call";
+"notice_ended_video_call" = "%@ ended the call";
+"notice_declined_video_call" = "%@ declined the call";
+"notice_conference_call_request" = "%@ requested a VoIP conference";
+"notice_conference_call_started" = "VoIP conference started";
+"notice_conference_call_finished" = "VoIP conference finished";
+
+// Notice Events with "You"
+"notice_room_invite_by_you" = "You invited %@";
+"notice_room_invite_you" = "%@ invited you";
+"notice_room_third_party_invite_by_you" = "You sent an invitation to %@ to join the room";
+"notice_room_third_party_invite_by_you_for_dm" = "You invited %@";
+"notice_room_third_party_registered_invite_by_you" = "You accepted the invitation for %@";
+"notice_room_third_party_revoked_invite_by_you" = "You revoked the invitation for %@ to join the room";
+"notice_room_third_party_revoked_invite_by_you_for_dm" = "You revoked %@'s invitation";
+"notice_room_join_by_you" = "You joined";
+"notice_room_leave_by_you" = "You left";
+"notice_room_reject_by_you" = "You rejected the invitation";
+"notice_room_kick_by_you" = "You kicked %@";
+"notice_room_unban_by_you" = "You unbanned %@";
+"notice_room_ban_by_you" = "You banned %@";
+"notice_room_withdraw_by_you" = "You withdrew %@'s invitation";
+"notice_avatar_url_changed_by_you" = "You changed your avatar";
+"notice_display_name_set_by_you" = "You set your display name to %@";
+"notice_display_name_changed_from_by_you" = "You changed your display name from %@ to %@";
+"notice_display_name_removed_by_you" = "You removed your display name";
+"notice_topic_changed_by_you" = "You changed the topic to \"%@\".";
+"notice_room_name_changed_by_you" = "You changed the room name to %@.";
+"notice_room_name_changed_by_you_for_dm" = "You changed the name to %@.";
+"notice_placed_voice_call_by_you" = "You placed a voice call";
+"notice_placed_video_call_by_you" = "You placed a video call";
+"notice_answered_video_call_by_you" = "You answered the call";
+"notice_ended_video_call_by_you" = "You ended the call";
+"notice_declined_video_call_by_you" = "You declined the call";
+"notice_conference_call_request_by_you" = "You requested a VoIP conference";
+"notice_room_name_removed_by_you" = "You removed the room name";
+"notice_room_name_removed_by_you_for_dm" = "You removed the name";
+"notice_room_topic_removed_by_you" = "You removed the topic";
+"notice_event_redacted_by_you" = " by you";
+"notice_profile_change_redacted_by_you" = "You updated your profile %@";
+"notice_room_created_by_you" = "You created and configured the room.";
+"notice_room_created_by_you_for_dm" = "You joined.";
+"notice_encryption_enabled_ok_by_you" = "You turned on end-to-end encryption.";
+"notice_encryption_enabled_unknown_algorithm_by_you" = "You turned on end-to-end encryption (unrecognised algorithm %@).";
+"notice_redaction_by_you" = "You redacted an event (id: %@)";
+"notice_room_history_visible_to_anyone_by_you" = "You made future room history visible to anyone.";
+"notice_room_history_visible_to_members_by_you" = "You made future room history visible to all room members.";
+"notice_room_history_visible_to_members_by_you_for_dm" = "You made future messages visible to all room members.";
+"notice_room_history_visible_to_members_from_invited_point_by_you" = "You made future room history visible to all room members, from the point they are invited.";
+"notice_room_history_visible_to_members_from_invited_point_by_you_for_dm" = "You made future messages visible to everyone, from when they get invited.";
+"notice_room_history_visible_to_members_from_joined_point_by_you" = "You made future room history visible to all room members, from the point they joined.";
+"notice_room_history_visible_to_members_from_joined_point_by_you_for_dm" = "You made future messages visible to everyone, from when they joined.";
+
+// Room Screen
+
+// general errors
+
+// Home Screen
+
+// Last seen time
+
+// call events
+
+/* -*-
+ Automatic localization for en
+
+ The following key/value pairs were extracted from the android i18n file:
+ /console/src/main/res/values/strings.xml.
+*/
+
+
+// titles
+
+// button names
+"ok" = "OK";
+"cancel" = "Cancel";
+"save" = "Save";
+"leave" = "Leave";
+"send" = "Send";
+"copy_button_name" = "Copy";
+"resend" = "Resend";
+"redact" = "Remove";
+"share" = "Share";
+"delete" = "Delete";
+"view" = "View";
+"rename" = "Rename";
+
+// actions
+"action_logout" = "Logout";
+"create_room" = "Create Room";
+"login" = "Login";
+"create_account" = "Create Account";
+"membership_invite" = "Invited";
+"membership_leave" = "Left";
+"membership_ban" = "Banned";
+"num_members_one" = "%@ user";
+"num_members_other" = "%@ users";
+"invite" = "Invite";
+"kick" = "Kick";
+"ban" = "Ban";
+"unban" = "Un-ban";
+"message_unsaved_changes" = "There are unsaved changes. Leaving will discard them.";
+
+// Login Screen
+"login_error_already_logged_in" = "Already logged in";
+"login_error_must_start_http" = "URL must start with http[s]://";
+
+// members list Screen
+
+// accounts list Screen
+
+// image size selection
+
+// invitation members list Screen
+
+// room creation dialog Screen
+
+// room info dialog Screen
+
+// room details dialog screen
+"room_details_title" = "Room Details";
+
+// contacts list screen
+"invitation_message" = "I\'d like to chat with you with matrix. Please, visit the website http://matrix.org to have more information.";
+
+// Settings screen
+"settings_title_config" = "Configuration";
+"settings_title_notifications" = "Notifications";
+
+// Notification settings screen
+"notification_settings_disable_all" = "Disable all notifications";
+"notification_settings_enable_notifications" = "Enable notifications";
+"notification_settings_enable_notifications_warning" = "All notifications are currently disabled for all devices.";
+"notification_settings_global_info" = "Notification settings are saved to your user account and are shared between all clients which support them (including desktop notifications).\n\nRules are applied in order; the first rule which matches defines the outcome for the message.\nSo: Per-word notifications are more important than per-room notifications which are more important than per-sender notifications.\nFor multiple rules of the same kind, the first one in the list that matches takes priority.";
+"notification_settings_per_word_notifications" = "Per-word notifications";
+"notification_settings_per_word_info" = "Words match case insensitively, and may include a * wildcard. So:\nfoo matches the string foo surrounded by word delimiters (e.g. punctuation and whitespace or start/end of line).\nfoo* matches any such word that begins foo.\n*foo* matches any such word which includes the 3 letters foo.";
+"notification_settings_always_notify" = "Always notify";
+"notification_settings_never_notify" = "Never notify";
+"notification_settings_word_to_match" = "word to match";
+"notification_settings_highlight" = "Highlight";
+"notification_settings_custom_sound" = "Custom sound";
+"notification_settings_per_room_notifications" = "Per-room notifications";
+"notification_settings_per_sender_notifications" = "Per-sender notifications";
+"notification_settings_sender_hint" = "\@user:domain.com";
+"notification_settings_select_room" = "Select a room";
+"notification_settings_other_alerts" = "Other Alerts";
+"notification_settings_contain_my_user_name" = "Notify me with sound about messages that contain my user name";
+"notification_settings_contain_my_display_name" = "Notify me with sound about messages that contain my display name";
+"notification_settings_just_sent_to_me" = "Notify me with sound about messages sent just to me";
+"notification_settings_invite_to_a_new_room" = "Notify me when I am invited to a new room";
+"notification_settings_people_join_leave_rooms" = "Notify me when people join or leave rooms";
+"notification_settings_receive_a_call" = "Notify me when I receive a call";
+"notification_settings_suppress_from_bots" = "Suppress notifications from bots";
+"notification_settings_by_default" = "By default...";
+"notification_settings_notify_all_other" = "Notify for all other messages/rooms";
+
+// gcm section
+"settings_config_home_server" = "Homeserver: %@";
+"settings_config_identity_server" = "Identity server: %@";
+"settings_config_user_id" = "User ID: %@";
+
+// Settings keys
+
+// call string
+"call_connecting" = "Connecting…";
+"call_ringing" = "Ringing…";
+"call_ended" = "Call ended";
+"incoming_video_call" = "Incoming Video Call";
+"incoming_voice_call" = "Incoming Voice Call";
+"call_invite_expired" = "Call Invite Expired";
+"call_remote_holded" = "%@ held the call";
+"call_holded" = "You held the call";
+"call_more_actions_hold" = "Hold";
+"call_more_actions_unhold" = "Resume";
+"call_more_actions_change_audio_device" = "Change Audio Device";
+"call_more_actions_audio_use_device" = "Device Speaker";
+"call_more_actions_transfer" = "Transfer";
+"call_more_actions_dialpad" = "Dial pad";
+"call_voice_with_user" = "Voice call with %@";
+"call_video_with_user" = "Video call with %@";
+"call_consulting_with_user" = "Consulting with %@";
+"call_transfer_to_user" = "Transfer to %@";
+
+// unrecognized SSL certificate
+"ssl_trust" = "Trust";
+"ssl_logout_account" = "Logout";
+"ssl_remain_offline" = "Ignore";
+"ssl_fingerprint_hash" = "Fingerprint (%@):";
+"ssl_could_not_verify" = "Could not verify identity of remote server.";
+"ssl_cert_not_trust" = "This could mean that someone is maliciously intercepting your traffic, or that your phone does not trust the certificate provided by the remote server.";
+"ssl_cert_new_account_expl" = "If the server administrator has said that this is expected, ensure that the fingerprint below matches the fingerprint provided by them.";
+"ssl_unexpected_existing_expl" = "The certificate has changed from one that was trusted by your phone. This is HIGHLY UNUSUAL. It is recommended that you DO NOT ACCEPT this new certificate.";
+"ssl_expected_existing_expl" = "The certificate has changed from a previously trusted one to one that is not trusted. The server may have renewed its certificate. Contact the server administrator for the expected fingerprint.";
+"ssl_only_accept" = "ONLY accept the certificate if the server administrator has published a fingerprint that matches the one above.";
+
+"auth_invalid_user_name" = "Invalid username";
+"auth_username_in_use" = "Username in use";
+"auth_reset_password_error_unauthorized" = "Unauthorized";
+"auth_reset_password_error_not_found" = "Not found";
diff --git a/Riot/Modules/MatrixKit/Assets/MatrixKitAssets.bundle/eo.lproj/MatrixKit.strings b/Riot/Modules/MatrixKit/Assets/MatrixKitAssets.bundle/eo.lproj/MatrixKit.strings
new file mode 100644
index 000000000..675f3b483
--- /dev/null
+++ b/Riot/Modules/MatrixKit/Assets/MatrixKitAssets.bundle/eo.lproj/MatrixKit.strings
@@ -0,0 +1,533 @@
+"notice_crypto_unable_to_decrypt" = "** Ne eblas malĉifri: %@ **";
+"notice_crypto_error_unknown_inbound_session_id" = "La salutaĵo de la sendanto ne sendis al ni la ŝlosilojn por tiu mesaĝo.";
+"notice_in_reply_to" = "Respondanta al";
+// room display name
+"room_displayname_empty_room" = "Malplena babilejo";
+"room_displayname_two_members" = "%@ kaj %@";
+"room_displayname_more_than_two_members" = "%@ kaj %@ aliaj";
+// Settings
+"settings" = "Agordoj";
+"message_reply_to_message_to_reply_to_prefix" = "Respondanta al";
+"room_event_encryption_verify_title" = "Kontroli salutaĵon\n\n";
+"back" = "Reiri";
+"close" = "Fermi";
+"continue" = "Daŭrigi";
+"retry" = "Reprovi";
+"cancel" = "Nuligi";
+"save" = "Konservi";
+"leave" = "Forlasi";
+"view" = "Vidi";
+"invite" = "Inviti";
+
+// members list Screen
+
+// accounts list Screen
+
+// image size selection
+
+// invitation members list Screen
+
+// room creation dialog Screen
+
+// room info dialog Screen
+
+// room details dialog screen
+"room_details_title" = "Detaloj pri ĉambro";
+"register_error_title" = "Registriĝo malsukcesis";
+"login_invalid_param" = "Nevalida parametro";
+"login_leave_fallback" = "Nuligi";
+"login_error_login_email_not_yet" = "La retpoŝta ligilo, kiun vi ankoraŭ ne klakis";
+"login_error_user_in_use" = "Tiu ĉi uzantonomo jam estas uzata";
+"login_error_limit_exceeded" = "Tro multaj petoj sendiĝis";
+"login_error_not_json" = "Ne enhavis validajn JSON-datumojn";
+"login_error_bad_json" = "Misformitaj JSON-datumoj";
+"login_error_unknown_token" = "La donita ĵetono de aliro ne rekoniĝis";
+"login_error_forbidden" = "Nevalidaj uzantonomo aŭ pasvorto";
+"login_error_registration_is_not_supported" = "Registriĝo ne estas nun subtenata";
+"login_error_do_not_support_login_flows" = "Nuntempe ni subtenas neniujn manierojn de salutado difinitajn de tiu ĉi hejmservilo";
+"login_error_no_login_flow" = "Ni malsukcesis akiri informojn pri aŭtentikigo de ĉi tiu hejmservilo";
+"login_error_title" = "Malsukcesis saluto";
+"login_prompt_email_token" = "Bonvolu enigi vian ĵetonon de retpoŝta validigo:";
+"login_email_placeholder" = "Retpoŝtadreso";
+"login_email_info" = "Specifu retpoŝtadresojn por ebligi vian pli facilan troviĝon de aliaj uzantoj, kaj ankaŭ por restarigi vian pasvorton okaze de perdo.";
+"login_display_name_placeholder" = "Prezenta nomo (ekz. Ivano Ĥlestakov)";
+"login_optional_field" = "malnepra";
+"login_password_placeholder" = "Pasvorto";
+"login_user_id_placeholder" = "Identigilo de Matrix (ekz. @ivano:matrix.org aŭ ivano)";
+"login_identity_server_info" = "Matrix havas identigajn servilojn por scii, kiuj retleteroj ktp. bezonas al kiuj identigiloj de Matrix. Nur https://matrix.org ekzistas nuntempe.";
+"login_identity_server_title" = "URL de identiga servilo:";
+"login_home_server_info" = "Via hejmservilo konservas ĉiujn viajn interparolojn kaj datumojn de konto";
+"login_home_server_title" = "URL de hejmservilo:";
+"login_server_url_placeholder" = "URL (ekz. https://matrix.org)";
+
+// Login Screen
+"login_create_account" = "Krei konton:";
+/* *********************** */
+/* iOS specific */
+/* *********************** */
+
+"matrix" = "Matrix";
+"login_desktop_device" = "Tabla komputilo";
+"login_tablet_device" = "Tabulkomputilo";
+"login_mobile_device" = "Telefono";
+"room_event_encryption_verify_message" = "Por certigi, ke ĉi tiu salutaĵo povas esti fidata, bonvolu kontakti ĝian posedanton per alia maniero (ekz. persone aŭ per telefona voko) kaj demandu, ĉu la ŝlosilo, kiun ĝi vidas en siaj agordoj de uzanto por ĉi tiu salutaĵo, akordas kun la ĉi-suba:\n\n\tNomo de salutaĵo: %@\n\tIdentigilo de salutaĵo: %@\n\tŜlosilo de salutaĵo: %@\n\nSe la ŝlosilo akordas, premu la kontrolan butonon ĉi-sube. Se ne, iu alia subaŭskultas la salutaĵon, kaj vi probable volas anstataŭe malpermesi ĝin.\n\nĈi tiu kontrola procedo plifaciliĝos estontece.";
+"room_event_encryption_info_device_fingerprint" = "Fingrospuro je Ed25519\n";
+"room_event_encryption_info_event_fingerprint_key" = "Asertita ŝlosilo de fingrospuro je Ed25519\n";
+"device_details_last_seen_format" = "%@ @ %@\n";
+"notice_feedback" = "Responda okazo (identigilo: %@): %@";
+"discard" = "Forĵeti";
+"abort" = "Ĉesigi";
+"login_use_fallback" = "Uzi repaŝan paĝon";
+"ssl_only_accept" = "Akceptu la atestilon NUR SE administranto de la servilo publikigis fingrospuron, kiu akordas kun la ĉi-supra.";
+"ssl_expected_existing_expl" = "La atestilo ŝanĝiĝis de atestilo antaŭe fidata al alia, kiu ne estas fidata. Eble la servilo renovigis sian atestilon. Kontaktu la administranton de la servilo por ricevi la atendindan fingrospuron.";
+"ssl_unexpected_existing_expl" = "La atestilo ŝanĝiĝis de tiu, kiun fidis via telefono. Tio estas TRE STRANGA. Oni rekomendas, ke vi NE AKCEPTU ĉi tiun novan atestilon.";
+"ssl_cert_new_account_expl" = "Se la administranto de la servilo diris, ke tio atendindas, certigu, ke la ĉi-suba fingrospuro akordas kun la fingrospuro donita de la administranto.";
+"ssl_cert_not_trust" = "Tio povus signifi, ke iu malice subaŭskultas vian rettrafikon, aŭ ke via telefono ne fidas la atestilon donitan de la fora servilo.";
+"ssl_could_not_verify" = "Ne povis kontroli identecon de fora servilo.";
+"ssl_fingerprint_hash" = "Fingrospuro (%@):";
+"ssl_remain_offline" = "Malatenti";
+"ssl_logout_account" = "Adiaŭi";
+
+// unrecognized SSL certificate
+"ssl_trust" = "Fidi";
+"call_transfer_to_user" = "Transdoni al %@";
+"call_consulting_with_user" = "Konsultante kun %@";
+"call_video_with_user" = "Vidvoko kun %@";
+"call_voice_with_user" = "Voĉvoko kun %@";
+"call_more_actions_dialpad" = "Ciferplato";
+"call_more_actions_transfer" = "Transdoni";
+"call_more_actions_audio_use_device" = "Soni aparate";
+"call_more_actions_audio_use_headset" = "Soni kapaŭskultile";
+"call_more_actions_change_audio_device" = "Ŝanĝi sonaparaton";
+"call_more_actions_unhold" = "Daŭrigi";
+"call_more_actions_hold" = "Paŭzigi";
+"call_holded" = "Vi paŭzigis la vokon";
+"call_remote_holded" = "%@ paŭzigis la vokon";
+"call_invite_expired" = "Inviti al voko atingis tempolimon";
+"incoming_voice_call" = "Envena voĉvoko";
+"incoming_video_call" = "Envena vidvoko";
+"call_ended" = "Voko finiĝis";
+"call_ringing" = "Sonorante…";
+
+// Settings keys
+
+// call string
+"call_connecting" = "Konektante…";
+"settings_config_user_id" = "Identigilo de uzanto: %@";
+"settings_config_identity_server" = "Identiga servilo: %@";
+
+// gcm section
+"settings_config_home_server" = "Hejmservilo: %@";
+"notification_settings_notify_all_other" = "Sciigi por ĉiuj aliaj mesaĝoj/ĉambroj";
+"notification_settings_by_default" = "Implicite…";
+"notification_settings_suppress_from_bots" = "Forteni sciigojn de robotoj";
+"notification_settings_receive_a_call" = "Sciigu min pri ricevitaj vokoj";
+"notification_settings_people_join_leave_rooms" = "Sciigu min pri aliĝoj al aŭ foriroj de ĉambroj";
+"notification_settings_invite_to_a_new_room" = "Sciigu min pri invitoj al novaj ĉambroj";
+"notification_settings_just_sent_to_me" = "Sciigu min per sono pri mesaĝoj, kiuj sendiĝis al mi individue";
+"notification_settings_contain_my_display_name" = "Sciigu min per sono pri mesaĝoj, kiuj enhavas mian prezentan nomon";
+"notification_settings_contain_my_user_name" = "Sciigu min per sono pri mesaĝoj, kiuj enhavas mian uzantonomon";
+"notification_settings_other_alerts" = "Aliaj atentigoj";
+"notification_settings_select_room" = "Elekti ĉambron";
+"notification_settings_sender_hint" = "@uzanto:retnomo.net";
+"notification_settings_per_sender_notifications" = "Sendintulaj sciigoj";
+"notification_settings_per_room_notifications" = "Ĉambraj sciigoj";
+"notification_settings_custom_sound" = "Propra sono";
+"notification_settings_highlight" = "Emfazo";
+"notification_settings_word_to_match" = "akordaj vortoj";
+"notification_settings_never_notify" = "Neniam sciigi";
+"notification_settings_always_notify" = "Ĉiam sciigi";
+"notification_settings_per_word_info" = "Vortoj akordas sendepende de grandeco, kaj povas enhavi la ĵokeron *. Sekve:\nekzemplo akordas kun la tekstoĉeno «ekzemplo» ĉirkaŭita de vortlimiloj (ekz. interpunkcio kaj spaco aŭ komenco/fino de linio).\nekzemplo* akordas kun ĉiu tia vorto, kiu komenciĝas per «ekzemplo».\n*ekzemplo* akordas kun ĉiu tia vorto, kiu enhavas la sinsekvon de literoj «ekzemplo».";
+"notification_settings_per_word_notifications" = "Vortaj sciigoj";
+"notification_settings_global_info" = "Agordoj pri sciigoj estas konservitaj en via konto de uzanto kaj havigitaj al ĉiuj klientoj, kiuj ilin subtenas (inkluzive sciigojn labortablajn).\n\nReguloj aplikiĝas laŭorde; la unua regulo, kiu akordas, difinas la rezulton por la mesaĝo.\nSekve: vortaj sciigoj estas pli gravaj ol ĉambraj sciigoj, kiuj estas pli gravaj ol sendintulaj sciigoj.\nPor pluraj reguloj samspecaj, la unua akorda en la listo estas prioritata.";
+"notification_settings_enable_notifications_warning" = "Ĉiuj sciigoj nun estas malŝaltitaj por ĉiuj aparatoj.";
+"notification_settings_enable_notifications" = "Ŝalti sciigojn";
+
+// Notification settings screen
+"notification_settings_disable_all" = "Malŝalti ĉiujn sciigojn";
+"settings_title_notifications" = "Sciigoj";
+
+// Settings screen
+"settings_title_config" = "Agordaro";
+
+// contacts list screen
+"invitation_message" = "Mi volus babili kun vi per Matrix. Bonvolu viziti la retpaĝon http://matrix.org por pliaj informoj.";
+"login_error_must_start_http" = "URL devas komenciĝi per http[s]://";
+
+// Login Screen
+"login_error_already_logged_in" = "Jam salutinta";
+"message_unsaved_changes" = "Restas nekonservitaj ŝanĝoj. Forlaso ilin forĵetos.";
+"unban" = "Malforbari";
+"ban" = "Forbari";
+"kick" = "Forpeli";
+"num_members_other" = "%@ uzantoj";
+"num_members_one" = "%@ uzanto";
+"membership_ban" = "Forbarita";
+"membership_leave" = "Foririnta";
+"membership_invite" = "Invitita";
+"create_account" = "Krei konton";
+"login" = "Saluti";
+"create_room" = "Krei ĉambron";
+
+// actions
+"action_logout" = "Adiaŭi";
+"delete" = "Forigi";
+"share" = "Havigi";
+"redact" = "Forigi";
+"resend" = "Resendi";
+"copy_button_name" = "Kopii";
+"send" = "Sendi";
+
+// Room Screen
+
+// general errors
+
+// Home Screen
+
+// Last seen time
+
+// call events
+
+/* -*-
+ Automatic localization for en
+
+ The following key/value pairs were extracted from the android i18n file:
+ /console/src/main/res/values/strings.xml.
+*/
+
+
+// titles
+
+// button names
+"ok" = "Bone";
+"notice_room_history_visible_to_members_from_joined_point_by_you" = "Vi videbligis estontan historion de la ĉambro al ĉiuj ĉambranoj, ekde ties aliĝo.";
+"notice_room_history_visible_to_members_from_invited_point_by_you_for_dm" = "Vi videbligis estontajn mesaĝojn al ĉiuj ĉambranoj, ekde ties invito.";
+"notice_room_history_visible_to_members_from_invited_point_by_you" = "Vi videbligis estontan historion de la ĉambro al ĉiuj ĉambranoj, ekde ties invito.";
+"notice_room_history_visible_to_members_by_you_for_dm" = "Vi videbligis estontajn mesaĝojn al ĉiuj ĉambranoj.";
+"notice_room_history_visible_to_members_from_joined_point_by_you_for_dm" = "Vi videbligis estontajn mesaĝojn al ĉiuj ĉambranoj, ekde ties aliĝo.";
+"notice_room_history_visible_to_members_by_you" = "Vi videbligis estontan historion de ĉambro al ĉiuj ĉambranoj.";
+"notice_room_history_visible_to_anyone_by_you" = "Vi videbligis estontan historion de ĉambro al ĉiu ajn.";
+"notice_encryption_enabled_unknown_algorithm_by_you" = "Vi ŝaltis tutvojan ĉifradon (nerekonita algoritmo %@).";
+"notice_encryption_enabled_ok_by_you" = "Vi ŝaltis tutvojan ĉifradon.";
+"notice_room_created_by_you_for_dm" = "Vi aliĝis.";
+"notice_room_created_by_you" = "Vi kreis kaj agordis la ĉambron.";
+"notice_profile_change_redacted_by_you" = "Vi ĝisdatigis vian profilon %@";
+"notice_event_redacted_by_you" = " de vi";
+"notice_room_topic_removed_by_you" = "Vi forigis la temon";
+"notice_room_name_removed_by_you_for_dm" = "Vi forigis la nomon";
+"notice_room_name_removed_by_you" = "Vi forigis nomon de la ĉambro";
+"notice_conference_call_request_by_you" = "Vi petis grupan vokon";
+"notice_declined_video_call_by_you" = "Vi rifuzis la vokon";
+"notice_ended_video_call_by_you" = "Vi finis la vokon";
+"notice_answered_video_call_by_you" = "Vi respondis la vokon";
+"notice_placed_video_call_by_you" = "Vi ekigis vidvokon";
+"notice_placed_voice_call_by_you" = "Vi ekigis voĉvokon";
+"notice_room_name_changed_by_you_for_dm" = "Vi ŝanĝis la nomon al %@.";
+"notice_room_name_changed_by_you" = "Vi ŝanĝis nomon de la ĉambro al %@.";
+"notice_topic_changed_by_you" = "Vi ŝanĝis la temon al «%@».";
+"notice_display_name_removed_by_you" = "Vi forigis vian prezentan nomon";
+"notice_display_name_changed_from_by_you" = "Vi ŝanĝis vian prezentan nomon de %@ al %@";
+"notice_display_name_set_by_you" = "Vi agordis vian prezentan nomon al %@";
+"notice_avatar_url_changed_by_you" = "Vi ŝanĝis vian profilbildon";
+"notice_room_withdraw_by_you" = "Vi nuligis la inviton por %@";
+"notice_room_ban_by_you" = "Vi forbaris uzanton %@";
+"notice_room_unban_by_you" = "Vi malforbaris uzanton %@";
+"notice_room_kick_by_you" = "Vi forpelis uzanton %@";
+"notice_room_reject_by_you" = "Vi rifuzis la inviton";
+"notice_room_leave_by_you" = "Vi foriris";
+"notice_room_join_by_you" = "Vi aliĝis";
+"notice_room_third_party_revoked_invite_by_you_for_dm" = "Vi nuligis la inviton por %@";
+"notice_room_third_party_revoked_invite_by_you" = "Vi nuligis la inviton al la ĉambro por %@";
+"notice_room_third_party_registered_invite_by_you" = "Vi akceptis la inviton por %@";
+"notice_room_third_party_invite_by_you_for_dm" = "Vi invitis uzanton %@";
+"notice_room_third_party_invite_by_you" = "Vi sendis inviton al la ĉambro al %@";
+"notice_room_invite_you" = "%@ invitis vin";
+
+// Notice Events with "You"
+"notice_room_invite_by_you" = "Vi invitis uzanton %@";
+"notice_conference_call_finished" = "Grupa voko finiĝis";
+"notice_conference_call_started" = "Grupa voko komenciĝis";
+"notice_conference_call_request" = "%@ petis grupan vokon";
+"notice_declined_video_call" = "%@ rifuzis la vokon";
+"notice_ended_video_call" = "%@ finis la vokon";
+"notice_answered_video_call" = "%@ respondis la vokon";
+"notice_placed_video_call" = "%@ ekigis vidvokon";
+"notice_placed_voice_call" = "%@ ekigis voĉvokon";
+"notice_room_name_changed_for_dm" = "%@ ŝanĝis la nomon al %@.";
+"notice_room_name_changed" = "%@ ŝanĝis nomon de la ĉambro al %@.";
+"notice_topic_changed" = "%@ ŝanĝis la temon al «%@».";
+"notice_display_name_removed" = "%@ forigis sian prezentan nomon";
+"notice_display_name_changed_from" = "%@ ŝanĝis sian prezentan nomon de %@ al %@";
+"notice_display_name_set" = "%@ ŝanĝis sian prezentan nomon al %@";
+"notice_avatar_url_changed" = "%@ ŝanĝis sian profilbildon";
+"notice_room_reason" = ". Kialo: %@";
+"notice_room_withdraw" = "%@ nuligis inviton por %@";
+"notice_room_ban" = "%@ forbaris uzanton %@";
+"notice_room_unban" = "%@ malforbaris uzanton %@";
+"notice_room_kick" = "%@ forpelis uzanton %@";
+"notice_room_reject" = "%@ rifuzis la inviton";
+"notice_room_leave" = "%@ foriris";
+"notice_room_join" = "%@ aliĝis";
+"notice_room_third_party_revoked_invite_for_dm" = "%@ nuligis la inviton por %@";
+"notice_room_third_party_revoked_invite" = "%@ nuligis la inviton al la ĉambro por %@";
+"notice_room_third_party_registered_invite" = "%@ akceptis la inviton por %@";
+"notice_room_third_party_invite_for_dm" = "%@ invitis uzanton %@";
+"notice_room_third_party_invite" = "%@ sendis inviton al la ĉambro al %@";
+
+/* -*-
+ Automatic localization for en
+
+ The following key/value pairs were extracted from the android i18n file:
+ /matrix-sdk/src/main/res/values/strings.xml.
+*/
+
+"notice_room_invite" = "%@ invitis uzanton %@";
+"language_picker_default_language" = "Implicita (%@)";
+
+// Language picker
+"language_picker_title" = "Elektu lingvon";
+
+// Country picker
+"country_picker_title" = "Elektu landon";
+"local_contacts_access_discovery_warning" = "Por trovi kontaktojn, kiuj jam uzas Matrix-on, %@ povas sendi retpoŝtadresojn kaj telefonnumerojn de via adresaro al via elektita identiga servilo de Matrix. Kiam eblas, personaj datumoj estas haketitaj antaŭ sendo – bonvolu kontroli la privatecan politikon de via identiga servilo por pliaj detaloj.";
+"local_contacts_access_discovery_warning_title" = "Trovado de uzantoj";
+"local_contacts_access_not_granted" = "Trovado de uzantoj per lokaj kontaktoj postulas aliron al viaj kontaktoj, sed %@ nun ne rajtas ilin uzi";
+"microphone_access_not_granted_for_call" = "Vokoj postulas aliron al la mikrofono, sed %@ nun ne rajtas ĝin uzi";
+
+// Permissions
+"camera_access_not_granted_for_call" = "Vidvokoj postulas aliron al la filmilo, sed %@ nun ne rajtas ĝin uzi";
+"ssl_homeserver_url" = "URL de hejmservilo: %@";
+"user_id_placeholder" = "ekz. @kjara:hejmservilo";
+"network_error_not_reachable" = "Bonvolu kontroli vian retkonekton";
+"power_level" = "Povnivelo";
+"public" = "Publika";
+"private" = "Privata";
+"default" = "implicita";
+"not_supported_yet" = "Ankoraŭ ne subtenata";
+"error_common_message" = "Io eraris. Bonvolu reprovi poste.";
+"error" = "Eraro";
+"unsent" = "Nesendita";
+"offline" = "eksterrete";
+
+// Others
+"user_id_title" = "Identigilo de uzanto:";
+"e2e_passphrase_create" = "Krei pasfrazon";
+"e2e_passphrase_not_match" = "Pasfrazoj devas akordi";
+"e2e_passphrase_empty" = "Pasfrazo maldevas esti malplena";
+"e2e_passphrase_confirm" = "Konfirmi pasfrazon";
+"e2e_export" = "Elporti";
+"e2e_export_prompt" = "Ĉi tiu procedo ebligas elporton de ŝlosiloj por mesaĝoj, kiujn vi ricevis en ĉifritaj ĉambroj, al loka dosiero. Poste vi povos enporti tiun dosieron en alian klienton de Matrix, por ke ankaŭ tiu kliento povu malĉifri la mesaĝojn.\nLa elportita dosiero ebligos legadon de videblaj ĉifritaj mesaĝoj al ĉiu, kiu povos ĝin legi; vi do provu ĝin teni en sekura loko.";
+
+// E2E export
+"e2e_export_room_keys" = "Elporti ĉambrajn ŝlosilojn";
+"e2e_passphrase_enter" = "Enigi pasfrazon";
+"e2e_import" = "Enporti";
+"e2e_import_prompt" = "Ĉi tiu procedo ebligas enporti ĉifrajn ŝlosilojn, kiujn vi antaŭe elportis el alia kliento de Matrix. Poste vi povos malĉifri ĉiujn mesaĝojn, kiujn ankaŭ la alia kliento povis malĉifri.\nLa elportitan dosieron protektas pasfrazo. Vi enigu la pasfrazon ĉi tien, por malĉifri la dosieron.";
+
+// E2E import
+"e2e_import_room_keys" = "Enporti ŝlosilojn de ĉambro";
+"format_time_d" = "j";
+"format_time_h" = "h";
+"format_time_m" = "m";
+
+// Time
+"format_time_s" = "s";
+"search_searching" = "Serĉo progresas…";
+
+// Search
+"search_no_results" = "Neniuj rezultoj";
+"group_section" = "Grupoj";
+
+// Groups
+"group_invite_section" = "Invitoj";
+"contact_local_contacts" = "Lokaj kontaktoj";
+
+// Contacts
+"contact_mx_users" = "Uzantoj de Matrix";
+"attachment_e2e_keys_import" = "Enporti…";
+"attachment_e2e_keys_file_prompt" = "Ĉi tiu dosiero enhavas ĉifrajn ŝlosilojn elportitajn el kliento de Matrix.\nĈu vi volas vidi enhavojn de la dosiero aŭ enporti la enhavatajn ŝlosilojn?";
+"attachment_multiselection_original" = "Originala grando";
+"attachment_multiselection_size_prompt" = "Ĉu vi volas sendi bildojn kiel:";
+"attachment_cancel_upload" = "Ĉu nuligi la alŝuton?";
+"attachment_cancel_download" = "Ĉu nuligi la elŝuton?";
+"attachment_large" = "Granda: %@";
+"attachment_medium" = "Meza: %@";
+"attachment_small" = "Malgranda: %@";
+"attachment_original" = "Originala: %@";
+
+// Attachment
+"attachment_size_prompt" = "Ĉu vi volas sendi en grando:";
+"room_member_power_level_prompt" = "Vi ne povos malfari ĉi tiun ŝanĝon, ĉar vi povigas la uzanton al la sama nivelo, kiun vi havas.\nĈu vi certas?";
+
+// Room members
+"room_member_ignore_prompt" = "Ĉu vi certe volas kaŝi ĉiujn mesaĝojn de tiu ĉi uzanto?";
+"message_reply_to_sender_sent_a_file" = "sendis dosieron.";
+"message_reply_to_sender_sent_an_audio_file" = "sendis sondosieron.";
+"message_reply_to_sender_sent_a_video" = "sendis filmon.";
+
+// Reply to message
+"message_reply_to_sender_sent_an_image" = "sendis bildon.";
+"room_no_conference_call_in_encrypted_rooms" = "Grupaj vokoj ne estas subtenataj en ĉifritaj ĉambroj";
+"room_no_power_to_create_conference_call" = "Vi bezonas permeson komenci grupan vokon en ĉi tiu ĉambro";
+"room_left_for_dm" = "Vi foriris";
+"room_left" = "Vi foriris de la ĉambro";
+"room_error_timeline_event_not_found" = "La aplikaĵo provis enlegi precizan punkton en la historio de ĉi tiu ĉambro, sed ne povis ĝin trovi";
+"room_error_timeline_event_not_found_title" = "Malsukcesis enlegi pozicion en historio";
+"room_error_cannot_load_timeline" = "Malsukcesis enlegi historion";
+"room_error_topic_edition_not_authorized" = "Vi ne rajtas redakti temon de ĉi tiu ĉambro";
+"room_error_name_edition_not_authorized" = "Vi ne rajtas redakti nomon de ĉi tiu ĉambro";
+"room_error_join_failed_empty_room" = "ANkoraŭ ne eblas ree aliĝi al malplena ĉambro.";
+"room_error_join_failed_title" = "Malsukcesis aliĝi al ĉambro";
+
+// Room
+"room_please_select" = "Bonvolu elekti ĉambron";
+"room_creation_participants_placeholder" = "(ekz. @kjara:hejmservilo1; @megumi:hejmservilo2…)";
+"room_creation_participants_title" = "Anoj:";
+"room_creation_alias_placeholder_with_homeserver" = "(ekz. #io%@)";
+"room_creation_alias_placeholder" = "(ekz. #io:ekzemplo.net)";
+"room_creation_alias_title" = "Kromnomo de ĉambro:";
+"room_creation_name_placeholder" = "(ekz. tagmanghGrupo)";
+
+// Room creation
+"room_creation_name_title" = "Nomo de ĉambro:";
+"account_error_push_not_allowed" = "Sciigoj ne estas permesitaj";
+"account_error_msisdn_wrong_description" = "Ĉi tio ne ŝajnas esti valida telefonnumero";
+"account_error_msisdn_wrong_title" = "Nevalida telefonnumero";
+"account_error_email_wrong_description" = "Ĉi tio ne ŝajnas esti valida retpoŝtadreso";
+"account_error_email_wrong_title" = "Nevalida retpoŝtadreso";
+"account_error_matrix_session_is_not_opened" = "Salutaĵo de Matrix ne estas malfermita";
+"account_error_picture_change_failed" = "Malsukcesis ŝanĝo de bildo";
+"account_error_display_name_change_failed" = "Malsukcesis ŝanĝo de prezenta nomo";
+"account_msisdn_validation_error" = "Ne povas kontroli vian telefonnumeron.";
+"account_msisdn_validation_message" = "Ni sendis telefonan mesaĝon kun aktiviga kodo. Bonvolu enigi la kodon ĉi-suben.";
+"account_msisdn_validation_title" = "Atendanta kontrolo";
+"account_email_validation_error" = "Ne povas kontroli retpoŝtadreson. Bonvolu kontroli vian retpoŝton kaj klaki al la enhavata ligilo. Tion farinte, klaku al «daŭrigi»";
+"account_email_validation_message" = "Bonvolu kontroli vian retpoŝton kaj trovi la enhavatan ligilon. Farinte tion, klaku al «daŭrigi».";
+"account_email_validation_title" = "Atendanta kontrolo";
+"account_linked_emails" = "Alligitaj retpoŝtadresoj";
+"account_link_email" = "Ligi al retpoŝtadreso";
+
+// Account
+"account_save_changes" = "Konservi ŝanĝojn";
+"room_event_encryption_verify_ok" = "Kontroli";
+"room_event_encryption_info_unverify" = "Malkontroli";
+"room_event_encryption_info_verify" = "Kontroli…";
+"room_event_encryption_info_device_not_verified" = "NE kontrolita";
+"room_event_encryption_info_device_verified" = "Kontrolita";
+"room_event_encryption_info_device_verification" = "Kontrolo\n";
+"room_event_encryption_info_device_id" = "Identigilo\n";
+"room_event_encryption_info_device_name" = "Publika nomo\n";
+"room_event_encryption_info_device_unknown" = "nekonata salutaĵo\n";
+"room_event_encryption_info_device" = "\nInformoj pri salutaĵo de sendinto\n";
+"room_event_encryption_info_event_none" = "neniu";
+"room_event_encryption_info_event_unencrypted" = "neĉifrita";
+"room_event_encryption_info_event_decryption_error" = "Malĉifra eraro\n";
+"room_event_encryption_info_event_session_id" = "Identigilo de salutaĵo\n";
+"room_event_encryption_info_event_algorithm" = "Algoritmo\n";
+"room_event_encryption_info_event_identity_key" = "Identiga ŝlosilo je Curve25519\n";
+"room_event_encryption_info_event_user_id" = "Identigilo de uzanto\n";
+"room_event_encryption_info_event" = "Informoj pri okazo\n";
+
+// Encryption information
+"room_event_encryption_info_title" = "Informoj pri tutvoja ĉifrado\n\n";
+"device_details_delete_prompt_message" = "Ĉi tio postulas plian aŭtentikigon.\nPor daŭrigi, bonvolu enigi vian pasvorton.";
+"device_details_delete_prompt_title" = "Aŭtentikigo";
+"device_details_rename_prompt_message" = "Publika nomo de salutaĵo estas videbla al ĉiu, kun kiu vi komunikas";
+"device_details_rename_prompt_title" = "Nomo de salutaĵo";
+"device_details_last_seen" = "Lastafoje vidita\n";
+"device_details_identifier" = "Identigilo\n";
+"device_details_name" = "Publika nomo\n";
+
+// Devices
+"device_details_title" = "Informoj pri salutaĵo\n";
+"notification_settings_room_rule_title" = "Ĉambro: «%@»";
+"settings_enter_validation_token_for" = "Enigi validigan pecon por %@:";
+"settings_enable_push_notifications" = "Ŝalti pasivajn sciigojn";
+"settings_enable_inapp_notifications" = "Ŝalti sciigojn en la aplikaĵo";
+"notice_sticker" = "glumarko";
+"notice_room_history_visible_to_members_from_joined_point_for_dm" = "%@ videbligis estontajn mesaĝojn al ĉiuj, ekde ties aliĝo.";
+"notice_room_history_visible_to_members_from_joined_point" = "%@ videbligis estontan historion de ĉambro al ĉiuj ĉambranoj, ekde ties aliĝo.";
+"notice_room_history_visible_to_members_from_invited_point_for_dm" = "%@ videbligis estontajn mesaĝojn al ĉiuj, ekde ties invito.";
+"notice_room_history_visible_to_members_from_invited_point" = "%@ videbligis estontan historion de ĉambro al ĉiuj ĉambranoj, ekde ties invito.";
+"notice_room_history_visible_to_members_for_dm" = "%@ videbligis estontajn mesaĝojn al ĉiuj ĉambranoj.";
+"notice_room_history_visible_to_members" = "%@ videbligis estontan historion de ĉambro al ĉiuj ĉambranoj.";
+"notice_room_history_visible_to_anyone" = "%@ videbligis estontan historion de la ĉambro al ĉiu ajn.";
+"notice_error_unknown_event_type" = "Nekonata speco de okazo";
+"notice_error_unexpected_event" = "Neatendita okazo";
+"notice_error_unsupported_event" = "Nesubtenata okazo";
+"notice_unsupported_attachment" = "Nesubtenata kunsendaĵo: %@";
+"notice_invalid_attachment" = "nevalida kunsendaĵo";
+"notice_file_attachment" = "dosiero kunsendita";
+"notice_location_attachment" = "loko kunsendita";
+"notice_video_attachment" = "filmo kunsendita";
+"notice_audio_attachment" = "sono kunsendita";
+"notice_image_attachment" = "bildo kunsendita";
+"notice_encryption_enabled_unknown_algorithm" = "%1$@ ŝaltis tutvojan ĉifradon (nerekonita algoritmo %2$@).";
+"notice_encryption_enabled_ok" = "%@ ŝaltis tutvojan ĉifradon.";
+"notice_encrypted_message" = "Ĉifrita mesaĝo";
+"notice_room_related_groups" = "Grupoj rilataj al ĉi tiu ĉambro estas: %@";
+"notice_room_aliases_for_dm" = "La kromnomoj estas: %@";
+"notice_room_aliases" = "Kromnomoj de la ĉamrbo estas: %@";
+"notice_room_power_level_event_requirement" = "La minimumaj povniveloj rilataj al okazoj estas:";
+"notice_room_power_level_acting_requirement" = "La minimuma povnivelo, kiun uzanto bezonas antaŭ agi, estas:";
+"notice_room_power_level_intro_for_dm" = "La povniveloj de ĉambranoj estas:";
+"notice_room_power_level_intro" = "La povniveloj de ĉambranoj estas:";
+"notice_room_join_rule_public_by_you_for_dm" = "Vi publikigis la individuan ĉambron.";
+"notice_room_join_rule_public_by_you" = "Vi publikigis la ĉambron.";
+"notice_room_join_rule_public_for_dm" = "%@ publikigis la individuan ĉambron.";
+"notice_room_join_rule_public" = "%@ publikigis la ĉambron.";
+"notice_room_join_rule_invite_by_you_for_dm" = "Vi ekpostulis inviton por aliĝoj.";
+"notice_room_join_rule_invite_by_you" = "Vi ekpostulis inviton por aliĝoj.";
+"notice_room_join_rule_invite_for_dm" = "%@ ekpostulis inviton por aliĝoj.";
+// New
+"notice_room_join_rule_invite" = "%@ ekpostulis inviton por aliĝoj.";
+// Old
+"notice_room_join_rule" = "La regulo de aliĝo estas: %@";
+"notice_room_created_for_dm" = "%@ aliĝis.";
+"notice_room_created" = "%@ kreis kaj agordis la ĉambron.";
+"notice_profile_change_redacted" = "%@ ĝisdatigis sian profilon %@";
+"notice_event_redacted_reason" = " [kialo: %@]";
+"notice_event_redacted_by" = " de %@";
+"notice_room_topic_removed" = "%@ forigis la temon";
+"notice_room_name_removed_for_dm" = "%@ forigis la nomon";
+"notice_room_name_removed" = "%@ forigis nomon de la ĉambro";
+
+// Events formatter
+"notice_avatar_changed_too" = "(ankaŭ profilbildo ŝanĝiĝis)";
+"unignore" = "Reatenti";
+"ignore" = "Malatenti";
+"resume_call" = "Daŭrigi";
+"end_call" = "Fini vokon";
+"reject_call" = "Rifuzi vokon";
+"answer_call" = "Respondi vokon";
+"show_details" = "Montri detalojn";
+"cancel_download" = "Nuligi elŝuton";
+"cancel_upload" = "Nuligi alŝuton";
+"select_all" = "Elekti ĉion";
+"resend_message" = "Resendi la mesaĝon";
+"reset_to_default" = "Restarigi implicitan";
+"invite_user" = "Inviton uzanton de Matrix";
+"capture_media" = "Foti/Filmi";
+"attach_media" = "Kunsendi vidaŭdaĵon el vidaŭdaĵujo";
+"select_account" = "Elekti konton";
+"mention" = "Mencii";
+"start_video_call" = "Komenci vidvokon";
+"start_voice_call" = "Komenci voĉvokon";
+"start_chat" = "Komenci babilon";
+"set_admin" = "Igi administranto";
+"set_moderator" = "Igi reguligisto";
+"set_default_power_level" = "Restarigi povnivelon";
+"set_power_level" = "Agordi povnivelon";
+"submit_code" = "Sendi kodon";
+"submit" = "Sendi";
+"sign_up" = "Registriĝi";
+"yes" = "Jes";
+
+// Action
+"no" = "Ne";
+"login_error_resource_limit_exceeded_contact_button" = "Kontakti administranton";
+"login_error_resource_limit_exceeded_message_contact" = "\n\nBonvolu kontakti la administranton de via servo por plu ĝin uzi.";
+"login_error_resource_limit_exceeded_message_monthly_active_user" = "Ĉi tiu hejmservilo atingis sian monatan limon de aktivaj uzantoj.";
+"login_error_resource_limit_exceeded_message_default" = "Ĉi tiu hejmservilo atingis unu el siaj rimedaj limoj.";
+"login_error_resource_limit_exceeded_title" = "Rimeda limo estas atingita";
+"login_error_forgot_password_is_not_supported" = "Forgesado de pasvorto nun ne estas subtenata";
diff --git a/Riot/Modules/MatrixKit/Assets/MatrixKitAssets.bundle/es.lproj/MatrixKit.strings b/Riot/Modules/MatrixKit/Assets/MatrixKitAssets.bundle/es.lproj/MatrixKit.strings
new file mode 100644
index 000000000..b8d1695da
--- /dev/null
+++ b/Riot/Modules/MatrixKit/Assets/MatrixKitAssets.bundle/es.lproj/MatrixKit.strings
@@ -0,0 +1,399 @@
+"matrix" = "Matrix";
+// Login Screen
+"login_create_account" = "Crear cuenta:";
+"login_server_url_placeholder" = "URL (p.ej. https://matrix.org)";
+"login_home_server_title" = "Servidor Local:";
+"login_home_server_info" = "Tu servidor local almacena todas tus conversaciones y los datos de tu cuenta";
+"login_identity_server_title" = "URL de servidor de identidad:";
+"login_identity_server_info" = "Matrix proporciona servidores de identidades para rastrear qué correos electrónicos, etc. pertenecen a qué IDs de Matrix. Actualmente solo existe https://matrix.org.";
+"login_user_id_placeholder" = "ID de Matrix (p.ej. @juan:matrix.org o juan)";
+"login_password_placeholder" = "Contraseña";
+"login_optional_field" = "opcional";
+"login_display_name_placeholder" = "Nombre público (p.ej. Juan Pérez)";
+"room_creation_participants_placeholder" = "(ej. @juan:servidordomestico1; @juan:servidordomestico2...)";
+"user_id_placeholder" = "ej: @juan:servidordomestico";
+"login_email_info" = "Especificar una dirección de correo electrónico permite que otros usuarios te encuentren en Matrix más fácilmente, y te dará una manera de restablecer tu contraseña en el futuro.";
+"login_email_placeholder" = "Dirección de correo electrónico";
+"login_prompt_email_token" = "Por favor ingresa tu código de validación de correo electrónico:";
+"login_error_title" = "No se pudo iniciar sesión";
+"login_error_no_login_flow" = "No pudimos recuperar la información de autenticación de este Servidor Local";
+"login_error_do_not_support_login_flows" = "Actualmente no admitimos cualquiera o todos los flujos de inicio de sesión definidos por este Servidor Local";
+"login_error_registration_is_not_supported" = "Actualmente no es posible registrarse";
+"login_error_forbidden" = "Nombre de usuario/contraseña inválidos";
+"login_error_unknown_token" = "No se reconoció el código de acceso especificado";
+"login_error_bad_json" = "JSON invalido";
+"login_error_not_json" = "No contenía un JSON válido";
+"login_error_limit_exceeded" = "Se enviaron demasiadas solicitudes";
+"login_error_user_in_use" = "Este nombre de usuario ya está en uso";
+"login_error_login_email_not_yet" = "Aún no se ha abierto el enlace del correo electrónico";
+"login_use_fallback" = "Utilizar la página de respaldo";
+"login_leave_fallback" = "Cancelar";
+"login_invalid_param" = "Parámetro inválido";
+"register_error_title" = "Falló el Registro";
+"login_error_forgot_password_is_not_supported" = "Actualmente no es posible restablecer la contraseña";
+"login_mobile_device" = "Móvil";
+"login_tablet_device" = "Tableta";
+"login_desktop_device" = "Escritorio";
+"login_error_resource_limit_exceeded_title" = "Límite de Recursos Excedido";
+"login_error_resource_limit_exceeded_message_default" = "Este servidor local ha excedido uno de sus límites de recursos.";
+"login_error_resource_limit_exceeded_message_monthly_active_user" = "Este servidor local ha alcanzado su límite Mensual de Usuarios Activos.";
+"login_error_resource_limit_exceeded_message_contact" = "\n\nPor favor, contacta al administrador de tu proveedor de servicio para continuar utilizando este servicio.";
+"login_error_resource_limit_exceeded_contact_button" = "Contacta al Administrador";
+// Action
+"no" = "No";
+"yes" = "Sí";
+"abort" = "Anular";
+"back" = "Atrás";
+"close" = "Cerrar";
+"continue" = "Continuar";
+"discard" = "Descartar";
+"dismiss" = "Ignorar";
+"retry" = "Re-intentar";
+"sign_up" = "Registrarse";
+"submit" = "Enviar";
+"submit_code" = "Enviar código";
+"set_default_power_level" = "Restablecer Nivel de Permisos";
+"set_moderator" = "Establecer Moderador";
+"set_admin" = "Establecer como Administrador";
+"start_chat" = "Iniciar Conversación";
+"start_voice_call" = "Iniciar Llamada de Voz";
+"start_video_call" = "Iniciar Llamada de Vídeo";
+"mention" = "Mencionar";
+"select_account" = "Seleccionar una cuenta";
+"attach_media" = "Adjuntar Medios desde Biblioteca";
+"capture_media" = "Tomar Foto/Vídeo";
+"invite_user" = "Invitar Usuario de Matrix";
+"reset_to_default" = "Restablecer valores por defecto";
+"resend_message" = "Reenviar el mensaje";
+"select_all" = "Seleccionar Todo";
+"cancel_upload" = "Cancelar Subida";
+"cancel_download" = "Cancelar Descarga";
+"show_details" = "Mostrar Detalles";
+"answer_call" = "Contestar Llamada";
+"reject_call" = "Rechazar Llamada";
+"end_call" = "Finalizar Llamada";
+"ignore" = "Ignorar";
+"unignore" = "Dejar de Ignorar";
+// Events formatter
+"notice_avatar_changed_too" = "(el avatar también se cambió)";
+"notice_room_name_removed" = "%@ eliminó el nombre de la sala";
+"notice_room_topic_removed" = "%@ eliminó el tema";
+"notice_event_redacted" = "";
+"notice_event_redacted_by" = " por %@";
+"notice_event_redacted_reason" = " [motivo: %@]";
+"notice_profile_change_redacted" = "%@ actualizó su perfil %@";
+"notice_room_created" = "%@ ha creado y configurado la sala.";
+"notice_room_join_rule" = "La regla para unirse es: %@";
+"notice_room_power_level_intro" = "El nivel de permisos de los miembros de la sala es:";
+"notice_room_power_level_acting_requirement" = "Los niveles de permisos mínimos que un usuario debe tener antes de actuar son:";
+"notice_room_power_level_event_requirement" = "Los niveles de permisos mínimos relacionados con eventos son:";
+"notice_room_aliases" = "Los aliases de la sala son: %@";
+"notice_room_related_groups" = "Los grupos asociados a esta sala son: %@";
+"notice_encrypted_message" = "Mensaje cifrado";
+"notice_encryption_enabled" = "%@ activó el cifrado de extremo a extremo (algoritmo %@)";
+"notice_image_attachment" = "imagen adjunta";
+"notice_audio_attachment" = "audio adjunto";
+"notice_video_attachment" = "vídeo adjunto";
+"notice_location_attachment" = "ubicación adjunta";
+"notice_file_attachment" = "archivo adjunto";
+"notice_invalid_attachment" = "archivo adjunto inválido";
+"notice_unsupported_attachment" = "No se admite el archivo adjunto: %@";
+"notice_feedback" = "Evento de retroalimentación (id: %@): %@";
+"notice_redaction" = "%@ redactó un evento (id: %@)";
+"notice_error_unsupported_event" = "No se admite el evento";
+"notice_error_unexpected_event" = "Evento inesperado";
+"notice_error_unknown_event_type" = "Tipo de evento desconocido";
+"notice_room_history_visible_to_anyone" = "%@ hizo visible el historial futuro de la sala para cualquier persona.";
+"notice_room_history_visible_to_members" = "%@ hizo visible el historial futuro de la sala para todos los miembros de la sala.";
+"notice_room_history_visible_to_members_from_invited_point" = "%@ hizo visible el historial futuro de la sala para todos los miembros de la sala, desde el momento en que son invitados.";
+"notice_room_history_visible_to_members_from_joined_point" = "%@ hizo visible el historial futuro de la sala para todos los miembros de la sala, desde el momento en que se unieron.";
+"notice_crypto_unable_to_decrypt" = "** No es posible descifrar: %@ **";
+"notice_crypto_error_unknown_inbound_session_id" = "La sesión emisor no nos ha enviado las claves para este mensaje.";
+"notice_sticker" = "pegatina";
+"notice_in_reply_to" = "En respuesta a";
+// room display name
+"room_displayname_empty_room" = "Sala vacía";
+"room_displayname_two_members" = "%@ y %@";
+"room_displayname_more_than_two_members" = "%@ y otros %u";
+// Settings
+"settings" = "Ajustes";
+"settings_enable_inapp_notifications" = "Habilitar notificaciones de la aplicación";
+"settings_enable_push_notifications" = "Habilitar notificaciones push";
+"settings_enter_validation_token_for" = "Ingresar el código de validación para %@:";
+"notification_settings_room_rule_title" = "Sala: '%@'";
+// Devices
+"device_details_title" = "Información de sesión\n";
+"device_details_name" = "Nombre público\n";
+"device_details_identifier" = "ID\n";
+"device_details_last_seen" = "Visto por última vez\n";
+"device_details_last_seen_format" = "%@ @ %@\n";
+"device_details_rename_prompt_message" = "Un nombre público de una sesión es invisible para personas con que Ud. se comunica";
+"device_details_delete_prompt_title" = "Autenticación";
+"device_details_delete_prompt_message" = "Esta operación requiere autenticación adicional.\nPara continuar, ingresa tu contraseña por favor.";
+// Encryption information
+"room_event_encryption_info_title" = "Información de cifrado de extremo a extremo\n\n";
+"room_event_encryption_info_event" = "Información de evento\n";
+"room_event_encryption_info_event_user_id" = "ID de Usuario\n";
+"room_event_encryption_info_event_identity_key" = "Clave de identidad Curve25519\n";
+"room_event_encryption_info_event_fingerprint_key" = "Clave de huella digital Ed25519 reclamada\n";
+"room_event_encryption_info_event_algorithm" = "Algoritmo\n";
+"room_event_encryption_info_event_session_id" = "ID de Sesión\n";
+"room_event_encryption_info_event_decryption_error" = "Error de descifrado\n";
+"room_event_encryption_info_event_unencrypted" = "sin cifrar";
+"room_event_encryption_info_event_none" = "ninguno";
+"room_event_encryption_info_device" = "\nInformación de la sesión emisora\n";
+"room_event_encryption_info_device_unknown" = "sesión desconocida\n";
+"room_event_encryption_info_device_name" = "Nombre público\n";
+"room_event_encryption_info_device_id" = "ID\n";
+"room_event_encryption_info_device_verification" = "Verificación\n";
+"room_event_encryption_info_device_fingerprint" = "huella digital Ed25519\n";
+"room_event_encryption_info_device_verified" = "Verificado";
+"room_event_encryption_info_device_not_verified" = "SIN verificar";
+"room_event_encryption_info_device_blocked" = "Prohibido";
+"room_event_encryption_info_verify" = "Verificar...";
+"room_event_encryption_info_unverify" = "Anular Verificación";
+"room_event_encryption_info_block" = "Prohibir";
+"room_event_encryption_info_unblock" = "Dejar de Prohibir";
+"room_event_encryption_verify_title" = "Verificar sesión\n\n";
+"room_event_encryption_verify_message" = "Para verificar que este sesión es confiable, por favor contactar a su propietario por algún otro medio (ej. cara a cara o por teléfono) y pregúntale si la clave que ve en sus Ajustes de Usuario para este dispositivo coincide con la clave a continuación:\n\n\tNombre de sesión: %@\n\tID de sesión: %@\n\tClave de sesión: %@\n\nSi coincide, oprime el botón de verificar a continuación. Si no coincide, entonces alguien está interceptando este sesión y probablemente prefieras oprimir el botón de prohibir.\n\nEn el futuro, este proceso de verificación será más sofisticado.";
+"room_event_encryption_verify_ok" = "Verificar";
+// Account
+"account_save_changes" = "Guardar cambios";
+"account_link_email" = "Añadir Correo Electrónico";
+"account_linked_emails" = "Correos electrónicos añadidos";
+"account_email_validation_title" = "Verificación Pendiente";
+"account_email_validation_message" = "Por favor, consulta tu correo electrónico y haz clic en el enlace que contiene. Una vez hecho esto, haz clic en continuar.";
+"account_email_validation_error" = "No es posible verificar la dirección de correo electrónico. Por favor, consulta tu correo electrónico y haz clic en el enlace que contiene. Una vez hecho esto, haz clic en continuar";
+"account_msisdn_validation_title" = "Verificación Pendiente";
+"account_msisdn_validation_message" = "Hemos enviado un SMS con un código de activación. Por favor, ingresa este código a continuación.";
+"account_msisdn_validation_error" = "No es posible verificar el número telefónico.";
+"account_error_display_name_change_failed" = "El cambio de nombre público falló";
+"account_error_picture_change_failed" = "El cambio de imagen falló";
+"account_error_matrix_session_is_not_opened" = "La sesión de Matrix no está abierta";
+"account_error_email_wrong_title" = "Dirección de Correo Electrónico Inválida";
+"account_error_email_wrong_description" = "Esto no parece ser una dirección de correo electrónico válida";
+"account_error_msisdn_wrong_title" = "Número Telefónico Inválido";
+"account_error_msisdn_wrong_description" = "Esto no parece ser un número telefónico válido";
+// Room creation
+"room_creation_name_title" = "Nombre de sala:";
+"room_creation_name_placeholder" = "(ej. grupoDeAlmuerzo)";
+"room_creation_alias_title" = "Alias de sala:";
+"room_creation_alias_placeholder" = "(ej. #foo:ejemplo.org)";
+"room_creation_alias_placeholder_with_homeserver" = "(ej. #foo%@)";
+"room_creation_participants_title" = "Participantes:";
+// Room
+"room_please_select" = "Por favor selecciona una sala";
+"room_error_join_failed_title" = "No se pudo unir a la sala";
+"room_error_join_failed_empty_room" = "Ahora mismo no es posible volver a unirse a una sala vacía.";
+"room_error_name_edition_not_authorized" = "No estás autorizado a editar el nombre de esta sala";
+"room_error_topic_edition_not_authorized" = "No estás autorizado a editar el tema de esta sala";
+"room_error_cannot_load_timeline" = "No se pudo cargar la línea de tiempo";
+"room_error_timeline_event_not_found_title" = "No se pudo cargar la posición en la línea de tiempo";
+"room_error_timeline_event_not_found" = "La aplicación estaba intentando cargar un momento específico en la línea de tiempo de esta sala pero pudo encontrarlo";
+"room_left" = "Saliste de la sala";
+"room_no_power_to_create_conference_call" = "Necesitas permiso para invitar a iniciar una conferencia en esta sala";
+"room_no_conference_call_in_encrypted_rooms" = "No se admiten llamadas de conferencia en salas cifradas";
+// Reply to message
+"message_reply_to_sender_sent_an_image" = "envió una imagen.";
+"message_reply_to_sender_sent_a_video" = "envió un vídeo.";
+"message_reply_to_sender_sent_an_audio_file" = "envió un archivo de audio.";
+"message_reply_to_sender_sent_a_file" = "envió un archivo.";
+"message_reply_to_message_to_reply_to_prefix" = "En respuesta a";
+// Room members
+"room_member_ignore_prompt" = "¿Seguro que quieres ocultar todos los mensajes de este usuario?";
+"room_member_power_level_prompt" = "No podrás deshacer este cambio porque estás promoviendo al usuario para tener el mismo nivel de autoridad que tú.\n¿Estás seguro?";
+// Attachment
+"attachment_size_prompt" = "Quieres enviar como:";
+"attachment_original" = "Tamaño real: %@";
+"attachment_small" = "Pequeño (~%@)";
+"attachment_medium" = "Mediano (~%@)";
+"attachment_large" = "Grande (~%@)";
+"attachment_cancel_download" = "¿Cancelar la descarga?";
+"attachment_cancel_upload" = "¿Cancelar la subida?";
+"attachment_multiselection_size_prompt" = "Quieres enviar imágenes como:";
+"attachment_multiselection_original" = "Tamaño Real";
+"attachment_e2e_keys_file_prompt" = "Este archivo contiene claves de cifrado exportadas de un cliente de Matrix.\n¿Quieres ver el contenido del archivo o importar las claves que contiene?";
+"attachment_e2e_keys_import" = "Importar...";
+// Contacts
+"contact_mx_users" = "Usuarios de Matrix";
+"contact_local_contacts" = "Contactos Locales";
+// Groups
+"group_invite_section" = "Invitaciones";
+"group_section" = "Grupos";
+// Search
+"search_no_results" = "No Hay Resultados";
+"search_searching" = "Búsqueda en curso...";
+// Time
+"format_time_s" = "s";
+"format_time_m" = "m";
+"format_time_h" = "h";
+"format_time_d" = "d";
+// E2E import
+"e2e_import_room_keys" = "Importar claves de sala";
+"e2e_import_prompt" = "Este proceso permite que importes claves de cifrado que hayas exportado previamente desde otro cliente de Matrix. Luego, podrás descifrar todos los mensajes que el otro cliente podía descifrar.\nEl archivo de exportación está protegido con una frase de contraseña. Debes ingresar la frase de contraseña aquí para descifrar el archivo.";
+"e2e_import" = "Importar";
+"e2e_passphrase_enter" = "Ingresar frase de contraseña";
+// E2E export
+"e2e_export_room_keys" = "Exportar claves de sala";
+"e2e_export_prompt" = "Este proceso te permite exportar las claves de los mensajes que hayas recibido en salas cifradas a un archivo local. Luego, podrás importar el archivo a otro cliente de Matrix, para que ese cliente también pueda descifrar estos mensajes.\nEl archivo exportado permitirá que cualquier persona que pueda leerlo descifre todos los mensajes cifrados que tú puedas ver, así que debes tener cuidado de mantenerlo seguro.";
+"e2e_export" = "Exportar";
+"e2e_passphrase_confirm" = "Confirmar frase de contraseña";
+"e2e_passphrase_empty" = "La frase de contraseña no debe estar vacía";
+"e2e_passphrase_not_match" = "Las frases de contraseña deben coincidir";
+// Others
+"user_id_title" = "ID de Usuario:";
+"offline" = "desconectado";
+"unsent" = "No enviado";
+"error" = "Error";
+"error_common_message" = "Ocurrió un error. Por favor inténtalo de nuevo más tarde.";
+"not_supported_yet" = "Aún no es posible";
+"default" = "por defecto";
+"private" = "Privado";
+"public" = "Público";
+"power_level" = "Nivel de Autoridad";
+"network_error_not_reachable" = "Por favor comprueba la conectividad de tu red";
+"ssl_homeserver_url" = "URL del Servidor Local: %@";
+// Permissions
+"camera_access_not_granted_for_call" = "Las llamadas de vídeo requieren acceso a la Cámara pero %@ no tiene permiso para utilizarla";
+"microphone_access_not_granted_for_call" = "Las llamadas requieren acceso al Micrófono pero %@ no tiene permiso para utilizarlo";
+"local_contacts_access_not_granted" = "El descubrimiento de usuarios desde los contactos locales requiere acceso a tus contactos pero %@ no tiene permiso para utilizarlo";
+"local_contacts_access_discovery_warning_title" = "Descubrimiento de usuarios";
+"local_contacts_access_discovery_warning" = "Para descubrir contactos que ya usan Matrix, %@ puede enviar correos electrónicos y números telefónicos desde tus Contactos hacia el Servidor de Identidades de Matrix. Cuando es posible, los datos personales se procesan antes de enviarlos. Por favor, compruebe la política de privacidad de su servidor de identidad para más detalles.";
+// Country picker
+"country_picker_title" = "Elige un país";
+// Language picker
+"language_picker_title" = "Elige un idioma";
+"language_picker_default_language" = "Por Defecto (%@)";
+"notice_room_invite" = "%@ invitó a %@";
+"notice_room_third_party_invite" = "%@ invitó a %@ a unirse a la sala";
+"notice_room_third_party_registered_invite" = "%@ aceptó la invitación para %@";
+"notice_room_join" = "%@ se unió";
+"notice_room_leave" = "%@ salió";
+"notice_room_reject" = "%@ rechazó la invitación";
+"notice_room_kick" = "%@ expulsó a %@";
+"notice_room_unban" = "%@ le quitó el veto a %@";
+"notice_room_ban" = "%@ vetó a %@";
+"notice_room_withdraw" = "%@ retiró la invitación de %@";
+"notice_room_reason" = ". Motivo: %@";
+"notice_avatar_url_changed" = "%@ cambió su avatar";
+"notice_display_name_set" = "%@ estableció %@ como su nombre público";
+"notice_display_name_changed_from" = "%@ cambió su nombre público de %@ a %@";
+"notice_display_name_removed" = "%@ eliminó su nombre público";
+"notice_topic_changed" = "%@ cambió el tema a: %@";
+"notice_room_name_changed" = "%@ cambió el nombre de la sala a: %@";
+"notice_placed_voice_call" = "%@ realizó una llamada de voz";
+"notice_answered_video_call" = "%@ contestó la llamada";
+"notice_ended_video_call" = "%@ finalizó la llamada";
+"notice_conference_call_request" = "%@ solicitó una conferencia de vozIP";
+"notice_conference_call_started" = "conferencia de vozIP iniciada";
+"notice_conference_call_finished" = "conferencia de vozIP finalizada";
+// button names
+"ok" = "Correcto";
+"cancel" = "Cancelar";
+"save" = "Guardar";
+"leave" = "Salir";
+"send" = "Enviar";
+"copy_button_name" = "Copiar";
+"resend" = "Reenviar";
+"redact" = "Eliminar";
+"share" = "Compartir";
+"set_power_level" = "Establecer nivel de permisos";
+"delete" = "Eliminar";
+"view" = "Ver";
+// actions
+"action_logout" = "Cerrar Sesión";
+"create_room" = "Crear Sala";
+"login" = "Iniciar Sesión";
+"create_account" = "Crear Cuenta";
+"membership_invite" = "Invitado";
+"membership_leave" = "Salió";
+"membership_ban" = "Vetado";
+"num_members_one" = "%@ usuario";
+"num_members_other" = "%@ usuarios";
+"invite" = "Invitar";
+"kick" = "Echar";
+"ban" = "Vetar";
+"unban" = "Quitar Veto";
+"message_unsaved_changes" = "Hay cambios sin guardar. Salir los descartará.";
+// Login Screen
+"login_error_already_logged_in" = "Ya ha iniciado sesión";
+"login_error_must_start_http" = "La URL debe comenzar con http[s]://";
+// room details dialog screen
+"room_details_title" = "Detalles de Sala";
+// contacts list screen
+"invitation_message" = "Me gustaría chatear contigo vía Matrix. Por favor, visita la página http://matrix.org para obtener más información.";
+// Settings screen
+"settings_title_config" = "Ajustes";
+"settings_title_notifications" = "Notificaciones";
+// Notification settings screen
+"notification_settings_disable_all" = "Deshabilitar todas las notificaciones";
+"notification_settings_enable_notifications" = "Habilitar notificaciones";
+"notification_settings_enable_notifications_warning" = "Actualmente, todas las notificaciones están deshabilitadas para todos los dispositivos.";
+"notification_settings_global_info" = "Los ajustes de notificaciones se guardan en tu cuenta de usuario y se comparten entre todos los clientes que las admiten (incluyendo las notificaciones de escritorio).\n\nLas reglas se aplican en órden; la primer regla que coincide define el resultado del mensaje.\nEntonces: las notificaciones por palabra son más importantes que las notificaciones por sala, que son más importantes que las notificaciones por emisor.\nPara múltiples reglas del mismo tipo, la primera en la lista que coincide tiene prioridad.";
+"notification_settings_per_word_notifications" = "Notificaciones por palabra";
+"notification_settings_per_word_info" = "Las palabras coinciden con mayúsculas y minúsculas, y pueden incluir un * comodín. Entonces:\nfoo coincide con la cadena de caracteres foo rodeada de delimitadores de palabras (ej. puntuación y espacios en blanco o inicios/finales de línea).\nfoo* coincide con cualquier palabra que comience con foo.\n*foo* coincide con cualquier palabra que incluya las 3 letras foo.";
+"notification_settings_always_notify" = "Siempre notificar";
+"notification_settings_never_notify" = "Nunca notificar";
+"notification_settings_word_to_match" = "palabra que coincida";
+"notification_settings_highlight" = "Destacar";
+"notification_settings_custom_sound" = "Sonido personalizado";
+"notification_settings_per_room_notifications" = "Notificaciones por sala";
+"notification_settings_per_sender_notifications" = "Notificaciones por emisor";
+"notification_settings_sender_hint" = "@usuario:dominio.com";
+"notification_settings_select_room" = "Selecciona una sala";
+"notification_settings_other_alerts" = "Otras Alertas";
+"notification_settings_contain_my_user_name" = "Notificarme con sonido por mensajes que contienen mi nombre de usuario";
+"notification_settings_contain_my_display_name" = "Notificarme con sonido por mensajes que contienen mi nombre público";
+"notification_settings_just_sent_to_me" = "Notificarme con sonido por mensajes enviados solo a mí";
+"notification_settings_invite_to_a_new_room" = "Notificarme cuando soy invitado a una nueva sala";
+"notification_settings_people_join_leave_rooms" = "Notificarme cuando las personas se unen o salen de las salas";
+"notification_settings_receive_a_call" = "Notificarme cuando reciba una llamada";
+"notification_settings_suppress_from_bots" = "Suprimir notificaciones de bots";
+"notification_settings_by_default" = "Por defecto...";
+"notification_settings_notify_all_other" = "Notificar por todos los demás mensajes/salas";
+// gcm section
+"settings_config_home_server" = "Servidor local: %@";
+"settings_config_identity_server" = "Servidor de identidad: %@";
+"settings_config_user_id" = "ID de Usuario: %@";
+// call string
+"call_waiting" = "Esperando...";
+"call_connecting" = "Conectando llamada...";
+"call_ended" = "Llamada finalizada";
+"call_ring" = "Llamando...";
+"incoming_video_call" = "Llamada de Vídeo Entrante";
+"incoming_voice_call" = "Llamada de Voz Entrante";
+"call_invite_expired" = "Expiró la Invitación a Llamada";
+// unrecognized SSL certificate
+"ssl_trust" = "Confiar";
+"ssl_logout_account" = "Cerrar Sesión";
+"ssl_remain_offline" = "Ignorar";
+"ssl_fingerprint_hash" = "Huella Digital (%@):";
+"ssl_could_not_verify" = "No se pudo verificar la identidad del servidor remoto.";
+"ssl_cert_not_trust" = "Esto podría significar que alguien está interceptando tu tráfico maliciosamente, o que tu teléfono no confía en el certificado proporcionado por el servidor remoto.";
+"ssl_cert_new_account_expl" = "Si el administrador del servidor dijo que esto es de esperarse, asegúrate que la huella digital que se muestra a continuación coincide con la huella digital proporcionada por el administrador.";
+"ssl_unexpected_existing_expl" = "El certificado cambió de uno que era confiable para tu teléfono. Esto es MUY INUSUAL. Se recomienda NO ACEPTAR este nuevo certificado.";
+"ssl_expected_existing_expl" = "El certificado cambió de uno que era confiable a uno que no es confiable. El servidor puede haber renovado su certificado. Contacta al administrador del servidor para obtener la huella digital.";
+"ssl_only_accept" = "SOLO acepta el certificado si el administrador del servidor ha publicado una huella digital que coincide con la indicada arriba.";
+"notice_placed_video_call" = "%@ realizó una llamada de vídeo";
+"e2e_passphrase_create" = "Crear contraseña";
+"notice_encryption_enabled_ok" = "%@ activó encriptación de extremo a extremo";
+"notice_encryption_enabled_unknown_algorithm" = "%1$@ activó encriptación de extremo a extremo (algoritmo %2$@ desconocido).";
+"device_details_rename_prompt_title" = "Nombre de Sesión";
+"account_error_push_not_allowed" = "No se permite notificaciones";
+"notice_room_third_party_revoked_invite" = "%@ revocó la invitación para %@ de unirse a la sala";
+"attachment_size_prompt_message" = "Puedes desactivar esto en ajustes.";
+"attachment_size_prompt_title" = "Confirma el tamaño para enviar";
+"message_reply_to_sender_sent_a_voice_message" = "ha enviado un mensaje de voz.";
+"room_left_for_dm" = "Te has salido";
+"room_displayname_all_other_members_left" = "%@ (ha salido)";
+"notice_room_join_rule_public_by_you_for_dm" = "Has hecho esta conversación pública.";
+"notice_room_join_rule_public_by_you" = "Has hecho la sala pública.";
+"notice_room_join_rule_public_for_dm" = "%@ ha hecho esta conversación pública.";
+"notice_room_join_rule_public" = "%@ ha hecho pública la sala.";
+"notice_room_join_rule_invite_by_you" = "Has hecho que solo se pueda unir a la sala por invitación.";
+"notice_room_name_removed_for_dm" = "%@ ha quitado el nombre";
+"notice_room_created_for_dm" = "%@ se ha unido.";
+"notice_room_join_rule_invite_for_dm" = "%@ ha hecho que solo sea posible unirse por invitación.";
+// New
+"notice_room_join_rule_invite" = "%@ ha hecho que la sala solo sea accesible por invitación.";
+"resume_call" = "Volver a la llamada";
diff --git a/Riot/Modules/MatrixKit/Assets/MatrixKitAssets.bundle/et.lproj/MatrixKit.strings b/Riot/Modules/MatrixKit/Assets/MatrixKitAssets.bundle/et.lproj/MatrixKit.strings
new file mode 100644
index 000000000..017cbbe88
--- /dev/null
+++ b/Riot/Modules/MatrixKit/Assets/MatrixKitAssets.bundle/et.lproj/MatrixKit.strings
@@ -0,0 +1,478 @@
+"matrix" = "Matrix";
+// Login Screen
+"login_create_account" = "Loo kasutajakonto:";
+"login_server_url_placeholder" = "URL (näiteks https://matrix.org)";
+"login_home_server_title" = "Koduserveri aadress:";
+"login_home_server_info" = "Sinu koduserver salvestab kõik vestlused ning kogu sinu kontoteabe";
+"login_identity_server_title" = "Isikutuvastusserveri aadress:";
+"login_password_placeholder" = "Salasõna";
+"login_optional_field" = "kui soovid";
+"login_display_name_placeholder" = "Kuvatav nimi (näiteks Kadri Maasikas)";
+"login_email_placeholder" = "E-posti aadress";
+"back" = "Tagasi";
+"close" = "Sulge";
+"continue" = "Jätka";
+"retry" = "Proovi uuesti";
+// Settings
+"settings" = "Seadistused";
+"settings_enable_inapp_notifications" = "Võta kasutusele rakenduse-sisesed teavitused";
+"settings_enable_push_notifications" = "Võta kasutusele tõuketeavitused";
+"notification_settings_room_rule_title" = "Jututuba: '%@'";
+// Devices
+"device_details_title" = "Sessiooniteave\n";
+"device_details_name" = "Avalik nimi\n";
+"device_details_identifier" = "Tunnus\n";
+"device_details_last_seen" = "Viimati nähtud\n";
+"device_details_last_seen_format" = "%@ @ %@\n";
+"device_details_rename_prompt_title" = "Sessiooni nimi";
+"device_details_rename_prompt_message" = "Sessiooni avalik nimi on nähtav neile, kellega sa suhtled";
+"device_details_delete_prompt_title" = "Autentimine";
+"account_error_email_wrong_title" = "Vigane e-posti aadress";
+"account_error_email_wrong_description" = "See ei tundu olema e-posti aadressi moodi";
+"account_error_msisdn_wrong_title" = "Vigane telefoninumber";
+"account_error_msisdn_wrong_description" = "See ei tundu olema telefoninumbri moodi";
+"account_error_push_not_allowed" = "Teavitused ei ole lubatud";
+// Room creation
+"room_creation_name_title" = "Jututoa nimi:";
+"room_member_power_level_prompt" = "Sa ei saa seda muudatust hiljem tagasi pöörata, sest annad teisele kasutajale samad õigused, mis sinul on.\nKas sa oled ikka kindel?";
+// Attachment
+"attachment_size_prompt" = "Kas sa soovid faili saata:";
+"attachment_original" = "Tegelikus suuruses (%@)";
+"attachment_small" = "Väiksena (%@)";
+"attachment_medium" = "Keskmisena (%@)";
+"attachment_large" = "Suurena (%@)";
+"attachment_cancel_download" = "Kas katkestame allalaadimise?";
+"attachment_cancel_upload" = "Kas katkestame üleslaadimise?";
+"attachment_multiselection_size_prompt" = "Kas sa soovid pilte saata:";
+"attachment_multiselection_original" = "Tegelikus suuruses";
+"attachment_e2e_keys_file_prompt" = "Selles failis leiduvad ühest Matrix'i kliendist eksporditud krüptovõtmed.\nKas sa soovid vaadata faili sisu või importida seal leiduvad võtmeid?";
+"attachment_e2e_keys_import" = "Impordi...";
+// Contacts
+"contact_mx_users" = "Matrix'i kasutajad";
+"contact_local_contacts" = "Kohalikud kasutajad";
+// Groups
+"group_invite_section" = "Kutsed";
+"group_section" = "Grupid";
+// Search
+"search_no_results" = "Tulemusi ei ole";
+"search_searching" = "Otsing on pooleli...";
+// Time
+"format_time_s" = "s";
+"format_time_m" = "m";
+"format_time_h" = "t";
+"format_time_d" = "p";
+// E2E import
+"e2e_import_room_keys" = "Impordi jututoa krüptovõtmed";
+"error_common_message" = "Ilmnes viga. Palun proovi hiljem uuesti.";
+"not_supported_yet" = "Pole veel toetatud";
+"default" = "vaikimisi";
+"public" = "Avalik";
+"cancel" = "Loobu";
+"save" = "Salvesta";
+"leave" = "Lahku";
+"resend" = "Saada uuesti";
+"redact" = "Eemalda";
+"share" = "Jaga";
+"delete" = "Kustuta";
+"view" = "Näita";
+// actions
+"action_logout" = "Logi välja";
+"create_room" = "Loo jututuba";
+"login" = "Logi sisse";
+"create_account" = "Loo konto";
+"membership_invite" = "Kutsutud";
+"num_members_one" = "%@ kasutaja";
+"num_members_other" = "%@ kasutajat";
+"invite" = "Kutsu";
+"kick" = "Müksa välja";
+"unban" = "Eemalda suhtluskeeld";
+"login_error_must_start_http" = "Serveri aadressi alguses peab olema http[s]://";
+// room details dialog screen
+"room_details_title" = "Jututoa üksikasjad";
+"settings_title_notifications" = "Teavitused";
+// Notification settings screen
+"notification_settings_disable_all" = "Lülita kõik teavitused välja";
+"notification_settings_enable_notifications" = "Võta teavitused kasutusele";
+"notification_settings_enable_notifications_warning" = "Kõik teavituste liigid on hetkel kõikidel seadmetel välja lülitatud.";
+"login_identity_server_info" = "Matrix'i spetsifikatsioon näeb ette isikutuvastusserverite kasutamist selleks, et tuvastada mis e-posti aadress kuulub mis Matrix'i kasutajale. Hetkel pakub sellist teenust vaid https://matrix.org .";
+"login_user_id_placeholder" = "Matrixi kasutajatunnus (näiteks @kadri:toredomeen.ee või kadri)";
+"login_error_title" = "Sisselogimine ei õnnestunud";
+"login_error_no_login_flow" = "Autentimisteabe laadimine sellest koduserverist ei õnnestunud";
+"login_error_do_not_support_login_flows" = "Hetkel me ei toeta ühtegi sisselogimisloogikat, mida see koduserver kasutab";
+"login_error_registration_is_not_supported" = "Registreerimine ei ole hetkel toetatud";
+"login_error_forbidden" = "Vigane kasutajanimi või salasõna";
+"login_error_bad_json" = "Vigane JSON";
+"login_error_not_json" = "Ei sisaldanud korrektset JSON'it";
+"login_error_limit_exceeded" = "Liiga palju samaaegseid sisselogimispäringuid";
+"login_error_user_in_use" = "See kasutajanimi on juba kasutusel";
+"login_use_fallback" = "Kasuta tagavaralehte";
+"login_leave_fallback" = "Loobu";
+"login_invalid_param" = "Vigane parameeter";
+"register_error_title" = "Registreerimine ei õnnestunud";
+"login_error_forgot_password_is_not_supported" = "Unustatud salasõna funktsionaalsus ei ole hetkel toetatud";
+"login_mobile_device" = "Mobiiltelefon";
+"login_tablet_device" = "Tahvelarvuti";
+"login_desktop_device" = "Töölaud";
+"login_error_resource_limit_exceeded_title" = "Ressursipiir on ületatud";
+"login_error_resource_limit_exceeded_message_default" = "See koduserver ületanud ühe oma ressursipiirangutest.";
+"login_error_resource_limit_exceeded_message_monthly_active_user" = "See koduserver on saavutanud igakuise aktiivsete kasutajate piiri.";
+"login_error_resource_limit_exceeded_message_contact" = "\n\nJätkamaks selle teenuse kasutamist palun võta ühendust oma teenuse haldajaga.";
+"login_error_resource_limit_exceeded_contact_button" = "Võta ühendust teenuse haldajaga";
+// Action
+"no" = "Ei";
+"yes" = "Jah";
+"abort" = "Katkesta";
+"discard" = "Loobu";
+"dismiss" = "Loobu";
+"sign_up" = "Registreeru";
+"submit" = "Saada";
+"submit_code" = "Saada kood";
+"set_power_level" = "Määra õigused";
+"set_default_power_level" = "Lähtesta õigused";
+"set_moderator" = "Määra moderaatoriks";
+"set_admin" = "Määra peakasutajaks";
+"start_chat" = "Alusta vestlust";
+"start_voice_call" = "Algata häälkõne";
+"start_video_call" = "Algata videokõne";
+"mention" = "Maini";
+"select_account" = "Vali kasutajakonto";
+"attach_media" = "Manusta meediafail galeriist";
+"capture_media" = "Tee foto või video";
+"invite_user" = "Saada kutse Matrix'i kasutajale";
+"reset_to_default" = "Lähtesta vaikeseadeteks";
+"resend_message" = "Saada sõnum uuesti";
+"select_all" = "Vali kõik";
+"cancel_upload" = "Katkesta üleslaadimine";
+"cancel_download" = "Katkesta allalaadimine";
+"show_details" = "Näita üksikasju";
+"answer_call" = "Vasta kõnele";
+"reject_call" = "Ära võta kõnet vastu";
+"end_call" = "Lõpeta kõne";
+"ignore" = "Eira";
+"unignore" = "Lõpeta eiramine";
+// Events formatter
+"notice_avatar_changed_too" = "(samuti sai avatar muudetud)";
+"notice_room_name_removed" = "%@ eemaldas jututoa nime";
+"notice_room_topic_removed" = "%@ eemaldas jututoa teema";
+"notice_event_redacted" = "";
+"notice_event_redacted_by" = " %@ poolt";
+"notice_event_redacted_reason" = " [põhjus: %@]";
+"notice_profile_change_redacted" = "%@ uuendas oma profiili %@";
+"notice_room_created" = "Kasutaja %@ lõi ja seadistas jututoa.";
+"notice_room_join_rule" = "Liitumise tingimused on: %@";
+"notice_room_power_level_intro" = "Jututoa liikmete õigused on:";
+"notice_room_power_level_acting_requirement" = "Minimaalselt vajalikud õigused enne neid tegevusi on:";
+"notice_room_power_level_event_requirement" = "Sündmustega seotud minimaalselt vajalikud õigused on:";
+"notice_room_aliases" = "Jututoa aliased on: %@";
+"notice_room_related_groups" = "Selle jututoaga seotud grupid on: %@";
+"notice_encrypted_message" = "Krüptitud sõnum";
+"notice_encryption_enabled_ok" = "%@ lülitas sisse läbiva krüptimise.";
+"notice_encryption_enabled_unknown_algorithm" = "%1$@ lülitas sisse läbiva krüptimise (tundmatu algoritm %2$@).";
+"notice_image_attachment" = "manustatud pilt";
+"notice_audio_attachment" = "manustatud helifail";
+"notice_video_attachment" = "manustatud videofail";
+"notice_location_attachment" = "manustatud asukohafail";
+"notice_file_attachment" = "manustatud fail";
+"notice_invalid_attachment" = "vigane manus";
+"notice_unsupported_attachment" = "Manus ei ole toetatud: %@";
+"notice_feedback" = "Tagasiside (id: %@): %@";
+"notice_redaction" = "%@ muutis sündmust (id: %@)";
+"notice_error_unsupported_event" = "Sündmuse tüüp ei ole toetatud";
+"notice_error_unexpected_event" = "Ootamatu sündmus";
+"notice_error_unknown_event_type" = "Tundmatu sündmuse tüüp";
+"notice_room_history_visible_to_anyone" = "%@muutis jututoa tulevase ajaloo loetavaks kõigile.";
+"notice_room_history_visible_to_members" = "%@ määras, et jututoa tulevane ajalugu on nähtav kõikidele selle liikmetele.";
+"notice_room_history_visible_to_members_from_invited_point" = "%@ määras, et jututoa tulevane ajalugu on nähtav kõikidele selle liikmetele nende kutsumise hetkest.";
+"notice_room_history_visible_to_members_from_joined_point" = "%@ määras, et jututoa tulevane ajalugu on nähtav kõikidele selle liikmetele nende liitumise hetkest.";
+"notice_crypto_unable_to_decrypt" = "** Ei õnnestu dekrüptida: %@ **";
+"notice_crypto_error_unknown_inbound_session_id" = "Sõnumi saatja sessioon ei ole selle sõnumi jaoks saatnud dekrüptimisvõtmeid.";
+"notice_sticker" = "kleeps";
+"notice_in_reply_to" = "Vastuseks kasutajale";
+// room display name
+"room_displayname_empty_room" = "Tühi jututuba";
+"room_displayname_two_members" = "%@ ja %@";
+"room_displayname_more_than_two_members" = "%@ ja %@ muud";
+"device_details_delete_prompt_message" = "See tegevus vajab täiendavat autentimist.\nJätkamaks palun sisesta oma salasõna.";
+// Encryption information
+"room_event_encryption_info_title" = "Läbiva krüptimise teave\n\n";
+"room_event_encryption_info_event" = "Sündmuse teave\n";
+"room_event_encryption_info_event_user_id" = "Kasutajatunnus\n";
+"room_event_encryption_info_event_fingerprint_key" = "Väidetav Ed25519 allkirja sõrmejälje võti\n";
+"room_event_encryption_info_event_algorithm" = "Algoritm\n";
+"room_event_encryption_info_event_session_id" = "Sessiooni tunnus\n";
+"room_event_encryption_info_event_decryption_error" = "Dekrüptimise viga\n";
+"room_event_encryption_info_event_unencrypted" = "krüptimata";
+"room_event_encryption_info_event_none" = "ei midagi";
+"room_event_encryption_info_device" = "\nSaatja sessiooni teave\n";
+"room_event_encryption_info_device_unknown" = "tundmatu sessioon\n";
+"room_event_encryption_info_device_name" = "Avalik nimi\n";
+"room_event_encryption_info_device_id" = "Tunnus\n";
+"room_event_encryption_info_device_verification" = "Verifikatsioon\n";
+"room_event_encryption_info_device_fingerprint" = "Ed25519 sõrmejälg\n";
+"room_event_encryption_info_device_verified" = "Verifitseeritud";
+"room_event_encryption_info_device_not_verified" = "EI OLE verifitseeritud";
+"room_event_encryption_info_unverify" = "Eemalda verifitseerimine";
+"room_event_encryption_verify_title" = "Verifitseeri sessioon\n\n";
+"room_event_encryption_verify_ok" = "Verifitseeri";
+// Account
+"account_save_changes" = "Salvesta muutused";
+"account_error_picture_change_failed" = "Pildi muutmine ei õnnestunud";
+"account_error_matrix_session_is_not_opened" = "Matrix'i sessioon pole avatud";
+"room_creation_name_placeholder" = "(näiteks HeadLõunasöögikohad)";
+"room_creation_alias_title" = "Jututoa alias:";
+"room_creation_alias_placeholder" = "(e.g. #midagi:domeen.ee)";
+"room_creation_alias_placeholder_with_homeserver" = "(näiteks #midagi%@)";
+"room_creation_participants_title" = "Osalejad:";
+"room_creation_participants_placeholder" = "(näiteks @kadri:koduserver1; @peeter:koduserver2...)";
+// Room
+"room_please_select" = "Palun vali jututuba";
+"room_error_join_failed_title" = "Jututoaga liitumine ei õnnestunud";
+"room_error_join_failed_empty_room" = "Hetkel ei ole võimalik liituda tühja jututoaga.";
+"room_error_timeline_event_not_found_title" = "Asukoha laadimine ajajoonel ei õnnestunud";
+"room_error_timeline_event_not_found" = "Rakendus üritas laadida teatud hetke selle jututoa ajajoonelt, kuid ei suutnud seda leida";
+"room_left" = "Sa lahkusid jututoast";
+"room_no_power_to_create_conference_call" = "Konverentsikõne alustamiseks selles jututoas on sul vaja õigusi";
+"room_no_conference_call_in_encrypted_rooms" = "Konverentsikõned ei ole krüptitud jututubades toetatud";
+// Reply to message
+"message_reply_to_sender_sent_an_image" = "saatis pildi.";
+"message_reply_to_sender_sent_a_video" = "saatis video.";
+"message_reply_to_sender_sent_an_audio_file" = "saatis helifaili.";
+"message_reply_to_sender_sent_a_file" = "saatis faili.";
+"message_reply_to_message_to_reply_to_prefix" = "Vastuseks kasutajale";
+// Room members
+"room_member_ignore_prompt" = "Kas sa oled kindel, et soovid peita kõik sõnumid selle kasutaja eest?";
+"e2e_import" = "Impordi";
+"e2e_passphrase_enter" = "Sisesta paroolifraas";
+// E2E export
+"e2e_export_room_keys" = "Ekspordi jututoa võtmed";
+"e2e_export_prompt" = "Selle toiminguga on sul võimalik saabunud krüptitud sõnumite võtmed eksportida sinu kontrollitavasse kohalikku faili. Seetõttu on sul tulevikus võimalik importida need võtmed mõnda teise Matrix'i klienti ning seeläbi muuta saabunud krüptitud sõnumid ka seal loetavaks.\nKes iganes saab kätte selle võtmefaili, saab ka dekrüptida kõiki sinu krüptitud sõnumeid, seega palun hoia teda turvaliselt.";
+"e2e_export" = "Ekspordi";
+"e2e_passphrase_confirm" = "Sisesta paroolifraas veel üks kord";
+"e2e_passphrase_empty" = "Paroolifraas ei tohi olla tühi";
+"e2e_passphrase_not_match" = "Paroolifraasid ei klapi omavahel";
+"e2e_passphrase_create" = "Loo paroolifraas";
+// Others
+"user_id_title" = "Kasutajatunnus:";
+"offline" = "võrgust väljas";
+"unsent" = "Saatmata";
+"error" = "Viga";
+"private" = "Privaatne";
+"power_level" = "Õiguste tase";
+"network_error_not_reachable" = "Palun kontrolli oma võrguühendust";
+"user_id_placeholder" = "näiteks @kati:mingidomeen.com";
+"ssl_homeserver_url" = "Koduserveri aadress: %@";
+// Permissions
+"camera_access_not_granted_for_call" = "Videokõned vajavad ligipääsu kaamerale, kuid %@'l pole selleks õigusi";
+"microphone_access_not_granted_for_call" = "Kõned vajavad ligipääsu mikrofonile, kuid %@'l pole selleks õigusi";
+"local_contacts_access_discovery_warning_title" = "Kasutajate leidmine";
+// Country picker
+"country_picker_title" = "Vali riik";
+// Language picker
+"language_picker_title" = "Vali keel";
+"language_picker_default_language" = "Vaikimisi (%@)";
+"notice_room_invite" = "%@ kutsus kasutajat %@";
+"notice_room_third_party_invite" = "%@ saatis kasutajale %@ kutse jututoaga liitumiseks";
+"notice_room_third_party_registered_invite" = "%@ võttis vastu kutse %@ nimel";
+"notice_room_third_party_revoked_invite" = "%@ võttis tagasi jututoaga liitumise kutse kasutajalt %@";
+"notice_room_join" = "%@ liitus";
+"notice_room_leave" = "%@ lahkus";
+"notice_room_reject" = "%@ lükkas tagasi kutse";
+"notice_room_kick" = "%@ müksas kasutajat %@";
+"notice_room_unban" = "%@ taastas %@ ligipääsu";
+"notice_room_ban" = "%@ keelas %@ ligipääsu";
+"notice_room_withdraw" = "%@ võttis tagasi kutse kasutajale %@";
+"notice_room_reason" = ". Põhjus: %@";
+"notice_avatar_url_changed" = "%@ muutis oma tunnuspilti";
+"notice_display_name_set" = "%@ määras oma kuvatavaks nimeks %@";
+"notice_display_name_changed_from" = "%@ muutis senise kuvatava nime %@ uueks nimeks %@";
+"notice_display_name_removed" = "%@ eemaldas oma kuvatava nime";
+"notice_topic_changed" = "%@ muutis uueks teemaks „%@“.";
+"notice_room_name_changed" = "%@ muutis jututoa uueks nimeks %@.";
+"notice_placed_voice_call" = "%@ alustas häälkõnet";
+"notice_placed_video_call" = "%@ alustas videokõnet";
+"notice_answered_video_call" = "%@ vastas kõnele";
+"notice_ended_video_call" = "%@ lõpetas kõne";
+"notice_conference_call_request" = "%@ saatis VoIP rühmakõne kutse";
+"notice_conference_call_started" = "VoIP rühmakõne algas";
+"notice_conference_call_finished" = "VoIP rühmakõne lõppes";
+// Notice Events with "You"
+"notice_room_invite_by_you" = "Sina kutsusid kasutajat %@";
+"notice_room_invite_you" = "%@ kutsus sind";
+"notice_room_third_party_invite_by_you" = "Sina saatsid kasutajale %@ kutse jututoaga liitumiseks";
+"notice_room_third_party_registered_invite_by_you" = "Sina võtsid vastu kutse %@ nimel";
+"notice_room_third_party_revoked_invite_by_you" = "Sina võtsid tagasi jututoaga liitumise kutse kasutajalt %@";
+"notice_room_join_by_you" = "Sina liitusid";
+"notice_room_leave_by_you" = "Sina lahkusid";
+"notice_room_reject_by_you" = "Sa lükkasid kutse tagasi";
+"notice_room_kick_by_you" = "Sina müksasid %@ välja";
+"notice_room_unban_by_you" = "Sina taastasid %@ ligipääsu";
+"notice_room_ban_by_you" = "Sina keelasid %@ ligipääsu";
+"notice_room_withdraw_by_you" = "Sina võtsid tagasi %@ kutse";
+"notice_avatar_url_changed_by_you" = "Sa muutsid oma tunnuspilti";
+"notice_display_name_set_by_you" = "Sina määrasid oma kuvatavaks nimeks %@";
+"notice_display_name_changed_from_by_you" = "Sina muutsid senise kuvatava nime %@ uueks nimeks %@";
+"notice_display_name_removed_by_you" = "Sa eemaldasid oma kuvatava nime";
+"notice_topic_changed_by_you" = "Sa muutsid uueks teemaks „%@“.";
+"notice_room_name_changed_by_you" = "Sa muutsid jututoa uueks nimeks %@.";
+"notice_placed_voice_call_by_you" = "Sa alustasid häälkõnet";
+"notice_placed_video_call_by_you" = "Sa alustasid videokõnet";
+"notice_answered_video_call_by_you" = "Sa vastasid kõnele";
+"notice_ended_video_call_by_you" = "Sa lõpetasid kõne";
+"notice_conference_call_request_by_you" = "Sa algatasid VoIP rühmakõne";
+"notice_room_name_removed_by_you" = "Sa eemaldasid jututoa nime";
+"notice_room_topic_removed_by_you" = "Sa eemaldasid teema";
+"notice_event_redacted_by_you" = " sinu poolt";
+"notice_profile_change_redacted_by_you" = "Sa uuendasid oma profiili %@";
+"notice_room_created_by_you" = "Sa lõid ja seadistasid jututoa.";
+"notice_encryption_enabled_ok_by_you" = "Sa lülitasid sisse läbiva krüptimise.";
+"notice_encryption_enabled_unknown_algorithm_by_you" = "Sa lülitasid sisse läbiva krüptimise (kasutusel on tundmatu algoritm %@).";
+"notice_redaction_by_you" = "Sa muutsid sündmust: (id: %@)";
+"notice_room_history_visible_to_anyone_by_you" = "Sa muutsid jututoa tulevase ajaloo loetavaks kõigile.";
+"notice_room_history_visible_to_members_by_you" = "Sina tegid jututoa tulevase ajaloo loetavaks kõikidele jututoa liikmetele.";
+"notice_room_history_visible_to_members_from_invited_point_by_you" = "Sina muutsid jututoa tulevase ajaloo loetavaks kõikidele jututoa liikmetele sellest hetkest, kui nad on kutse saanud.";
+"notice_room_history_visible_to_members_from_joined_point_by_you" = "Sina muutsid jututoa tulevase ajaloo loetavaks kõikidele jututoa liikmetele sellest hetkest, kui nad liitusid jututoaga.";
+// button names
+"ok" = "Sobib";
+"send" = "Saada";
+"copy_button_name" = "Kopeeri";
+"membership_leave" = "Lahkus";
+"ban" = "Keela ligipääs";
+"message_unsaved_changes" = "Osa muudatusi on salvestamata. Lahkudes need kaovad.";
+// Login Screen
+"login_error_already_logged_in" = "Sa oled juba sisse loginud";
+// contacts list screen
+"invitation_message" = "Ma soovin sinuga vestelda Matrix'i võrgu vahendusel. Lisateavet leiad veebisaidist https://matrix.org/ .";
+// Settings screen
+"settings_title_config" = "Seadistused";
+"notification_settings_always_notify" = "Teavita alati";
+"notification_settings_never_notify" = "Ära teavita iialgi";
+"notification_settings_word_to_match" = "vastendatav sõna";
+"notification_settings_highlight" = "Tõsta esile";
+"notification_settings_custom_sound" = "Kohandatud heli";
+"notification_settings_per_room_notifications" = "Jututoa-kohased teavitused";
+"notification_settings_per_sender_notifications" = "Saatjakohased teavitused";
+"notification_settings_sender_hint" = "@kasutaja:domeen.ee";
+"notification_settings_select_room" = "Vali jututuba";
+"notification_settings_other_alerts" = "Muud hoiatused";
+"notification_settings_contain_my_user_name" = "Teavita mind helimärguandega sõnumitest, mis sisaldavad minu kasutajanime";
+"notification_settings_contain_my_display_name" = "Teavita mind helimärguandega sõnumitest, mis sisaldavad minu kuvatavat nime";
+"notification_settings_just_sent_to_me" = "Teavita mind helimärguandega sõnumitest, mis on saadetud vaid mulle";
+"notification_settings_invite_to_a_new_room" = "Teavita mind, kui ma olen saanud kutse uude jututuppa";
+"notification_settings_people_join_leave_rooms" = "Teavita mind, kui teised kasutajad liituvad jututoaga või lahkuvad sealt";
+"notification_settings_receive_a_call" = "Teavita mind, kui mulle tuleb kõne";
+"notification_settings_suppress_from_bots" = "Ära luba teavitusi robototelt";
+"notification_settings_by_default" = "Vaikimisi...";
+"notification_settings_notify_all_other" = "Teavita mind kõikide muude sõnumite ja jututubade puhul";
+// gcm section
+"settings_config_home_server" = "Koduserver: %@";
+"settings_config_identity_server" = "Isikutuvastusserver: %@";
+"settings_config_user_id" = "Kasutajatunnus: %@";
+// call string
+"call_waiting" = "Ootan...";
+"call_connecting" = "Kõne on ühendamisel…";
+"call_ended" = "Kõne lõppes";
+"call_ring" = "Helistan...";
+"incoming_video_call" = "Saabuv videokõne";
+"incoming_voice_call" = "Saabuv häälkõne";
+"call_invite_expired" = "Kõnekutse aegus";
+// unrecognized SSL certificate
+"ssl_trust" = "Usalda";
+"ssl_logout_account" = "Logi välja";
+"ssl_remain_offline" = "Eira";
+"ssl_fingerprint_hash" = "Sõrmejälg (%@):";
+"login_email_info" = "Lisades oma e-posti aadressi saad võimaldada teistel Matrix'i kasutajatel sind lihtsamini leida ning annad endale lisavõimaluse salasõna muutmiseks, kui seda tulevikus vaja peaks olema.";
+"login_prompt_email_token" = "Palun sisesta oma e-posti aadressi registeerimiskirjas näidatud tunnusluba:";
+"login_error_unknown_token" = "Sisestatud tunnusluba ei ole õige";
+"login_error_login_email_not_yet" = "E-posti teel saadetud linki pole veel klõpsitud";
+"settings_enter_validation_token_for" = "Sisesta %@ tuvastamise tunnusluba:";
+"room_event_encryption_info_verify" = "Verifitseeri...";
+"account_link_email" = "Seotud e-posti aadress";
+"account_linked_emails" = "Seotud e-posti aadressid";
+"account_email_validation_title" = "Verifikatsioon on ootel";
+"account_email_validation_message" = "Palun vaata oma e-kirju ning klõpsi meie saadetud kirjas leiduvat linki. Kui see on tehtud, siis vajuta Jätka-nuppu.";
+"account_email_validation_error" = "E-posti aadressi õigsust pole veel õnnestunud kontrollida. Palun vaata oma e-kirju ning klõpsi meie saadetud kirjas leiduvat linki. Kui see on tehtud, siis vajuta Jätka-nuppu";
+"account_msisdn_validation_title" = "Verifikatsioon on ootel";
+"account_msisdn_validation_message" = "Me oleme SMS'iga saatnud aktiveerimiskoodi. Palun sisesta see kood siia.";
+"account_msisdn_validation_error" = "Telefoninumbri verifitseerimine ei õnnestunud.";
+"account_error_display_name_change_failed" = "Kuvatava nime muutmine ei õnnestunud";
+"room_error_name_edition_not_authorized" = "Sinul pole õigusi selle jututoa nime muutmiseks";
+"room_error_topic_edition_not_authorized" = "Sinul pole õigusi selle jututoa teema muutmiseks";
+"room_error_cannot_load_timeline" = "Ajajoone laadimine ei õnnestunud";
+"e2e_import_prompt" = "Selle toiminguga saad importida krüptimisvõtmed, mis sa viimati olid teisest Matrix'i kliendist eksportinud. Seejärel on võimalik dekrüptida ka siin kõik need samad sõnumid, mida see teine klient suutis dekrüptida.\nSee ekspordifail on krüptitud paroolifraasiga. Faili dekrüptimiseks sisesta siia paroolifraas.";
+"local_contacts_access_discovery_warning" = "Selleks, et leida Matrixi võrgu kasutajaid, võib %@ saata sinu aadressiraamatus leiduvad e-posti aadressid ja telefoninumbrid sinu valitud Matrixi isikutuvastusserverile. Kui server seda toetab, siis andmed muudetakse enne saatmist räsideks - täpsema teabe leiad oma isikutuvastusserveri privaatsuspoliitikast.";
+"membership_ban" = "Suhtluskeeld";
+"notification_settings_per_word_notifications" = "Sõnadega seotud teavitused";
+"ssl_could_not_verify" = "Serveri õigsust ei olnud võimalik kontrollida.";
+"room_event_encryption_info_event_identity_key" = "Curve25519 identiteedi võti\n";
+"room_event_encryption_info_device_blocked" = "Mustas nimekirjas";
+"room_event_encryption_info_block" = "Lisa musta nimekirja";
+"room_event_encryption_info_unblock" = "Eemalda mustast nimekirjast";
+"room_event_encryption_verify_message" = "Tegemaks kindlaks, et seda sessiooni võid usaldada, palun kohtu tema omanikuga mõnel muul viisil (näiteks isiklikult või telefonikõne vahendusel) ning küsi, kas võtmed, mida ta näeb oma kasutajaseadistustes kattuvad alljärgnevaga:\n\n\tSessioni nimi: %@\n\tSessioni tunnus: %@\n\tSessioni võti: %@\n\nKui andmed kattuvad, siis vajuta järgnevat verifitseerimise nuppu. Kui ei kattu, siis tõenäoliselt keegi võõras suudab seda teist sessiooni kontrollida ning sa ilmselt eelistaks lisada teda musta nimekirja.\n\nTulevikus see verifitseerimise toiming võib minna veelgi nutikamaks.";
+"local_contacts_access_not_granted" = "Kasutajate leidmine sinu kohaliku aadressiraamatu alusel eeldab talle ligipääsu, kuid %@'l puuduvad selleks õigused";
+"notification_settings_global_info" = "Teavituste seadistused salvestatakse serverisse koos sinu konto muude andmetega ning neid jagatakse kõikide klientrakendustega, kes sellist võimalust toetavad (sh töölauateavitused).\n\nReegleid rakendatakse järjekorras ning esimene vastavus määrab ka tulemuse.\nSeega: sõnakohased teavitused on olulisemad, kui jututoa-kohased teavitused ning need omakorda olulisemad kui saatjakohased teavitused.\nMitme sarnase reegli puhul kehtib põhimõte, et esimene loendi alusel leitud vaste on määrav.";
+"notification_settings_per_word_info" = "Sõnade otsing ei ole tõstutundlik ning võib kasutada asendusteks * metamärki. Näiteks:\n- midagi alusel otsitakse kõiki sõnu „midagi“ (eraldajaks kirjavahemärgi, tühikud või rea algus ja lõpp);\n- midagi* alusel otsitakse kõiki sõnu mille alguses on „midagi“;\n- *midagi* otsib mis iganes sõnu, kus leidub järjest 6 tähte „midagi“.";
+"ssl_cert_not_trust" = "See võib tähendada, et keegi on suuteline pahatahtlikult sinu veebiliiklust pealtkuulama või sinu telefon ei usalda serveri kasutatavat sertifikaati.";
+"ssl_cert_new_account_expl" = "Kui serveri haldaja on sind teavitanud, et nii võib juhtuda, siis kontrolli, et sertifikaadi sõrmejälg vastab sellele, mille haldaja sulle on andnud.";
+"ssl_unexpected_existing_expl" = "Võrreldes selle sertifikaadiga, mida sinu nutiseade seni usaldas, on praegune sertifikaat muutunud. See on VÄGA EBATAVALINE. Me soovitame, et ÄRA NÕUSTU selle uue sertifikaadiga.";
+"ssl_expected_existing_expl" = "Senise usaldusväärse sertifikaadi asemel kasutab server nüüd mitteusaldusväärset sertifikaati. See võib tähendada et haldaja on seda serveris muutnud. Et võrrelda viimase kehtiva sertifikaadi sõrmejälge, palun võta haldajaga ühendust.";
+"ssl_only_accept" = "NÕUSTU sertifikaadiga vaid siis, kui serveri haldaja antud sõrmejälg klapib sellega, mida sa hetkel siin näed.";
+// New
+"notice_room_join_rule_invite" = "%@ määras, et jututuppa pääseb vaid kutsega.";
+"notice_room_join_rule_invite_by_you" = "Sina määrasid, et jututuppa pääseb vaid kutsega.";
+"notice_room_join_rule_public" = "%@ muutis jututoa avalikuks.";
+"notice_room_join_rule_public_by_you" = "Sa muutsid jututoa avalikuks.";
+"notice_room_name_removed_for_dm" = "%@ eemaldas jututoa nime";
+"notice_room_created_for_dm" = "%@ liitus.";
+"notice_room_join_rule_invite_for_dm" = "%@ määras, et jututuppa pääseb vaid kutsega.";
+"notice_room_join_rule_invite_by_you_for_dm" = "Sina määrasid, et jututuppa pääseb vaid kutsega.";
+"notice_room_join_rule_public_for_dm" = "%@ muutis jututoa avalikuks.";
+"notice_room_join_rule_public_by_you_for_dm" = "Sa muutsid jututoa avalikuks.";
+"notice_room_power_level_intro_for_dm" = "Jututoa liikmete õigused on:";
+"notice_room_aliases_for_dm" = "Jututoa aliased on: %@";
+"notice_room_history_visible_to_members_for_dm" = "%@ määras, et jututoa tulevane ajalugu on nähtav kõikidele selle liikmetele.";
+"notice_room_history_visible_to_members_from_invited_point_for_dm" = "%@ määras, et jututoa tulevane ajalugu on nähtav kõikidele selle liikmetele liitumiskutse saatmise hetkest.";
+"notice_room_history_visible_to_members_from_joined_point_for_dm" = "%@ määras, et jututoa tulevane ajalugu on nähtav kõikidele selle liikmetele nende liitumise hetkest.";
+"room_left_for_dm" = "Sina lahkusid";
+"notice_room_third_party_invite_for_dm" = "%@ kutsus kasutajat %@";
+"notice_room_third_party_revoked_invite_for_dm" = "%@ võttis tagasi kasutaja %@ kutse";
+"notice_room_name_changed_for_dm" = "%@ muutis jututoa uueks nimeks %@.";
+"notice_room_third_party_invite_by_you_for_dm" = "Sina kutsusid kasutajat %@";
+"notice_room_third_party_revoked_invite_by_you_for_dm" = "Sina võtsid tagasi kasutaja %@ kutse";
+"notice_room_name_changed_by_you_for_dm" = "Sa muutsid jututoa uueks nimeks %@.";
+"notice_room_name_removed_by_you_for_dm" = "Sa eemaldasid jututoa nime";
+"notice_room_created_by_you_for_dm" = "Sina liitusid.";
+"notice_room_history_visible_to_members_by_you_for_dm" = "Sina tegid jututoa tulevase ajaloo loetavaks kõikidele jututoa liikmetele.";
+"notice_room_history_visible_to_members_from_invited_point_by_you_for_dm" = "Sina määrasid, et jututoa tulevane ajalugu on nähtav kõikidele selle liikmetele liitumiskutse saatmise hetkest.";
+"notice_room_history_visible_to_members_from_joined_point_by_you_for_dm" = "Sina määrasid, et jututoa tulevane ajalugu on nähtav kõikidele selle liikmetele nende liitumise hetkest.";
+"call_more_actions_dialpad" = "Numbriklahvistik";
+"call_more_actions_transfer" = "Suuna kõne edasi";
+"call_more_actions_audio_use_device" = "Kasuta seadme kõlarit";
+"call_more_actions_audio_use_headset" = "Kasuta kõrvaklappe";
+"call_more_actions_change_audio_device" = "Muuda heliseadet";
+"call_more_actions_unhold" = "Jätka";
+"call_more_actions_hold" = "Pane ootele";
+"call_holded" = "Sina panid kõne ootele";
+"call_remote_holded" = "%@ pani kõne ootele";
+"notice_declined_video_call_by_you" = "Sina keeldusid kõnest";
+"notice_declined_video_call" = "%@ keeldus kõnest";
+"resume_call" = "Jätka";
+"call_consulting_with_user" = "Pean nõu kasutajaga %@";
+"call_transfer_to_user" = "Suunan kõne kasutajale %@";
+"call_video_with_user" = "Videokõne kasutajaga %@";
+"call_voice_with_user" = "Häälkõne kasutajaga %@";
+"call_ringing" = "Helistan…";
+"e2e_passphrase_too_short" = "Salafraas on liiga lühike (pikkus peaks olema vähemalt %d tähemärki)";
+"microphone_access_not_granted_for_voice_message" = "Häälsõnumite salvestamiseks on vajalik ligipääs mikrofonile, kuid %@'l pole selleks õigusi";
+"message_reply_to_sender_sent_a_voice_message" = "saatis häälsõnumi.";
+"attachment_large_with_resolution" = "Suurena %@ (~%@)";
+"attachment_medium_with_resolution" = "Keskmisena %@ (~%@)";
+"attachment_small_with_resolution" = "Väiksena %@ (~%@)";
+"attachment_size_prompt_message" = "Seadistustest saad määrata, et see funktsionaalsus pole kasutusel.";
+"attachment_size_prompt_title" = "Saatmiseks kinnita meedia suurus";
+"room_displayname_all_other_participants_left" = "%@ (lahkus(id))";
+"auth_reset_password_error_not_found" = "Pole leitav";
+"auth_reset_password_error_unauthorized" = "Tegevus pole lubatud";
+"auth_username_in_use" = "Selline kasutajanimi on juba olemas";
+"auth_invalid_user_name" = "Vigane kasutajanimi";
+"rename" = "Muuda nime";
+"room_displayname_all_other_members_left" = "%@ (lahkus(id))";
diff --git a/Riot/Modules/MatrixKit/Assets/MatrixKitAssets.bundle/eu.lproj/MatrixKit.strings b/Riot/Modules/MatrixKit/Assets/MatrixKitAssets.bundle/eu.lproj/MatrixKit.strings
new file mode 100644
index 000000000..6a119cc14
--- /dev/null
+++ b/Riot/Modules/MatrixKit/Assets/MatrixKitAssets.bundle/eu.lproj/MatrixKit.strings
@@ -0,0 +1,383 @@
+"notice_conference_call_started" = "VoIP konferentzia hasita";
+"notice_conference_call_finished" = "VoIP konferentzia amaituta";
+// Events formatter
+"notice_avatar_changed_too" = "(abatarra ere aldatu da)";
+"notice_crypto_error_unknown_inbound_session_id" = "Igorlearen saioak ez dizkigu mezu honetarako gakoak bidali.";
+"room_error_join_failed_empty_room" = "Ezin da oraingoz hutsik dagoen gela batetara berriro sartu.";
+"notice_encrypted_message" = "Zifratutako mezua";
+"login_email_placeholder" = "E-mail helbidea";
+// room display name
+"room_displayname_empty_room" = "Gela hutsa";
+"room_displayname_two_members" = "%@ eta %@";
+"room_displayname_more_than_two_members" = "%@ eta beste %u";
+// Settings
+"settings" = "Ezarpenak";
+// button names
+"ok" = "Ados";
+"cancel" = "Utzi";
+"save" = "Gorde";
+"leave" = "Atera";
+"send" = "Bidali";
+"copy_button_name" = "Kopiatu";
+"resend" = "Birbidali";
+"redact" = "Kendu";
+"share" = "Partekatu";
+"delete" = "Ezabatu";
+"room_no_power_to_create_conference_call" = "Gonbidatzeko baimena behar duzu gela honetan konferentzia bat hasteko";
+"room_no_conference_call_in_encrypted_rooms" = "Konferentzia deiak ez daude onartuta zifratutako geletan";
+"invite" = "Gonbidatu";
+"close" = "Itxi";
+"start_voice_call" = "Hasi ahots deia";
+"start_video_call" = "Hasi bideo deia";
+"submit" = "Bidali";
+"login_password_placeholder" = "Pasahitza";
+"login_home_server_title" = "Hasiera-zerbitzariaren URLa:";
+"login_identity_server_title" = "Identitate-zerbitzariaren URL-a:";
+"login_error_must_start_http" = "http[s]:// gisa hasi behar da URLa";
+"login_error_forbidden" = "Erabiltzaile-izen / pasahitz baliogabea";
+"login_error_unknown_token" = "Ez da ezagutzen zehaztutako sarbide katea";
+"login_error_bad_json" = "Gaizki osatutako JSON";
+"login_error_not_json" = "Ez zuen baliozko JSON-ik";
+"login_error_limit_exceeded" = "Eskaera gehiegi bidali dira";
+"login_error_user_in_use" = "Erabiltzaile-izen hau hartuta dago";
+"login_error_login_email_not_yet" = "Oraindik erabili ez den e-maileko esteka";
+"attachment_cancel_download" = "Utzi deskarga?";
+"attachment_cancel_upload" = "Utzi deskarga?";
+"call_ended" = "Deia amaitu da";
+"incoming_video_call" = "Bideo-deia jasotzen";
+"incoming_voice_call" = "Ahots-deia jasotzen";
+"continue" = "Jarraitu";
+// Time
+"format_time_s" = "s";
+"format_time_m" = "m";
+"format_time_h" = "h";
+"format_time_d" = "e";
+"ban" = "Debekatu";
+"mention" = "Aipamena";
+"room_member_power_level_prompt" = "Ezin izango duzu aldaketa hau desegin kidea zure botere maila berera ekartzen ari zarelako.\nZiur zaude?";
+// unrecognized SSL certificate
+"ssl_trust" = "Fidatu";
+"ssl_logout_account" = "Amaitu saioa";
+"ssl_remain_offline" = "Ezikusi";
+"ssl_could_not_verify" = "Ezin izan da urruneko zerbitzariaren identitatea egiaztatu.";
+"ssl_cert_not_trust" = "Honek esan lezake inor zure trafikoa antzematen dabilela asmo txarrez, edo zure telefonoa ez dela fidatzen urruneko zerbitzariaren ziurtagiriaz.";
+"ssl_cert_new_account_expl" = "Zerbitzariaren kudeatzaileak hau gerta daitekeela esaten badu, ziurtatu beheko hatz-marka beraiek emandako hatz-markarekin bat datorrela.";
+"ssl_unexpected_existing_expl" = "Ziurtagiria aldatu da eta ez da zure telefonoak onartzen zuena. Hau OSO ARRAROA da. Ziurtagiri berri hau EZ ONARTZEA aholkatzen da.";
+"ssl_expected_existing_expl" = "Ziurtagiria aldatu da, onartutako batetik onartu gabeko batera. Agian Zerbitzariak ziurtagiria berriztu du. Jarri kontaktuan zerbitzariaren kudeatzailearekin hatz-marka eskatzeko.";
+"ssl_only_accept" = "SOILIK onartu ziurtagiria zerbitzariaren kudeatzaileak goikoarekin bat datorren hatz-marka bat argitaratu badu.";
+// room details dialog screen
+"room_details_title" = "Gelaren xehetasunak";
+"cancel_upload" = "Utzi igoera";
+"cancel_download" = "Utzi deskarga";
+"settings_title_notifications" = "Jakinarazpenak";
+"device_details_delete_prompt_message" = "Eragiketa honek autentifikazio gehigarria behar du.\nJarraitzeko, idatzi zure pasahitza.";
+"device_details_delete_prompt_title" = "Autentifikazioa";
+// Language picker
+"language_picker_title" = "Hautatu hizkuntza";
+"account_email_validation_title" = "Egiaztaketa egiteke";
+"account_email_validation_message" = "Irakurri zure e-maila eta egin klik dakarren estekan. Behin eginda, egin klik Jarraitu botoian.";
+"account_email_validation_error" = "Ezin izan da e-mail helbidea egiaztatu. Irakurrri zure e-maila eta egin klik dakarren estekan. Behin eginda, egin klik Jarraitu botoian";
+// Country picker
+"country_picker_title" = "Hautatu herrialde bat";
+"account_msisdn_validation_message" = "Aktibazio kodea duen SMS mezu bat bidali dizugu. Idatzi kode hori hemen azpian.";
+"room_event_encryption_info_event_none" = "bat ere ez";
+// E2E export
+"e2e_export_room_keys" = "Esportatu gelako gakoak";
+"e2e_export" = "Esportatu";
+"e2e_passphrase_enter" = "Idatzi pasaesaldia";
+"e2e_passphrase_confirm" = "Berretsi pasaesaldia";
+// E2E import
+"e2e_import_room_keys" = "Inportatu gelako gakoak";
+"e2e_import" = "Inportatu";
+"room_event_encryption_info_device_verified" = "Egiaztatuta";
+"room_event_encryption_info_device_blocked" = "Blokeatuta";
+"room_event_encryption_verify_ok" = "Egiaztatu";
+"room_event_encryption_info_unverify" = "Kendu egiaztaketa";
+"room_event_encryption_info_block" = "Blokeatu";
+"room_event_encryption_info_unblock" = "Desblokeatu";
+"view" = "Ikusi";
+"back" = "Atzera";
+"retry" = "Saiatu berriro";
+"dismiss" = "Baztertu";
+"start_chat" = "Hasi txata";
+"room_event_encryption_info_event_unencrypted" = "zifratu gabe";
+"room_event_encryption_info_device_not_verified" = "EZ egiaztatuta";
+"room_event_encryption_info_verify" = "Egiaztatu…";
+"account_error_email_wrong_title" = "E-mail helbide baliogabea";
+"account_error_email_wrong_description" = "Honek ez du baliozko e-mail baten antzik";
+"room_error_join_failed_title" = "Huts egin du gelara elkartzean";
+"room_error_timeline_event_not_found_title" = "Huts egin du denbora-lerroko puntua kargatzean";
+"e2e_passphrase_empty" = "Pasaesaldia ezin da hutsik egon";
+"e2e_passphrase_not_match" = "Pasaesaldiak bat etorri behar dira";
+"error" = "Errorea";
+"create_room" = "Sortu gela";
+"membership_invite" = "Gonbidatuta";
+"kick" = "Kanporatu";
+"matrix" = "Matrix";
+// Login Screen
+"login_create_account" = "Sortu kontua:";
+"login_server_url_placeholder" = "URL-a (adib. https://matrix.org)";
+"login_home_server_info" = "Zure hasiera-zerbitzariak gordetzen ditu zure elkarrizketa guztiak eta kontuaren datuak";
+"login_identity_server_info" = "Matrixek identitate zerbitzariak hornitzen ditu e-mailak eta abar zein Matrix ID-ri dagokien jakiteko, Orain https://matrix.org da dagoen bakarra.";
+"login_user_id_placeholder" = "Matrix ID-a (adib. @urko:matrix.org edo urko)";
+"login_optional_field" = "aukerakoa";
+"login_display_name_placeholder" = "Pantaila-izena (adib. Urko Etxeberria)";
+"login_error_title" = "Saio hasierak huts egin du";
+"login_email_info" = "Zehaztu e-mail helbide bat beste Matrix erabiltzaileek zu errazago aurkitzeko, e-mail helbidea izateak ere etorkizunean pasahitza aldatzea ahalbidetuko dizu.";
+"login_prompt_email_token" = "Sartu zure e-mail egiaztaketa tokena:";
+"login_error_no_login_flow" = "Huts egin du autentifikazio informazioa hasiera zerbitzari honetatik jasotzean";
+"login_leave_fallback" = "Utzi";
+"login_invalid_param" = "Parametro baliogabea";
+"register_error_title" = "Erregistratzeak huts egin du";
+// Action
+"no" = "Ez";
+"yes" = "Bai";
+"abort" = "Abortatu";
+"discard" = "Baztertu";
+"sign_up" = "Erregistratu";
+"submit_code" = "Bidali kodea";
+"login_error_do_not_support_login_flows" = "Orain ez ditugu onartzen hasiera zerbitzari honek zehaztutako saio hasiera metodo guztiak, edo batere";
+"login_error_registration_is_not_supported" = "Oraindik ezin da aplikazioarekin erregistratu";
+"login_use_fallback" = "Erabili ordezko orria";
+"login_error_forgot_password_is_not_supported" = "Oraindik ezin da aplikazioarekin pasahitza berreskuratu";
+"set_default_power_level" = "Leheneratu botere maila";
+"set_moderator" = "Ezarri moderatzailea";
+"set_admin" = "Ezarri kudeatzailea";
+"select_account" = "Hautatu kontu bat";
+"attach_media" = "Erantsi media liburutegitik";
+"capture_media" = "Atera argazkia / bideoa";
+"invite_user" = "Gonbidatu matrix erabiltzailea";
+"reset_to_default" = "Leheneratu lehenetsitakora";
+"resend_message" = "Birbidali mezua";
+"select_all" = "Hautatu guztia";
+"show_details" = "Erakutsi xehetasunak";
+"answer_call" = "Erantzun deia";
+"reject_call" = "Ukatu deia";
+"end_call" = "Amaitu deia";
+"ignore" = "Ezikusi";
+"unignore" = "Berriro aintzat hartu";
+"notice_room_name_removed" = "%@ erabiltzaileak gelaren izena kendu du";
+"notice_room_topic_removed" = "%@ erabiltzaileak gelaren mintzagaia kendu du";
+"notice_event_redacted" = "";
+"notice_event_redacted_by" = " nork: %@";
+"notice_event_redacted_reason" = " [arrazoia: %@]";
+"notice_room_created" = "%@ erabiltzaileak gela sortu du";
+"notice_room_join_rule" = "Elkartzeko baldintza: %@";
+"notice_room_power_level_intro" = "Gelako kideen botere maila:";
+// Others
+"user_id_title" = "Erabiltzaile ID-a:";
+"offline" = "deskonektatuta";
+"unsent" = "Bidali gabe";
+"not_supported_yet" = "Oraindik ez da onartzen";
+"default" = "lehenetsia";
+"private" = "Pribatua";
+"public" = "Publikoa";
+"power_level" = "Botere maila";
+"network_error_not_reachable" = "Egiaztatu zure sare konexioa";
+"user_id_placeholder" = "adib: @urko:hasierazerbitzaria";
+"ssl_homeserver_url" = "Hasiera-zerbitzariaren URL-a: %@";
+"language_picker_default_language" = "Lehenetsia (%@)";
+"notice_room_invite" = "%@ erabiltzaileak %@ gonbidatu du";
+"notice_room_third_party_invite" = "%@ erabiltzaileak gelara elkartzeko gonbidapen bat bidali dio %@ erabiltzaileari";
+"notice_room_third_party_registered_invite" = "%@ erabiltzaileak %@ gelarako gonbidapena onartu du";
+"notice_room_join" = "%@ elkartu da";
+"notice_room_leave" = "%@ atera da";
+"notice_room_reject" = "%@ erabiltzaileak gonbidapena baztertu du";
+"notice_room_kick" = "%@ erabiltzaileak %@ kanporatu du";
+"notice_room_unban" = "%@ erabiltzaileak debekua kendu dio %@ erabiltzaileari";
+"notice_room_ban" = "%@ erabiltzaileak %@ debekatu du";
+"notice_profile_change_redacted" = "%@ erabiltzaileak bere %@ profila eguneratu du";
+"notice_room_power_level_acting_requirement" = "Hemen aritu ahal izateko erabiltzaileak behar duen botere maila:";
+"notice_image_attachment" = "irudi-eranskina";
+"notice_audio_attachment" = "audio-eranskina";
+"notice_video_attachment" = "bideo-eranskina";
+"notice_location_attachment" = "kokaleku-eranskina";
+"notice_file_attachment" = "fitxategi-eranskina";
+"notice_invalid_attachment" = "eranskin baliogabea";
+"notice_unsupported_attachment" = "Onartu gabeko eranskina: %@";
+"notice_crypto_unable_to_decrypt" = "** Ezin izan da deszifratu: %@ **";
+"settings_enable_inapp_notifications" = "Gaitu aplikazio barneko jakinarazpenak";
+"notification_settings_room_rule_title" = "Gela: '%@'";
+// Devices
+"device_details_title" = "Saioaren informazioa\n";
+"device_details_name" = "Izen publikoa\n";
+"device_details_identifier" = "ID-a\n";
+"device_details_last_seen" = "Azkenekoz ikusia\n";
+"device_details_last_seen_format" = "%@ @ %@\n";
+"device_details_rename_prompt_message" = "Saio baten izen publikoa zurekin komunikatzen den jendeak ikusi dezake";
+// Encryption information
+"room_event_encryption_info_title" = "Muturretik muturrerako zifratzearen informazioa\n\n";
+"room_event_encryption_info_event_user_id" = "Erabiltzailearen ID-a\n";
+"room_event_encryption_info_event_identity_key" = "Curve25519 identitate-gakoa\n";
+"room_event_encryption_info_event_algorithm" = "Algoritmoa\n";
+"room_event_encryption_info_event_session_id" = "Saioaren ID-a\n";
+"room_event_encryption_info_event_decryption_error" = "Deszifratze errorea\n";
+"room_event_encryption_info_device" = "\nIgorlearen saioaren informazioa\n";
+"room_event_encryption_info_device_unknown" = "saio ezezaguna\n";
+"room_event_encryption_info_device_name" = "Izen publikoa\n";
+"room_event_encryption_info_device_id" = "ID-a\n";
+"room_event_encryption_info_device_fingerprint" = "Ed25519 hatz-marka\n";
+// Account
+"account_save_changes" = "Gorde aldaketak";
+"account_msisdn_validation_error" = "Ezin izan da telefono zenbakia egiaztatu.";
+"account_msisdn_validation_title" = "Egiaztaketa egiteke";
+"login_mobile_device" = "Mugikorra";
+"login_tablet_device" = "Tableta";
+"login_desktop_device" = "Mahaigainekoa";
+"notification_settings_people_join_leave_rooms" = "Jakinarazi niri jendea gelera elkartu edo gelatik ateratzean";
+"notice_room_power_level_event_requirement" = "Gertaerekin lotutako gutxieneko botere maila:";
+"notice_room_aliases" = "Gelaren ezizenak: %@";
+"notice_encryption_enabled" = "%@ erabiltzaileak muturretik muturrera zifratzea gaitu du (%@ algoritmoa)";
+"notice_redaction" = "%@ erabiltzaileak gertaera bat kendu du (id: %@)";
+"notice_error_unsupported_event" = "Onartu gabeko gertaera";
+"notice_error_unexpected_event" = "Ustekabeko gertaera";
+"notice_error_unknown_event_type" = "Gertaera mota ezezaguna";
+"notice_room_history_visible_to_anyone" = "%@ erabiltzaileak etorkizuneko gelaren historiala ikusgai jarri du edonorentzat.";
+"notice_room_history_visible_to_members" = "%@ erabiltzaileak etorkizuneko gelaren historiala ikusgai jarri du gelako kide guztientzat, gonbidapena egiten zaienetik.";
+"notice_room_history_visible_to_members_from_invited_point" = "%@ erabiltzaileak etorkizuneko gelaren historiala ikusgai jarri du gelako kide guztientzat, gonbidapena egiten zaienetik.";
+"notice_room_history_visible_to_members_from_joined_point" = "%@ erabiltzaileak etorkizuneko gelaren historiala ikusgai jarri du gelako kide guztientzat, elkartzen direnetik.";
+"room_event_encryption_info_event" = "Gertaeraren informazioa\n";
+"room_event_encryption_info_device_verification" = "Egiaztaketa\n";
+"room_event_encryption_verify_title" = "Egiaztatu saioa\n\n";
+"account_link_email" = "Lotu e-maila";
+"account_linked_emails" = "Lotutako e-mailak";
+"account_error_display_name_change_failed" = "Huts egin du pantaila-izenaren aldaketak";
+"account_error_picture_change_failed" = "Huts egin du irudiaren aldaketak";
+"account_error_matrix_session_is_not_opened" = "Matrix saioa ez dago irekita";
+"account_error_msisdn_wrong_title" = "Telefono zenbaki baliogabea";
+"account_error_msisdn_wrong_description" = "Honek ez du baliozko telefono zenbaki baten antzik";
+// Room creation
+"room_creation_name_title" = "Gelaren izena:";
+"room_creation_name_placeholder" = "(adib. lagunKuadrilla)";
+"room_creation_alias_title" = "Gelaren ezizena:";
+"room_creation_alias_placeholder" = "(adib. #gela:adibidea.org)";
+"room_creation_alias_placeholder_with_homeserver" = "(adib. #gela%@)";
+"room_creation_participants_title" = "Parte hartzaileak:";
+"room_creation_participants_placeholder" = "(adib. @miren:1zerbitzaria; @peio:2zerbitzaria…)";
+// Room
+"room_please_select" = "Hautatu gela bat";
+"room_error_name_edition_not_authorized" = "Ez duzu gela honen izena aldatzeko baimenik";
+"room_error_topic_edition_not_authorized" = "Ez duzu gela honen mintzagaia aldatzeko baimenik";
+"notice_topic_changed" = "%@ erabiltzaileak mintzagaia honetara aldatu du: %@";
+"notice_room_name_changed" = "%@ erabiltzaileak gelaren izena honetara aldatu du: %@";
+"settings_enable_push_notifications" = "Gaitu jakinarazpenak";
+"settings_enter_validation_token_for" = "Sartu %@ balidazio tokena:";
+"room_event_encryption_info_event_fingerprint_key" = "Aldarrikatutako Ed25519 hatz-marka gakoa\n";
+"room_error_cannot_load_timeline" = "Huts egin du denbora lerroa kargatzean";
+"room_left" = "Gelatik atera zara";
+// Room members
+"room_member_ignore_prompt" = "Ziur erabiltzaile honen mezu guztiak ezkutatu nahi dituzula?";
+// Attachment
+"attachment_size_prompt" = "Nola bidali nahi duzu:";
+"attachment_original" = "Jatorrizko tamaina: %@";
+"attachment_small" = "Txikia: %@";
+"attachment_medium" = "Ertaina: %@";
+"attachment_large" = "Handia: %@";
+"attachment_multiselection_size_prompt" = "Irudiak nola bidali nahi dituzu:";
+"attachment_multiselection_original" = "Jatorrizko tamaina";
+"attachment_e2e_keys_file_prompt" = "Fitxategi honek Matrix bezero batetik esportatutako zifratze gakoak ditu.\nFitxategiaren edukia ikusi nahi duzu edo dauzkan gakoak inportatu?";
+"attachment_e2e_keys_import" = "Inportatu…";
+// Contacts
+"contact_mx_users" = "Matrix erabiltzaileak";
+"contact_local_contacts" = "Kontaktu lokalak";
+// Search
+"search_no_results" = "Emaitzarik ez";
+"search_searching" = "Bilaketa abian…";
+// Permissions
+"camera_access_not_granted_for_call" = "Bideo deiek kamera atzitzeko baimena behar dute baina %@ aplikazioak ez du baimenik";
+"microphone_access_not_granted_for_call" = "Deiek mikrofonoa atzitzeko baimena behar dute baina %@ aplikazioak ez du baimenik";
+"local_contacts_access_discovery_warning_title" = "Erabiltzaileak aurkitzea";
+"local_contacts_access_discovery_warning" = "Matrix darabilten kontaktuak aukitzeko, %@ aplikazioak zure helbide-liburuko kontaktuen e-mail eta telefono zenbakiak igo ahal ditu zuk hautatutako Matrix identitate-zerbitzarira. AHal denean, datu pertsonalak hasheatu egingo dira bidali aurretik, egiaztatu zure identitate-zerbitzariaren pribatutasun politika xehetasun gehiagorako.";
+"notice_room_withdraw" = "%@ erabiltzaileak %@ erabiltzailearen gonbidapena atzera bota du";
+"notice_room_reason" = ".Arrazoia: %@";
+"notice_avatar_url_changed" = "%@ erabiltzaileak abatarra aldatu du";
+"notice_display_name_set" = "%@ erabiltzaileak bere pantaila-izena aldatu du beste honetara: %@";
+"notice_display_name_changed_from" = "%@ erabiltzaileak bere pantaila-izena aldatu du, honetatik: %@ honetara: %@";
+"notice_display_name_removed" = "%@ erabiltzaileak bere pantaila-izena kendu du";
+"notice_placed_voice_call" = "%@ erabiltzaileak ahots deia hasi du";
+"notice_placed_video_call" = "%@ erabiltzaileak bideo deia hasi du";
+"notice_answered_video_call" = "%@ erabiltzaileak deia erantzun du";
+"notice_ended_video_call" = "%@ erabiltzaileak deia amaitu du";
+"notice_conference_call_request" = "%@ erabiltzaileak VoIP konferentzia bat eskatu du";
+"set_power_level" = "Ezarri botere-maila";
+// actions
+"action_logout" = "Amaitu saioa";
+"login" = "Hasi saioa";
+"create_account" = "Sortu kontua";
+"membership_leave" = "Atera da";
+"membership_ban" = "Debekatua";
+"num_members_one" = "erabiltzaile %@";
+"num_members_other" = "%@ erabiltzaile";
+"unban" = "Kendu debekua";
+"message_unsaved_changes" = "Gorde gabeko aldaketak daude. Irtenez gero baztertuko dira.";
+// Login Screen
+"login_error_already_logged_in" = "Saioa hasita zegoen jada";
+// contacts list screen
+"invitation_message" = "Zurekin hitz egin nahiko nuke matrix erabilita, zoaz http://matrix.org webgunera informazio gehiagorako.";
+// Settings screen
+"settings_title_config" = "Konfigurazioa";
+// Notification settings screen
+"notification_settings_disable_all" = "Desgaitu jakinarazpen guztiak";
+"notification_settings_enable_notifications" = "Gaitu jakinarazpenak";
+"notification_settings_enable_notifications_warning" = "Jakinarazpen guztiak desgaituta daude gailu guztientzat.";
+"notification_settings_per_word_notifications" = "Hitzen jakinarazpenak";
+"notification_settings_always_notify" = "Jakinarazi beti";
+"notification_settings_never_notify" = "Ez jakinarazi inoiz";
+"notification_settings_word_to_match" = "behatu beharreko hitza";
+"notification_settings_highlight" = "Nabarmendu";
+"notification_settings_custom_sound" = "Soinu pertsonalizatua";
+"notification_settings_per_room_notifications" = "Gelen jakinarazpenak";
+"notification_settings_per_sender_notifications" = "Igorleen jakinarazpenak";
+"notification_settings_sender_hint" = "@erabiltzailea:domeinua.eus";
+"notification_settings_select_room" = "Hautatu gela bat";
+"notification_settings_other_alerts" = "Beste alertak";
+"notification_settings_contain_my_user_name" = "Jakinarazi niri soinuarekin nire erabiltzaile izena duten mezuak daudenean";
+"notification_settings_contain_my_display_name" = "Jakinarazi niri soinuarekin nire pantaila-izena duten mezuak daudenean";
+"notification_settings_just_sent_to_me" = "Jakinarazi niri soinuarekin niri bakarrik bidalitako mezuak daudenean";
+"notification_settings_invite_to_a_new_room" = "Jakinarazi niri gela batera gonbidatzen nautenean";
+"notification_settings_receive_a_call" = "Jakinarazi niri dei bat jasotzen dudanean";
+"notification_settings_suppress_from_bots" = "Kendu boten jakinarazpenak";
+"notification_settings_by_default" = "Lehenetsita…";
+"notification_settings_notify_all_other" = "Jakinarazi beste mezu/gela guztietarako";
+// gcm section
+"settings_config_home_server" = "Hasiera-zerbitzaria: %@";
+"settings_config_identity_server" = "Identitate zerbitzaria: %@";
+"settings_config_user_id" = "Erabiltzaile ID-a: %@";
+// call string
+"call_waiting" = "Itxaroten…";
+"call_connecting" = "Deia konektatzen…";
+"call_ring" = "Deitzen…";
+"call_invite_expired" = "Dei-gonbidapena iraungi da";
+"ssl_fingerprint_hash" = "Hatz-marka (%@):";
+"notice_feedback" = "Informazio gertaera (id: %@): %@";
+"room_event_encryption_verify_message" = "Saio hau fidagarria dela egiaztatzeko, jarri kontaktuan jabearekin beste medio batzuk erabilita (adib. aurrez-aurre edo telefonoz deituz) eta galdetu bere erabiltzaile-ezarpenetan ikusten duten gakoa honekin bat datorren:\n\n\tSaioaren izena: %@\n\tSaioaren ID-a: %@\n\tSaioaren gakoa: %@\n\nBat badatoz, sakatu beheko egiaztatu botoia. Ez badatoz bat, beste norbait egon daiteke saioa atzematen eta ziur aski zerrenda beltzaren botoia zapaldu nahiko duzu.\n\nEtorkizunean egiaztaketa prozesu hau sofistikatuagoa izango da.";
+"room_error_timeline_event_not_found" = "Aplikazioa gela honen denbora lerroko puntu zehatz bat kargatzen saiatu da baina ezin izan du aurkitu";
+"e2e_import_prompt" = "Prozesu honek aurretik beste Matrix bezero batetik esportatutako zifratze gakoak inportatzea ahalbidetzen du. Beste bezeroak dezifratu zitekeen mezuak deszifratu ahal izango duzu.\nEsportazio fitxategia pasaesaldi batez babestuta dago. Pasaesaldi hori hemen jarri behar duzu fitxategia deszifratzeko.";
+"e2e_export_prompt" = "Prozesu honek zifratutako geletan jaso dituzun mezuen gakoak fitxategi lokal batera esportatzea ahalbidetzen dizu. Gero beste Matrix bezero batera inportatu ditzakezu, bezero horrek ere mezu hauek dezifratu ahal izateko.\nEsportatutako fitxategiak berau irakurri dezakeen edonori zuk ikusi ditzakezun mezuak deszifratzea ahalbidetuko dio, beraz kontuz non gordetzen duzun.";
+"local_contacts_access_not_granted" = "Kontaktu lokaletatik erabiltzaileak aurkitzeko zure kontaktua atzitzeko baimena behar da baina %@ aplikazioak ez du baimenik";
+"notification_settings_global_info" = "Jakinarazpen ezarpenak zure erabiltzaile kontuan gordetzen dira eta onartutako bezeroen artean partekatzen dira (mahaigaineko jakinarazpenak barne).\n\nArauak ordenan aplikatzen dira; bat datorren lehen arauak zehazten du zer gertatzen den mezuarekin.\nBeraz: Hitzen jakinarazpenak gelen jakinarazpenak baino garrantzitsuagoak dira, eta hauek igorleen jakinarazpenak baino garrantzitsuagoak.\nMota bereko arauentzat, zerrendan bat datorren lehenak lehentasuna hartzen du.";
+"notification_settings_per_word_info" = "Hitzak ez dituzte bereizten maiuskula eta minuskulak, eta * komodina izan dezakete. Beraz:\nmar bat dator mar katearekin hitz mugatzaileez inguratuta dagoenean (adib. puntuazioa, zuriunea, lerro hasiera edo bukaera).\nmar* bat dator mar-ez hasten den edozein hitzekin.\n*mar* bat dator edozein posiziotan mar duten hitzekin.";
+"notice_room_related_groups" = "Gela honekin lotura duten taldeak hauek dira: %@";
+// Groups
+"group_invite_section" = "Gonbidapenak";
+"group_section" = "Taldeak";
+"notice_sticker" = "eranskailua";
+"notice_in_reply_to" = "Honi erantzunez";
+"error_common_message" = "Errore bat gertatu da. Saiatu berriro geroago.";
+// Reply to message
+"message_reply_to_sender_sent_an_image" = "irudi bat bidali du.";
+"message_reply_to_sender_sent_a_video" = "bideo bat bidali du.";
+"message_reply_to_sender_sent_an_audio_file" = "audio fitxategi bat bidali du.";
+"message_reply_to_sender_sent_a_file" = "fitxategi bat bidali du.";
+"message_reply_to_message_to_reply_to_prefix" = "Honi erantzunez";
+"login_error_resource_limit_exceeded_title" = "Baliabide muga gaindituta";
+"login_error_resource_limit_exceeded_message_default" = "Hasiera zerbitzari honek bere baliabide mugetako bat gainditu du.";
+"login_error_resource_limit_exceeded_message_monthly_active_user" = "Hasiera zerbitzari honek bere hilabeteko erabiltzaile aktiboen muga gainditu du.";
+"login_error_resource_limit_exceeded_message_contact" = "\n\nJarri kontaktuan zerbitzuaren administratzailearekin zerbitzu hau erabiltzen jarraitzeko.";
+"login_error_resource_limit_exceeded_contact_button" = "Kontaktatu administratzailea";
+"e2e_passphrase_create" = "Sortu pasaesaldia";
+"account_error_push_not_allowed" = "Jakinarazpenak ez dira onartzen";
+"device_details_rename_prompt_title" = "Saioaren izena";
+"notice_room_third_party_revoked_invite" = "%@ erabiltzaileak %@ gelara elkartzeko gonbidapena indargabetu du";
+"notice_encryption_enabled_ok" = "%@ erabiltzaileak zifratzea gaitu du.";
+"notice_encryption_enabled_unknown_algorithm" = "%1$@ erabiltzaileak zifratzea gaitu du. (%2$@ algoritmo ezezaguna).";
diff --git a/Riot/Modules/MatrixKit/Assets/MatrixKitAssets.bundle/fa.lproj/MatrixKit.strings b/Riot/Modules/MatrixKit/Assets/MatrixKitAssets.bundle/fa.lproj/MatrixKit.strings
new file mode 100644
index 000000000..d2b523506
--- /dev/null
+++ b/Riot/Modules/MatrixKit/Assets/MatrixKitAssets.bundle/fa.lproj/MatrixKit.strings
@@ -0,0 +1,549 @@
+
+
+"e2e_import_prompt" = "این فرآیند به شما امکان می دهد کلیدهای رمزگذاری را که قبلاً از کلاینت دیگری صادر کرده اید، وارد کنید. سپس می توانید هر پیامی را که کلاینت دیگر رمزگشایی می کند رمزگشایی کنید.\nپرونده با عبارت عبور محافظت می شود. برای رمزگشایی پرونده باید عبارت عبور را در اینجا وارد کنید.";
+"room_member_power_level_prompt" = "شما نمی توانید این تغییر را لغو کنید زیرا در حال ارتقا سطح کاربر به همان سطح خودتان هستید.\nآیا مطمئن هستید؟";
+"notification_settings_per_word_info" = "کلمات به صورت غیر حساس(case insensitive)تطابق میکنند و ممکن است شامل یک wildcard * باشد. بنابراین:\nfoo با رشته foo که توسط جداکنندههای کلمه احاطه شده تطابق میکند (به عنوان مثال علائم نگارشی و فضای خالی یا شروع و پایان خط).\nfoo* با هر کلمه ای که با foo شروع شود مطابقت دارد.\n*foo* با هر کلمهای که حداقل سه حرف foo را داشته باشد تطابق میکند.";
+"ssl_only_accept" = "فقط در صورتی که ادمین سرور اثر انگشت متناسب با مورد بالا را منتشر کرده باشد، گواهی را بپذیرید.";
+"ssl_expected_existing_expl" = "این گواهی از یک گواهینامه مورد اعتماد قبلی به یک گواهینامه مورد اعتماد دیگر تبدیل شده است. سرور ممکن است گواهینامه خود را تمدید کرده باشد. برای اطمینان از اثر انگشت با ادمین سرور تماس بگیرید.";
+"ssl_unexpected_existing_expl" = "این گواهینامه از گواهی مورد اعتماد تلفن شما تغییر کرده است. این بسیار غیر معمول است. توصیه می شود این گواهینامه جدید را قبول نکنید.";
+"ssl_cert_new_account_expl" = "درصورتی که ادمین سرور بگوید مشکلی وجود ندارد، اطمینان حاصل کنید که اثر انگشت زیر با اثر انگشت ارائه شده توسط او مطابقت دارد.";
+"ssl_cert_not_trust" = "این می تواند به این معنی باشد که شخصی ثالثی ترافیک شما را رهگیری می کند یا اینکه تلفن شما به گواهی ارائه شده توسط سرور اعتماد ندارد.";
+"ssl_could_not_verify" = "هویت سرور تأیید نشد.";
+"ssl_fingerprint_hash" = "اثر انگشت (%@):";
+"ssl_remain_offline" = "نادیدهگیری";
+"ssl_logout_account" = "خروج";
+
+// unrecognized SSL certificate
+"ssl_trust" = "اعتماد کردن";
+"call_more_actions_dialpad" = "پد شماره گیری";
+"call_more_actions_transfer" = "انتقال";
+"call_more_actions_audio_use_device" = "بلندگوی دستگاه";
+"call_more_actions_audio_use_headset" = "استفاده از هدست صوتی";
+"call_more_actions_change_audio_device" = "تغییر خروجی صدا";
+"call_more_actions_unhold" = "از سرگیری";
+"call_more_actions_hold" = "قرار دادن در حالت انتظار";
+"call_holded" = "شما تماس را در حالت انتظار قرار دادهاید";
+"call_remote_holded" = "%@ تماس را در حالت انتظار قرار داده است";
+"call_invite_expired" = "تماس منقضی شده است";
+"incoming_video_call" = "تماس ویدیویی ورودی";
+"incoming_voice_call" = "تماس صوتی ورودی";
+"call_ring" = "در حال تماس...";
+"call_ended" = "مکالمه تلفنی تمام شد";
+"call_connecting" = "در حال اتصال …";
+
+// Settings keys
+
+// call string
+"call_waiting" = "در انتظار...";
+"settings_config_user_id" = "شناسه کاربری: %@";
+"settings_config_identity_server" = "سرور هویتسنجی: %@";
+
+// gcm section
+"settings_config_home_server" = "سرور: %@";
+"notification_settings_notify_all_other" = "برای سایر پیام ها / اتاق ها اطلاع بده";
+"notification_settings_by_default" = "به صورت پیش فرض...";
+"notification_settings_suppress_from_bots" = "اعلانهای رباتها را سرکوب کن";
+"notification_settings_receive_a_call" = "هنگام دریافت تماس به من اطلاع بده";
+"notification_settings_people_join_leave_rooms" = "وقتی افراد به اتاق میپیوندند یا از اتاق خارج می شوند، به من اطلاع بده";
+"notification_settings_invite_to_a_new_room" = "وقتی به اتاق جدیدی دعوت شدم به من اطلاع بده";
+"notification_settings_just_sent_to_me" = "در مورد پیامهایی که فقط برای من ارسال شده است با صوت به من اطلاع بده";
+"notification_settings_contain_my_display_name" = "در مورد پیامهایی که حاوی نام من هستند با صدا به من اطلاع بده";
+"notification_settings_contain_my_user_name" = "در مورد پیام هایی که حاوی نام کاربری من است با صوت به من اطلاع بده";
+"notification_settings_other_alerts" = "هشدارهای دیگر";
+"notification_settings_select_room" = "اتاقی را انتخاب کنید";
+"notification_settings_sender_hint" = "@user:domain.com";
+"notification_settings_per_sender_notifications" = "اعلانهای ارسال کننده محور";
+"notification_settings_per_room_notifications" = "اعلانهای اتاقمحور";
+"notification_settings_custom_sound" = "صدای سفارشی";
+"notification_settings_highlight" = "هایلایت";
+"notification_settings_word_to_match" = "کلمه مطابقت";
+"notification_settings_never_notify" = "هرگز اطلاع نده";
+"notification_settings_always_notify" = "همیشه اطلاع بده";
+"notification_settings_per_word_notifications" = "اعلانهای کلمهمحور";
+"notification_settings_global_info" = "تنظیمات اعلان در حساب کاربری شما ذخیره می شود و بین همه کلاینتهایی که از آنها پشتیبانی می کنند به اشتراک گذاشته می شود (از جمله اعلان های دسکتاپ).\n\nقوانین به ترتیب اعمال می شود. اولین قانونی که مطابقت دارد نتیجه پیام را مشخص می کند.\nبنابراین: اعلانهای کلمهمحور از اعلانهای اتاقمحور مهمتر و اعلانهای اتاقمحور از اعلانهای ارسالکنندهمحور مهمتر هستند.\nبرای چندین قانون از یک نوع، اولین قانونی که در لیست مطابقت دارد در اولویت است.";
+"notification_settings_enable_notifications_warning" = "همه اعلانها در حال حاضر برای همه دستگاهها غیرفعال هستند.";
+
+// room display name
+"room_displayname_empty_room" = "اتاق خالی";
+"notice_in_reply_to" = "در پاسخ به";
+"notice_sticker" = "استیکر";
+"notice_crypto_error_unknown_inbound_session_id" = "نشست فرستنده کلیدهای این پیام را برای ما ارسال نکرده است.";
+"notice_room_history_visible_to_members_from_joined_point" = "%@ تاریخچه آیندهی اتاق را از همان زمانی که افراد به اتاق پیوستند برای آنان قابل مشاهده کرد.";
+"notice_room_history_visible_to_members_from_invited_point" = "%@ تاریخچه آیندهی اتاق را از همان زمانی که افراد دعوت شده اند برای آنان قابل مشاهده کرد.";
+"login_email_info" = "یک ایمیل مشخص کنید تا سایر کاربران بتوانند شما را در ماتریکس با سهولت بیشتری پیدا کنند و به شما راهی برای تنظیم مجدد رمز عبور خود در آینده بدهد.";
+"login_identity_server_info" = "ماتریکس سرورهای هویتسنجی را برای ردیابی اینکه کدام ایمیلها و غیره متعلق به کدام شناسههای ماتریکس هستند فراهم می کند. در حال حاضر فقط https://matrix.org وجود دارد.";
+"notice_video_attachment" = "پیوست ویدیویی";
+"notice_audio_attachment" = "پیوست صوتی";
+"notice_image_attachment" = "پیوست تصویر";
+"notice_encryption_enabled_unknown_algorithm" = "%1$@ رمزگذاری سرتاسری را فعال کرد (الگوریتم ناشناخته %2$@).";
+"notice_encryption_enabled_ok" = "%@ رمزگذاری سرتاسری را فعال کرد.";
+"notice_encrypted_message" = "پیام رمزگذاری شده";
+"notice_room_related_groups" = "گروه های مرتبط با این اتاق عبارتند از: %@";
+"notice_room_aliases_for_dm" = "نام های مستعار عبارتند از: %@";
+"notice_room_aliases" = "نام مستعار اتاق: %@";
+"notice_room_power_level_event_requirement" = "حداقل سطح قدرت مربوط به رویدادها عبارت است از:";
+"notice_room_power_level_acting_requirement" = "حداقل سطح قدرت که کاربر باید قبل از اقدام داشته باشد:";
+"notice_room_power_level_intro_for_dm" = "سطح قدرت اعضا عبارت است از:";
+"notice_room_power_level_intro" = "سطح قدرت اعضای اتاق عبارت است از:";
+"notice_room_join_rule_public_by_you_for_dm" = "شما اینجا را عمومی کردید.";
+"notice_room_join_rule_public_by_you" = "شما اتاق را عمومی کردید.";
+"notice_room_join_rule_public_for_dm" = "%@ اینجا را عمومی کرد.";
+"notice_room_join_rule_public" = "%@ اتاق را عمومی کرد.";
+"notice_room_join_rule_invite_by_you_for_dm" = "شما گفتگو را به حالت \"فقط با دعوت\" تنظیم کردید.";
+"notice_room_join_rule_invite_by_you" = "شما اتاق را به حالت \"فقط با دعوت\" تنظیم کردید.";
+"notice_room_join_rule_invite_for_dm" = "%@ گفتگو را به حالت \"فقط با دعوت\" تنظیم کرد.";
+// New
+"notice_room_join_rule_invite" = "%@ اتاق را به حالت \"فقط با دعوت\" تنظیم کرد.";
+// Old
+"notice_room_join_rule" = "قانون پیوستن: %@";
+"notice_room_created_for_dm" = "%@ پیوست.";
+"notice_room_created" = "%@ اتاق را ایجاد و پیکربندی کرد.";
+"notice_profile_change_redacted" = "%@ پروفایل خود را بروز کرد %@";
+"notice_event_redacted_reason" = " [علت: %@]";
+"notice_event_redacted_by" = " توسط %@";
+"notice_event_redacted" = "<%@ واکنش نشان داد>";
+"notice_room_topic_removed" = "%@ موضوع را حذف کرد";
+"notice_room_name_removed_for_dm" = "%@ نام را حذف کرد";
+"notice_room_name_removed" = "%@ نام اتاق را حذف کرد";
+
+// Events formatter
+"notice_avatar_changed_too" = "(آواتار هم تغییر کرد)";
+"unignore" = "عدم نادیدهگیری";
+"ignore" = "چشم پوشی";
+"resume_call" = "از سرگیری";
+"end_call" = "پایان تماس";
+"reject_call" = "رد تماس";
+"answer_call" = "پاسخ دادن به تماس";
+"show_details" = "نمایش جزئیات";
+"cancel_download" = "لغو بارگیری";
+"cancel_upload" = "لغو بارگذاری";
+"select_all" = "انتخاب همه";
+"resend_message" = "ارسال مجدد پیام";
+"reset_to_default" = "تنظیم به حالت پیش فرض";
+"invite_user" = "دعوت کاربر ماتریکس";
+"capture_media" = "گرفتن عکس/فیلم";
+"attach_media" = "پیوست رسانه از کتابخانه";
+"select_account" = "یک حساب انتخاب کنید";
+"mention" = "اشاره";
+"start_video_call" = "شروع تماس ویدیویی";
+"start_voice_call" = "شروع تماس صوتی";
+"start_chat" = "شروع گفتگو";
+"set_admin" = "تنظیم مدیر";
+"set_moderator" = "تنظیم معاون";
+"set_default_power_level" = "تنظیم مجدد سطح قدرت";
+"set_power_level" = "تنظیم سطح قدرت";
+"submit_code" = "ارسال کد";
+"submit" = "ارسال";
+"sign_up" = "ثبت نام";
+"retry" = "تلاش مجدد";
+"dismiss" = "رد";
+"discard" = "رها کردن";
+"continue" = "ادامه";
+"close" = "بستن";
+"back" = "بازگشت";
+"abort" = "لغو";
+"yes" = "بله";
+
+// Action
+"no" = "خیر";
+"login_error_resource_limit_exceeded_contact_button" = "تماس با ادمین";
+"login_error_resource_limit_exceeded_message_contact" = "\n\nلطفاً برای ادامه استفاده از این سرویس با سرپرست سرویس خود تماس بگیرید.";
+"login_error_resource_limit_exceeded_message_monthly_active_user" = "این سرور به محدودیت کاربر فعال ماهانه خود رسیده است.";
+"login_error_resource_limit_exceeded_message_default" = "این سرور از یکی از محدودیت های منابع خود فراتر رفته است.";
+"login_error_resource_limit_exceeded_title" = "از محدودیت منابع فراتر رفت";
+"login_desktop_device" = "دسکتاپ";
+"login_tablet_device" = "تبلت";
+"login_mobile_device" = "تلفن همراه";
+"login_error_forgot_password_is_not_supported" = "گذرواژه فراموش شده در حال حاضر پشتیبانی نمی شود";
+"register_error_title" = "ثبت نام انجام نشد";
+"login_invalid_param" = "پارامتر نامعتبر است";
+"login_leave_fallback" = "لغو";
+"login_use_fallback" = "استفاده از صفحه بازگشت";
+"login_error_login_email_not_yet" = "لینک ایمیل هنوز کلیک نشده است";
+"login_error_user_in_use" = "این نام کاربری قبلاً استفاده شده است";
+"login_error_limit_exceeded" = "درخواستهای زیادی ارسال شده است";
+"login_error_not_json" = "حاوی JSON معتبر نبود";
+"login_error_bad_json" = "JSON بد شکل";
+"login_error_unknown_token" = "توکن دسترسی وارد شده معتبر نیست";
+"login_error_forbidden" = "نام کاربری / گذرواژه نامعتبر است";
+"login_error_registration_is_not_supported" = "ثبت نام در حال حاضر پشتیبانی نمی شود";
+"login_error_do_not_support_login_flows" = "در حال حاضر ما از روش ورود به سیستم تعریف شده توسط سرور پشتیبانی نمی کنیم";
+"login_error_no_login_flow" = "ما موفق به بازیابی اطلاعات احراز هویت از این سرور نشدیم";
+"login_error_title" = "ورود ناموفق بود";
+"login_prompt_email_token" = "لطفا کد اعتبارسنجی ایمیل خود را وارد کنید:";
+"login_email_placeholder" = "آدرس ایمیل";
+"login_display_name_placeholder" = "نام (به عنوان مثال محمد حسینی)";
+"login_optional_field" = "اختیاری";
+"login_password_placeholder" = "گذرواژه";
+"login_user_id_placeholder" = "شناسه ماترکیس (به عنوان مثال @bob:matrix.org یا Bob)";
+"login_identity_server_title" = "آدرس سرور هویتسنجی:";
+"login_home_server_info" = "سرور تمام مکالمات و دادههای حساب شما را ذخیره می کند";
+"login_home_server_title" = "آدرس سرور:";
+"login_server_url_placeholder" = "URL (به عنوان مثال https://matrix.org)";
+
+// Login Screen
+"login_create_account" = "ایجاد حساب:";
+/* *********************** */
+/* iOS specific */
+/* *********************** */
+
+"matrix" = "ماتریکس";
+"notification_settings_enable_notifications" = "فعال سازی اعلانها";
+
+// Notification settings screen
+"notification_settings_disable_all" = "همه اعلانها را غیرفعال کنید";
+"settings_title_notifications" = "اعلانها";
+
+// Settings screen
+"settings_title_config" = "پیکربندی";
+
+// contacts list screen
+"invitation_message" = "من دوست دارم با شما با ماتریس گپ بزنم. لطفاً برای کسب اطلاعات بیشتر به وب سایت http://matrix.org مراجعه کنید.";
+
+// members list Screen
+
+// accounts list Screen
+
+// image size selection
+
+// invitation members list Screen
+
+// room creation dialog Screen
+
+// room info dialog Screen
+
+// room details dialog screen
+"room_details_title" = "جزئیات اتاق";
+"login_error_must_start_http" = "آدرس باید با http[s]:// شروع شود";
+
+// Login Screen
+"login_error_already_logged_in" = "قبلاً وارد سیستم شدهاید";
+"message_unsaved_changes" = "تغییرات ذخیره نشدهای وجود دارد. ترک کردن موجب از بین رفتن این تغییرات میشود.";
+"unban" = "لغو تحریم";
+"ban" = "تحریم";
+"kick" = "اخراج";
+"invite" = "دعوت";
+"num_members_other" = "%@ کاربر";
+"num_members_one" = "%@ کاربر";
+"membership_ban" = "تحریم";
+"membership_leave" = "ترک کرد";
+"membership_invite" = "دعوت کرد";
+"create_account" = "ایجاد حساب";
+"login" = "ورود";
+"create_room" = "ایجاد اتاق";
+
+// actions
+"action_logout" = "خروج";
+"view" = "مشاهده";
+"delete" = "حذف";
+"share" = "اشتراک گذاری";
+"redact" = "حذف";
+"resend" = "ارسال مجدد";
+"copy_button_name" = "کپی";
+"send" = "ارسال";
+"leave" = "ترک";
+"save" = "ذخیره";
+"cancel" = "لغو";
+
+// Room Screen
+
+// general errors
+
+// Home Screen
+
+// Last seen time
+
+// call events
+
+/* -*-
+ Automatic localization for en
+
+ The following key/value pairs were extracted from the android i18n file:
+ /console/src/main/res/values/strings.xml.
+*/
+
+
+// titles
+
+// button names
+"ok" = "خب";
+"notice_room_history_visible_to_members_from_joined_point_by_you_for_dm" = "پیامهای آینده را از زمان ملحق شدن افراد برای همه قابل مشاهده کردید.";
+"notice_room_history_visible_to_members_from_joined_point_by_you" = "شما تاریخچه آینده اتاق را از همان زمانی که به آن ملحق شدهاند، برای همه اعضای اتاق قابل مشاهده کردید.";
+"notice_room_history_visible_to_members_from_invited_point_by_you_for_dm" = "شما پیامهای آینده را از زمان دعوت شدن برای همه قابل مشاهده کردید.";
+"notice_room_history_visible_to_members_from_invited_point_by_you" = "شما تاریخچه آینده اتاق را از همان زمانی که دعوت شدهاند ، برای همه اعضای اتاق قابل مشاهده کردید.";
+"notice_room_history_visible_to_members_by_you_for_dm" = "شما پیامهای آینده را برای همه اعضای اتاق قابل مشاهده کردید.";
+"notice_room_history_visible_to_anyone_by_you" = "شما تاریخچه آینده اتاق را برای همه قابل مشاهده کردید.";
+"notice_room_history_visible_to_members_by_you" = "شما تاریخچه آینده اتاق را برای همه اعضای اتاق قابل مشاهده کردید.";
+"notice_redaction_by_you" = "شما یک رویداد را مجدداً ویرایش کردید (شناسه: %@)";
+"notice_encryption_enabled_unknown_algorithm_by_you" = "رمزگذاری سرتاسری را فعال کردید (الگوریتم ناشناخته %@).";
+"notice_encryption_enabled_ok_by_you" = "رمزگذاری سرتاسری را فعال کردید.";
+"notice_room_created_by_you" = "شما اتاق را ایجاد و پیکربندی کردید.";
+"notice_profile_change_redacted_by_you" = "شما نمایه خود را به روز کردید %@";
+"notice_event_redacted_by_you" = " توسط شما";
+"notice_room_topic_removed_by_you" = "موضوع را حذف کردید";
+"notice_room_name_removed_by_you_for_dm" = "نام را حذف کردید";
+"notice_room_name_removed_by_you" = "نام اتاق را حذف کردید";
+"notice_conference_call_request_by_you" = "شما درخواست جلسه دادید";
+"notice_declined_video_call_by_you" = "شما تماس را رد کردید";
+"notice_ended_video_call_by_you" = "تماس را تمام کردید";
+"notice_answered_video_call_by_you" = "شما به تماس پاسخ دادید";
+"notice_placed_video_call_by_you" = "شما یک تماس ویدیویی برقرار کردید";
+"notice_placed_voice_call_by_you" = "شما یک تماس صوتی برقرار کردید";
+"notice_room_name_changed_by_you_for_dm" = "نام را به %@ تغییر دادید.";
+"notice_room_name_changed_by_you" = "نام اتاق را به %@ تغییر دادید.";
+"notice_topic_changed_by_you" = "شما موضوع را به \"%@\" تغییر دادید.";
+"notice_display_name_removed_by_you" = "نام خود را حذف کردید";
+"notice_display_name_changed_from_by_you" = "نام خود را از %@ به %@ تغییر دادید";
+"notice_display_name_set_by_you" = "نام خود را به %@ تنظیم کردید";
+"notice_avatar_url_changed_by_you" = "آواتار خود را تغییر دادید";
+"notice_room_withdraw_by_you" = "شما دعوت %@ را پس گرفتید";
+"notice_room_ban_by_you" = "شما %@ را تحریم کردید";
+"notice_room_unban_by_you" = "شما %@ را از تحریم خارج کردید";
+"notice_room_kick_by_you" = "شما %@ را اخراج کردید";
+"notice_room_reject_by_you" = "شما دعوت را رد کردید";
+"notice_room_leave_by_you" = "خارج شدید";
+"notice_room_join_by_you" = "شما پیوستید";
+"notice_room_third_party_revoked_invite_by_you_for_dm" = "شما دعوت %@ را لغو کردید";
+"notice_room_third_party_revoked_invite_by_you" = "شما دعوت از %@ برای پیوستن به اتاق را لغو کردید";
+"notice_room_third_party_registered_invite_by_you" = "شما دعوت %@ را پذیرفتید";
+"notice_room_third_party_invite_by_you_for_dm" = "شما %@ را دعوت کردید";
+"notice_room_third_party_invite_by_you" = "شما دعوتنامهای به %@ برای پیوستن به اتاق ارسال کردهاید";
+"notice_room_invite_you" = "%@ شما را دعوت کرد";
+
+// Notice Events with "You"
+"notice_room_invite_by_you" = "شما %@ را دعوت کردید";
+"notice_conference_call_finished" = "جلسه به پایان رسید";
+"notice_conference_call_started" = "جلسه آغاز شد";
+"notice_conference_call_request" = "%@ درخواست جلسه مجازی کرد";
+"notice_declined_video_call" = "%@ تماس را رد کرد";
+"notice_ended_video_call" = "%@ به تماس پایان داد";
+"notice_answered_video_call" = "%@ به تماس پاسخ داد";
+"notice_placed_video_call" = "%@ تماس تصویری برقرار کرد";
+"notice_placed_voice_call" = "%@ تماس صوتی برقرار کرد";
+"notice_room_name_changed_for_dm" = "%@ نام را به %@ تغییر داد.";
+"notice_room_name_changed" = "%@ نام اتاق را به %@ تغییر داد.";
+"notice_topic_changed" = "%@ موضوع را به \"%@\" تغییر داد.";
+"notice_display_name_removed" = "%@ نام خود را حذف کرد";
+"notice_display_name_changed_from" = "%@ نام خود را از %@ به %@ تغییر داد";
+"notice_display_name_set" = "%@ نام خود را به %@ تنظیم کرد";
+"notice_avatar_url_changed" = "%@ آواتار خود را تغییر داد";
+"notice_room_reason" = ". دلیل: %@";
+"notice_room_withdraw" = "%@ دعوت %@ را پس گرفت";
+"notice_room_ban" = "%@ %@ را تحریم کرد";
+"notice_room_unban" = "%@ %@ را از تحریم خارج کرد";
+"notice_room_kick" = "%@ %@ را اخراج کرد";
+"notice_room_reject" = "%@ دعوت را رد کرد";
+"notice_room_leave" = "%@ ترک کرد";
+"notice_room_join" = "%@ پیوست";
+"notice_room_third_party_revoked_invite_for_dm" = "%@ دعوت %@ را لغو کرد";
+"notice_room_third_party_revoked_invite" = "%@ دعوت از %@ برای پیوستن به اتاق را لغو کرد";
+"notice_room_third_party_registered_invite" = "%@ دعوت %@ را پذیرفت";
+"notice_room_third_party_invite_for_dm" = "%@ %@ را دعوت کرد";
+"notice_room_third_party_invite" = "%@ برای پیوستن به اتاق به %@ دعوتنامه ارسال کرد";
+
+/* -*-
+ Automatic localization for en
+
+ The following key/value pairs were extracted from the android i18n file:
+ /matrix-sdk/src/main/res/values/strings.xml.
+*/
+
+"notice_room_invite" = "%@ %@ را دعوت کرد";
+"language_picker_default_language" = "پیش فرض (%@)";
+
+// Language picker
+"language_picker_title" = "انتخاب زبان";
+
+// Country picker
+"country_picker_title" = "یک کشور را انتخاب کنید";
+"local_contacts_access_not_granted" = "کشف کاربران از دفترچه مخاطبین نیاز به دسترسی به مخاطبین شما دارد اما %@ اجازه استفاده از آنها را ندارد";
+"local_contacts_access_discovery_warning" = "برای کشف مخاطبی که در حال حاظر از ماتریکس استفاده میکند، %@ می تواند آدرسهای ایمیل و شماره تلفنهای موجود در دفترچه آدرس خود را به سرور هویتسنجی انتخابی شما ارسال کند. در صورت پشتیبانی، داده های شخصی قبل از ارسال هش می شوند - لطفا برای اطلاعات بیشتر سیاست حفظ حریم خصوصی سرور هویتسنجی خود را بررسی کنید.";
+"local_contacts_access_discovery_warning_title" = "کشف کاربران";
+"microphone_access_not_granted_for_call" = "تماس ها نیاز به دسترسی به میکروفن دارند اما %@ اجازه استفاده از آن را ندارد";
+
+// Permissions
+"camera_access_not_granted_for_call" = "تماس های ویدئویی نیاز به دسترسی به دوربین دارند اما %@ اجازه استفاده از آن را ندارد";
+"ssl_homeserver_url" = "آدرس سرور: %@";
+"user_id_placeholder" = "مثال: @akbar:homeserver";
+"network_error_not_reachable" = "لطفاً اتصال شبکه خود را بررسی کنید";
+"power_level" = "سطح قدرت";
+"public" = "عمومی";
+"private" = "خصوصی";
+"not_supported_yet" = "در حال حاظر پشتیبانی نمیشود";
+"error_common_message" = "خطایی رخ داد لطفاً بعداً دوباره امتحان کنید.";
+"error" = "خطا";
+"unsent" = "ارسال نشده";
+
+// Others
+"user_id_title" = "شناسه کاربری:";
+"e2e_passphrase_create" = "ایجاد عبارت عبور";
+"e2e_passphrase_not_match" = "عبارات عبور باید مطابقت داشته باشند";
+"e2e_passphrase_empty" = "عبارت عبور نباید خالی باشد";
+"e2e_passphrase_confirm" = "عبارت عبور را تأیید کنید";
+"e2e_export" = "ذخیره";
+"e2e_export_prompt" = "این فرآیند به شما امکان می دهد کلیدهای پیام هایی را که در اتاق های رمزگذاری شده دریافت کردهاید در یک فایل ذخیره کنید. سپس می توانید فایل را در آینده به یک کلاینت دیگر وارد کنید، بنابراین کلاینت همچنین می تواند این پیام ها را رمزگشایی کند.\nفایل ذخیره شده به هر کسی که می تواند آن را بخواند اجازه می دهد تا پیامهای رمزگذاری شدهای را که می بینید رمزگشایی کند، بنابراین باید مراقب امنیت آن باشید.";
+
+// E2E export
+"e2e_export_room_keys" = "کلیدهای اتاق را صادر کنید";
+"e2e_passphrase_enter" = "عبارت عبور را وارد کنید";
+"e2e_import" = "وارد كردن";
+"notice_room_created_by_you_for_dm" = "شما پیوستید.";
+"default" = "پیشفرض";
+"offline" = "آفلاین";
+
+// E2E import
+"e2e_import_room_keys" = "ورود کلیدهای اتاق";
+"format_time_d" = "d";
+"format_time_h" = "h";
+"format_time_m" = "m";
+
+// Time
+"format_time_s" = "s";
+"search_searching" = "در حال جستجو ...";
+
+// Search
+"search_no_results" = "بدون نتیجه";
+"group_section" = "گروهها";
+
+// Groups
+"group_invite_section" = "دعوتها";
+"contact_local_contacts" = "مخاطبین محلی";
+
+// Contacts
+"contact_mx_users" = "کاربران ماتریکس";
+"attachment_e2e_keys_import" = "وارد كردن...";
+"attachment_e2e_keys_file_prompt" = "این پرونده شامل کلیدهای رمزگذاری کلاینت ماتریکس است.\nآیا می خواهید محتوای پرونده را مشاهده کنید یا کلیدهای موجود در آن را وارد کنید؟";
+"attachment_multiselection_original" = "اندازه واقعی";
+"attachment_multiselection_size_prompt" = "تصاویر به چه صورت ارسال شود:";
+
+// Attachment
+"attachment_size_prompt" = "میخواهید به چه صورت ارسال کنید:";
+"attachment_cancel_upload" = "بارگذاری لغو شود؟";
+"attachment_cancel_download" = "بارگیری لغو شود؟";
+"attachment_large" = "بزرگ: %@";
+"attachment_medium" = "متوسط: %@";
+"attachment_small" = "کوچک: %@";
+"attachment_original" = "اندازه واقعی: %@";
+
+// Room members
+"room_member_ignore_prompt" = "آیا مطمئن هستید که می خواهید همه پیام های این کاربر را پنهان کنید؟";
+"message_reply_to_message_to_reply_to_prefix" = "در پاسخ به";
+"message_reply_to_sender_sent_a_file" = "پروندهای ارسال شد.";
+"message_reply_to_sender_sent_an_audio_file" = "یک فایل صوتی ارسال شد.";
+
+// Reply to message
+"message_reply_to_sender_sent_an_image" = "یک تصویر ارسال شد.";
+"message_reply_to_sender_sent_a_video" = "یک ویدیو ارسال شد.";
+"room_no_conference_call_in_encrypted_rooms" = "جلسات در اتاق های رمزگذاری شده پشتیبانی نمی شوند";
+"room_no_power_to_create_conference_call" = "برای شروع جلسه در این اتاق نیاز به دسترسی دعوت دارید";
+"room_left_for_dm" = "خارج شدید";
+"room_left" = "شما از اتاق خارج شدید";
+"room_error_timeline_event_not_found" = "برنامه سعی داشت نقطه خاصی را در پیامهای این اتاق بارگیری کند اما نتوانست آن را پیدا کند";
+"room_error_timeline_event_not_found_title" = "خطا در بارگیری موقعیت پیامها";
+"room_error_cannot_load_timeline" = "خطا در بارگیری پیامها";
+"room_error_topic_edition_not_authorized" = "شما مجاز به ویرایش موضوع در اتاق نیستید";
+"room_error_name_edition_not_authorized" = "شما مجاز به ویرایش این نام اتاق نیستید";
+"room_error_join_failed_empty_room" = "در حال حاضر امکان عضویت مجدد در یک اتاق خالی وجود ندارد.";
+"room_error_join_failed_title" = "پیوستن به اتاق با خطا مواجه شد";
+
+// Room
+"room_please_select" = "لطفا یک اتاق انتخاب کنید";
+"room_creation_participants_placeholder" = "(به عنوان مثال، @ali:homeserver1; @akbar:homeserver1; ...)";
+"room_creation_participants_title" = "شركت كنندگان:";
+"room_creation_alias_placeholder_with_homeserver" = "(به عنوان مثال، #foo%@)";
+"room_creation_alias_placeholder" = "(به عنوان مثال #foo:example.org)";
+"room_creation_alias_title" = "نام مستعار اتاق:";
+"room_creation_name_placeholder" = "به عنوان مثال(گروه ناهار)";
+
+// Room creation
+"room_creation_name_title" = "نام اتاق:";
+"account_error_push_not_allowed" = "اعلان مجاز نیست";
+"account_error_msisdn_wrong_description" = "به نظر نمی رسد این شماره تلفن معتبری باشد";
+"account_error_msisdn_wrong_title" = "شماره تلفن نامعتبر";
+"account_error_email_wrong_description" = "به نظر نمی رسد این یک آدرس ایمیل معتبر باشد";
+"account_error_email_wrong_title" = "آدرس ایمیل نامعتبر است";
+"account_error_matrix_session_is_not_opened" = "نشست ماتریس باز نیست";
+"account_error_picture_change_failed" = "تغییر تصویر انجام نشد";
+"account_error_display_name_change_failed" = "تغییر نام ناموفق بود";
+"account_msisdn_validation_error" = "تأیید شماره تلفن امکان پذیر نیست.";
+"account_msisdn_validation_message" = "ما یک پیامک با کد فعالسازی ارسال کردهایم. لطفاً این کد را در زیر وارد کنید.";
+"account_msisdn_validation_title" = "در انتظار تأیید";
+"room_event_encryption_verify_message" = "برای تأیید اینکه این نشست قابل اعتماد است، لطفاً با استفاده از روشهای دیگر (مثلاً به صورت حضوری یا تماس تلفنی) با مالک آن تماس بگیرید و از آنها سوال کنید که آیا کلیدی که در تنظیمات کاربر خود برای این نشست می بینند با کلید زیر مطابقت دارد:\n\n نام نشست: %@\nشناسه نشست: %@\nکلید نشست: %@\n\nدر صورت مطابقت ، دکمه تأیید را در زیر فشار دهید. در صورت عدم تطابق، شخص ثالثی این نشست را رهگیری می کند و شما قاعدتا باید دکمه لیست سیاه را فشار دهید.\n\nدر آینده این روند تأیید پیچیده تر خواهد بود.";
+"account_email_validation_error" = "تأیید آدرس ایمیل انجام نشد. لطفاً ایمیل خود را بررسی کرده و روی پیوند حاوی آن کلیک کنید. پس از انجام این کار ، روی ادامه کلیک کنید";
+"account_email_validation_message" = "لطفاً ایمیل خود را بررسی کرده و روی پیوند حاوی آن کلیک کنید. پس از انجام این کار ، روی ادامه کلیک کنید.";
+"account_email_validation_title" = "در انتظار تایید";
+"account_linked_emails" = "ایمیل های متصل شده";
+"account_link_email" = "اتصال به ایمیل";
+
+// Account
+"account_save_changes" = "ذخیره تغییرات";
+"room_event_encryption_verify_ok" = "تأیید";
+"room_event_encryption_verify_title" = "تایید نشست\n\n";
+"room_event_encryption_info_unblock" = "خروج از لیست سیاه";
+"room_event_encryption_info_block" = "لیست سیاه";
+"room_event_encryption_info_unverify" = "تأیید نکردن";
+"room_event_encryption_info_verify" = "تأیید کنید ...";
+"room_event_encryption_info_device_blocked" = "در لیست سیاه قرار گرفت";
+"room_event_encryption_info_device_not_verified" = "تأیید نشده است";
+"room_event_encryption_info_device_verified" = "تأیید شده";
+"room_event_encryption_info_device_fingerprint" = "اثر انگشت Ed25519\n";
+"room_event_encryption_info_device_verification" = "تأیید هویت\n";
+"room_event_encryption_info_device_id" = "شناسه\n";
+"room_event_encryption_info_device_name" = "نام عمومی\n";
+"room_event_encryption_info_device_unknown" = "نشست ناشناخته\n";
+"room_event_encryption_info_device" = "\nاطلاعات نشست ارسال کننده\n";
+"room_event_encryption_info_event_none" = "هیچ یک";
+"room_event_encryption_info_event_unencrypted" = "رمزگذاری نشده";
+"room_event_encryption_info_event_decryption_error" = "خطای رمزگشایی\n";
+"room_event_encryption_info_event_session_id" = "شناسه جلسه\n";
+"room_event_encryption_info_event_algorithm" = "الگوریتم\n";
+"room_event_encryption_info_event_fingerprint_key" = "کلید اثر انگشت Ed25519 ادعا شده\n";
+"room_event_encryption_info_event_identity_key" = "کلید Curve25519\n";
+"room_event_encryption_info_event_user_id" = "شناسه کاربر\n";
+"room_event_encryption_info_event" = "اطلاعات رویداد\n";
+
+// Encryption information
+"room_event_encryption_info_title" = "اطلاعات رمزنگاری سرتاسری\n\n";
+"device_details_delete_prompt_message" = "این عملیات نیاز به احراز هویت مجدد دارد.\nبرای ادامه ، لطفاً گذرواژه خود را وارد کنید.";
+"device_details_delete_prompt_title" = "احراز هویت";
+"device_details_rename_prompt_message" = "نام عمومی یک نشست برای افرادی که با آنها ارتباط برقرار می کنید قابل مشاهده است";
+"device_details_rename_prompt_title" = "نام نشست";
+"device_details_last_seen_format" = "%@ @ %@\n";
+"device_details_last_seen" = "آخرین بازدید\n";
+"device_details_identifier" = "شناسه\n";
+"device_details_name" = "نام عمومی\n";
+
+// Devices
+"device_details_title" = "اطلاعات نشست\n";
+"notification_settings_room_rule_title" = "اتاق: %@";
+"settings_enter_validation_token_for" = "توکن فعالسازی را برای %@ وارد کنید:";
+"settings_enable_push_notifications" = "فعالسازی اعلان";
+"settings_enable_inapp_notifications" = "فعالسازی اعلان درون برنامه ای";
+
+// Settings
+"settings" = "تنظیمات";
+"room_displayname_more_than_two_members" = "%@ و %@ نفر دیگر";
+"room_displayname_two_members" = "%@ و %@";
+"notice_crypto_unable_to_decrypt" = "** رمزگشایی امکان پذیر نیست: %@ **";
+"notice_room_history_visible_to_members_from_joined_point_for_dm" = "%@ پیام های آینده را از زمان پیوستن افراد قابل مشاهده کرد.";
+"notice_room_history_visible_to_members_from_invited_point_for_dm" = "%@ پیام های آینده را از زمان دعوت شدن برای همه قابل مشاهده کرد.";
+"notice_room_history_visible_to_members_for_dm" = "%@ پیامهای آینده را برای همه اعضای اتاق قابل مشاهده کرد.";
+"notice_room_history_visible_to_members" = "%@ تاریخچه آینده اتاق را برای همه اعضای اتاق قابل مشاهده کرد.";
+"notice_room_history_visible_to_anyone" = "%@ تاریخچه آینده اتاق را برای همه قابل مشاهده کرد.";
+"notice_error_unknown_event_type" = "نوع رویداد ناشناخته";
+"notice_error_unexpected_event" = "رویداد غیرمنتظره";
+"notice_error_unsupported_event" = "رویداد پشتیبانی نشده";
+"notice_redaction" = "%@ یک رویداد را تغییر داد (شناسه: %@)";
+"notice_feedback" = "بازخورد (شناسه: %@): %@";
+"notice_unsupported_attachment" = "پیوست پشتیبانی نشده: %@";
+"notice_invalid_attachment" = "پیوست نامعتبر";
+"notice_file_attachment" = "پیوست پرونده";
+"notice_location_attachment" = "پیوست مکان";
+"call_transfer_to_user" = "انتقال به %@";
+"call_consulting_with_user" = "تماس با %@";
+"call_video_with_user" = "تماس تصویری با %@";
+"call_voice_with_user" = "تماس صوتی با %@";
+"call_ringing" = "در حال زنگ خوردن…";
+"microphone_access_not_granted_for_voice_message" = "جهت ارسال پیام صوتی نیاز به دسترسی به میکروفون وجود دارد اما %@ دسترسی استفاده از آن را ندارد";
+"e2e_passphrase_too_short" = "کلمه عبور بیش از حد کوتاه است (حداقل میبایست %d کاراکتر باشد)";
+"message_reply_to_sender_sent_a_voice_message" = "یک پیام صوتی ارسال کنید.";
diff --git a/Riot/Modules/MatrixKit/Assets/MatrixKitAssets.bundle/fi.lproj/MatrixKit.strings b/Riot/Modules/MatrixKit/Assets/MatrixKitAssets.bundle/fi.lproj/MatrixKit.strings
new file mode 100644
index 000000000..08a9690f9
--- /dev/null
+++ b/Riot/Modules/MatrixKit/Assets/MatrixKitAssets.bundle/fi.lproj/MatrixKit.strings
@@ -0,0 +1,151 @@
+"matrix" = "Matrix";
+// Login Screen
+"login_create_account" = "Luo tili:";
+"login_server_url_placeholder" = "URL (esim. https://matrix.org)";
+"login_home_server_title" = "Kotipalvelin:";
+"login_identity_server_title" = "Identiteettipalvelin:";
+"view" = "Näytä";
+"back" = "Takaisin";
+"continue" = "Jatka";
+"leave" = "Poistu";
+"invite" = "Kutsu";
+"retry" = "Yritä uudelleen";
+"cancel" = "Peruuta";
+"save" = "Tallenna";
+"login_password_placeholder" = "Salasana";
+"login_optional_field" = "valinnainen";
+"login_home_server_info" = "Kotipalvelin tallentaa kaikki keskustelu- ja tilitietosi";
+"login_display_name_placeholder" = "Näyttönimi (esim. Matti Meikäläinen)";
+"login_email_placeholder" = "Sähköpostiosoite";
+"login_error_title" = "Kirjautuminen epäonnistui";
+"login_error_registration_is_not_supported" = "Rekisteröinti ei ole tuettu tällä hetkellä";
+"login_error_forbidden" = "Virheellinen käyttäjätunnus tai salasana";
+"login_error_limit_exceeded" = "Liian monta pyyntöä on lähetetty";
+"login_leave_fallback" = "Peruuta";
+"register_error_title" = "Rekisteröinti epäonnistui";
+"login_mobile_device" = "Mobiili";
+"login_tablet_device" = "Tabletti";
+"login_desktop_device" = "Työpöytä";
+"close" = "Sulje";
+"select_account" = "Valitse tili";
+"invite_user" = "Kutsu Matrix-käyttäjä";
+"select_all" = "Valitse kaikki";
+"cancel_upload" = "Peruuta lähetys";
+"cancel_download" = "Peruuta lataus";
+"show_details" = "Näytä tiedot";
+"answer_call" = "Vastaa puheluun";
+"reject_call" = "Hylkää puhelu";
+"end_call" = "Lopeta puhelu";
+"notice_event_redacted_reason" = " [syy: %@]";
+"notice_encrypted_message" = "Salattu viesti";
+"notice_image_attachment" = "kuvaliite";
+"notice_audio_attachment" = "ääniliite";
+"notice_video_attachment" = "videoliite";
+"notice_location_attachment" = "sijaintiliite";
+"notice_file_attachment" = "tiedostoliite";
+"notice_invalid_attachment" = "virheellinen liite";
+// room display name
+"room_displayname_empty_room" = "Tyhjä huone";
+"room_displayname_two_members" = "%@ ja %@";
+// Settings
+"settings" = "Asetukset";
+"notification_settings_room_rule_title" = "Huone: '%@'";
+"device_details_name" = "Julkinen nimi\n";
+"device_details_last_seen" = "Viimeksi nähty\n";
+"device_details_rename_prompt_message" = "Istunnon julkinen nimi näkyy henkilöille, joiden kanssa keskustelet";
+"room_event_encryption_info_event_algorithm" = "Algoritmi\n";
+"room_event_encryption_info_event_session_id" = "Istunnon tunnus\n";
+"room_event_encryption_info_device_unknown" = "tuntematon istunto\n";
+"room_event_encryption_info_device_name" = "Julkinen nimi\n";
+// Account
+"account_save_changes" = "Tallenna muutokset";
+"account_error_matrix_session_is_not_opened" = "Matrix-istunto ei ole avattuna";
+"account_error_email_wrong_title" = "Virheellinen sähköpostiosoite";
+"account_error_email_wrong_description" = "Tämä ei vaikuta kelvolliselta sähköpostiosoitteelta";
+"account_error_msisdn_wrong_title" = "Virheellinen puhelinnumero";
+"account_error_msisdn_wrong_description" = "Tämä ei vaikuta kelvolliselta puhelinnumerolta";
+// Room creation
+"room_creation_name_title" = "Huoneen nimi:";
+// Room
+"room_please_select" = "Valitse huone";
+"room_error_join_failed_title" = "Liittyminen huoneeseen epäonnistui";
+"room_left" = "Poistuit huoneesta";
+// Reply to message
+"message_reply_to_sender_sent_an_image" = "lähetti kuvan.";
+"message_reply_to_sender_sent_a_video" = "lähetti videon.";
+"message_reply_to_sender_sent_an_audio_file" = "lähetti äänitiedoston.";
+"message_reply_to_sender_sent_a_file" = "lähetti tiedoston.";
+"attachment_original" = "Todellinen koko: %@";
+"attachment_cancel_download" = "Perutaanko lataus?";
+"attachment_cancel_upload" = "Perutaanko lähetys?";
+"attachment_multiselection_original" = "Todellinen koko";
+"attachment_e2e_keys_import" = "Tuo...";
+// Contacts
+"contact_mx_users" = "Matrix-käyttäjät";
+"contact_local_contacts" = "Paikalliset yhteystiedot";
+"group_section" = "Ryhmät";
+// Search
+"search_no_results" = "Ei tuloksia";
+"search_searching" = "Haku käynnissä...";
+"e2e_import" = "Tuo";
+"e2e_export" = "Vie";
+"error" = "Virhe";
+"default" = "oletus";
+"private" = "Yksityinen";
+"public" = "Julkinen";
+"network_error_not_reachable" = "Tarkista verkkoyhteytesi";
+// Country picker
+"country_picker_title" = "Valitse maa";
+// Language picker
+"language_picker_title" = "Valitse kieli";
+"language_picker_default_language" = "Oletus (%@)";
+"notice_answered_video_call" = "%@ vastasi puheluun";
+"notice_ended_video_call" = "%@ lopetti puhelun";
+// button names
+"ok" = "OK";
+"send" = "Lähetä";
+"copy_button_name" = "Kopioi";
+"redact" = "Poista";
+"share" = "Jaa";
+"delete" = "Poista";
+// actions
+"action_logout" = "Kirjaudu ulos";
+"create_room" = "Luo huone";
+"login" = "Kirjaudu sisään";
+"create_account" = "Luo tili";
+// Login Screen
+"login_error_already_logged_in" = "Jo sisäänkirjautuneena";
+// room details dialog screen
+"room_details_title" = "Huoneen tiedot";
+"settings_title_notifications" = "Ilmoitukset";
+// Notification settings screen
+"notification_settings_disable_all" = "Poista käytöstä kaikki ilmoitukset";
+"notification_settings_enable_notifications" = "Käytä ilmoituksia";
+"notification_settings_always_notify" = "Ilmoita aina";
+"notification_settings_never_notify" = "Älä ilmoita koskaan";
+"notification_settings_select_room" = "Valitse huone";
+// gcm section
+"settings_config_home_server" = "Kotipalvelin: %@";
+"settings_config_identity_server" = "Identiteettipalvelin: %@";
+// call string
+"call_waiting" = "Odottaa...";
+"call_connecting" = "Puhelu yhdistyy...";
+"call_ended" = "Puhelu loppui";
+"call_ring" = "Soitetaan...";
+"incoming_video_call" = "Saapuva videopuhelu";
+"incoming_voice_call" = "Saapuva äänipuhelu";
+"call_invite_expired" = "Puhelukutsu vanhentui";
+"ssl_logout_account" = "Kirjaudu ulos";
+"ssl_fingerprint_hash" = "Sormenjälki (%@):";
+"room_event_encryption_info_verify" = "Vahvista...";
+"room_event_encryption_info_unverify" = "Poista vahvistus";
+"room_event_encryption_verify_title" = "Varmenna istunto\n\n";
+"room_event_encryption_verify_ok" = "Vahvista";
+"account_email_validation_error" = "Sähköpostin vahvistaminen epäonnistui. Tarkistathan sähköpostisi ja seuraa linkkiä, joka on lähettämässämme viestissä. Sen jälkeen, täppää jatka";
+"account_msisdn_validation_title" = "Vahvistus meneillään";
+"account_msisdn_validation_error" = "Puhelinnumeron vahvistus epäonnistui.";
+"account_error_display_name_change_failed" = "Näyttönimen vaihtaminen epäonnistui";
+"account_error_picture_change_failed" = "Kuvan vaihtaminen epäonnistui";
+"ssl_could_not_verify" = "Etäpalvelimen identiteetin vahvistaminen epäonnistui.";
+"login_user_id_placeholder" = "Matrix ID (esim. @matti:matrix.org tai pelkästään matti)";
+"login_identity_server_info" = "Matrix tarjoaa identiteettipalvelimen joka osaa kertaa mikä sähköpostiosoite tai puhelinnumero vastaa mitäkin Matrix ID:tä. Vain https://matrix.org on tällä hetkellä käytettävissä.";
diff --git a/Riot/Modules/MatrixKit/Assets/MatrixKitAssets.bundle/fr.lproj/MatrixKit.strings b/Riot/Modules/MatrixKit/Assets/MatrixKitAssets.bundle/fr.lproj/MatrixKit.strings
new file mode 100644
index 000000000..758c3a3cb
--- /dev/null
+++ b/Riot/Modules/MatrixKit/Assets/MatrixKitAssets.bundle/fr.lproj/MatrixKit.strings
@@ -0,0 +1,475 @@
+"view" = "Afficher";
+"back" = "Retour";
+"continue" = "Continuer";
+"leave" = "Quitter";
+"invite" = "Inviter";
+"retry" = "Réessayer";
+"cancel" = "Annuler";
+"save" = "Enregistrer";
+// room details dialog screen
+"room_details_title" = "Détails du salon";
+"matrix" = "Matrix";
+// Login Screen
+"login_create_account" = "Créer un compte :";
+"login_server_url_placeholder" = "URL (par ex. https://matrix.org)";
+"login_home_server_title" = "URL du serveur d’accueil :";
+"login_home_server_info" = "Votre serveur d’accueil stocke toutes vos discussions et les données de votre compte";
+"login_identity_server_title" = "URL du serveur d’identité :";
+"login_identity_server_info" = "Matrix fournit des serveurs d’identité pour lier les e-mail, etc aux identifiants Matrix. Seul https://matrix.org existe pour l’instant.";
+"login_user_id_placeholder" = "Identifiant Matrix (par ex. @bob:matrix.org ou bob)";
+"login_password_placeholder" = "Mot de passe";
+"login_optional_field" = "facultatif";
+"login_display_name_placeholder" = "Nom d’affichage (par ex. Bob Obson)";
+"login_email_info" = "Définir une adresse e-mail permet aux autres utilisateurs de vous retrouver plus facilement sur Matrix et vous permettra de réinitialiser votre mot de passe.";
+"login_email_placeholder" = "Adresse e-mail";
+"login_prompt_email_token" = "Veuillez saisir votre jeton de validation d’e-mail :";
+"login_error_title" = "Échec d’authentification";
+"login_error_no_login_flow" = "Échec de la récupération des informations d’authentification depuis ce serveur d’accueil";
+"login_error_do_not_support_login_flows" = "Aucun des parcours d’authentification définis par ce serveur d'accueil n’est pris en charge pour le moment";
+"login_error_registration_is_not_supported" = "L’inscription n'est pas prise en charge pour le moment";
+"login_error_forbidden" = "Nom d’utilisateur ou mot de passe invalide";
+"login_error_unknown_token" = "Le jeton d’accès spécifié n'est pas reconnu";
+"login_error_bad_json" = "JSON mal formaté";
+"login_error_not_json" = "Ne contenait pas de JSON valide";
+"login_error_limit_exceeded" = "Trop de requêtes ont été envoyées";
+"login_error_user_in_use" = "Ce nom d’utilisateur est déjà utilisé";
+"login_error_login_email_not_yet" = "Vous n’avez pas encore cliqué sur le lien dans l’e-mail";
+"login_use_fallback" = "Utiliser la page de secours";
+"login_leave_fallback" = "Annuler";
+"login_invalid_param" = "Paramètre invalide";
+"register_error_title" = "Échec lors de l’inscription";
+"login_error_forgot_password_is_not_supported" = "La réinitialisation du mot de passe n’est pas prise en charge pour le moment";
+// Action
+"no" = "Non";
+"yes" = "Oui";
+"abort" = "Abandonner";
+"close" = "Fermer";
+"discard" = "Abandonner";
+"dismiss" = "Ignorer";
+"sign_up" = "S’inscrire";
+"submit" = "Valider";
+"submit_code" = "Envoyer le code";
+"set_default_power_level" = "Réinitialiser le rang";
+"set_moderator" = "Nommer modérateur";
+"set_admin" = "Nommer administrateur";
+"start_chat" = "Nouvelle conversation privée";
+"start_voice_call" = "Commencer un appel audio";
+"start_video_call" = "Commencer un appel vidéo";
+"mention" = "Mentionner";
+"select_account" = "Sélectionner un compte";
+"attach_media" = "Joindre un média de la médiathèque";
+"capture_media" = "Prendre une photo/vidéo";
+"invite_user" = "Inviter un utilisateur matrix";
+"reset_to_default" = "Réinitialiser aux valeurs par défaut";
+"resend_message" = "Renvoyer le message";
+"select_all" = "Tout sélectionner";
+"cancel_upload" = "Annuler l’envoi";
+"cancel_download" = "Annuler le téléchargement";
+"show_details" = "Afficher les détails";
+"answer_call" = "Répondre à l’appel";
+"reject_call" = "Rejeter l’appel";
+"end_call" = "Terminer l’appel";
+"ignore" = "Ignorer";
+"unignore" = "Ne plus ignorer";
+// Events formatter
+"notice_avatar_changed_too" = "(l’avatar a aussi changé)";
+"notice_room_name_removed" = "%@ a supprimé le nom du salon";
+"notice_room_topic_removed" = "%@ a supprimé le sujet";
+"notice_event_redacted" = "";
+"notice_event_redacted_by" = " par %@";
+"notice_event_redacted_reason" = " [raison : %@]";
+"notice_profile_change_redacted" = "%@ ont mis à jour leur profil %@";
+"notice_room_created" = "%@ a créé et configuré le salon.";
+"notice_room_join_rule" = "La règle pour rejoindre le salon est : %@";
+"notice_room_power_level_intro" = "Les rangs des membres du salon sont :";
+"notice_room_power_level_acting_requirement" = "Le rang minimum qu’un utilisateur doit avoir pour interagir est :";
+"notice_room_power_level_event_requirement" = "Le rang minimum lié aux événements est :";
+"notice_room_aliases" = "Les alias du salon sont : %@";
+"notice_room_related_groups" = "Les groupes associés à ce salon sont : %@";
+"notice_encrypted_message" = "Message chiffré";
+"notice_encryption_enabled" = "%@ a activé le chiffrement de bout en bout (algorithme %@)";
+"notice_image_attachment" = "image en pièce-jointe";
+"notice_audio_attachment" = "audio en pièce-jointe";
+"notice_video_attachment" = "vidéo en pièce-jointe";
+"notice_file_attachment" = "fichier en pièce-jointe";
+"notice_invalid_attachment" = "pièce-jointe non valide";
+"notice_unsupported_attachment" = "Pièce-jointe non prise en charge : %@";
+"notice_redaction" = "%@ a supprimé un événement (id : %@)";
+"notice_error_unsupported_event" = "Évènement non pris en charge";
+"notice_error_unexpected_event" = "Événement inattendu";
+"notice_error_unknown_event_type" = "Type d’événement inconnu";
+"notice_room_history_visible_to_anyone" = "%@ a rendu l’historique futur du salon visible à tout le monde.";
+"notice_room_history_visible_to_members" = "%@ a rendu l’historique futur du salon visible à tous les membres du salon.";
+"notice_room_history_visible_to_members_from_invited_point" = "%@ a rendu l’historique futur du salon visible à tous les membres, à partir du moment où ils ont été invités.";
+"notice_room_history_visible_to_members_from_joined_point" = "%@ a rendu l’historique futur du salon visible à tous les membres, à partir de leur arrivée.";
+"notice_crypto_unable_to_decrypt" = "** Déchiffrement impossible : %@ **";
+"notice_crypto_error_unknown_inbound_session_id" = "La session de l’expéditeur ne nous a pas envoyé les clés pour ce message.";
+"notice_sticker" = "autocollant";
+// room display name
+"room_displayname_empty_room" = "Salon vide";
+"room_displayname_two_members" = "%@ et %@";
+"room_displayname_more_than_two_members" = "%@ et %u autres";
+// Settings
+"settings" = "Paramètres";
+"settings_enable_inapp_notifications" = "Activer les notifications dans l’application";
+"settings_enable_push_notifications" = "Activer les notifications push";
+"settings_enter_validation_token_for" = "Saisir le jeton de validation pour %@ :";
+"notification_settings_room_rule_title" = "Salon : « %@ »";
+// Devices
+"device_details_title" = "Informations sur la session\n";
+"device_details_name" = "Nom public\n";
+"device_details_identifier" = "Identifiant\n";
+"device_details_last_seen" = "Vu pour la dernière fois\n";
+"device_details_last_seen_format" = "%@ @ %@\n";
+"device_details_rename_prompt_message" = "Le nom public de la session est visible par les personnes avec qui vous communiquez";
+"device_details_delete_prompt_title" = "Authentification";
+"device_details_delete_prompt_message" = "Cette opération requiert une nouvelle authentification.\nPour poursuivre, saisissez votre mot de passe.";
+// Encryption information
+"room_event_encryption_info_title" = "Informations sur le chiffrement de bout en bout\n\n";
+"room_event_encryption_info_event" = "Informations sur l’événement\n";
+"room_event_encryption_info_event_user_id" = "Identifiant utilisateur\n";
+"room_event_encryption_info_event_identity_key" = "Clé d’identité Curve25519\n";
+"room_event_encryption_info_event_fingerprint_key" = "Clé d’empreinte Ed25519 déclarée\n";
+"room_event_encryption_info_event_algorithm" = "Algorithme\n";
+"room_event_encryption_info_event_session_id" = "Identifiant de session\n";
+"room_event_encryption_info_event_decryption_error" = "Erreur de déchiffrement\n";
+"room_event_encryption_info_event_unencrypted" = "non chiffré";
+"room_event_encryption_info_event_none" = "aucun";
+"room_event_encryption_info_device" = "\nInformations sur la session de l’expéditeur\n";
+"room_event_encryption_info_device_unknown" = "session inconnue\n";
+"room_event_encryption_info_device_name" = "Nom public\n";
+"room_event_encryption_info_device_id" = "Identifiant\n";
+"room_event_encryption_info_device_verification" = "Vérification\n";
+"room_event_encryption_info_device_fingerprint" = "Empreinte Ed25519\n";
+"room_event_encryption_info_device_verified" = "Vérifié";
+"room_event_encryption_info_device_not_verified" = "NON vérifié";
+"room_event_encryption_info_device_blocked" = "Sur liste noire";
+"room_event_encryption_info_verify" = "Vérifier…";
+"room_event_encryption_info_unverify" = "Annuler la vérification";
+"room_event_encryption_info_block" = "Ajouter à la liste noire";
+"room_event_encryption_info_unblock" = "Supprimer de la liste noire";
+"room_event_encryption_verify_title" = "Vérifier la session\n\n";
+"room_event_encryption_verify_message" = "Pour vérifier que cette session est fiable, contactez son propriétaire par un autre moyen (par ex. en personne ou au téléphone) et demandez-lui si la clé qu’il voit dans ses paramètres utilisateur pour cette session est identique à la clé ci-dessous :\n\n\tNom de la session : %@\n\tIdentifiant de la session : %@\n\tClé de la session : %@\n\nSi les clés sont identiques, cliquez sur le bouton vérifier ci-dessous. Sinon, quelqu’un est probablement en train d’intercepter cette session et vous devriez plutôt l’ajouter à la liste noire.\n\nÀ l'avenir, ce processus de vérification sera plus élaboré.";
+"room_event_encryption_verify_ok" = "Vérifier";
+// Account
+"account_save_changes" = "Enregistrer les modifications";
+"account_link_email" = "Lier un e-mail";
+"account_linked_emails" = "E-mail liés";
+"account_email_validation_title" = "Vérification en attente";
+"account_email_validation_message" = "Vérifiez vos e-mails et cliquez sur le lien fourni. Ensuite, cliquez sur continuer.";
+"account_email_validation_error" = "Impossible de vérifier l'adresse e-mail. Vérifiez vos e-mails et cliquez sur le lien fourni. Ensuite, cliquez sur continuer";
+"account_msisdn_validation_title" = "Vérification en attente";
+"account_msisdn_validation_message" = "Nous vous avons envoyé un SMS avec un code d’activation. Veuillez le saisir ci-dessous.";
+"account_msisdn_validation_error" = "Impossible de vérifier le numéro de téléphone.";
+"account_error_display_name_change_failed" = "Échec de modification du nom d’affichage";
+"account_error_picture_change_failed" = "Échec de modification de l’image";
+"account_error_matrix_session_is_not_opened" = "La session Matrix n’est pas ouverte";
+"account_error_email_wrong_title" = "Adresse e-mail non valide";
+"account_error_email_wrong_description" = "L’adresse e-mail ne semble pas valide";
+"account_error_msisdn_wrong_title" = "Numéro de téléphone non valide";
+"account_error_msisdn_wrong_description" = "Le numéro de téléphone ne semble pas valide";
+// Room creation
+"room_creation_name_title" = "Nom du salon :";
+"room_creation_name_placeholder" = "(par ex. groupeDej)";
+"room_creation_alias_title" = "Alias du salon :";
+"room_creation_alias_placeholder" = "(par ex. #foo:exemple.org)";
+"room_creation_alias_placeholder_with_homeserver" = "(par ex. #foo%@)";
+"room_creation_participants_title" = "Membres :";
+"room_creation_participants_placeholder" = "(par ex. @bob:serveurdaccueil1 ; @john:serveurdaccueil2...)";
+// Room
+"room_please_select" = "Sélectionnez un salon";
+"room_error_join_failed_title" = "Échec de l’inscription au salon";
+"room_error_join_failed_empty_room" = "Il est impossible pour le moment de rejoindre un salon vide.";
+"room_error_name_edition_not_authorized" = "Vous n’êtes pas autorisé à modifier le nom du salon";
+"room_error_topic_edition_not_authorized" = "Vous n’êtes pas autorisé à modifier le sujet du salon";
+"room_error_cannot_load_timeline" = "Échec du chargement du fil de discussion";
+"room_error_timeline_event_not_found_title" = "Échec du chargement de la position dans le fil de discussion";
+"room_error_timeline_event_not_found" = "L’application a tenté de charger un instant précis dans l’historique du salon, mais ne l’a pas trouvée";
+"room_left" = "Vous avez quitté le salon";
+"room_no_power_to_create_conference_call" = "Des permissions sont requises pour inviter ou démarrer une téléconférence dans ce salon";
+"room_no_conference_call_in_encrypted_rooms" = "Les téléconférences ne sont pas prises en charges dans les salons chiffrés";
+// Room members
+"room_member_ignore_prompt" = "Voulez-vous vraiment masquer tous les messages de cet utilisateur ?";
+"room_member_power_level_prompt" = "Vous ne pourrez pas annuler cette modification car vous promouvez cet utilisateur au même rang que le vôtre.\nEn êtes-vous sûr ?";
+// Attachment
+"attachment_size_prompt" = "Voulez-vous envoyer au format :";
+"attachment_original" = "Taille réelle (%@)";
+"attachment_small" = "Petit (~%@)";
+"attachment_medium" = "Moyen (~%@)";
+"attachment_large" = "Grand (~%@)";
+"attachment_cancel_download" = "Annuler le téléchargement ?";
+"attachment_cancel_upload" = "Annuler l’envoi ?";
+"attachment_multiselection_size_prompt" = "Voulez-vous envoyer l’image au format :";
+"attachment_multiselection_original" = "Taille réelle";
+"attachment_e2e_keys_file_prompt" = "Ce fichier contient des clés de chiffrement exportées d’un client Matrix.\nVoulez-vous voir le contenu du fichier ou importer les clés qu'il contient ?";
+"attachment_e2e_keys_import" = "Importer…";
+// Contacts
+"contact_mx_users" = "Utilisateurs Matrix";
+"contact_local_contacts" = "Contacts locaux";
+// Groups
+"group_invite_section" = "Invitations";
+"group_section" = "Groupes";
+// Search
+"search_no_results" = "Aucun résultat";
+"search_searching" = "Recherche en cours…";
+// Time
+"format_time_s" = "s";
+"format_time_m" = "m";
+"format_time_h" = "h";
+"format_time_d" = "j";
+// E2E import
+"e2e_import_room_keys" = "Importer les clés du salon";
+"e2e_import_prompt" = "Ce processus permet d’importer les clés de chiffrement que vous avez précédemment exportées d’un autre client Matrix. Vous pourrez ensuite déchiffrer tous les messages que l’autre client pouvait déchiffrer.\nLe fichier exporté est protégé par une phrase secrète. Entrez la phrase secrète ci-dessous pour déchiffrer le fichier.";
+"e2e_import" = "Importer";
+"e2e_passphrase_enter" = "Entrer la phrase secrète";
+// E2E export
+"e2e_export_room_keys" = "Exporter les clés de salon";
+"e2e_export_prompt" = "Ce processus permet d’exporter vers un fichier local les clés des messages que vous avez reçu sur les salons chiffrés. Vous pourrez ensuite importer ce fichier dans un autre client Matrix, pour qu’il puisse déchiffrer aussi ces messages.\nLe fichier exporté permettra à tous ceux qui y ont accès de déchiffrer tous les messages chiffrés que vous pouvez voir, donc vous devriez le conserver dans un endroit sûr.";
+"e2e_export" = "Exporter";
+"e2e_passphrase_confirm" = "Confirmer la phrase secrète";
+"e2e_passphrase_empty" = "La phrase secrète ne peut pas être vide";
+"e2e_passphrase_not_match" = "Les phrases secrètes doivent être identiques";
+// Others
+"user_id_title" = "Identifiant utilisateur :";
+"offline" = "hors ligne";
+"unsent" = "Non envoyé";
+"error" = "Erreur";
+"not_supported_yet" = "Pas encore pris en charge";
+"default" = "par défaut";
+"private" = "Privé";
+"public" = "Public";
+"power_level" = "Rang";
+"network_error_not_reachable" = "Vérifiez votre connexion au réseau";
+"user_id_placeholder" = "ex : @bob:serveurdaccueil";
+"ssl_homeserver_url" = "URL du serveur d’accueil : %@";
+// Permissions
+"camera_access_not_granted_for_call" = "Pour passer un appel vidéo l’accès à l’appareil photo est indispensable mais %@ n’a pas les permissions nécessaires";
+"microphone_access_not_granted_for_call" = "Pour passer un appel l’accès au microphone est indispensable mais %@ n’a pas les permissions nécessaires";
+"local_contacts_access_not_granted" = "Pour découvrir des utilisateurs à partir des contacts locaux, l’accès aux contacts est indispensable mais %@ n’a pas les permissions nécessaires";
+"local_contacts_access_discovery_warning_title" = "Découverte des utilisateurs";
+"local_contacts_access_discovery_warning" = "Pour découvrir des contacts utilisant déjà Matrix, %@ peut envoyer les adresses e-mail et les numéros de téléphone de votre répertoire au serveur d’identité que vous avez choisi. S’il le prend en charge, vos données personnelles sont hachées avant d’être envoyées − vérifiez la politique de confidentialité de votre serveur d’identité pour plus de détails.";
+// Country picker
+"country_picker_title" = "Choisissez un pays";
+"notice_room_invite" = "%@ a invité %@";
+"notice_room_third_party_invite" = "%@ a invité %@ à rejoindre ce salon";
+"notice_room_third_party_registered_invite" = "%@ a accepté l’invitation à %@";
+"notice_room_join" = "%@ est arrivé";
+"notice_room_leave" = "%@ est parti";
+"notice_room_reject" = "%@ a rejeté l’invitation";
+"notice_room_kick" = "%@ a expulsé %@";
+"notice_room_unban" = "%@ a révoqué le bannissement de %@";
+"notice_room_ban" = "%@ a banni %@";
+"notice_room_withdraw" = "%@ a annulé l’invitation de %@";
+"notice_room_reason" = ". Raison : %@";
+"notice_avatar_url_changed" = "%@ a changé d’avatar";
+"notice_display_name_set" = "%@ a modifié son nom en %@";
+"notice_display_name_changed_from" = "%@ a modifié son nom de %@ à %@";
+"notice_display_name_removed" = "%@ a supprimé son nom d’affichage";
+"notice_topic_changed" = "%@ a modifié le sujet en : « %@ ».";
+"notice_room_name_changed" = "%@ a modifié le nom du salon en %@.";
+"notice_placed_voice_call" = "%@ a passé un appel audio";
+"notice_placed_video_call" = "%@ a passé un appel vidéo";
+"notice_answered_video_call" = "%@ a répondu à l’appel";
+"notice_ended_video_call" = "%@ a terminé l’appel";
+"notice_conference_call_request" = "%@ a débuté une téléconférence en VoIP";
+"notice_conference_call_started" = "Téléconférence en VoIP démarrée";
+"notice_conference_call_finished" = "Téléconférence en VoIP terminée";
+// button names
+"ok" = "OK";
+"send" = "Envoyer";
+"copy_button_name" = "Copier";
+"resend" = "Renvoyer";
+"redact" = "Effacer";
+"share" = "Partager";
+"set_power_level" = "Définir le rang";
+"delete" = "Supprimer";
+// actions
+"action_logout" = "Se déconnecter";
+"create_room" = "Créer un salon";
+"login" = "Connexion";
+"create_account" = "Créer un compte";
+"membership_invite" = "Invité";
+"membership_leave" = "Parti";
+"membership_ban" = "Banni";
+"num_members_one" = "%@ utilisateur";
+"num_members_other" = "%@ utilisateurs";
+"kick" = "Expulser";
+"ban" = "Bannir";
+"unban" = "Révoquer le bannissement";
+"message_unsaved_changes" = "Il y a des modifications non enregistrées. Quitter les annulera.";
+// Login Screen
+"login_error_already_logged_in" = "Déjà connecté";
+"login_error_must_start_http" = "L’URL doit débuter par http[s]://";
+// contacts list screen
+"invitation_message" = "Je souhaiterais discuter avec vous sur Matrix. Veuillez visiter le site web http://matrix.org pour plus d’informations.";
+// Settings screen
+"settings_title_config" = "Configuration";
+"settings_title_notifications" = "Notifications";
+// Notification settings screen
+"notification_settings_disable_all" = "Désactiver toutes les notifications";
+"notification_settings_enable_notifications" = "Activer les notifications";
+"notification_settings_enable_notifications_warning" = "Toutes les notifications sont actuellement désactivées pour tous les appareils.";
+"notification_settings_global_info" = "Les paramètres de notification sont sauvegardés sur le compte utilisateur et partagés entre tous les clients qui les prennent en charge (y compris les notifications de bureau).\n\nLes règles s’appliquent dans l'ordre ; la première règle applicable définit le résultat.\nEn conséquence : les notification par mot-clé sont plus importantes que les notifications par salon, qui sont plus importantes que les notifications par expéditeur.\nEntre plusieurs règles du même type, la première dans la liste est prioritaire.";
+"notification_settings_per_word_notifications" = "Notifications par mot-clé";
+"notification_settings_per_word_info" = "Les mots sont détectés sans tenir compte de la casse et peuvent contenir un joker *. Par conséquent :\nfoo détecte la chaîne foo entourée de délimiteurs de mots (par ex. ponctuation, espace et début ou fin de ligne).\nfoo* détecte tous les mots commençant par foo.\n*foo* détecte tous les mots qui contiennent les trois lettres foo.";
+"notification_settings_always_notify" = "Toujours notifier";
+"notification_settings_never_notify" = "Ne jamais notifier";
+"notification_settings_word_to_match" = "mots à détecter";
+"notification_settings_highlight" = "Mettre en valeur";
+"notification_settings_custom_sound" = "Son personnalisé";
+"notification_settings_per_room_notifications" = "Notifications par salon";
+"notification_settings_per_sender_notifications" = "Notifications par expéditeur";
+"notification_settings_sender_hint" = "@utilisateur:domaine.com";
+"notification_settings_select_room" = "Choisir un salon";
+"notification_settings_other_alerts" = "Autres alertes";
+"notification_settings_contain_my_user_name" = "Me notifier par un son lorsqu’un message contient mon nom d’utilisateur";
+"notification_settings_contain_my_display_name" = "Me notifier par un son lorsqu’un message contient mon nom d’affichage";
+"notification_settings_just_sent_to_me" = "Me notifier par un son lorsqu’un message privé m’est envoyé";
+"notification_settings_invite_to_a_new_room" = "Me notifier lorsque je suis invité dans un salon";
+"notification_settings_people_join_leave_rooms" = "Me notifier lorsque des personnes rejoignent et quittent les salons";
+"notification_settings_receive_a_call" = "Me notifier lorsque je reçois un appel";
+"notification_settings_suppress_from_bots" = "Supprimer les notifications des robots";
+"notification_settings_by_default" = "Par défaut…";
+"notification_settings_notify_all_other" = "Notifier pour tous les autres messages ou salons";
+// gcm section
+"settings_config_home_server" = "Serveur d’accueil : %@";
+"settings_config_identity_server" = "Serveur d’identité : %@";
+"settings_config_user_id" = "Identifiant utilisateur : %@";
+// call string
+"call_waiting" = "En attente…";
+"call_connecting" = "Connexion…";
+"call_ended" = "Appel terminé";
+"call_ring" = "Appel…";
+"incoming_video_call" = "Appel vidéo entrant";
+"incoming_voice_call" = "Appel audio entrant";
+"call_invite_expired" = "La demande d’appel a expiré";
+// unrecognized SSL certificate
+"ssl_trust" = "Faire confiance";
+"ssl_logout_account" = "Se déconnecter";
+"ssl_remain_offline" = "Ignorer";
+"ssl_fingerprint_hash" = "Empreinte (%@) :";
+"ssl_could_not_verify" = "Impossible de vérifier l’identité du serveur distant.";
+"ssl_cert_not_trust" = "Cela pourrait signifier que quelqu’un de malveillant intercepte votre trafic, ou que votre téléphone ne fait pas confiance au certificat fourni par le serveur distant.";
+"ssl_cert_new_account_expl" = "Si l’administrateur affirme que ce comportement est normal, assurez-vous que l’empreinte ci-dessous est identique à celle qu’il fournit.";
+"ssl_unexpected_existing_expl" = "Le certificat a changé depuis qu’il a été approuvé par votre téléphone. Ce comportement est INATTENDU. Il est recommandé de ne PAS ACCEPTER ce nouveau certificat.";
+"ssl_expected_existing_expl" = "Le certificat était fiable et a été remplacé par un certificat qui ne l’est pas. Le serveur a peut-être renouvelé son certificat. Contactez l’administrateur du serveur pour lui demander l’empreinte de son certificat.";
+"ssl_only_accept" = "Accepter le certificat UNIQUEMENT si l’administrateur du serveur a publié une empreinte correspondant à celle ci-dessus.";
+"notice_feedback" = "Événement en retour (identifiant : %@) : %@";
+"notice_location_attachment" = "position en pièce-jointe";
+// Language picker
+"language_picker_title" = "Choisissez une langue";
+"language_picker_default_language" = "Défaut (%@)";
+"login_mobile_device" = "Téléphone";
+"login_tablet_device" = "Tablette";
+"login_desktop_device" = "Ordinateur";
+"notice_in_reply_to" = "En réponse à";
+"error_common_message" = "Une erreur est survenue. Veuillez réessayer ultérieurement.";
+// Reply to message
+"message_reply_to_sender_sent_an_image" = "a envoyé une image.";
+"message_reply_to_sender_sent_a_video" = "a envoyé une vidéo.";
+"message_reply_to_sender_sent_an_audio_file" = "a envoyé un fichier audio.";
+"message_reply_to_sender_sent_a_file" = "a envoyé un fichier.";
+"message_reply_to_message_to_reply_to_prefix" = "En réponse à";
+"login_error_resource_limit_exceeded_title" = "Limite de ressources dépassée";
+"login_error_resource_limit_exceeded_message_default" = "Ce serveur d’accueil a dépassé une de ses limites de ressources.";
+"login_error_resource_limit_exceeded_message_monthly_active_user" = "Ce serveur d’accueil a atteint sa limite mensuelle d’utilisateurs actifs.";
+"login_error_resource_limit_exceeded_message_contact" = "\n\nVeuillez contacter l’administrateur de votre service pour continuer à l’utiliser.";
+"login_error_resource_limit_exceeded_contact_button" = "Contacter l’administrateur";
+"e2e_passphrase_create" = "Créer la phrase secrète";
+"account_error_push_not_allowed" = "Les notifications ne sont pas autorisées";
+"notice_room_third_party_revoked_invite" = "%@ a retiré l’invitation de %@ à rejoindre ce salon";
+"device_details_rename_prompt_title" = "Nom de la session";
+"notice_encryption_enabled_ok" = "%@ a activé le chiffrement de bout en bout.";
+"notice_encryption_enabled_unknown_algorithm" = "%1$@ a activé le chiffrement de bout en bout (algorithme %2$@ inconnu).";
+// Notice Events with "You"
+"notice_room_invite_by_you" = "Vous avez invité %@";
+"notice_room_invite_you" = "%@ vous a invité";
+"notice_room_third_party_invite_by_you" = "Vous avez envoyé une invitation à %@ pour rejoindre le salon";
+"notice_room_third_party_registered_invite_by_you" = "Vous avez accepté l’invitation pour %@";
+"notice_room_third_party_revoked_invite_by_you" = "Vous avez révoqué l’invitation pour que %@ rejoigne le salon";
+"notice_room_join_by_you" = "Vous avez rejoint le salon";
+"notice_room_leave_by_you" = "Vous êtes parti";
+"notice_room_reject_by_you" = "Vous avez rejeté l’invitation";
+"notice_room_kick_by_you" = "Vous avez expulsé %@";
+"notice_room_unban_by_you" = "Vous avez révoqué le bannissement de %@";
+"notice_room_ban_by_you" = "Vous avez banni %@";
+"notice_room_withdraw_by_you" = "Vous avez annulé l’invitation de %@";
+"notice_avatar_url_changed_by_you" = "Vous avez changé votre avatar";
+"notice_display_name_set_by_you" = "Vous avez défini votre nom d’affichage en %@";
+"notice_display_name_changed_from_by_you" = "Vous avez changé votre nom d’affichage de %@ vers %@";
+"notice_display_name_removed_by_you" = "Vous avez supprimé votre nom d’affichage";
+"notice_topic_changed_by_you" = "Vous avez changé le sujet en « %@ ».";
+"notice_room_name_changed_by_you" = "Vous avez changé le nom du salon en %@.";
+"notice_placed_voice_call_by_you" = "Vous avez passé un appel audio";
+"notice_placed_video_call_by_you" = "Vous avez passé un appel vidéo";
+"notice_answered_video_call_by_you" = "Vous avez répondu à l’appel";
+"notice_ended_video_call_by_you" = "Vous avez terminé l’appel";
+"notice_conference_call_request_by_you" = "Vous avez demandé une conférence VoIP";
+"notice_room_name_removed_by_you" = "Vous avez supprimé le nom du salon";
+"notice_room_topic_removed_by_you" = "Vous avez supprimé le sujet";
+"notice_event_redacted_by_you" = " par vous";
+"notice_profile_change_redacted_by_you" = "Vous avez mis à jour votre profil %@";
+"notice_room_created_by_you" = "Vous avez créé et configuré le salon.";
+"notice_encryption_enabled_ok_by_you" = "Vous avez activé le chiffrement de bout en bout.";
+"notice_encryption_enabled_unknown_algorithm_by_you" = "Vous avez activé le chiffrement de bout en bout (algorithme non reconnu %@).";
+"notice_redaction_by_you" = "Vous avez supprimé un évènement (id : %@)";
+"notice_room_history_visible_to_anyone_by_you" = "Vous avez rendu l’historique futur du salon visible par tout le monde.";
+"notice_room_history_visible_to_members_by_you" = "Vous avez rendu l’historique futur du salon visible par tous les membres du salon.";
+"notice_room_history_visible_to_members_from_invited_point_by_you" = "Vous avez rendu l’historique futur du salon visible par tous les membres, à partir du moment où ils ont été invités.";
+"notice_room_history_visible_to_members_from_joined_point_by_you" = "Vous avez rendu l’historique futur du salon visible par tous les membres, à partir de leur arrivée.";
+"notice_room_history_visible_to_members_from_joined_point_by_you_for_dm" = "Vous avez rendu les messages ultérieurs visibles à tous le monde, à partir de leur arrivée.";
+"notice_room_history_visible_to_members_from_invited_point_by_you_for_dm" = "Vous avez rendu les messages ultérieurs visibles à tous le monde, à partir du moment où ils sont invités.";
+"notice_room_history_visible_to_members_by_you_for_dm" = "Vous avez rendu les messages ultérieurs visibles à tous les membres du salon.";
+"notice_room_created_by_you_for_dm" = "Vous êtes arrivé.";
+"notice_room_name_removed_by_you_for_dm" = "Vous avez supprimé le nom";
+"notice_room_name_changed_by_you_for_dm" = "Vous avez changé le nom en %@.";
+"notice_room_third_party_revoked_invite_by_you_for_dm" = "Vous avez supprimé l’invitation de %@";
+"notice_room_third_party_invite_by_you_for_dm" = "Vous avez invité %@";
+"notice_room_name_changed_for_dm" = "%@ a changé le nom en %@.";
+"notice_room_third_party_revoked_invite_for_dm" = "%@ a supprimé l’invitation de %@";
+"notice_room_third_party_invite_for_dm" = "%@ a invité %@";
+"room_left_for_dm" = "Vous êtes parti";
+"notice_room_history_visible_to_members_from_joined_point_for_dm" = "%@ a rendu les messages ultérieurs visibles à tout le monde, à partir de leur arrivée.";
+"notice_room_history_visible_to_members_from_invited_point_for_dm" = "%@ a rendu les messages ultérieurs visibles à tout le monde, à partir du moment où ils sont invités.";
+"notice_room_history_visible_to_members_for_dm" = "%@ a rendu les messages ultérieurs visibles à tous les membres du salon.";
+"notice_room_aliases_for_dm" = "Les alias sont : %@";
+"notice_room_power_level_intro_for_dm" = "Les rangs des membres sont :";
+"notice_room_join_rule_public_by_you_for_dm" = "Vous avez rendu le salon public.";
+"notice_room_join_rule_public_by_you" = "Vous avez rendu le salon public.";
+"notice_room_join_rule_public_for_dm" = "%@ a rendu le salon public.";
+"notice_room_join_rule_public" = "%@ a rendu le salon public.";
+"notice_room_join_rule_invite_by_you_for_dm" = "Vous avez rendu le salon joignable sur invitation exclusivement.";
+"notice_room_join_rule_invite_by_you" = "Vous avez rendu le salon joignable sur invitation exclusivement.";
+"notice_room_join_rule_invite_for_dm" = "%@ a rendu le salon joignable sur invitation exclusivement.";
+// New
+"notice_room_join_rule_invite" = "%@ a rendu le salon joignable sur invitation exclusivement.";
+"notice_room_created_for_dm" = "%@ est arrivé.";
+"notice_room_name_removed_for_dm" = "%@ a supprimé le nom";
+"call_more_actions_dialpad" = "Pavé de numérotation";
+"call_more_actions_transfer" = "Transférer";
+"call_more_actions_audio_use_device" = "Haut parleur de l’appareil";
+"call_more_actions_audio_use_headset" = "Utiliser les écouteurs";
+"call_more_actions_change_audio_device" = "Changer de périphérique audio";
+"call_more_actions_unhold" = "Reprendre";
+"call_more_actions_hold" = "Mettre en attente";
+"call_holded" = "Vous avez mis l’appel en attente";
+"call_remote_holded" = "%@ a mis l’appel en attente";
+"notice_declined_video_call_by_you" = "Vous avez refusé l’appel";
+"notice_declined_video_call" = "%@ a refusé l’appel";
+"resume_call" = "Reprendre";
+"call_transfer_to_user" = "Transfert à %@";
+"call_consulting_with_user" = "Consultation de %@";
+"call_video_with_user" = "Appel vidéo avec %@";
+"call_voice_with_user" = "Appel audio avec %@";
+"call_ringing" = "Sonnerie…";
+"e2e_passphrase_too_short" = "La phrase secrète est trop courte, elle doit compter au moins %d caractères";
+"microphone_access_not_granted_for_voice_message" = "Pour les messages vocaux, l’accès au microphone est indispensable mais %@ n’a pas les permissions nécessaires";
+"message_reply_to_sender_sent_a_voice_message" = "envoyer un message vocal.";
+"attachment_large_with_resolution" = "Grand %@ (~%@)";
+"attachment_medium_with_resolution" = "Moyen %@ (~%@)";
+"attachment_small_with_resolution" = "Petit %@ (~%@)";
+"attachment_size_prompt_message" = "Vous pouvez désactiver ceci dans les paramètres.";
+"attachment_size_prompt_title" = "Préciser la taille pour l’envoi";
+"auth_username_in_use" = "";
+"auth_invalid_user_name" = "Nom d’utilisateur invalide";
+"rename" = "Renommer";
diff --git a/Riot/Modules/MatrixKit/Assets/MatrixKitAssets.bundle/hu.lproj/MatrixKit.strings b/Riot/Modules/MatrixKit/Assets/MatrixKitAssets.bundle/hu.lproj/MatrixKit.strings
new file mode 100644
index 000000000..1bf93bad0
--- /dev/null
+++ b/Riot/Modules/MatrixKit/Assets/MatrixKitAssets.bundle/hu.lproj/MatrixKit.strings
@@ -0,0 +1,479 @@
+"matrix" = "Matrix";
+// Login Screen
+"login_create_account" = "Fiók létrehozása:";
+"login_server_url_placeholder" = "URL (például https://matrix.org)";
+"login_home_server_title" = "Matrix szerver URL:";
+"view" = "Megtekintés";
+"back" = "Vissza";
+"continue" = "Folytatás";
+"leave" = "Elhagyás";
+"invite" = "Meghívás";
+"login_home_server_info" = "A matrix szerver tárolja minden beszélgetésedet és felhasználói fiók adatodat";
+"login_identity_server_title" = "Azonosítási szerver URL:";
+"login_identity_server_info" = "Matrix biztosít egy azonosító szerver ami nyilvántartja, hogy melyik matrix azonosítóhoz milyen e-mail cím, stb. tartozik. Jelenleg csak a https://matrix.org az egyetlen ilyen szerver.";
+"login_user_id_placeholder" = "Matrix azonosító (pl.: @bob:matrix.org vagy csak bob)";
+"login_password_placeholder" = "Jelszó";
+"login_optional_field" = "opcionális";
+"login_display_name_placeholder" = "Megjelenített név (pl. Bob Obson)";
+"login_email_info" = "Az e-mail cím megadásával más felhasználók könnyebben találhatnak meg a Matrixon és lehetőséget ad a jelszó alaphelyzetbe állítására.";
+"login_email_placeholder" = "E-mail cím";
+"login_prompt_email_token" = "Kérlek add meg az e-mail érvényesítő kódot:";
+"login_error_title" = "A bejelentkezés sikertelen";
+"login_error_no_login_flow" = "Nem sikerült letölteni az azonosítási információkat erről a saját szerverről";
+"login_error_do_not_support_login_flows" = "Jelenleg nem támogatunk egyetlen bejelentkezési sémát sem azok közül amit a saját szerver ismer";
+"login_error_registration_is_not_supported" = "A regisztráció jelenleg nem támogatott";
+"login_error_forbidden" = "Érvénytelen felhasználói név/jelszó";
+"login_error_unknown_token" = "A megadott hozzáférési kód nem ismert";
+"login_error_bad_json" = "Hibás JSON";
+"login_error_not_json" = "Nem tartalmaz helyes JSON adatot";
+"login_error_limit_exceeded" = "Túl sok kérés lett elküldve";
+"login_error_user_in_use" = "Ez a felhasználói név már használatban van";
+"login_error_login_email_not_yet" = "Az e-mailben küldött hivatkozásra még nem kattintottál rá";
+"login_use_fallback" = "Alternatív oldal használata";
+"login_leave_fallback" = "Mégse";
+"login_invalid_param" = "Érvénytelen paraméter";
+"register_error_title" = "A regisztráció sikertelen";
+"login_error_forgot_password_is_not_supported" = "Az „elfelejtett jelszó” jelenleg nem támogatott";
+"login_mobile_device" = "Mobil";
+"login_tablet_device" = "Tablet";
+"login_desktop_device" = "Asztali";
+// Action
+"no" = "Nem";
+"yes" = "Igen";
+"abort" = "Megszakítás";
+"close" = "Bezár";
+"discard" = "Elvetés";
+"dismiss" = "Elutasítás";
+"retry" = "Újra";
+"sign_up" = "Regisztráció";
+"submit" = "Elküldés";
+"submit_code" = "Kód küldése";
+"set_default_power_level" = "Hozzáférési szint visszaállítása";
+"set_moderator" = "Beállítás moderátornak";
+"set_admin" = "Beállítás adminisztrátornak";
+"start_chat" = "Csevegés kezdése";
+"start_voice_call" = "Hanghívás kezdése";
+"start_video_call" = "Videóhívás kezdése";
+"mention" = "Megemlítés";
+"select_account" = "Felhasználói fiók kiválasztása";
+"attach_media" = "Média fájl csatolása a könyvtárból";
+"capture_media" = "Fénykép/videó készítése";
+"invite_user" = "Matrix felhasználó meghívása";
+"reset_to_default" = "Alapértelmezés visszaállítása";
+"resend_message" = "Üzenet újraküldése";
+"select_all" = "Mind kijelölése";
+"cancel_upload" = "Feltöltés megszakítása";
+"cancel_download" = "Letöltés megszakítása";
+"show_details" = "Részletek megmutatása";
+"answer_call" = "Hívás fogadása";
+"reject_call" = "Hívás elutasítása";
+"end_call" = "Hívás befejezése";
+"ignore" = "Figyelmen kívül hagyás";
+"unignore" = "Figyelembe vétel";
+// Events formatter
+"notice_avatar_changed_too" = "(a felhasználó képe is megváltozott )";
+"notice_room_name_removed" = "%@ törölte a szoba nevét";
+"notice_room_topic_removed" = "%@ törölte a témát";
+"notice_event_redacted" = "";
+"notice_event_redacted_by" = " %@ által";
+"notice_event_redacted_reason" = " [indok: %@]";
+"notice_profile_change_redacted" = "%@ frissítette az adatait %@";
+"notice_room_created" = "%@ szobát készített és beállította.";
+"notice_room_join_rule" = "A csatlakozási szabály: %@";
+"notice_room_power_level_intro" = "A szoba tagjainak a hozzáférési szintje:";
+"notice_room_power_level_acting_requirement" = "Mielőtt a felhasználó ezt tehetné legalább az alábbi hozzáférési szinttel kell rendelkeznie:";
+"notice_room_power_level_event_requirement" = "Az eseményekhez kapcsolódó minimális hozzáférési szintek:";
+"notice_room_aliases" = "A szoba becenevei: %@";
+"notice_room_related_groups" = "A szobához kapcsolódó csoportok: %@";
+"notice_encrypted_message" = "Titkosított üzenet";
+"notice_encryption_enabled" = "%@ bekapcsolta a végponttól végpontig titkosítást (algoritmus: %@)";
+"notice_image_attachment" = "képmelléklet";
+"notice_audio_attachment" = "hangmelléklet";
+"notice_video_attachment" = "videómelléklet";
+"notice_location_attachment" = "helyadat-melléklet";
+"notice_file_attachment" = "fájlmelléklet";
+"notice_invalid_attachment" = "érvénytelen melléklet";
+"notice_unsupported_attachment" = "Nem támogatott melléklet: %@";
+"notice_feedback" = "Visszajelzés esemény (azon.: %@): %@";
+"notice_redaction" = "%@ kitakart egy eseményt (azon.: %@)";
+"notice_error_unsupported_event" = "Nem támogatott esemény";
+"notice_error_unexpected_event" = "Nem várt esemény";
+"notice_error_unknown_event_type" = "Ismeretlen eseménytípus";
+"notice_room_history_visible_to_anyone" = "%@ a szoba jövőbeni üzeneteit mindenki számára láthatóvá tette.";
+"notice_room_history_visible_to_members" = "%@ a szoba jövőbeni üzeneteit a szobában lévő minden felhasználó számára láthatóvá tette.";
+"notice_room_history_visible_to_members_from_invited_point" = "%@ a szoba jövőbeni üzeneteit a szobában lévő félhasználók számára a meghívásuk pillanatától tette láthatóvá.";
+"notice_room_history_visible_to_members_from_joined_point" = "%@ a szoba jövőbeni üzeneteit a szobában lévő felhasználók számára a csatlakozásuktól tette láthatóvá.";
+"notice_crypto_unable_to_decrypt" = "** Nem sikerül visszafejteni: %@ **";
+"notice_crypto_error_unknown_inbound_session_id" = "A küldő munkamenete nem küldte el a kulcsot ehhez az üzenethez.";
+"notice_sticker" = "Matrica";
+"notice_in_reply_to" = "Válaszolva erre";
+// Settings
+"settings" = "Beállítások";
+"settings_enable_inapp_notifications" = "Alkalmazáson belüli értesítések engedélyezése";
+"settings_enable_push_notifications" = "Leküldéses értesítések engedélyezése";
+"settings_enter_validation_token_for" = "Érvényesítő kód megadása (%@):";
+"notification_settings_room_rule_title" = "Szoba: „%@”";
+// Devices
+"device_details_title" = "Munkamenet információk\n";
+"device_details_name" = "Nyilvános név\n";
+"device_details_identifier" = "Azon.\n";
+"device_details_last_seen" = "Utoljára ekkor láttuk:\n";
+"device_details_last_seen_format" = "%@ @ %@\n";
+"device_details_rename_prompt_message" = "A munkamenet nyilvános neve megjelenik azoknál az embereknél, akikkel beszélgetsz";
+"device_details_delete_prompt_title" = "Hitelesítés";
+"device_details_delete_prompt_message" = "A művelethez további hitelesítés szükséges.\nA továbblépéshez add meg a jelszavadat.";
+// Encryption information
+"room_event_encryption_info_title" = "Végpontok közötti titkosítási információk\n\n";
+"room_event_encryption_info_event" = "Esemény információ\n";
+"room_event_encryption_info_event_user_id" = "Felhasználó azonosító\n";
+"room_event_encryption_info_event_identity_key" = "Curve25519 azonosítási kulcs\n";
+"room_event_encryption_info_event_fingerprint_key" = "Claimed Ed25519 ujjlenyomat kulcs\n";
+"room_event_encryption_info_event_algorithm" = "Algoritmus\n";
+"room_event_encryption_info_event_session_id" = "Munkamenet-azonosító\n";
+"room_event_encryption_info_event_decryption_error" = "Visszafejtési hiba\n";
+"room_event_encryption_info_event_unencrypted" = "titkosítatlan";
+"room_event_encryption_info_event_none" = "nincs";
+"room_event_encryption_info_device" = "\nKüldő munkamenetének információi\n";
+"room_event_encryption_info_device_unknown" = "ismeretlen munkamenet\n";
+"room_event_encryption_info_device_name" = "Nyilvános név\n";
+"room_event_encryption_info_device_id" = "Azon.\n";
+"room_event_encryption_info_device_verification" = "Ellenőrzés\n";
+"room_event_encryption_info_device_fingerprint" = "Ed25519 ujjlenyomat\n";
+"room_event_encryption_info_device_verified" = "Ellenőrizve";
+"room_event_encryption_info_device_not_verified" = "NINCS ellenőrizve";
+"room_event_encryption_info_device_blocked" = "Tiltólistán";
+"room_event_encryption_info_verify" = "Ellenőriz...";
+"room_event_encryption_info_unverify" = "Ellenőrzés visszavonása";
+"room_event_encryption_info_block" = "Tiltólistára tétel";
+"room_event_encryption_info_unblock" = "Törlés a tiltólistáról";
+"room_event_encryption_verify_title" = "Munkamenet ellenőrzése\n\n";
+"room_event_encryption_verify_ok" = "Ellenőrzés";
+// Account
+"account_save_changes" = "Változások mentése";
+"account_link_email" = "E-mail cím összekötése";
+"account_linked_emails" = "Hozzárendelt e-mail címek";
+"account_email_validation_title" = "Ellenőrzés folyamatban";
+"account_msisdn_validation_title" = "Ellenőrzés folyamatban";
+"account_msisdn_validation_error" = "A telefonszám ellenőrzése sikertelen.";
+"account_error_display_name_change_failed" = "Megjelenítési név megváltoztatása sikertelen";
+"account_error_picture_change_failed" = "A kép megváltoztatása sikertelen";
+"account_error_matrix_session_is_not_opened" = "A Matrix munkamenet nincs megnyitva";
+"account_error_email_wrong_title" = "Érvénytelen e-mail cím";
+"account_error_email_wrong_description" = "Ez nem tűnik érvényes e-mail címnek";
+"account_error_msisdn_wrong_title" = "Érvénytelen telefonszám";
+"account_error_msisdn_wrong_description" = "Nem tűnik érvényes telefonszámnak";
+// Room creation
+"room_creation_name_title" = "Szoba neve:";
+"room_creation_name_placeholder" = "(pl.: ebédCsoport)";
+"room_creation_alias_title" = "Szoba beceneve:";
+"room_creation_alias_placeholder" = "(pl.: #foo:example.org)";
+"room_creation_alias_placeholder_with_homeserver" = "(pl.: #foo%@)";
+"room_creation_participants_title" = "Résztvevők:";
+"room_creation_participants_placeholder" = "(pl.: @bob:homeserver1; @john:homeserver2...)";
+// Room
+"room_please_select" = "Kérlek válassz szobát";
+"room_error_join_failed_title" = "A szobához való csatlakozás nem sikerült";
+"room_error_join_failed_empty_room" = "Üres szobába jelenleg nem lehet belépni.";
+"room_error_name_edition_not_authorized" = "A szoba nevének megváltoztatásához nincs jogosultságod";
+"room_event_encryption_verify_message" = "Ennek a munkamenet hitelességének a vizsgálatához vedd fel a kapcsolatot a tulajdonossal egy másik csatornán (pl. személyes találkozó vagy telefonhívás) és kérdezd meg, hogy amit ő lát a személyes beállításoknál mint a munkamenethez tartozó kulcs, az megegyezik az alábbi kulccsal:\n\n\tMunkamenet neve: %@\n\tMunkamenet azonosító: %@\n\tMunkamenet kulcs: %@\n\nHa egyezik akkor nyomd meg az ellenőrizve gombot alább. Ha nem egyezik, akkor valaki jogosulatlanul akarja használni a munkamenetet; talán a legjobb, ha a tiltólista gombot nyomod meg inkább.\n\nA jövőben ezen az ellenőrzési módon javítani fogunk.";
+"account_email_validation_message" = "Ellenőrizd a leveleidet, és kattints a levélben lévő hivatkozásra. Ha az megvan, akkor kattints itt a tovább gombra.";
+"account_email_validation_error" = "Az e-mail címet nem sikerült ellenőrizni. Ellenőrizd a leveleidet, és kattints a levélben lévő hivatkozásra. Ha az megvan, akkor kattints itt a tovább gombra";
+"account_msisdn_validation_message" = "SMS-t küldtünk az aktiváló kóddal. Add meg az aktiváló kódot alább.";
+"room_error_topic_edition_not_authorized" = "Nem vagy jogosult a szoba témájának szerkesztésére";
+"room_error_cannot_load_timeline" = "Az idővonalat nem sikerült betölteni";
+"room_error_timeline_event_not_found_title" = "Az idővonali pozíciót nem sikerült letölteni";
+"room_error_timeline_event_not_found" = "Az alkalmazás megpróbált az idővonalról betölteni egy időpillanatot de nem találja";
+"room_left" = "Elhagytad a szobát";
+"room_no_power_to_create_conference_call" = "Ebben a szobában nincs jogosultságod meghívni valakit konferenciát indítani";
+"room_no_conference_call_in_encrypted_rooms" = "Titkosított szobákban a konferenciahívások nem támogatottak";
+// Reply to message
+"message_reply_to_sender_sent_an_image" = "kép elküldve.";
+"message_reply_to_sender_sent_a_video" = "videó elküldve.";
+"message_reply_to_sender_sent_an_audio_file" = "hangfájl elküldve.";
+"message_reply_to_sender_sent_a_file" = "fájl elküldve.";
+"message_reply_to_message_to_reply_to_prefix" = "Válaszolva erre";
+// Room members
+"room_member_ignore_prompt" = "Biztos, hogy eltakarod ennek a felhasználónak az összes üzenetét?";
+"room_member_power_level_prompt" = "Valószínűleg nem fogod tudni visszavonni ezt a műveletet, mivel ugyanarra a szintre emeled a felhasználót mint amin te magad vagy.\nBiztos vagy benne?";
+// Attachment
+"attachment_size_prompt" = "Hogy szeretnéd elküldeni:";
+"attachment_original" = "Jelenlegi méret (%@)";
+"attachment_small" = "Kicsi (~%@)";
+"attachment_medium" = "Közepes (~%@)";
+"attachment_large" = "Nagy (~%@)";
+"attachment_cancel_download" = "Megszakítod a letöltést?";
+"attachment_cancel_upload" = "Megszakítod a feltöltést?";
+"attachment_multiselection_size_prompt" = "Hogy szeretnéd elküldeni a képet:";
+"attachment_multiselection_original" = "Jelenlegi méret";
+"attachment_e2e_keys_file_prompt" = "Ez a fájl a Matrix kliensből kimentett titkosító kulcsokat tartalmaz.\nSzeretnéd megjeleníteni a fájl tartalmát vagy betöltöd a kulcsokat amiket tartalmaz?";
+"attachment_e2e_keys_import" = "Betöltés...";
+// Contacts
+"contact_mx_users" = "Matrix felhasználók";
+"contact_local_contacts" = "Helyi névjegyek";
+// Groups
+"group_invite_section" = "Meghívók";
+"group_section" = "Csoportok";
+// Search
+"search_no_results" = "Nincs találat";
+"search_searching" = "Keresés folyamatban...";
+// Time
+"format_time_s" = "mp";
+"format_time_m" = "p";
+"format_time_h" = "ó";
+"format_time_d" = "n";
+// E2E import
+"e2e_import_room_keys" = "Szoba kulcsok betöltése";
+"e2e_import_prompt" = "Ez a folyamat betölti azokat a titkosítási kulcsokat amiket előzőleg egy másik Matrix kliensből mentettél ki. Ez után minden olyan üzenetet vissza tudsz fejteni amit a másik eszköz vissza tud.\nA kulcsokat tartalmazó fájl jelszóval védett. Add meg itt a jelszót a fájl visszafejtéséhez.";
+"e2e_import" = "Betöltés";
+"e2e_passphrase_enter" = "Jelszó megadása";
+// E2E export
+"e2e_export_room_keys" = "Szoba kulcsok kimentése";
+"e2e_export_prompt" = "Ezzel a folyamattal kimentheted azokat a kulcsokat amiket a titkosított szobákban az üzenetek visszafejtésére használtál. Így később ezt a fájlt egy másik Matrix kliensbe betöltve a kliens vissza tudja fejteni ezeket az üzeneteket.\nAki el tudja olvasni a kimentett kulcsokat tartalmazó fájlt vissza fogja tudni fejteni az üzeneteket amiket látsz, ezért tartsd a fájlt biztonságosan.";
+"e2e_export" = "Kiment";
+"e2e_passphrase_confirm" = "Jelszó megerősítése";
+"e2e_passphrase_empty" = "A jelszó nem lehet üres";
+"e2e_passphrase_not_match" = "A jelszavaknak egyezniük kell";
+// Others
+"user_id_title" = "Felhasználói azonosító:";
+"offline" = "kapcsolat nélkül";
+"unsent" = "Elküldetlen";
+"error" = "Hiba";
+"error_common_message" = "Hiba történt. Kérlek próbáld meg később.";
+"not_supported_yet" = "Jelenleg nem támogatott";
+"default" = "alapértelmezett";
+"private" = "Privát";
+"public" = "Nyilvános";
+"power_level" = "Hozzáférési szint";
+"network_error_not_reachable" = "Ellenőrizd a hálózati hozzáférésed";
+"user_id_placeholder" = "pl.: @bob:matrixszerver";
+"ssl_homeserver_url" = "Matrix szerver URL: %@";
+// Permissions
+"camera_access_not_granted_for_call" = "Videó hívásokhoz engedélyezni kell a hozzáférést a kamerához de %@ nem rendelkezik ilyen engedéllyel";
+"microphone_access_not_granted_for_call" = "Hívásokhoz engedélyezni kell a hozzáférést a mikrofonhoz, de %@ nem rendelkezik ilyen engedéllyel";
+"local_contacts_access_not_granted" = "Címjegyzékben található felhasználók feltérképezéséhez engedéllyel kell rendelkezni a címjegyzékhez, de %@ nem rendelkezik ilyen engedéllyel";
+"local_contacts_access_discovery_warning_title" = "Felhasználók keresése";
+"local_contacts_access_discovery_warning" = "Az olyan ismerősök felderítéséhez akik már használják a Matrixot, %@ el tudja küldeni a címjegyzékben található e-mail címeket és telefonszámokat az általad választott Matrix azonosítási szervernek. Ahol lehetséges a személyes adatok hash-elve lesznek - kérlek ellenőrizd az azonosítási szervered adatvédelmi szabályait.";
+// Country picker
+"country_picker_title" = "Válassz országot";
+// Language picker
+"language_picker_title" = "Válassz nyelvet";
+"language_picker_default_language" = "Alapértelmezett (%@)";
+"notice_room_invite" = "%@ meghívta %@ felhasználót";
+"notice_room_third_party_invite" = "%@ meghívót küldött %@ felhasználónak, hogy lépjen be a szobába";
+"notice_room_third_party_registered_invite" = "%@ elfogadta a meghívást ide: %@";
+"notice_room_join" = "%@ csatlakozott";
+"notice_room_leave" = "%@ távozott";
+"notice_room_reject" = "%@ elutasította a meghívást";
+"notice_room_kick" = "%@ kirúgta: %@";
+"notice_room_unban" = "%@ visszaengedte: %@";
+"notice_room_ban" = "%@ kitiltotta: %@";
+"notice_room_withdraw" = "%@ visszavonta %@ meghívóját";
+"notice_room_reason" = ". Ok: %@";
+"notice_avatar_url_changed" = "%@ megváltoztatta a profilképét";
+"notice_display_name_set" = "%@ a becenevét %@ névre állította be";
+"notice_display_name_changed_from" = "%@ megváltoztatta a becenevét %@ névről %@ névre";
+"notice_display_name_removed" = "%@ törölte a becenevét";
+"notice_topic_changed" = "%@ megváltoztatta a témát erre: „%@”.";
+"notice_room_name_changed" = "%@ megváltoztatta a szoba nevét erre: %@.";
+"notice_placed_voice_call" = "%@ hanghívást kezdeményezett";
+"notice_placed_video_call" = "%@ videóhívást kezdeményezett";
+"notice_answered_video_call" = "%@ fogadta a hívást";
+"notice_ended_video_call" = "%@ befejezte a hívást";
+"notice_conference_call_request" = "%@ VoIP konferenciát kezdeményezett";
+"notice_conference_call_started" = "VoIP konferencia indult";
+"notice_conference_call_finished" = "VoIP konferencia befejeződött";
+// button names
+"ok" = "Rendben";
+"cancel" = "Mégse";
+"save" = "Ment";
+"send" = "Küld";
+"copy_button_name" = "Másol";
+"resend" = "Újraküld";
+"redact" = "Töröl";
+"share" = "Megosztás";
+"set_power_level" = "Hozzáférési szint beállítása";
+"delete" = "Töröl";
+// actions
+"action_logout" = "Kilép";
+"create_room" = "Szoba készítése";
+"login" = "Belép";
+"create_account" = "Felhasználói fiók készítés";
+"membership_invite" = "Meghívva";
+"membership_leave" = "Elhagyva";
+"membership_ban" = "Kitiltva";
+"num_members_one" = "%@ felhasználó";
+"num_members_other" = "%@ felhasználó";
+"kick" = "Elküld";
+"ban" = "Kitilt";
+"unban" = "Visszaenged";
+"message_unsaved_changes" = "Mentetlen beállítások vannak. Ha kilépsz a beállítások elvesznek.";
+// Login Screen
+"login_error_already_logged_in" = "Már bejelentkeztél";
+"login_error_must_start_http" = "Az URL-nek http[s]:// -sel kell kezdődnie";
+// room details dialog screen
+"room_details_title" = "Szoba adatai";
+// contacts list screen
+"invitation_message" = "Szeretnék veled beszélgetni a Matrix-szal. További információkért látogasd meg a http://matrix.org weboldalt.";
+// Settings screen
+"settings_title_config" = "Beállítások";
+"settings_title_notifications" = "Értesítések";
+// Notification settings screen
+"notification_settings_disable_all" = "Minden értesítés kikapcsolása";
+"notification_settings_enable_notifications" = "Értesítések engedélyezése";
+"notification_settings_enable_notifications_warning" = "Minden értesítés tiltva van minden eszközhöz.";
+"notification_settings_global_info" = "Az értesítések beállításai a felhasználói fiókhoz van elmentve és minden eszköz használhatja amelyik támogatja (beleértve az asztali értesítéseket is).\n\nA szabályok sorrendje számít; az első szabály ami illeszkedik határozza meg a viselkedést az üzenethez.\nÍgy a kulcsszó szintű értesítések fontosabbak mint a szoba szintűek amik fontosabbak mint a küldő szintű értesítések.\nHa több szabály van ugyanolyan kategóriából a listában az első szabály ami illeszkedik lesz a meghatározó.";
+"notification_settings_per_word_notifications" = "Kulcsszó alapú értesítések";
+"notification_settings_per_word_info" = "A szavaknál a kis-, és nagybetű nincs megkülönböztetve és tartalmazhat „wildcard” karaktert (*). Például:\nfoo illeszkedik minden szövegre ahol a foo szó elválasztó karakterrel van körülvéve (pl.: írásjel, szóköz, sor eleje, sor vége).\nfoo* illeszkedik minden szövegre ami foo-val kezdődik.\n*foo* illeszkedik minden szövegre ami a három betűt (foo) tartalmazza.";
+"notification_settings_always_notify" = "Mindig értesít";
+"notification_settings_never_notify" = "Soha ne értesítsen";
+"notification_settings_word_to_match" = "szó amire illeszkedjen";
+"notification_settings_highlight" = "Kiemel";
+"notification_settings_custom_sound" = "Egyedi hang";
+"notification_settings_per_room_notifications" = "Szoba szintű értesítések";
+"notification_settings_per_sender_notifications" = "Küldő szintű értesítések";
+"notification_settings_sender_hint" = "@felhasznalo:domain.com";
+"notification_settings_select_room" = "Válassz szobát";
+"notification_settings_other_alerts" = "További figyelmeztetések";
+"notification_settings_contain_my_user_name" = "Hangos értesítés ha az üzenet tartalmazza a nevemet";
+"notification_settings_contain_my_display_name" = "Hangos értesítés ha az üzenet a becenevemet tartalmazza";
+"notification_settings_just_sent_to_me" = "Hangos értesítés ha az üzenetet csak nekem küldték";
+"notification_settings_invite_to_a_new_room" = "Értesítés ha meghívnak egy új szobába";
+"notification_settings_people_join_leave_rooms" = "Értesítés, ha valaki belép vagy elhagy szobát";
+"notification_settings_receive_a_call" = "Értesítés, ha hívást kapok";
+"notification_settings_suppress_from_bots" = "Robotoktól való értesítések tiltása";
+"notification_settings_by_default" = "Alapértelmezetten...";
+"notification_settings_notify_all_other" = "Értesítés minden egyéb üzenethez/szobához";
+// gcm section
+"settings_config_home_server" = "Matrix szerver: %@";
+"settings_config_identity_server" = "Azonosítási szerver: %@";
+"settings_config_user_id" = "Felhasználói azonosító: %@";
+// call string
+"call_waiting" = "Vár...";
+"call_connecting" = "Kapcsolás…";
+"call_ended" = "Hívás vége";
+"call_ring" = "Hívás...";
+"incoming_video_call" = "Érkező videó hívás";
+"incoming_voice_call" = "Érkező hang hívás";
+"call_invite_expired" = "Hívás meghívás lejárt";
+// unrecognized SSL certificate
+"ssl_trust" = "Megbízhatóság";
+"ssl_logout_account" = "Kilép";
+"ssl_remain_offline" = "Figyelmen kívül hagy";
+"ssl_fingerprint_hash" = "Ujjlenyomat (%@):";
+"ssl_could_not_verify" = "A távoli szerver nem azonosítható.";
+"ssl_cert_not_trust" = "Ez azt jelentheti, hogy valaki lehallgatja a forgalmat vagy a telefon nem tekinti megbízhatónak a szerver tanúsítványát.";
+"ssl_cert_new_account_expl" = "Ha a szolgáltatás adminisztrátorának információi alapján ez várható, ellenőrizd az ujjlenyomatot azzal amit az adminisztrátor közölt.";
+"ssl_unexpected_existing_expl" = "A tanúsítvány amit eddig a telefon elfogadott megváltozott. Ez nagyon GYANÚS. Ajánlott az új tanúsítvány ELUTASÍTÁSA.";
+"ssl_expected_existing_expl" = "Az eddig elfogadott tanúsítvány egy nem elfogadottra módosult. A szerver lehet, hogy megújította a tanúsítványát. Vedd fel a kapcsolatot a szerver adminisztrátorával az új ujjlenyomat ellenőrzéséhez.";
+"ssl_only_accept" = "CSAK akkor fogadd el a tanúsítványt ha a szerver adminisztrátora közzétette az ujjlenyomatot és az megegyezik az alábbival.";
+"login_error_resource_limit_exceeded_title" = "Erőforrás korlát túllépés";
+"login_error_resource_limit_exceeded_message_default" = "Ez a Matrix szerver túllépte az egyik erőforrás-korlátját.";
+"login_error_resource_limit_exceeded_message_monthly_active_user" = "Ez a Matrix szerver elérte a havi aktív felhasználói korlátját.";
+"login_error_resource_limit_exceeded_message_contact" = "\n\nKérlek vedd fel a kapcsolatot a szolgáltatás adminisztrátorával, hogy tovább használhasd.";
+"login_error_resource_limit_exceeded_contact_button" = "Adminisztrátor kapcsolatfelvétel";
+// room display name
+"room_displayname_empty_room" = "Üres szoba";
+"room_displayname_two_members" = "%@ és %@";
+"room_displayname_more_than_two_members" = "%@ és %@ mások";
+"e2e_passphrase_create" = "Jelmondat készítés";
+"account_error_push_not_allowed" = "Értesítések nincsenek engedélyezve";
+"notice_room_third_party_revoked_invite" = "%@ visszavonta a a meghívót ehhez a szobához: %@";
+"device_details_rename_prompt_title" = "Munkamenet neve";
+"notice_encryption_enabled_ok" = "%@ bekapcsolta a végpontok közötti titkosítást.";
+"notice_encryption_enabled_unknown_algorithm" = "%1$@ bekapcsolta a végpontok közötti titkosítást (ismeretlen algoritmus: %2$@).";
+// Notice Events with "You"
+"notice_room_invite_by_you" = "Meghívtad őt: %@";
+"notice_room_invite_you" = "%@ meghívott";
+"notice_room_third_party_invite_by_you" = "Meghívót küldtél neki: %@, hogy be tudjon lépni a szobába";
+"notice_room_third_party_registered_invite_by_you" = "A meghívót ide: %@ elfogadtad";
+"notice_room_third_party_revoked_invite_by_you" = "Visszavontad a meghívót ehhez a szobához: %@";
+"notice_room_join_by_you" = "Beléptél";
+"notice_room_leave_by_you" = "Távoztál";
+"notice_room_reject_by_you" = "A meghívót elutasítottad";
+"notice_room_kick_by_you" = "Kirúgtad őt: %@";
+"notice_room_unban_by_you" = "Visszaengedted őt: %@";
+"notice_room_ban_by_you" = "Kitiltottad őt: %@";
+"notice_room_withdraw_by_you" = "%@ meghívóját visszavontad";
+"notice_avatar_url_changed_by_you" = "A profilképedet megváltoztattad";
+"notice_display_name_set_by_you" = "A megjelenő nevedet megváltoztattad erre: %@";
+"notice_display_name_changed_from_by_you" = "A megjelenő nevedet megváltoztattad erről: %@ erre: %@";
+"notice_display_name_removed_by_you" = "A megjelenő nevedet törölted";
+"notice_topic_changed_by_you" = "A témát megváltoztattad erre: „%@”.";
+"notice_room_name_changed_by_you" = "A szoba nevét megváltoztattad erre: %@.";
+"notice_placed_voice_call_by_you" = "Hanghívást kezdeményeztél";
+"notice_placed_video_call_by_you" = "Videóhívást kezdeményeztél";
+"notice_answered_video_call_by_you" = "Fogadtad a hívást";
+"notice_ended_video_call_by_you" = "Befejezted a hívást";
+"notice_conference_call_request_by_you" = "VoIP konferenciát kezdeményeztél";
+"notice_room_name_removed_by_you" = "A szoba nevét törölted";
+"notice_room_topic_removed_by_you" = "A szoba témáját törölted";
+"notice_event_redacted_by_you" = " nálad";
+"notice_profile_change_redacted_by_you" = "A profilodat megváltoztattad: %@";
+"notice_room_created_by_you" = "A szobát létrehoztad és beállítottad.";
+"notice_encryption_enabled_ok_by_you" = "A végpontok közötti titkosítást bekapcsoltad.";
+"notice_encryption_enabled_unknown_algorithm_by_you" = "A végpontok közötti titkosítást bekapcsoltad (az algoritmus ismeretlen: %@).";
+"notice_redaction_by_you" = "Kitakartál egy eseményt (azon.: %@)";
+"notice_room_history_visible_to_anyone_by_you" = "Láthatóvá tetted a szoba jövőbeni üzeneteit mindenki számára.";
+"notice_room_history_visible_to_members_by_you" = "Láthatóvá tetted a szoba jövőbeni üzeneteit a szobában tartózkodók számára.";
+"notice_room_history_visible_to_members_from_invited_point_by_you" = "A szoba jövőbeni üzeneteit a szobában lévő felhasználók számára a meghívásuk pillanatától láthatóvá tetted.";
+"notice_room_history_visible_to_members_from_joined_point_by_you" = "A szoba jövőbeni üzeneteit a szobában lévő felhasználók számára a szobába való belépésük pillanatától láthatóvá tetted.";
+"notice_room_name_removed_for_dm" = "%@ törölte a nevet";
+"notice_room_created_for_dm" = "%@ csatlakozott.";
+// New
+"notice_room_join_rule_invite" = "%@meghívásossá tette a szobát.";
+"notice_room_join_rule_invite_for_dm" = "%@meghívásossá tette.";
+"notice_room_join_rule_invite_by_you" = "A szobát meghívásossá tetted.";
+"notice_room_join_rule_invite_by_you_for_dm" = "Meghívásossá tetted.";
+"notice_room_join_rule_public" = "%@ nyilvánossá tette a szobát.";
+"notice_room_join_rule_public_for_dm" = "%@ nyilvánossá tette.";
+"notice_room_join_rule_public_by_you" = "A szobát nyilvánossá tetted.";
+"notice_room_join_rule_public_by_you_for_dm" = "Ezt nyilvánossá tetted.";
+"notice_room_power_level_intro_for_dm" = "A tagok a hozzáférési szintje:";
+"notice_room_aliases_for_dm" = "A becenevek: %@";
+"notice_room_history_visible_to_members_for_dm" = "%@ a jövőbeni üzeneteket láthatóvá tette a szobában lévő minden felhasználó számára.";
+"notice_room_history_visible_to_members_from_invited_point_for_dm" = "%@ a jövőbeni üzeneteket láthatóvá tette mindenki számára a meghívásuk pillanatától.";
+"notice_room_history_visible_to_members_from_joined_point_for_dm" = "%@ a jövőbeni üzeneteket láthatóvá tette mindenki számára a belépésük pillanatától.";
+"room_left_for_dm" = "Távoztál";
+"notice_room_third_party_invite_for_dm" = "%@ meghívta %@ felhasználót";
+"notice_room_third_party_revoked_invite_for_dm" = "%@ visszavonta az ő meghívóját: %@";
+"notice_room_name_changed_for_dm" = "%@ megváltoztatta a nevet erre: %@.";
+"notice_room_third_party_invite_by_you_for_dm" = "Meghívtad őt: %@";
+"notice_room_third_party_revoked_invite_by_you_for_dm" = "Visszavontad az ő meghívóját: %@";
+"notice_room_name_changed_by_you_for_dm" = "Megváltoztattad a nevet erre: %@.";
+"notice_room_name_removed_by_you_for_dm" = "A nevet törölted";
+"notice_room_created_by_you_for_dm" = "Beléptél.";
+"notice_room_history_visible_to_members_by_you_for_dm" = "A jövőbeni üzeneteket láthatóvá tetted a szobában lévő minden felhasználó számára.";
+"notice_room_history_visible_to_members_from_invited_point_by_you_for_dm" = "A jövőbeni üzeneteket láthatóvá tetted mindenki számára a meghívásuk pillanatától.";
+"notice_room_history_visible_to_members_from_joined_point_by_you_for_dm" = "A jövőbeni üzeneteket láthatóvá tetted mindenki számára a belépésük pillanatától.";
+"call_more_actions_dialpad" = "Tárcsázó számlap";
+"call_more_actions_transfer" = "Átadás";
+"call_more_actions_audio_use_device" = "Eszköz hangszóró";
+"call_more_actions_audio_use_headset" = "Fejhallgató használata";
+"call_more_actions_change_audio_device" = "Hang eszköz megváltoztatása";
+"call_more_actions_unhold" = "Folytatás";
+"call_more_actions_hold" = "Várakoztat";
+"call_holded" = "Felfüggesztette a hívást";
+"call_remote_holded" = "%@ felfüggesztette a hívást";
+"notice_declined_video_call_by_you" = "Elutasította a hívást";
+"notice_declined_video_call" = "%@ elutasította a hívást";
+"resume_call" = "Folytatás";
+"call_transfer_to_user" = "Hívásátirányítás ide: %@";
+"call_consulting_with_user" = "Konzultáció vele: %@";
+"call_video_with_user" = "Videóhívás vele: %@";
+"call_voice_with_user" = "Hanghívás vele: %@";
+"call_ringing" = "Hívás…";
+"e2e_passphrase_too_short" = "A jelmondat túl rövid (legalább %d karakter hosszúnak kell lennie)";
+"microphone_access_not_granted_for_voice_message" = "Ha hangüzenetekhez a mikrofonhoz szükséges a hozzáférés, de %@ nem rendelkezik a használatához szükséges engedéllyel";
+"message_reply_to_sender_sent_a_voice_message" = "hang üzenet elküldve.";
+"attachment_large_with_resolution" = "Nagy %@ (~%@)";
+"attachment_medium_with_resolution" = "Közepes %@ (~%@)";
+"attachment_small_with_resolution" = "Kicsi %@ (~%@)";
+"attachment_size_prompt_message" = "Ezt a beállításokban kikapcsolhatod.";
+"attachment_size_prompt_title" = "Méret megerősítése küldéshez";
+"room_displayname_all_other_participants_left" = "%@ (Bal)";
+"auth_reset_password_error_not_found" = "Nem található";
+"auth_reset_password_error_unauthorized" = "Nem engedélyezett";
+"auth_username_in_use" = "A felhasználónév foglalt";
+"auth_invalid_user_name" = "Érvénytelen felhasználónév";
+"rename" = "Átnevez";
+"room_displayname_all_other_members_left" = "%@ (Bal)";
diff --git a/Riot/Modules/MatrixKit/Assets/MatrixKitAssets.bundle/id.lproj/MatrixKit.strings b/Riot/Modules/MatrixKit/Assets/MatrixKitAssets.bundle/id.lproj/MatrixKit.strings
new file mode 100644
index 000000000..3bee04d44
--- /dev/null
+++ b/Riot/Modules/MatrixKit/Assets/MatrixKitAssets.bundle/id.lproj/MatrixKit.strings
@@ -0,0 +1,557 @@
+
+
+"auth_username_in_use" = "Nama pengguna telah dipakai";
+"invite" = "Undang";
+"rename" = "Ubah Nama";
+"view" = "Tampilkan";
+"cancel" = "Batalkan";
+"leave" = "Tinggalkan";
+"save" = "Simpan";
+"notice_room_power_level_intro_for_dm" = "Level kekuatan anggota pesan langsung adalah:";
+"notice_room_power_level_intro" = "Level kekuatan anggota ruangan adalah:";
+"notice_room_join_rule_public_by_you_for_dm" = "Anda membuat pesan langsung ini publik.";
+"notice_room_join_rule_public_by_you" = "Anda membuat ruangan ini publik.";
+"notice_room_join_rule_public_for_dm" = "%@ membuat pesan langsung ini publik.";
+"notice_room_join_rule_public" = "%@ membuat ruangan ini publik.";
+"notice_room_join_rule_invite_by_you_for_dm" = "Anda membuat pesan langsung ini undangan saja.";
+"notice_room_join_rule_invite_by_you" = "Anda membuat ruangan ini undangan saja.";
+"notice_room_join_rule_invite_for_dm" = "%@ membuat pesan langsung ini undangan saja.";
+// New
+"notice_room_join_rule_invite" = "%@ membuat ruangan ini undangan saja.";
+// Old
+"notice_room_join_rule" = "Peraturan bergabung adalah: %@";
+"notice_room_created_for_dm" = "%@ bergabung.";
+"notice_room_created" = "%@ membuat dan mengatur ruangan ini.";
+"notice_profile_change_redacted" = "%@ memperbarui profilnya %@";
+"notice_event_redacted_reason" = " [alasan: %@]";
+"notice_event_redacted_by" = " dari %@";
+"notice_event_redacted" = "";
+"notice_room_topic_removed" = "%@ menghapus topik ruangan";
+"notice_room_name_removed_for_dm" = "%@ menghapus nama pesan langsung";
+"notice_room_name_removed" = "%@ menghapus nama ruangan";
+
+// Events formatter
+"notice_avatar_changed_too" = "(avatar juga diganti)";
+"unignore" = "Hapus Pengabaian";
+"ignore" = "Abaikan";
+"resume_call" = "Lanjutkan";
+"end_call" = "Akhiri Panggilan";
+"reject_call" = "Tolak Panggilan";
+"answer_call" = "Jawab Panggilan";
+"show_details" = "Tampilkan Detail";
+"cancel_upload" = "Batal Mengunggah";
+"cancel_download" = "Batal Mengunduh";
+"select_all" = "Pilih Semua";
+"resend_message" = "Kirim ulang pesan";
+"reset_to_default" = "Atur ulang ke bawaan";
+"invite_user" = "Undang pengguna Matrix";
+"capture_media" = "Ambil Foto/Video";
+"attach_media" = "Lampirkan Media dari Library";
+"select_account" = "Pilih sebuah akun";
+"mention" = "Sebutan";
+"start_video_call" = "Mulai Panggilan Video";
+"start_voice_call" = "Mulai Panggilan Suara";
+"start_chat" = "Mulai Mengobrol";
+"set_moderator" = "Tetapkan Moderator";
+"set_admin" = "Tetapkan Admin";
+"set_power_level" = "Atur Level Kekuatan";
+"set_default_power_level" = "Atur Ulang Level Kekuatan";
+"submit_code" = "Kirim kode";
+"submit" = "Kirim";
+"sign_up" = "Daftar";
+"retry" = "Coba Lagi";
+"dismiss" = "Lupakan";
+"discard" = "Buang";
+"continue" = "Lanjutkan";
+"close" = "Tutup";
+"back" = "Kembali";
+"abort" = "Batalkan";
+"yes" = "Ya";
+
+// Action
+"no" = "Tidak";
+"login_error_resource_limit_exceeded_contact_button" = "Hubungi Administrator";
+"login_error_resource_limit_exceeded_message_contact" = "\n\nSilakan hubungi service homeserver Anda untuk melanjutkan menggunakan perangkat ini.";
+"login_error_resource_limit_exceeded_message_monthly_active_user" = "Homeserver ini telah mencapai batas Pengguna Aktif Bulanan.";
+"login_error_resource_limit_exceeded_message_default" = "Homeserver ini telah melebihi batas sumbernya.";
+"login_error_resource_limit_exceeded_title" = "Melebihi Batas Sumber";
+"login_desktop_device" = "Desktop";
+"login_tablet_device" = "Tablet";
+"login_mobile_device" = "Mobile";
+"login_error_forgot_password_is_not_supported" = "Lupa kata sandi saat ini belum didukung";
+"register_error_title" = "Pendaftaran Gagal";
+"login_invalid_param" = "Parameter tidak valid";
+"login_leave_fallback" = "Batalkan";
+"login_use_fallback" = "Gunakan halaman fallback";
+"login_error_login_email_not_yet" = "Tautan email yang belum diklik";
+"login_error_user_in_use" = "Nama pengguna ini sudah dipakai";
+"login_error_limit_exceeded" = "Terlalu banyak permintaan yang dikirim";
+"login_error_not_json" = "Tidak mengandung JSON yang valid";
+"login_error_unknown_token" = "Token akses yang ditentukan tidak diketahui";
+"login_error_bad_json" = "JSON cacat";
+"login_error_forbidden" = "Nama pengguna/kata sandi tidak valid";
+"login_error_registration_is_not_supported" = "Pendaftaran saat ini tidak didukung";
+"login_error_do_not_support_login_flows" = "Saat ini kami tidak mendukung salah satu atau semua alur masuk yang ditentukan oleh homeserver ini";
+"login_error_no_login_flow" = "Kami gagal untuk menerima informasi otentikasi dari homeserver ini";
+"login_error_title" = "Login Gagal";
+"login_prompt_email_token" = "Harap masukkan token validasi email Anda:";
+"login_email_placeholder" = "Alamat email";
+"login_email_info" = "Menentukan alamat email memungkinkan pengguna lain untuk menemukan Anda di Matrix dengan lebih mudah, dan akan memberi Anda cara untuk menyetel ulang sandi di masa mendatang.";
+"login_display_name_placeholder" = "Nama tampilan (mis. Bob Obson)";
+"login_optional_field" = "opsional";
+"login_password_placeholder" = "Kata sandi";
+"login_user_id_placeholder" = "ID Matrix (mis. @bob:matrix.org atau bob)";
+"login_identity_server_info" = "Matrix menyediakan server identitas untuk melacak email mana, dll., milik ID Matrix mana. Hanya https://matrix.org yang saat ini ada.";
+"login_identity_server_title" = "URL server identitas:";
+"login_home_server_info" = "Homeserver Anda menyimpan semua pesan Anda dan data akun";
+"login_home_server_title" = "URL Homeserver:";
+"login_server_url_placeholder" = "URL (mis. https://matrix.org)";
+
+// Login Screen
+"login_create_account" = "Buat akun:";
+/* *********************** */
+/* iOS specific */
+/* *********************** */
+
+"matrix" = "Matrix";
+"auth_reset_password_error_not_found" = "Tidak ditemukan";
+"auth_reset_password_error_unauthorized" = "Tidak diotorisasi";
+"auth_invalid_user_name" = "Nama pengguna tidak valid";
+"ssl_only_accept" = "HANYA terima sertifikat jika administrator server telah mempublikasikan sidik jari yang cocok dengan sidik jari di atas.";
+"ssl_expected_existing_expl" = "Sertifikat ini telah berubah dari yang sebelumnya tepercaya menjadi yang tidak tepercaya. Servernya mungkin telah memperbarui sertifikatnya. Hubungi administrator server untuk sidik jari yang diharapkan.";
+"ssl_unexpected_existing_expl" = "Sertifikat ini telah berubah dari yang dipercaya oleh ponsel Anda. Ini SANGAT TIDAK BIASA. Anda disarankan untuk TIDAK MENERIMA sertifikat baru ini.";
+"ssl_cert_new_account_expl" = "Jika administrator server mengatakan bahwa ini diharapkan, pastikan bahwa sidik jari di bawah ini cocok dengan sidik jari yang disediakannya.";
+"ssl_cert_not_trust" = "Ini bisa berarti bahwa seseorang mencegat lalu lintas Anda, atau bahwa ponsel Anda tidak mempercayai sertifikat yang disediakan oleh server jarak jauh.";
+"ssl_could_not_verify" = "Tidak dapat memverifikasi identitas server jarak jauh.";
+"ssl_fingerprint_hash" = "Sidik Jari (%@):";
+"ssl_remain_offline" = "Abaikan";
+"ssl_logout_account" = "Keluar";
+
+// unrecognized SSL certificate
+"ssl_trust" = "Percayai";
+"call_transfer_to_user" = "Pindahkan ke %@";
+"call_consulting_with_user" = "Mengkonsultasi dengan %@";
+"call_video_with_user" = "Panggilan video dengan %@";
+"call_voice_with_user" = "Panggilan suara dengan %@";
+"call_more_actions_dialpad" = "Tombol penyetel";
+"call_more_actions_transfer" = "Pindahkan";
+"call_more_actions_audio_use_device" = "Speaker Perangkat";
+"call_more_actions_change_audio_device" = "Ubah Perangkat Audio";
+"call_more_actions_unhold" = "Lanjutkan";
+"call_more_actions_hold" = "Jeda";
+"call_holded" = "Anda menjeda panggilan ini";
+"call_remote_holded" = "%@ menjeda panggilan ini";
+"call_invite_expired" = "Undangan Panggilan Kedaluwarsa";
+"incoming_voice_call" = "Masuk Panggilan Suara";
+"incoming_video_call" = "Masuk Panggilan Video";
+"call_ended" = "Panggilan diakhiri";
+"call_ringing" = "Berdering…";
+
+// Settings keys
+
+// call string
+"call_connecting" = "Menghubungkan…";
+"settings_config_user_id" = "ID Pengguna: %@";
+"settings_config_identity_server" = "Server identitas: %@";
+
+// gcm section
+"settings_config_home_server" = "Homeserver: %@";
+"notification_settings_notify_all_other" = "Beritahu untuk semua pesan/ruangan lainnya";
+"notification_settings_by_default" = "Secara default...";
+"notification_settings_suppress_from_bots" = "Jangan beritahu saya tentang notifikasi dari bot";
+"notification_settings_receive_a_call" = "Beritahu saya ketika saya menerima panggilan";
+"notification_settings_people_join_leave_rooms" = "Beritahu saya ketika ada orang bergabung atau meninggalkan ruangan";
+"notification_settings_invite_to_a_new_room" = "Beritahu saya ketika saya diundang ke ruangan baru";
+"notification_settings_just_sent_to_me" = "Beritahu saya dengan suara tentang pesan yang baru saja dikirim ke saya";
+"notification_settings_contain_my_display_name" = "Beritahu saya dengan suara tentang pesan yang berisi nama tampilan saya";
+"notification_settings_contain_my_user_name" = "Beritahu saya dengan suara tentang pesan yang berisi nama pengguna saya";
+"notification_settings_other_alerts" = "Pemberitahuan Lainnya";
+"notification_settings_select_room" = "Pilih sebuah ruangan";
+"notification_settings_sender_hint" = "@pengguna:domain.com";
+"notification_settings_per_sender_notifications" = "Notifikasi per pengirim";
+"notification_settings_per_room_notifications" = "Notifikasi per ruangan";
+"notification_settings_custom_sound" = "Suara kustom";
+"notification_settings_highlight" = "Highlight";
+"notification_settings_word_to_match" = "kata untuk dicocokkan";
+"notification_settings_never_notify" = "Jangan diberitahu";
+"notification_settings_always_notify" = "Selalu diberitahu";
+"notification_settings_per_word_info" = "Kata-kata tidak cocok dengan huruf besar-kecil, dan mungkin menyertakan karakter pengganti *. Jadi:\nfoo cocok dengan string foo yang dikelilingi oleh pembatas kata (misalnya tanda baca dan spasi atau awal/akhir baris).\nfoo* cocok dengan kata apa pun yang dimulai foo.\n*foo* cocok dengan kata apa pun yang menyertakan 3 huruf foo.";
+"notification_settings_per_word_notifications" = "Notifikasi per kata";
+"notification_settings_global_info" = "Pengaturan notifikasi disimpan ke akun pengguna Anda dan dibagikan di antara semua client yang mendukungnya (termasuk pemberitahuan desktop).\n\nAturan diterapkan secara berurutan; aturan pertama yang cocok menentukan hasil untuk pesan.\nJadi: Notifikasi per kata lebih penting daripada notifikasi per ruangan yang lebih penting daripada notifikasi per pengirim.\nUntuk beberapa aturan dengan jenis yang sama, yang pertama dalam daftar yang cocok akan diprioritaskan.";
+"notification_settings_enable_notifications_warning" = "Semua notifikasi saat ini dinonaktifkan untuk semua perangkat.";
+"notification_settings_enable_notifications" = "Aktifkan notifikasi";
+
+// Notification settings screen
+"notification_settings_disable_all" = "Nonaktifkan semua notifikasi";
+"settings_title_notifications" = "Notifikasi";
+
+// Settings screen
+"settings_title_config" = "Konfigurasi";
+
+// members list Screen
+
+// accounts list Screen
+
+// image size selection
+
+// invitation members list Screen
+
+// room creation dialog Screen
+
+// room info dialog Screen
+
+// room details dialog screen
+"room_details_title" = "Detail Ruangan";
+"login_error_must_start_http" = "URL harus dimulai dengan http[s]://";
+
+// Login Screen
+"login_error_already_logged_in" = "Sudah masuk";
+"message_unsaved_changes" = "Ada perubahan yang belum disimpan. Meninggalkannya akan membuang mereka.";
+"unban" = "Hilangkan Cekalan";
+"ban" = "Cekal";
+"kick" = "Keluarkan";
+"num_members_other" = "%@ pengguna";
+"num_members_one" = "%@ pengguna";
+"membership_ban" = "Dicekal";
+"membership_leave" = "Keluar";
+"membership_invite" = "Diundang";
+"create_account" = "Buat Akun";
+"create_room" = "Buat Ruangan";
+"login" = "Masuk";
+
+// actions
+"action_logout" = "Keluar";
+"delete" = "Hapus";
+"share" = "Bagikan";
+"redact" = "Hapus";
+"resend" = "Kirim Ulang";
+"copy_button_name" = "Salin";
+"send" = "Kirim";
+
+// Room Screen
+
+// general errors
+
+// Home Screen
+
+// Last seen time
+
+// call events
+
+/* -*-
+ Automatic localization for en
+
+ The following key/value pairs were extracted from the android i18n file:
+ /console/src/main/res/values/strings.xml.
+*/
+
+
+// titles
+
+// button names
+"ok" = "OK";
+"notice_room_history_visible_to_members_from_joined_point_by_you_for_dm" = "Anda membuat sejarah pesan di masa mendatang dapat dilihat oleh semuanya, sejak mereka bergabung.";
+"notice_room_history_visible_to_members_from_joined_point_by_you" = "Anda membuat sejarah ruangan di masa mendatang dapat dilihat oleh semua anggota ruang, sejak mereka bergabung.";
+"notice_encryption_enabled_unknown_algorithm_by_you" = "Anda mengaktifkan enkripsi ujung-ke-ujung (algoritma %@ tidak dikenal).";
+"notice_room_third_party_revoked_invite" = "%@ menghilangkan undangannya %@ untuk bergabung ke ruangan ini";
+"notice_room_third_party_revoked_invite_by_you" = "Anda menghilangkan undangannya %@ untuk bergabung ke ruangan ini";
+"account_email_validation_error" = "Tidak dapat memverifikasi alamat email. Silakan cek email Anda dan tekan tautannya yang ada. Setelah selesai, tekan lanjut";
+
+// contacts list screen
+"invitation_message" = "Saya ingin berkomunikasi dengan Anda dengan Matrix. Silakan kunjungi ke websitenya di https://matrix.org untuk informasi selanjutnya.";
+"notice_room_history_visible_to_members_from_invited_point_by_you_for_dm" = "Anda membuat sejarah pesan di masa mendatang dapat dilihat oleh semuanya, sejak mereka diundang.";
+"notice_room_history_visible_to_members_from_invited_point_by_you" = "Anda membuat sejarah ruangan di masa mendatang dapat dilihat oleh semua anggota ruangan, sejak mereka diundang.";
+"notice_room_history_visible_to_members_by_you_for_dm" = "Anda membuat pesan di masa depan dapat dilihat oleh semua anggota ruangan.";
+"notice_room_history_visible_to_members_by_you" = "Anda membuat sejarah ruangan di masa mendatang dapat dilihat oleh semua anggota ruangan.";
+"notice_room_history_visible_to_anyone_by_you" = "Anda membuat sejarah ruangan di masa mendatang dapat dilihat oleh siapa saja.";
+"notice_redaction_by_you" = "Anda menghapus sebuah peristiwa (id: %@)";
+"notice_encryption_enabled_ok_by_you" = "Anda mengaktifkan enkripsi ujung-ke-ujung.";
+"notice_room_created_by_you_for_dm" = "Anda bergabung.";
+"notice_room_created_by_you" = "Anda membuat dan mengatur ruangan ini.";
+"notice_profile_change_redacted_by_you" = "Anda memperbarui profil Anda %@";
+"notice_event_redacted_by_you" = " oleh Anda";
+"notice_room_topic_removed_by_you" = "Anda menghilangkan topik ruangan ini";
+"notice_room_name_removed_by_you_for_dm" = "Anda menghilangkan nama ruangan ini";
+"notice_room_name_removed_by_you" = "Anda menghilangkan nama ruangan ini";
+"notice_conference_call_request_by_you" = "Anda telah meminta konferensi VoIP";
+"notice_declined_video_call_by_you" = "Anda menolak anggilannya";
+"notice_ended_video_call_by_you" = "Anda mengakhiri pangilannya";
+"notice_answered_video_call_by_you" = "Anda menjawab panggilannya";
+"notice_placed_video_call_by_you" = "Anda melakukan panggilan video";
+"notice_placed_voice_call_by_you" = "Anda melakukan panggilan suara";
+"notice_room_name_changed_by_you_for_dm" = "Anda mengubah nama ruangan ini ke%@.";
+"notice_room_name_changed_by_you" = "Anda mengubah nama ruangan ini ke %@.";
+"notice_topic_changed_by_you" = "Anda mengubah topik ruangan nini ke \"%@\".";
+"notice_display_name_removed_by_you" = "Anda menghilangkan nama tampilan Anda";
+"notice_display_name_changed_from_by_you" = "Anda mengubah nama tampilan Anda dari %@ ke %@";
+"notice_display_name_set_by_you" = "Anda mengubah nama tampilan Anda ke %@";
+"notice_avatar_url_changed_by_you" = "Anda mengubah avatar Anda";
+"notice_room_withdraw_by_you" = "Anda menghilangkan undangannya %@";
+"notice_room_ban_by_you" = "Anda mencekal %@";
+"notice_room_unban_by_you" = "Anda menghilangkan cekalan %@";
+"notice_room_kick_by_you" = "Anda mengeluarkan %@";
+"notice_room_reject_by_you" = "Anda menolak undangannya";
+"notice_room_leave_by_you" = "Anda keluar";
+"notice_room_join_by_you" = "Anda bergabung";
+"notice_room_third_party_revoked_invite_by_you_for_dm" = "Anda menghilangkan undangannya %@";
+"notice_room_third_party_registered_invite_by_you" = "Anda menerima undangan untuk %@";
+"notice_room_third_party_invite_by_you_for_dm" = "Anda mengundang %@";
+"notice_room_third_party_invite_by_you" = "Anda mengirim sebuah undangan ke @% untuk bergabung ke ruangan ini";
+"notice_room_invite_you" = "%@ mengundang Anda";
+
+// Notice Events with "You"
+"notice_room_invite_by_you" = "Anda mengundang %@";
+"notice_conference_call_finished" = "Konferensi VoIP diakhiri";
+"notice_conference_call_started" = "Konferensi VoIP dimulai";
+"notice_conference_call_request" = "%@ telah meminta konferensi VoIP";
+"notice_declined_video_call" = "%@ menolak panggilannya";
+"notice_ended_video_call" = "%@ mengakhiri panggilannya";
+"notice_answered_video_call" = "%@ menjawab panggilannya";
+"notice_placed_video_call" = "%@ melakukan panggilan video";
+"notice_placed_voice_call" = "%@ melakukan panggilan suara";
+"notice_room_name_changed_for_dm" = "%@ mengubah nama ruangan ini ke %@.";
+"notice_room_name_changed" = "%@ mengubah nama ruangan ini ke %@.";
+"notice_topic_changed" = "%@ mengubah topik ruangan ini ke \"%@\".";
+"notice_display_name_removed" = "%@ menghilangkan nama tampilannya";
+"notice_display_name_changed_from" = "%@ mengubah nama tampilannya dari %@ ke %@";
+"notice_display_name_set" = "%@ mengubah nama tampilannya ke %@";
+"notice_avatar_url_changed" = "%@ mengubah avatarnya";
+"notice_room_reason" = ". Alasan: %@";
+"notice_room_withdraw" = "%@ menghilangkan undangannya %@";
+"notice_room_ban" = "%@ mencekal %@";
+"notice_room_unban" = "%@ menghilangkan cekalan %@";
+"notice_room_kick" = "%@ mengeluarkan %@";
+"notice_room_reject" = "%@ menolak undangannya";
+"notice_room_leave" = "%@ keluar";
+"notice_room_join" = "%@ bergabung";
+"notice_room_third_party_revoked_invite_for_dm" = "%@ menghilangkan undangannya %@";
+"notice_room_third_party_registered_invite" = "%@ menerima undangan untuk %@";
+"notice_room_third_party_invite_for_dm" = "%@ mengundang %@";
+"notice_room_third_party_invite" = "%@ mengirim sebuah undangan ke %@ untuk bergabung ke ruangan ini";
+
+/* -*-
+ Automatic localization for en
+
+ The following key/value pairs were extracted from the android i18n file:
+ /matrix-sdk/src/main/res/values/strings.xml.
+*/
+
+"notice_room_invite" = "%@ mengundang %@";
+"language_picker_default_language" = "Bawaan (%@)";
+
+// Language picker
+"language_picker_title" = "Pilih sebuah bahasa";
+
+// Country picker
+"country_picker_title" = "Pilih sebuah negara";
+"microphone_access_not_granted_for_voice_message" = "Pesan suara membutuhkan akses ke Mikrofon tetapi %@ tidak memiliki izin untuk menggunakannya";
+"local_contacts_access_discovery_warning" = "Untuk menemukan kontak Anda yang sudah menggunakan Matrix, %@ dapat mengirim alamat email dan nomor telepon di kontak Anda ke server identitas Matrix yang Anda pilih. Di mana saja yang didukung, data personal akan di-hash sebelum dikirim - mohon cek kebijakan privasi identitas server Anda untuk detail lainnya.";
+"local_contacts_access_discovery_warning_title" = "Penemuan pengguna";
+"local_contacts_access_not_granted" = "Penemuan pengguna dari kontak lokal membutuhkan akses ke kontak Anda tetapi %@ tidak memiliki izin untuk menggunakannya";
+"microphone_access_not_granted_for_call" = "Panggilan membutuhkan akses ke Mikrofon tetapi %@ tidak memiliki izin untuk menggunakannya";
+
+// Permissions
+"camera_access_not_granted_for_call" = "Panggilan video membutuhkan akses ke Kamera tetapi %@ tidak memiliki izin untuk menggunakannya";
+"ssl_homeserver_url" = "URL Homeserver: %@";
+"user_id_placeholder" = "misal: @bob:homeserver";
+"network_error_not_reachable" = "Mohon cek koneksi jaringan Anda";
+"power_level" = "Level Kekuatan";
+"public" = "Publik";
+"private" = "Privat";
+"default" = "bawaan";
+"not_supported_yet" = "Belum didukung";
+"error_common_message" = "Sebuah kesalahan terjadi. Coba lagi nanti.";
+"error" = "Gagal";
+"unsent" = "Belum Terkirim";
+"offline" = "offline";
+
+// Others
+"user_id_title" = "ID Pangguna:";
+"e2e_passphrase_create" = "Buat frasa sandi";
+"e2e_passphrase_not_match" = "Frasa sandi harus cocok";
+"e2e_passphrase_too_short" = "Frasa sandi terlalu pendek (Harus minimal %d karakter panjangnya)";
+"e2e_passphrase_empty" = "Frasa sandi tidak boleh kosong";
+"e2e_passphrase_confirm" = "Konfirmasi frasa sandi";
+"e2e_export" = "Ekspor";
+"e2e_export_prompt" = "Proses ini memungkinkan Anda untuk mengekspor kunci untuk pesan yang Anda telah terima di ruangan terenkripsi ke file lokal. Anda nanti akan dapat mengimpor filenya ke client Matrix lainnya di masa mendatang, supaya client itu juga bisa mendekripsi pesan yang terenkripsi.\nFile yang diekspor akan memungkinan siapa saja yang dapat membaca untuk mendekripsikan pesan terenkripsi apa saja yang Anda bisa lihat, jadi Anda harus berhati-hati untuk menyimpannya secara aman.";
+
+// E2E export
+"e2e_export_room_keys" = "Ekspor kunci ruangan";
+"e2e_passphrase_enter" = "Masukkan frasa sandi";
+"e2e_import" = "Impor";
+"e2e_import_prompt" = "Proses ini memungkinkan Anda untuk mengimpor kunci enkripsi yang Anda punya sebelumnya yang diekspor dari client Matrix lain. Anda nanti akan dapat mendekripsi pesan apa saja yang client lain dapat mendekripsinya.\nFile yang diekspor dilindungi dengan frasa sandi. Anda seharusnya masukkan frasa sandinya di sini, untuk mendekripsi filenya.";
+
+// E2E import
+"e2e_import_room_keys" = "Impor kunci ruangan";
+"format_time_d" = "h";
+"format_time_h" = "j";
+"format_time_m" = "m";
+
+// Time
+"format_time_s" = "d";
+"search_searching" = "Pencarian sedang dilakukan...";
+
+// Search
+"search_no_results" = "Tidak Ada Hasil";
+"group_section" = "Grup";
+
+// Groups
+"group_invite_section" = "Undangan";
+"contact_local_contacts" = "Kontak Local";
+
+// Contacts
+"contact_mx_users" = "Pengguna Matrix";
+"attachment_e2e_keys_import" = "Impor...";
+"attachment_e2e_keys_file_prompt" = "File ini berisi kunci enkripsi yang diimpor dari client Matrix lain.\nApakah Anda ingin menampilkan konten file atau impor kunci yang berisi?";
+"attachment_multiselection_original" = "Ukuran Sebenarnya";
+"attachment_multiselection_size_prompt" = "Apakah Anda ingin mengirim gambarnya sebagai:";
+"attachment_cancel_upload" = "Batalkan unggahannya?";
+"attachment_cancel_download" = "Batalkan unduhannya?";
+"attachment_large_with_resolution" = "Besar %@ (~%@)";
+"attachment_medium_with_resolution" = "Sedang %@ (~%@)";
+"attachment_small_with_resolution" = "Kecil %@ (~%@)";
+"attachment_large" = "Besar (~%@)";
+"attachment_medium" = "Sedang (~%@)";
+"attachment_small" = "Kecil (~%@)";
+"attachment_original" = "Ukuran Sebenarnya (%@)";
+"attachment_size_prompt_message" = "Anda dapat menonaktifkannya di pengaturan.";
+"attachment_size_prompt_title" = "Konfirmasi ukuran untuk dikirim";
+
+// Attachment
+"attachment_size_prompt" = "Apakah Anda ingin mengirimnya sebagai:";
+"room_member_power_level_prompt" = "Anda tidak akan lagi membatalkan perubahan ini ketika Anda mempromosikan penggunanya untuk memiliki level kekuatan yang sama dengan Anda sendiri.\nApakah Anda yakin?";
+
+// Room members
+"room_member_ignore_prompt" = "Apakah Anda yakin untuk menyembunyikan semua pesan dari pengguna ini?";
+"message_reply_to_message_to_reply_to_prefix" = "Membalas ke";
+"message_reply_to_sender_sent_a_file" = "mengirim sebuah file.";
+"message_reply_to_sender_sent_a_voice_message" = "mengirim sebuah pesan suara.";
+"message_reply_to_sender_sent_an_audio_file" = "mengirim sebuah file audio.";
+"message_reply_to_sender_sent_a_video" = "mengirim sebuah video.";
+
+// Reply to message
+"message_reply_to_sender_sent_an_image" = "mengirim sebuah gambar.";
+"room_no_conference_call_in_encrypted_rooms" = "Panggilan konferensi tidak didukung di ruangan terenkripsi";
+"room_no_power_to_create_conference_call" = "Anda membutuhkan izin untuk mengundang untuk memulai konferensi di ruangan ini";
+"room_left_for_dm" = "Anda keluar";
+"room_left" = "Anda meninggalkan ruangan ini";
+"room_error_timeline_event_not_found" = "Aplikasi ini sedang mencoba untuk memuat titik tertenu di linimasa ruangan ini tetapi tidak dapat menemukannya";
+"room_error_timeline_event_not_found_title" = "Gagal untuk memuat posisi linimasa";
+"room_error_cannot_load_timeline" = "Gagal untuk memuat linimasa";
+"room_error_topic_edition_not_authorized" = "Anda tidak diizinkan untuk mengubah topik ruangan ini";
+"room_error_name_edition_not_authorized" = "Anda tidak diizinkan untuk mengubah nama ruangan ini";
+"room_error_join_failed_empty_room" = "Saat ini tidak mungkin untuk bergabung ke ruangan yang kosong.";
+"room_error_join_failed_title" = "Gagal untuk bergabung ke ruangan";
+
+// Room
+"room_please_select" = "Silakan pilih sebuah ruangan";
+"room_creation_participants_placeholder" = "(mis. @bob:homeserver1; @john:homeserver2...)";
+"room_creation_participants_title" = "Anggota:";
+"room_creation_alias_placeholder_with_homeserver" = "(mis. #foo%@)";
+"room_creation_alias_placeholder" = "(mis. #foo:example.org)";
+"room_creation_alias_title" = "Alias ruangan:";
+"room_creation_name_placeholder" = "(mis. grupMakanSiang)";
+
+// Room creation
+"room_creation_name_title" = "Nama ruangan:";
+"account_error_push_not_allowed" = "Notifikasi tidak diizinkan";
+"account_error_msisdn_wrong_description" = "Ini sepertinya bukan nomor telepon yang valid";
+"account_error_msisdn_wrong_title" = "Nomor Telepon Tidak Valid";
+"account_error_email_wrong_description" = "Ini sepertinya bukan alamat email yang valid";
+"account_error_email_wrong_title" = "Alamat Email Tidak Valid";
+"account_error_matrix_session_is_not_opened" = "Sesi Matrix tidak dibuka";
+"account_error_picture_change_failed" = "Penggantian gambar gagal";
+"account_error_display_name_change_failed" = "Penggantian nama tampilan gagal";
+"account_msisdn_validation_error" = "Tidak dapat memverifikasi nomor telepon.";
+"account_msisdn_validation_message" = "Kami telah mengirim sebuah SMS dengan kode aktivasi. Silakan masukkan kodenya di bawah.";
+"account_msisdn_validation_title" = "Menunggu Verifikasi";
+"account_email_validation_message" = "Silakan cek email Anda dan tekan tautannya yang ada. Setelah selesai, tekan lanjut.";
+"account_email_validation_title" = "Menunggu Verifikasi";
+"account_linked_emails" = "Email yang tertaut";
+"account_link_email" = "Tautkan Email";
+
+// Account
+"account_save_changes" = "Simpan perubahan";
+"room_event_encryption_verify_ok" = "Verifikasi";
+"room_event_encryption_verify_message" = "Untuk memverifikasi bahwa sesi ini dapat dipercaya, harap hubungi pemiliknya menggunakan cara lain (misalnya secara langsung atau melalui panggilan telepon) dan tanyakan apakah kunci yang mereka lihat di Pengaturan Pengguna untuk sesi ini cocok dengan kunci di bawah ini:\n\n\tNama sesi: %@\n\tID sesi: %@\n\tKunci sesi: %@\n\nJika cocok, tekan tombol verifikasi di bawah. Jika tidak, maka orang lain mencegat sesi ini dan Anda mungkin ingin menekan tombol daftar hitam sebagai gantinya.\n\nDi masa yang mendatang proses verifikasi ini akan semakin canggih.";
+"room_event_encryption_verify_title" = "Verifikasi sesi\n\n";
+"room_event_encryption_info_unblock" = "Hilangkan dari daftar hitam";
+"room_event_encryption_info_block" = "Tambahkan ke daftar hitam";
+"room_event_encryption_info_unverify" = "Hilangkan verifikasi";
+"room_event_encryption_info_verify" = "Verifikasi...";
+"room_event_encryption_info_device_blocked" = "Di dalam daftar hitam";
+"room_event_encryption_info_device_not_verified" = "TIDAK terverifikasi";
+"room_event_encryption_info_device_verified" = "Terverifikasi";
+"room_event_encryption_info_device_fingerprint" = "Sidik jari Ed25519\n";
+"room_event_encryption_info_device_verification" = "Verifikasi\n";
+"room_event_encryption_info_device_id" = "ID\n";
+"room_event_encryption_info_device_name" = "Nama Publik\n";
+"room_event_encryption_info_device_unknown" = "sesi tidak dikenal\n";
+"room_event_encryption_info_device" = "\nInformasi sesi pengirim\n";
+"room_event_encryption_info_event_none" = "tidak ada";
+"room_event_encryption_info_event_unencrypted" = "tidak terenkripsi";
+"room_event_encryption_info_event_decryption_error" = "Kesalahan saat mendekripsi\n";
+"room_event_encryption_info_event_session_id" = "ID Sesi\n";
+"room_event_encryption_info_event_algorithm" = "Algoritma\n";
+"room_event_encryption_info_event_fingerprint_key" = "Mendapatkan kunci sidik jari Ed25519\n";
+"room_event_encryption_info_event_identity_key" = "Kunci identitas Curve25519\n";
+"room_event_encryption_info_event_user_id" = "ID Pengguna\n";
+"room_event_encryption_info_event" = "Informasi peristiwa\n";
+
+// Encryption information
+"room_event_encryption_info_title" = "Informasi enkripsi ujung-ke-ujung\n\n";
+"device_details_delete_prompt_message" = "Operasi ini membutuhkan otentikasi tambahan.\nUntuk melanjutkan, silakan masukkan kata sandi Anda.";
+"device_details_delete_prompt_title" = "Otentikasi";
+"device_details_rename_prompt_message" = "Nama publik sesi dapat dilihat oleh orang yang berkomunikasi dengan Anda";
+"device_details_rename_prompt_title" = "Nama Sesi";
+"device_details_last_seen_format" = "%@ @ %@\n";
+"device_details_last_seen" = "Terakhir dilihat\n";
+"device_details_identifier" = "ID\n";
+"device_details_name" = "Nama Publik\n";
+
+// Devices
+"device_details_title" = "Informasi sesi\n";
+"notification_settings_room_rule_title" = "Ruangan: '%@'";
+"settings_enter_validation_token_for" = "Masukkan token validasi untuk %@:";
+"settings_enable_push_notifications" = "Aktifkan notifikasi push";
+"settings_enable_inapp_notifications" = "Aktifkan notifikasi di dalam aplikasi";
+
+// Settings
+"settings" = "Pengaturan";
+"room_displayname_all_other_members_left" = "%@ (Keluar)";
+"room_displayname_more_than_two_members" = "%@ dan %@ lainnya";
+"room_displayname_two_members" = "%@ dan %@";
+
+// room display name
+"room_displayname_empty_room" = "Ruangan kosong";
+"notice_in_reply_to" = "Membalas ke";
+"notice_sticker" = "stiker";
+"notice_crypto_error_unknown_inbound_session_id" = "Sesi pengirim belum mengirim kami kunci untuk pesan ini.";
+"notice_crypto_unable_to_decrypt" = "** Tidak dapat mendekripsi: %@ **";
+"notice_room_history_visible_to_members_from_joined_point_for_dm" = "%@ membuat pesan di masa mendatang dapat dilihat oleh semuanya, sejak mereka bergabung.";
+"notice_room_history_visible_to_members_from_invited_point_for_dm" = "%@ membuat sejarah pesan di masa mendatang dapat dilihat oleh semuanya, sejak mereka diundang.";
+"notice_room_history_visible_to_members_from_joined_point" = "%@ membuat sejarah ruangan di masa mendatang dapat dilihat oleh semua anggota ruangan, sejak mereka bergabung.";
+"notice_room_history_visible_to_anyone" = "%@ membuat sejarah ruangan di masa mendatang dapat dilihat oleh siapa saja.";
+"notice_room_history_visible_to_members" = "%@ membuat sejarah ruangan di masa mendatang dapat dilihat oleh semua anggota ruangan.";
+"notice_room_history_visible_to_members_for_dm" = "%@ membuat semua pesan di masa mendatang dapat dilihat oleh semua anggota ruangan.";
+"notice_room_history_visible_to_members_from_invited_point" = "%@ membuat sejarah ruangan di masa mendatang dapat dilihat oleh semua anggota ruang, sejak mereka diundang.";
+"notice_error_unknown_event_type" = "Tipe peristiwa yang tidak dikenal";
+"notice_error_unexpected_event" = "Peristiwa yang tidak terduga";
+"notice_error_unsupported_event" = "Peristiwa yang tidak didukung";
+"notice_redaction" = "%@ menghapus sebuah peristiwa (id: %@)";
+"notice_feedback" = "Peristiwa umpan balik (id: %@): %@";
+"notice_unsupported_attachment" = "Lampiran yang tidak didukung: %@";
+"notice_invalid_attachment" = "lampiran tidak valid";
+"notice_file_attachment" = "lampiran file";
+"notice_location_attachment" = "lampiran lokasi";
+"notice_video_attachment" = "lampiran video";
+"notice_audio_attachment" = "lampiran audio";
+"notice_image_attachment" = "lampiran gambar";
+"notice_encryption_enabled_unknown_algorithm" = "%1$@ mengaktifkan enkripsi ujung-ke-ujung (algoritma %2$@ tidak dikenal).";
+"notice_encryption_enabled_ok" = "%@ mengaktifkan enkripsi ujung-ke-ujung.";
+"notice_encrypted_message" = "Pesan terenkripsi";
+"notice_room_related_groups" = "Grup yang terkait dengan ruangan ini adalah: %@";
+"notice_room_aliases_for_dm" = "Aliasnya adalah: %@";
+"notice_room_aliases" = "Alias ruangannya adalah: %@";
+"notice_room_power_level_event_requirement" = "Tingkat daya minimum yang terkait dengan peristiwa adalah:";
+"notice_room_power_level_acting_requirement" = "Tingkat daya minimum yang harus dimiliki pengguna sebelum bertindak adalah:";
diff --git a/Riot/Modules/MatrixKit/Assets/MatrixKitAssets.bundle/is.lproj/MatrixKit.strings b/Riot/Modules/MatrixKit/Assets/MatrixKitAssets.bundle/is.lproj/MatrixKit.strings
new file mode 100644
index 000000000..8b1378917
--- /dev/null
+++ b/Riot/Modules/MatrixKit/Assets/MatrixKitAssets.bundle/is.lproj/MatrixKit.strings
@@ -0,0 +1 @@
+
diff --git a/Riot/Modules/MatrixKit/Assets/MatrixKitAssets.bundle/it.lproj/MatrixKit.strings b/Riot/Modules/MatrixKit/Assets/MatrixKitAssets.bundle/it.lproj/MatrixKit.strings
new file mode 100644
index 000000000..630520ca6
--- /dev/null
+++ b/Riot/Modules/MatrixKit/Assets/MatrixKitAssets.bundle/it.lproj/MatrixKit.strings
@@ -0,0 +1,478 @@
+"matrix" = "Matrix";
+// Login Screen
+"login_create_account" = "Nuovo utente:";
+"login_server_url_placeholder" = "URL (es. https://matrix.org)";
+"login_home_server_title" = "URL homeserver:";
+"login_home_server_info" = "Lo storico delle conversazioni e i dati utente sono salvati sul tuo homeserver";
+"login_identity_server_title" = "URL del server d'identità:";
+"login_identity_server_info" = "Matrix fornisce dei server d'identità per associare i tuoi recapiti (es. l'indirizzo email) al tuo ID Matrix. Attualmente esiste solo il server https://matrix.org.";
+"login_user_id_placeholder" = "ID Matrix (es. @gianni:matrix.org o gianni)";
+"login_password_placeholder" = "Password";
+"login_optional_field" = "opzionale";
+"login_display_name_placeholder" = "Nome completo (es. Gianni Rossi)";
+"login_email_info" = "Indicare un indirizzo email consente ad altri utenti di trovarti facilmente in Matrix, e ti da modo di resettare la password in caso di necessità.";
+"login_email_placeholder" = "Indirizzo email";
+"login_prompt_email_token" = "Inserisci il token di validazione della tua email:";
+"login_error_title" = "Accesso fallito";
+"login_error_no_login_flow" = "Impossibile ottenere i dati di autenticazione da questo homeserver";
+"login_error_do_not_support_login_flows" = "Al momento non è supportato alcuno dei flussi di accesso definiti da questo homeserver";
+"login_error_registration_is_not_supported" = "La registrazione non è consentita al momento";
+"login_error_forbidden" = "Nome utente o password errati";
+"login_error_unknown_token" = "Il token di accesso inserito non è stato riconosciuto";
+"login_error_bad_json" = "JSON malformato";
+"login_error_not_json" = "Contenuto JSON non valido";
+"login_error_limit_exceeded" = "Limite di richieste superato";
+"login_error_user_in_use" = "Questo nome utente è già in uso";
+"login_error_login_email_not_yet" = "Il link inviato via email che non è stata ancora visitato";
+"login_use_fallback" = "Usa la pagina alternativa";
+"login_leave_fallback" = "Annulla";
+"login_invalid_param" = "Parametro non valido";
+"register_error_title" = "Registrazione fallita";
+"login_error_forgot_password_is_not_supported" = "Le password dimenticate non sono supportate";
+"notice_room_join_rule" = "Regole per accedere: %@";
+// contacts list screen
+"invitation_message" = "Vorrei comunicare con te usando Matrix. Visita il sito web http://matrix.org per avere maggiori informazioni.";
+"login_mobile_device" = "Mobile";
+"login_tablet_device" = "Tablet";
+"login_desktop_device" = "Desktop";
+"login_error_resource_limit_exceeded_title" = "Superato il limite delle risorse";
+"login_error_resource_limit_exceeded_message_default" = "Questo homeserver ha superato uno dei suoi limiti di risorsa.";
+"login_error_resource_limit_exceeded_message_monthly_active_user" = "Questo homeserver ha raggiunto il limite massimo di utenti attivi in un mese.";
+"login_error_resource_limit_exceeded_message_contact" = "\n\nContatta l’amministratore del sistema per continuare a usare questo servizio.";
+"login_error_resource_limit_exceeded_contact_button" = "Contatta l’amministratore";
+// Action
+"no" = "No";
+"yes" = "Si";
+"abort" = "Annulla";
+"back" = "Indietro";
+"close" = "Chiudi";
+"continue" = "Continua";
+"discard" = "Annulla";
+"dismiss" = "Annulla";
+"retry" = "Riprova";
+"sign_up" = "Registrati";
+"submit" = "Invia";
+"submit_code" = "Invia codice";
+"set_power_level" = "Imposta livello di accesso";
+"set_default_power_level" = "Ripristina livello di accesso";
+"set_moderator" = "Imposta moderatore";
+"set_admin" = "Imposta amministratore";
+"start_chat" = "Nuova chat";
+"start_voice_call" = "Nuova telefonata";
+"start_video_call" = "Nuova videochiamata";
+"mention" = "Menziona";
+"select_account" = "Seleziona un utente";
+"attach_media" = "Allega contenuto multimediale";
+"capture_media" = "Riprendi foto/video";
+"invite_user" = "Invita un utente Matrix";
+"reset_to_default" = "Ripristina predefinito";
+"resend_message" = "Reinvia messaggio";
+"select_all" = "Seleziona tutto";
+"cancel_upload" = "Annulla caricamento";
+"cancel_download" = "Annulla scaricamento";
+"show_details" = "Mostra dettagli";
+"answer_call" = "Rispondi a chiamata";
+"reject_call" = "Rifiuta chiamata";
+"end_call" = "Chiudi chiamata";
+"ignore" = "Ignora";
+"unignore" = "Non ignorare";
+// Events formatter
+"notice_avatar_changed_too" = "(anche l’avatar è cambiato)";
+"notice_room_name_removed" = "%@ ha cancellato il nome del canale";
+"notice_room_topic_removed" = "%@ ha cancellato il titolo";
+"notice_event_redacted" = "";
+"notice_event_redacted_by" = " da %@";
+"notice_event_redacted_reason" = " [motivo: %@]";
+"notice_profile_change_redacted" = "%@ ha aggiornato il suo profilo %@";
+"notice_room_created" = "%@ ha creato e configurato la stanza.";
+"notice_room_power_level_intro" = "Il livello di accesso dei partecipanti è:";
+"notice_room_power_level_acting_requirement" = "Il livello minimo di accesso per partecipare è:";
+"notice_room_power_level_event_requirement" = "Il livello minimo di accesso per visualizzare eventi è:";
+"notice_room_aliases" = "Gli alias di questo canale sono: %@";
+"notice_room_related_groups" = "I gruppi associati a questo canale sono: %@";
+"notice_encrypted_message" = "Messaggio criptato";
+"notice_encryption_enabled" = "%@ ha attivato la crittografia end-to-end (algoritmo %@)";
+"notice_image_attachment" = "allegato immagine";
+"notice_audio_attachment" = "allegato audio";
+"notice_video_attachment" = "allegato video";
+"notice_location_attachment" = "allegato posizione";
+"notice_file_attachment" = "allegato file";
+"notice_invalid_attachment" = "allegato non valido";
+"notice_unsupported_attachment" = "Allegato non supportato: %@";
+"notice_feedback" = "Evento di feedback (id: %@): %@";
+"notice_redaction" = "%@ ha modificato un evento (id: %@)";
+"notice_error_unsupported_event" = "Evento non supportato";
+"notice_error_unexpected_event" = "Evento inatteso";
+"notice_error_unknown_event_type" = "Tipo di evento sconosciuto";
+"notice_room_history_visible_to_anyone" = "%@ ha permesso a chiunque di visualizzare lo storico della conversazione.";
+"notice_room_history_visible_to_members" = "%@ ha permesso di visualizzare lo storico della conversazione solo ai partecipanti.";
+"notice_room_history_visible_to_members_from_invited_point" = "%@ ha permesso di visualizzare lo storico della conversazione solo ai partecipanti, dal momento del loro invito.";
+"notice_room_history_visible_to_members_from_joined_point" = "%@ ha permesso di visualizzare lo storico della conversazione solo ai partecipanti, dal momento della loro entrata.";
+"notice_crypto_unable_to_decrypt" = "** Impossibile decriptare: %@ **";
+"notice_crypto_error_unknown_inbound_session_id" = "La sessione del mittente non ci ha inviato le chiavi per questo messaggio.";
+"notice_sticker" = "etichetta";
+"notice_in_reply_to" = "In risposta a";
+// room display name
+"room_displayname_empty_room" = "Canale senza partecipanti";
+"room_displayname_two_members" = "%@ e %@";
+"room_displayname_more_than_two_members" = "%@ e %@ altri";
+// Settings
+"settings" = "Impostazioni";
+"settings_enable_inapp_notifications" = "Abilita notifiche In-App";
+"settings_enable_push_notifications" = "Abilita notifiche push";
+"settings_enter_validation_token_for" = "Inserisci token di validazione per %@:";
+"notification_settings_room_rule_title" = "Canale: '%@'";
+// Devices
+"device_details_title" = "Informazioni sessione\n";
+"device_details_name" = "Nome pubblico\n";
+"device_details_identifier" = "ID\n";
+"device_details_last_seen" = "Ultimo contatto\n";
+"device_details_last_seen_format" = "%@ @ %@\n";
+"device_details_rename_prompt_title" = "Nome sessione";
+"device_details_rename_prompt_message" = "Il nome pubblico della sessione è visibile alle persone con cui comunichi";
+"device_details_delete_prompt_title" = "Autenticazione";
+"device_details_delete_prompt_message" = "Questa operazione necessita di ulteriore autenticazione.\nInserisci la tua password per procedere.";
+// Encryption information
+"room_event_encryption_info_title" = "Informazioni crittografia End-to-end\n\n";
+"room_event_encryption_info_event" = "Informazioni evento\n";
+"room_event_encryption_info_event_user_id" = "ID utente:\n";
+"room_event_encryption_info_event_identity_key" = "Chiave di identificazione Curve25519\n";
+"room_event_encryption_info_event_fingerprint_key" = "Richiamata chiave per l’impronta Ed25519\n";
+"room_event_encryption_info_event_algorithm" = "Algoritmo\n";
+"room_event_encryption_info_event_session_id" = "ID sessione\n";
+"room_event_encryption_info_event_decryption_error" = "Errore decrittazione\n";
+"room_event_encryption_info_event_unencrypted" = "non criptato";
+"room_event_encryption_info_event_none" = "nessuna";
+"room_event_encryption_info_device" = "\nInformazioni sessione mittente\n";
+"room_event_encryption_info_device_unknown" = "sessione sconosciuta\n";
+"room_event_encryption_info_device_name" = "Nome pubblico\n";
+"room_event_encryption_info_device_id" = "ID\n";
+"room_event_encryption_info_device_verification" = "Verifica\n";
+"room_event_encryption_info_device_fingerprint" = "Impronta Ed25519\n";
+"room_event_encryption_info_device_verified" = "Verificato";
+"room_event_encryption_info_device_not_verified" = "NON verificato";
+"room_event_encryption_info_device_blocked" = "Bloccato";
+"room_event_encryption_info_verify" = "Verifica...";
+"room_event_encryption_info_unverify" = "Annulla verifica";
+"room_event_encryption_info_block" = "Bloccato";
+"room_event_encryption_info_unblock" = "Sbloccato";
+"room_event_encryption_verify_title" = "Verifica sessione\n\n";
+"room_event_encryption_verify_message" = "Per verificare che questa sessione possa essere fidata, si prega di contattare il suo proprietario in altro modo (es. di persona o via telefonica) e chiedergli se la chiave che lui vede nelle Impostazioni Utente per la sessione sia uguale a questa:\n\n\tNome sessione: %@\n\tID sessione: %@\n\tChiave sessione: %@\n\nSe le chiavi sono identiche, premere il pulsante di verifica qui sotto. Se non lo sono, allora quancun altro sta intercettando questa sessione e probabilmente dovresti bloccarlo.\n\nIn futuro questo processo di verifica sarà più sofisticato.";
+"room_event_encryption_verify_ok" = "Verifica";
+// Account
+"account_save_changes" = "Salva cambiamenti";
+"account_link_email" = "Collegamento email";
+"account_linked_emails" = "Indirizzi email collegati";
+"account_email_validation_title" = "Verifica in corso";
+"account_email_validation_message" = "Controlla l'email e clicca sul collegamento che contiene. Dopo averlo fatto, clicca su continua.";
+"account_email_validation_error" = "Impossibile verificare l'indirizzo email. Controlla l'email e clicca sul collegamento che contiene. Dopo averlo fatto, clicca su continua";
+"account_msisdn_validation_title" = "Verifica in corso";
+"account_msisdn_validation_message" = "Abbiamo inviato un SMS con un codice di attivazione. Inserisci il codice qui sotto.";
+"account_msisdn_validation_error" = "Impossibile verificare il numero di telefono.";
+"account_error_display_name_change_failed" = "Cambio del nome completo fallito";
+"account_error_picture_change_failed" = "Cambio dell'immagine fallito";
+"account_error_matrix_session_is_not_opened" = "La sessione Matrix non è attiva";
+"account_error_email_wrong_title" = "Indirizzo email non valido";
+"account_error_email_wrong_description" = "Questo non sembra essere un indirizzo email valido";
+"account_error_msisdn_wrong_title" = "Numero di telefono non valido";
+"account_error_msisdn_wrong_description" = "Questo non sembra essere un numero telefonico valido";
+"account_error_push_not_allowed" = "Notifiche non permesse";
+// Room creation
+"room_creation_name_title" = "Nome canale:";
+"room_creation_name_placeholder" = "(es. gruppoPranzo)";
+"room_creation_alias_title" = "Alias canale:";
+"room_creation_alias_placeholder" = "(es. #blah:dominio.it)";
+"room_creation_alias_placeholder_with_homeserver" = "(es. #blah%@)";
+"room_creation_participants_title" = "Partecipanti:";
+"room_creation_participants_placeholder" = "(e.g. @gianni:homeserver1; @alice:homeserver2...)";
+// Room
+"room_please_select" = "Seleziona un canale";
+"room_error_join_failed_title" = "Accesso al canale fallito";
+"room_error_join_failed_empty_room" = "Al momento non è possibile entrare in una stanza vuota.";
+"room_error_name_edition_not_authorized" = "Non sei autorizzato a modificare il nome di questo canale";
+"room_error_topic_edition_not_authorized" = "Non sei autorizzato a modificare l'argomento di questo canale";
+"room_error_cannot_load_timeline" = "Caricamento storico dei messaggi fallito";
+"room_error_timeline_event_not_found_title" = "Caricamento della posizione nello storico fallito";
+"room_error_timeline_event_not_found" = "L'applicazione ha cercato di caricare un punto specifico dello storico dei messaggi in questo canale, ma non è riuscita a trovarlo";
+"room_left" = "Sei uscito dalla stanza";
+"room_no_power_to_create_conference_call" = "Hai bisogno del permesso per invitare a iniziare una conferenza in questo canale";
+"room_no_conference_call_in_encrypted_rooms" = "Le chiamate in conferenza non sono supportate nei canali criptati";
+// Reply to message
+"message_reply_to_sender_sent_an_image" = "immagine inviata.";
+"message_reply_to_sender_sent_a_video" = "video inviato.";
+"message_reply_to_sender_sent_an_audio_file" = "file audio inviato.";
+"message_reply_to_sender_sent_a_file" = "file inviato.";
+"message_reply_to_message_to_reply_to_prefix" = "In risposta a";
+// Room members
+"room_member_ignore_prompt" = "Sei sicuro di voler nascondere tutti i messaggi da questo utente?";
+"room_member_power_level_prompt" = "Non potrai annullare questa modifica perché stai innalzando i permessi dell'utente al tuo stesso livello di accesso.\nSei sicuro?";
+// Attachment
+"attachment_size_prompt" = "Vuoi inviare come:";
+"attachment_original" = "Dim. effettiva (%@)";
+"attachment_small" = "Piccolo (~%@)";
+"attachment_medium" = "Medio (~%@)";
+"attachment_large" = "Grande (~%@)";
+"attachment_cancel_download" = "Interrompi scaricamento?";
+"attachment_cancel_upload" = "Interrompi caricamento?";
+"attachment_multiselection_size_prompt" = "Vuoi inviare le immagini come:";
+"attachment_multiselection_original" = "Originali";
+"attachment_e2e_keys_file_prompt" = "Questo file contiene le chiavi crittografiche esportate da un client Matrix.\nVuoi visualizzarlo o importare le chiavi che contiene?";
+"attachment_e2e_keys_import" = "Importa...";
+// Contacts
+"contact_mx_users" = "Utenti Matrix";
+"contact_local_contacts" = "Contatti locali";
+// Groups
+"group_invite_section" = "Inviti";
+"group_section" = "Gruppi";
+// Search
+"search_no_results" = "Nessun risultato";
+"search_searching" = "Ricerca in corso...";
+// Time
+"format_time_s" = "s";
+"format_time_m" = "m";
+"format_time_h" = "h";
+"format_time_d" = "d";
+// E2E import
+"e2e_import_room_keys" = "Importa le chiavi del canale";
+"e2e_import_prompt" = "Questo processo ti consente di importare le chiavi di crittografia che hai precedentemente esportato da un altro client Matrix. Sarai quindi in grado di decifrare gli stessi messaggi leggibili dall'altro client.\nIl file esportato è protetto da password. Devi inserirla qui per decifrare il file.";
+"e2e_import" = "Importa";
+"e2e_passphrase_enter" = "Inserisci password";
+// E2E export
+"e2e_export_room_keys" = "Esporta chiavi del canale";
+"e2e_export_prompt" = "Questo processo consente di esportare in un file locale le chiavi per leggere i messaggi ricevuti in canali criptati. Sarai quindi in grado di importare il file in un altro client Matrix, in modo da rendergli possibile decriptare quei messaggi in futuro.\nIl file esportato consentirà a chiunque di decriptare tutti i messaggi che puoi leggere, quindi dovresti tenerlo al sicuro.";
+"e2e_export" = "Esporta";
+"e2e_passphrase_confirm" = "Conferma password di accesso";
+"e2e_passphrase_empty" = "La password di accesso non può essere vuota";
+"e2e_passphrase_not_match" = "Le password di accesso devono corrispondere";
+"e2e_passphrase_create" = "Crea password di accesso";
+// Others
+"user_id_title" = "ID utente:";
+"offline" = "non in linea";
+"unsent" = "Non inviato";
+"error" = "Errore";
+"error_common_message" = "Si è verificato un errore. Riprova più tardi.";
+"not_supported_yet" = "Non ancora supportato";
+"default" = "predefinito";
+"private" = "Privato";
+"public" = "Pubblico";
+"power_level" = "Livello di accesso";
+"network_error_not_reachable" = "Verifica di essere connesso alla rete";
+"user_id_placeholder" = "es.: @gianni@homeserver";
+"ssl_homeserver_url" = "URL homeserver: %@";
+// Permissions
+"camera_access_not_granted_for_call" = "Le video chiamate necessitano di accesso alla videocamera ma %@ non ha il permesso di usarla";
+"microphone_access_not_granted_for_call" = "Le telefonate necessitano l'accesso al microfono, ma %@ non ha il permesso di usarlo";
+"local_contacts_access_not_granted" = "La ricerca degli utenti fra i contatti locali necessita l'accesso alla rubrica, ma %@ non ha il permesso di usarla";
+"local_contacts_access_discovery_warning_title" = "Ricerca utenti";
+"local_contacts_access_discovery_warning" = "Per scoprire chi fra i tuoi contatti sta già usando Matrix, %@ può inviare gli indirizzi email e i numeri telefonici della tua rubrica al tuo server d'identità Matrix. Se possibile, i dati personali vengono codificati prima di essere inviati - controlla la politica di riservatezza del tuo server d'identità per maggiori dettagli.";
+// Country picker
+"country_picker_title" = "Scegli un paese";
+// Language picker
+"language_picker_title" = "Scegli una lingua";
+"language_picker_default_language" = "Predefinito (%@)";
+"notice_room_invite" = "%@ invitato %@";
+"notice_room_third_party_invite" = "%@ ha invitato %@ a unirsi al canale";
+"notice_room_third_party_registered_invite" = "%@ ha accettato l'invito per %@";
+"notice_room_third_party_revoked_invite" = "%@ ha ritirato l'invito per %@ a unirsi al canale";
+"notice_room_join" = "%@ si è unito al canale";
+"notice_room_leave" = "%@ ha lasciato il canale";
+"notice_room_reject" = "%@ ha rifiutato l'invito";
+"notice_room_kick" = "%@ ha espulso %@";
+"notice_room_unban" = "%@ ha revocato il divieto di accesso a %@";
+"notice_room_ban" = "%@ ha vietato l'accesso a %@";
+"notice_room_withdraw" = "%@ ha ritirato l'invito di %@";
+"notice_room_reason" = ". Motivo: %@";
+"notice_avatar_url_changed" = "%@ ha modificato la sua immagine";
+"notice_display_name_set" = "%@ ha impostato il suo nome in %@";
+"notice_display_name_changed_from" = "%@ ha cambiato il suo nome da %@ a %@";
+"notice_display_name_removed" = "%@ ha rimosso il suo nome";
+"notice_topic_changed" = "%@ ha cambiato l'argomento in \"%@\".";
+"notice_room_name_changed" = "%@ ha cambiato il nome della stanza in %@.";
+"notice_placed_voice_call" = "%@ ha effettuato una telefonata";
+"notice_placed_video_call" = "%@ ha iniziato una videochiamata";
+"notice_answered_video_call" = "%@ ha risposto alla chiamata";
+"notice_ended_video_call" = "%@ ha terminato la chiamata";
+"notice_conference_call_request" = "%@ ha richiesto una conferenza VoIP";
+"notice_conference_call_started" = "Conferenza VoIP iniziata";
+"notice_conference_call_finished" = "Conferenza VoIP terminata";
+// button names
+"ok" = "OK";
+"cancel" = "Annulla";
+"save" = "Salva";
+"leave" = "Esci";
+"send" = "Invia";
+"copy_button_name" = "Copia";
+"resend" = "Invia di nuovo";
+"redact" = "Cancella";
+"share" = "Condividi";
+"delete" = "Elimina";
+"view" = "Visualizza";
+// actions
+"action_logout" = "Esci";
+"create_room" = "Crea un canale";
+"login" = "Entra";
+"create_account" = "Crea utente";
+"membership_invite" = "Invitati";
+"membership_leave" = "Uscito";
+"membership_ban" = "Espulso";
+"num_members_one" = "%@ utente";
+"num_members_other" = "%@ utenti";
+"invite" = "Invita";
+"kick" = "Espelli";
+"ban" = "Vieta accesso";
+"unban" = "Consenti accesso";
+"message_unsaved_changes" = "Ci sono modifiche non salvate. Uscendo saranno perse.";
+// Login Screen
+"login_error_already_logged_in" = "Accesso già effettuato";
+"login_error_must_start_http" = "l'URL deve iniziare con http[s]://";
+// room details dialog screen
+"room_details_title" = "Dettagli stanza";
+// Settings screen
+"settings_title_config" = "Configurazione";
+"settings_title_notifications" = "Notifiche";
+// Notification settings screen
+"notification_settings_disable_all" = "Disabilita tutte le notifiche";
+"notification_settings_enable_notifications" = "Abilita le notifiche";
+"notification_settings_enable_notifications_warning" = "Le notifiche sono al momento disabilitate per tutti i dispositivi.";
+"notification_settings_global_info" = "Le impostazioni di notifica sono salvate nel tuo account e condivise fra i client che le supportano (incluse le notifiche del desktop)\n\nLe regole si applicano in ordine; la prima regola che corrisponde definisce l'esito del messaggio.\nQuindi: Le notifiche per-parola hanno la precedenza sulle notifiche per-canale, che precedono quelle per-mittente.\nSe esistono diverse regole dello stesso tipo, viene applicata la prima della lista.";
+"notification_settings_per_word_notifications" = "Notifiche per-parola";
+"notification_settings_per_word_info" = "Le parole sono confrontate senza tenere conto dei caratteri maiuscoli/minuscoli, e possono includere asterischi. Quindi:\nblah corrisponde alla stringa blah con qualsiasi delimitatore di parola (es. segni di punteggiatura e spazi o segni di accapo).\nblah* corrisponde a qualsiasi parola inizi con blah.\n*blah* corrisponde a qualsiasi parola contenga le 4 lettere blah.";
+"notification_settings_always_notify" = "Notifica sempre";
+"notification_settings_never_notify" = "Non notificare mai";
+"notification_settings_word_to_match" = "parola da cercare";
+"notification_settings_highlight" = "Evidenziare";
+"notification_settings_custom_sound" = "Suono personalizzato";
+"notification_settings_per_room_notifications" = "Notifiche per-canale";
+"notification_settings_per_sender_notifications" = "Notifiche per-mittente";
+"notification_settings_sender_hint" = "@utente:dominio.it";
+"notification_settings_select_room" = "Seleziona un canale";
+"notification_settings_other_alerts" = "Altri avvisi";
+"notification_settings_contain_my_user_name" = "Notifica con un suono i messaggi che contengono il mio nome utente";
+"notification_settings_contain_my_display_name" = "Notifica con un suono i messaggi che contengono il mio nome completo";
+"notification_settings_just_sent_to_me" = "Notifica con un suono i messaggi inviati solo a me";
+"notification_settings_invite_to_a_new_room" = "Notifica quando sono invitato in un nuovo canale";
+"notification_settings_people_join_leave_rooms" = "Notifica quando gli utenti entrano o escono dai canali";
+"notification_settings_receive_a_call" = "Notifica quando ricevo una chiamata";
+"notification_settings_suppress_from_bots" = "Sopprimi le notifiche dai bot";
+"notification_settings_by_default" = "Come predefinito...";
+"notification_settings_notify_all_other" = "Notifica tutti gli altri messaggi/canali";
+// gcm section
+"settings_config_home_server" = "Homeserver: %@";
+"settings_config_identity_server" = "Server d'identità: %@";
+"settings_config_user_id" = "ID utente: %@";
+// call string
+"call_waiting" = "Attendere...";
+"call_connecting" = "In connessione…";
+"call_ended" = "Chiamata terminata";
+"call_ring" = "Chiamata in corso...";
+"incoming_video_call" = "Videochiamata in arrivo";
+"incoming_voice_call" = "Telefonata in arrivo";
+"call_invite_expired" = "Tempo di chiamata scaduto";
+// unrecognized SSL certificate
+"ssl_trust" = "Fidati";
+"ssl_logout_account" = "Esci";
+"ssl_remain_offline" = "Ignora";
+"ssl_fingerprint_hash" = "Impronta (%@):";
+"ssl_could_not_verify" = "Impossibile verificare l'identità del server remoto.";
+"ssl_cert_not_trust" = "Potrebbe voler dire che qualcuno sta intercettando il tuo traffico, o che il tuo telefono non si fida del certificato offerto dal server remoto.";
+"ssl_cert_new_account_expl" = "Se l'amministratore del server ti ha detto che questo sarebbe successo, accertati che l'impronta qui sotto corrisponda a quella che lui ti ha fornito.";
+"ssl_unexpected_existing_expl" = "Il certificato è diverso da quello di cui il tuo telefono si fidava. Questo è un COMPORTAMENTO ANOMALO. Si consiglia di NON ACCETTARE questo nuovo certificato.";
+"ssl_expected_existing_expl" = "Il certificato è cambiato da uno precedentemente accettato a uno che non è fidato. Il server potrebbe averlo rinnovato. Contatta l'amministratore del server per verificarne l'impronta.";
+"ssl_only_accept" = "Accetta il certificato SOLAMENTE se l'amministratore del server ha pubblicato un'impronta che corrisponde a quella qui sopra.";
+"notice_encryption_enabled_ok" = "%@ ha attivato la crittografia end-to-end.";
+"notice_encryption_enabled_unknown_algorithm" = "%1$@ ha attivato la crittografia end-to-end (algoritmo %2$@ non riconosciuto).";
+// Notice Events with "You"
+"notice_room_invite_by_you" = "Hai invitato %@";
+"notice_room_invite_you" = "%@ ti ha invitato";
+"notice_room_third_party_invite_by_you" = "Hai mandato un invito a %@ a unirsi alla stanza";
+"notice_room_third_party_registered_invite_by_you" = "Hai accettato l'invito per %@";
+"notice_room_third_party_revoked_invite_by_you" = "Hai revocato l'invito per %@ a unirsi alla stanza";
+"notice_room_join_by_you" = "Sei entrato";
+"notice_room_leave_by_you" = "Sei uscito";
+"notice_room_reject_by_you" = "Hai rifiutato l'invito";
+"notice_room_kick_by_you" = "Hai buttato fuori %@";
+"notice_room_unban_by_you" = "Hai riammesso %@";
+"notice_room_ban_by_you" = "Hai bandito %@";
+"notice_room_withdraw_by_you" = "Hai ritirato l'invito di %@";
+"notice_avatar_url_changed_by_you" = "Hai cambiato il tuo avatar";
+"notice_display_name_set_by_you" = "Hai impostato il tuo nome visualizzato a %@";
+"notice_display_name_changed_from_by_you" = "Hai cambiato il tuo nome visualizzato da %@ a %@";
+"notice_display_name_removed_by_you" = "Hai rimosso il tuo nome visualizzato";
+"notice_topic_changed_by_you" = "Hai cambiato l'argomento in \"%@\".";
+"notice_room_name_changed_by_you" = "Hai cambiato il nome della stanza in %@.";
+"notice_placed_voice_call_by_you" = "Hai iniziato una telefonata";
+"notice_placed_video_call_by_you" = "Hai iniziato una videochiamata";
+"notice_answered_video_call_by_you" = "Hai risposto alla chiamata";
+"notice_ended_video_call_by_you" = "Hai terminato la chiamata";
+"notice_conference_call_request_by_you" = "Hai richiesto una conferenza VoIP";
+"notice_room_name_removed_by_you" = "Hai rimosso il nome della stanza";
+"notice_room_topic_removed_by_you" = "Hai rimosso l'argomento";
+"notice_event_redacted_by_you" = " da te";
+"notice_profile_change_redacted_by_you" = "Hai aggiornato il tuo profilo %@";
+"notice_room_created_by_you" = "Hai creato e configurato la stanza.";
+"notice_encryption_enabled_ok_by_you" = "Hai attivato la crittografia end-to-end.";
+"notice_encryption_enabled_unknown_algorithm_by_you" = "Hai attivato la crittografia end-to-end (algoritmo %@ sconosciuto).";
+"notice_redaction_by_you" = "Hai corretto un evento (id: %@)";
+"notice_room_history_visible_to_anyone_by_you" = "Hai reso visibile a chiunque la cronologia futura della stanza.";
+"notice_room_history_visible_to_members_by_you" = "Hai reso visibile a tutti i membri della stanza la cronologia futura della stanza.";
+"notice_room_history_visible_to_members_from_invited_point_by_you" = "Hai reso visibile a tutti i membri della stanza la cronologia futura della stanza, dal momento del loro invito.";
+"notice_room_history_visible_to_members_from_joined_point_by_you" = "Hai reso visibile a tutti i membri della stanza la cronologia futura della stanza, dal momento della loro entrata.";
+// New
+"notice_room_join_rule_invite" = "%@ ha reso la stanza solo su invito.";
+"notice_room_join_rule_invite_by_you" = "Hai reso la stanza solo su invito.";
+"notice_room_join_rule_public" = "%@ ha reso la stanza pubblica.";
+"notice_room_join_rule_public_by_you" = "Hai reso la stanza pubblica.";
+"notice_room_name_removed_for_dm" = "%@ ha rimosso il nome";
+"notice_room_created_for_dm" = "%@ è entrato.";
+"notice_room_join_rule_invite_for_dm" = "%@ l'ha resa solo su invito.";
+"notice_room_join_rule_invite_by_you_for_dm" = "L'hai resa solo su invito.";
+"notice_room_join_rule_public_for_dm" = "%@ l'ha resa pubblica.";
+"notice_room_join_rule_public_by_you_for_dm" = "L'hai resa pubblica.";
+"notice_room_power_level_intro_for_dm" = "Il livello di accesso dei partecipanti è:";
+"notice_room_aliases_for_dm" = "Gli alias sono: %@";
+"notice_room_history_visible_to_members_for_dm" = "%@ ha reso visibili i messaggi futuri a tutti i membri della stanza.";
+"notice_room_history_visible_to_members_from_invited_point_for_dm" = "%@ ha reso visibili i messaggi futuri a chiunque, dal momento dell'invito.";
+"notice_room_history_visible_to_members_from_joined_point_for_dm" = "%@ ha reso visibili i messaggi futuri a chiunque, dal momento dell'entrata.";
+"room_left_for_dm" = "Sei uscito";
+"notice_room_third_party_invite_for_dm" = "%@ ha invitato %@";
+"notice_room_third_party_revoked_invite_for_dm" = "%@ ha revocato l'invito per %@";
+"notice_room_name_changed_for_dm" = "%@ ha cambiato il nome in %@.";
+"notice_room_third_party_invite_by_you_for_dm" = "Hai invitato %@";
+"notice_room_third_party_revoked_invite_by_you_for_dm" = "Hai revocato l'invito per %@";
+"notice_room_name_changed_by_you_for_dm" = "Hai cambiato il nome in %@.";
+"notice_room_name_removed_by_you_for_dm" = "Hai rimosso il nome";
+"notice_room_created_by_you_for_dm" = "Sei entrato.";
+"notice_room_history_visible_to_members_by_you_for_dm" = "Hai reso visibili i messaggi futuri a tutti i membri della stanza.";
+"notice_room_history_visible_to_members_from_invited_point_by_you_for_dm" = "Hai reso visibili i messaggi futuri a chiunque, dal momento dell'invito.";
+"notice_room_history_visible_to_members_from_joined_point_by_you_for_dm" = "Hai reso visibili i messaggi futuri a chiunque, dal momento dell'entrata.";
+"call_more_actions_dialpad" = "Tastierino numerico";
+"call_more_actions_transfer" = "Trasferisci";
+"call_more_actions_audio_use_device" = "Altoparlante dispositivo";
+"call_more_actions_audio_use_headset" = "Usa audio da cuffie";
+"call_more_actions_change_audio_device" = "Cambia dispositivo audio";
+"call_more_actions_unhold" = "Riprendi";
+"call_more_actions_hold" = "In attesa";
+"call_holded" = "Hai messo la chiamata in attesa";
+"call_remote_holded" = "%@ ha messo la chiamata in attesa";
+"notice_declined_video_call_by_you" = "Hai rifiutato la chiamata";
+"notice_declined_video_call" = "%@ ha rifiutato la chiamata";
+"resume_call" = "Riprendi";
+"call_transfer_to_user" = "Trasferisci a %@";
+"call_consulting_with_user" = "Consultazione con %@";
+"call_video_with_user" = "Videochiamata con %@";
+"call_voice_with_user" = "Telefonata con %@";
+"call_ringing" = "Sta squillando…";
+"e2e_passphrase_too_short" = "Password troppo corta (deve avere almeno %d caratteri)";
+"microphone_access_not_granted_for_voice_message" = "I messaggi vocali hanno bisogno dell'accesso al microfono, ma %@ non ha il permesso di usarlo";
+"message_reply_to_sender_sent_a_voice_message" = "inviato un messaggio vocale.";
+"attachment_large_with_resolution" = "Grande %@ (~%@)";
+"attachment_medium_with_resolution" = "Medio %@ (~%@)";
+"attachment_small_with_resolution" = "Piccolo %@ (~%@)";
+"attachment_size_prompt_message" = "Puoi disattivarlo nelle impostazioni.";
+"attachment_size_prompt_title" = "Conferma dimensione da inviare";
+"auth_reset_password_error_not_found" = "Non trovato";
+"auth_reset_password_error_unauthorized" = "Non autorizzato";
+"auth_invalid_user_name" = "Nome utente non valido";
+"room_displayname_all_other_members_left" = "%@ (Uscito)";
+"auth_username_in_use" = "Nome utente in uso";
+"rename" = "Rinomina";
diff --git a/Riot/Modules/MatrixKit/Assets/MatrixKitAssets.bundle/ja.lproj/MatrixKit.strings b/Riot/Modules/MatrixKit/Assets/MatrixKitAssets.bundle/ja.lproj/MatrixKit.strings
new file mode 100644
index 000000000..c4918a35e
--- /dev/null
+++ b/Riot/Modules/MatrixKit/Assets/MatrixKitAssets.bundle/ja.lproj/MatrixKit.strings
@@ -0,0 +1,409 @@
+"matrix" = "Matrix";
+// Login Screen
+"login_create_account" = "アカウント作成:";
+"login_server_url_placeholder" = "URL (例 https://matrix.org)";
+"login_home_server_title" = "接続先サーバURL:";
+"login_home_server_info" = "あなたの接続先サーバは、あなたの全ての会話とアカウント情報を保存します";
+"login_identity_server_title" = "認証サーバURL:";
+"login_password_placeholder" = "パスワード";
+"login_email_placeholder" = "メールアドレス";
+// Action
+"no" = "いいえ";
+"yes" = "はい";
+"back" = "戻る";
+"close" = "閉じる";
+"continue" = "続く";
+"sign_up" = "登録";
+"resend_message" = "メッセージを再送信する";
+"select_all" = "全て選択";
+"show_details" = "詳細を表示する";
+"login_identity_server_info" = "Matrixは、どの電子メールなどがどのMatrix IDに属しているかを追跡するアイデンティティサーバーを提供します。 現在 https://matrix.org のみが存在します。";
+"login_user_id_placeholder" = "Matrix ID(例 @bob:matrix.org または bob)";
+"login_optional_field" = "オプション";
+"login_display_name_placeholder" = "表示名 (例 Bob Obson)";
+"login_email_info" = "メールアドレスを指定すると、他のユーザーがあなたをMatrixで簡単に見つけることができ、今後パスワードをリセットすることができます。";
+"login_prompt_email_token" = "メールの検証トークンを入力してください:";
+"login_error_title" = "ログインに失敗しました";
+"login_error_no_login_flow" = "このホームサーバーから認証情報を取得できませんでした";
+"login_error_do_not_support_login_flows" = "現在、このホームサーバーによって定義されたログインフローの一部またはすべてをサポートしていません";
+"login_error_registration_is_not_supported" = "登録は現在サポートされていません";
+"login_error_forbidden" = "無効なユーザー名/パスワード";
+"login_error_unknown_token" = "指定されたアクセストークンが認識されませんでした";
+"login_error_bad_json" = "不正な形式のJSON";
+"login_error_not_json" = "有効なJSONを含んでいませんでした";
+"login_error_limit_exceeded" = "あまりにも多くのリクエストが送られました";
+"login_error_user_in_use" = "このユーザー名はすでに使用されています";
+"login_error_login_email_not_yet" = "まだクリックされていないメールリンク";
+"login_use_fallback" = "フォールバックページを使用する";
+"login_leave_fallback" = "キャンセル";
+"login_invalid_param" = "無効なパラメーター";
+"register_error_title" = "登録に失敗しました";
+"login_error_forgot_password_is_not_supported" = "Forgot passwordは現在サポートされていません";
+"login_mobile_device" = "携帯";
+"login_tablet_device" = "タブレット";
+"login_desktop_device" = "デスクトップ";
+"login_error_resource_limit_exceeded_title" = "リソース制限を超えました";
+"login_error_resource_limit_exceeded_message_default" = "このホームサーバーは、リソース制限の1つを超えています。";
+"login_error_resource_limit_exceeded_message_monthly_active_user" = "このホームサーバーは、月間アクティブユーザー制限を超えています。";
+"login_error_resource_limit_exceeded_message_contact" = "\n\nこのサービスを続行するには、サービス管理者に連絡してください。";
+"login_error_resource_limit_exceeded_contact_button" = "管理者に連絡する";
+"abort" = "中断する";
+"discard" = "破棄";
+"dismiss" = "却下する";
+"retry" = "再試行";
+"submit" = "提出";
+"submit_code" = "コードを送信";
+"set_default_power_level" = "権限レベルをリセット";
+"set_moderator" = "モデレーターを設定";
+"set_admin" = "管理者を設定";
+"start_chat" = "チャットを始める";
+"start_voice_call" = "音声通話を開始";
+"start_video_call" = "ビデオ通話を開始";
+"mention" = "記載";
+"select_account" = "アカウントを選択";
+"attach_media" = "ライブラリからメディアを添付";
+"capture_media" = "写真/ビデオを撮る";
+"invite_user" = "matrixユーザーを招待";
+"reset_to_default" = "デフォルトにリセット";
+"cancel_upload" = "アップロードをキャンセル";
+"cancel_download" = "ダウンロードをキャンセル";
+"answer_call" = "通話に応答する";
+"reject_call" = "通話を拒否する";
+"end_call" = "通話終了";
+"ignore" = "無視";
+// Events formatter
+"notice_avatar_changed_too" = "(アバターも変わった)";
+"notice_room_name_removed" = "%@は部屋名を削除しました";
+"notice_room_topic_removed" = "%@はトピックを削除しました";
+"notice_event_redacted" = "<編集された%@>";
+"notice_event_redacted_by" = " %@により";
+"notice_event_redacted_reason" = " [理由: %@]";
+"notice_profile_change_redacted" = "%@は彼らのプロフィール %@を更新しました";
+"notice_room_created" = "%@は部屋を作成しました";
+"notice_room_join_rule" = "結合ルールは次のとおり: %@";
+"notice_room_power_level_intro" = "ルームメンバーの権限レベル:";
+"notice_room_power_level_acting_requirement" = "アクション前にユーザーの必要な最小権限レベル:";
+"notice_room_power_level_event_requirement" = "イベントに関連する最小権限レベル:";
+"notice_room_aliases" = "ルームエイリアス: %@";
+"notice_room_related_groups" = "この部屋に関連付けられたグループ: %@";
+"notice_encrypted_message" = "暗号化されたメッセージ";
+"notice_encryption_enabled" = "%@はエンドツーエンド暗号化を有効にする (アルゴリズム %@)";
+"notice_image_attachment" = "画像添付";
+"notice_audio_attachment" = "音声添付";
+"notice_video_attachment" = "動画添付";
+"notice_location_attachment" = "位置情報添付";
+"notice_file_attachment" = "ファイル添付";
+"notice_invalid_attachment" = "無効な添付";
+"notice_unsupported_attachment" = "サポートされていない添付: %@";
+"notice_feedback" = "フィードバックイベント (id: %@): %@";
+"notice_redaction" = "%@はイベントを編集しました (id: %@)";
+"notice_error_unsupported_event" = "サポートされていないイベント";
+"notice_error_unexpected_event" = "予期しないイベント";
+"notice_error_unknown_event_type" = "不明なイベントタイプ";
+"notice_room_history_visible_to_anyone" = "%@ 誰でも将来の部屋履歴を表示されます。";
+"notice_room_history_visible_to_members" = "%@ ルームメンバー全員に将来の部屋履歴を表示されます。";
+"notice_room_history_visible_to_members_from_invited_point" = "%@ ルームメンバー全員に招待した時点からの部屋履歴を表示されます。";
+"notice_room_history_visible_to_members_from_joined_point" = "%@ ルームメンバー全員に参加した時点からの部屋履歴を表示されます。";
+"notice_crypto_unable_to_decrypt" = "** 復号化できません: %@ **";
+"notice_crypto_error_unknown_inbound_session_id" = "送信者のセッションからこのメッセージのキーが送信されていません。";
+"notice_sticker" = "ステッカー";
+"notice_in_reply_to" = "に返信";
+// room display name
+"room_displayname_empty_room" = "空の部屋";
+"room_displayname_two_members" = "%@ と %@";
+"room_displayname_more_than_two_members" = "%@ と %@ 他";
+// Settings
+"settings" = "設定";
+"settings_enable_inapp_notifications" = "アプリ内通知を有効にする";
+"settings_enable_push_notifications" = "プッシュ通知を有効にする";
+"settings_enter_validation_token_for" = "%@の検証トークンを入力:";
+"notification_settings_room_rule_title" = "部屋: '%@'";
+// Devices
+"device_details_title" = "セッション情報\n";
+"device_details_name" = "名前\n";
+"device_details_identifier" = "ID\n";
+"device_details_last_seen" = "ラストシーン\n";
+"device_details_last_seen_format" = "%@ @ %@\n";
+"device_details_rename_prompt_message" = "端末名:";
+"device_details_delete_prompt_title" = "認証";
+"device_details_delete_prompt_message" = "この操作には、追加の認証が必要です。\n続行するには、パスワードを入力してください。";
+// Encryption information
+"room_event_encryption_info_title" = "エンドツーエンド暗号化情報\n\n";
+"room_event_encryption_info_event" = "イベント情報\n";
+"room_event_encryption_info_event_user_id" = "ユーザーID\n";
+"room_event_encryption_info_event_identity_key" = "Curve25519 identity key\n";
+"room_event_encryption_info_event_fingerprint_key" = "クレームされたEd25519指紋キー\n";
+"room_event_encryption_info_event_algorithm" = "アルゴリズム\n";
+"room_event_encryption_info_event_session_id" = "セッションID\n";
+"room_event_encryption_info_event_decryption_error" = "復号化エラー\n";
+"room_event_encryption_info_event_unencrypted" = "暗号化されていない";
+"room_event_encryption_info_event_none" = "なし";
+"room_event_encryption_info_device" = "\n送信者セッション情報\n";
+"room_event_encryption_info_device_unknown" = "未知のセッション\n";
+"room_event_encryption_info_device_name" = "名前\n";
+"room_event_encryption_info_device_id" = "ID\n";
+"room_event_encryption_info_device_verification" = "検証\n";
+"room_event_encryption_info_device_fingerprint" = "Ed25519 fingerprint\n";
+"room_event_encryption_info_device_verified" = "検証済み";
+"room_event_encryption_info_device_not_verified" = "検証されていない";
+"room_event_encryption_info_device_blocked" = "ブラックリストに載せた";
+"room_event_encryption_info_verify" = "検証中...";
+"room_event_encryption_info_unverify" = "未検証";
+"room_event_encryption_info_block" = "ブラックリスト";
+"room_event_encryption_info_unblock" = "ブラックでないリスト";
+"room_event_encryption_verify_title" = "セッション検証\n\n";
+"room_event_encryption_verify_message" = "このセッションが信頼できることを確認するには、他の方法(個人や電話など)で所有者に連絡し、セッションのユーザー設定で表示される鍵が以下のキーと一致するかどうかを尋ねます。\n\nセッション名: %@\nセッションID: %@\nセッションkey: %@\n\n一致する場合は、下の確認ボタンを押します。 それ以外の人がこのセッションを傍受している場合は、代わりにブラックリストボタンを押してください。\n\n将来この検証プロセスはより洗練されたものになるでしょう。";
+"room_event_encryption_verify_ok" = "検証";
+// Account
+"account_save_changes" = "変更を保存";
+"account_link_email" = "リンクメール";
+"account_linked_emails" = "リンクされたメール";
+"account_email_validation_title" = "検証保留中";
+"account_email_validation_message" = "メールをチェックし、それに含まれているリンクをクリックしてください。 これが完了したら、[続行]をクリックします。";
+"account_email_validation_error" = "メールアドレスを確認できません。 あなたのメールをチェックし、それに含まれているリンクをクリックしてください。 これが完了したら、[続行]をクリックします";
+"account_msisdn_validation_title" = "検証保留中";
+"account_msisdn_validation_message" = "アクティベーションコード付きのSMSを送信しました。 以下にこのコードを入力してください。";
+"account_msisdn_validation_error" = "電話番号を確認できません。";
+"account_error_display_name_change_failed" = "表示名の変更に失敗しました";
+"account_error_picture_change_failed" = "画像の変更に失敗しました";
+"account_error_matrix_session_is_not_opened" = "Matrixセッションが開かれていません";
+"account_error_email_wrong_title" = "無効な電子メールアドレス";
+"account_error_email_wrong_description" = "これは有効なメールアドレスではないようです";
+"account_error_msisdn_wrong_title" = "無効な電話番号";
+"account_error_msisdn_wrong_description" = "これは有効な電話番号ではないようです";
+// Room creation
+"room_creation_name_title" = "部屋名:";
+"room_creation_name_placeholder" = "(例 ランチグループ)";
+"room_creation_alias_title" = "部屋の別名:";
+"room_creation_alias_placeholder" = "(例 #foo:example.org)";
+"room_creation_alias_placeholder_with_homeserver" = "(例 #foo%@)";
+"room_creation_participants_title" = "参加者:";
+"room_creation_participants_placeholder" = "(例 @bob:homeserver1; @john:homeserver2...)";
+// Room
+"room_please_select" = "部屋を選択してください";
+"room_error_join_failed_title" = "部屋に参加できませんでした";
+"room_error_join_failed_empty_room" = "現在空の部屋に再参加することはできません。";
+"room_error_name_edition_not_authorized" = "この部屋の名前を編集する権限がありません";
+"room_error_topic_edition_not_authorized" = "この部屋のトピックを編集する権限がありません";
+"room_error_cannot_load_timeline" = "タイムラインの読み込みに失敗しました";
+"room_error_timeline_event_not_found_title" = "タイムラインの位置を読み込めませんでした";
+"room_error_timeline_event_not_found" = "アプリケーションがこのルームのタイムラインに特定のポイントをロードしようとしましたが、それを見つけることができませんでした";
+"room_left" = "あなたは部屋を出ました";
+"room_no_power_to_create_conference_call" = "この部屋で会議を開始するために招待する権限が必要です";
+"room_no_conference_call_in_encrypted_rooms" = "暗号化された会議室では会議通話はサポートされません";
+// Reply to message
+"message_reply_to_sender_sent_an_image" = "画像を送信しました。";
+"message_reply_to_sender_sent_a_video" = "動画を送りました。";
+"message_reply_to_sender_sent_an_audio_file" = "オーディオファイルを送信しました。";
+"message_reply_to_sender_sent_a_file" = "ファイルを送信しました。";
+"message_reply_to_message_to_reply_to_prefix" = "に返信";
+// Room members
+"room_member_ignore_prompt" = "このユーザーからのすべてのメッセージを非表示にしますか?";
+"room_member_power_level_prompt" = "この変更を元に戻すことはできません。ユーザーが自分と同じレベルの権限を持つように促しますが、よろしいですか?";
+// Attachment
+"attachment_size_prompt" = "次のように送信しますか:";
+"attachment_original" = "実際のサイズ: %@";
+"attachment_small" = "小: %@";
+"attachment_medium" = "中: %@";
+"attachment_large" = "大: %@";
+"attachment_cancel_download" = "ダウンロードをキャンセルしますか?";
+"attachment_cancel_upload" = "アップロードをキャンセルしますか?";
+"attachment_multiselection_size_prompt" = "画像を次のように送信しますか:";
+"attachment_multiselection_original" = "実際のサイズ";
+"attachment_e2e_keys_file_prompt" = "このファイルには、Matrixクライアントからエクスポートされた暗号化キーが含まれています。\nファイルの内容を表示するか、ファイルの内容をインポートしますか?";
+"attachment_e2e_keys_import" = "インポート...";
+// Contacts
+"contact_mx_users" = "Matrixユーザー";
+"contact_local_contacts" = "ローカルの連絡先";
+// Groups
+"group_invite_section" = "招待";
+"group_section" = "グループ";
+// Search
+"search_no_results" = "結果がありません";
+"search_searching" = "検索中...";
+// Time
+"format_time_s" = "秒";
+"format_time_m" = "分";
+"format_time_h" = "時";
+"format_time_d" = "日";
+// E2E import
+"e2e_import_room_keys" = "ルームキーをインポート";
+"e2e_import_prompt" = "このプロセスでは、以前に別のMatrixクライアントからエクスポートした暗号化キーをインポートできます。 これにより、他のクライアントが解読できるすべてのメッセージを解読することができます。\nエクスポートファイルはパスフレーズで保護されています。 ファイルを解読するには、パスフレーズをここに入力する必要があります。";
+"e2e_import" = "インポート";
+"e2e_passphrase_enter" = "パスフレーズを入力";
+// E2E export
+"e2e_export_room_keys" = "ルームキーのエクスポート";
+"e2e_export_prompt" = "このプロセスでは、暗号化されたルームで受信したメッセージのキーをローカルファイルにエクスポートできます。 その後、クライアントがこれらのメッセージを復号化できるように、ファイルを別のMatrixクライアントにインポートすることができます。\nエクスポートされたファイルは、誰でも閲覧できる暗号化されたメッセージを復号化することができるので、安全に保つように注意する必要があります。";
+"e2e_export" = "エクスポート";
+"e2e_passphrase_confirm" = "パスフレーズを確認";
+"e2e_passphrase_empty" = "パスフレーズは空であってはいけません";
+"e2e_passphrase_not_match" = "パスフレーズは一致する必要があります";
+"e2e_passphrase_create" = "パスフレーズの作成";
+// Others
+"user_id_title" = "ユーザーID:";
+"offline" = "オフライン";
+"unsent" = "未送信";
+"error" = "エラー";
+"error_common_message" = "エラーが発生しました。 後でもう一度お試しください。";
+"not_supported_yet" = "まだサポートされていません";
+"default" = "既定";
+"private" = "Private";
+"public" = "Public";
+"power_level" = "権限レベル";
+"network_error_not_reachable" = "ネットワーク接続を確認してください";
+"user_id_placeholder" = "例: @bob:homeserver";
+"ssl_homeserver_url" = "ホームサーバーのURL: %@";
+// Permissions
+"camera_access_not_granted_for_call" = "ビデオ通話はカメラにアクセスする必要がありますが、%@にはそのカメラを使用する権限がありません";
+"microphone_access_not_granted_for_call" = "通話にはマイクへのアクセスが必要ですが、%@には使用許可がありません";
+"local_contacts_access_not_granted" = "ローカルの連絡先からユーザーを探すには連絡先にアクセスする必要がありますが、%@にはそのアクセス権限がありません";
+"local_contacts_access_discovery_warning_title" = "ユーザーの探索";
+"local_contacts_access_discovery_warning" = "%@は、ユーザーを検索するためにあなたの連絡先から電子メールと電話番号をアップロードしたい";
+// Country picker
+"country_picker_title" = "国を選択する";
+// Language picker
+"language_picker_title" = "言語を選択する";
+"language_picker_default_language" = "既定値 (%@)";
+"notice_room_invite" = "%@は%@を招待しました";
+"notice_room_third_party_invite" = "%@は、部屋に参加するよう%@へ招待状を送った";
+"notice_room_third_party_registered_invite" = "%@は%@の招待を受け入れました";
+"notice_room_join" = "%@ は参加しました";
+"notice_room_leave" = "%@ は退出しました";
+"notice_room_reject" = "%@は招待を拒否しました";
+"notice_room_kick" = "%@は%@を追い出しました";
+"notice_room_unban" = "%@は%@を追放解除した";
+"notice_room_ban" = "%@は%@を追放した";
+"notice_room_withdraw" = "%@は%@の招待を辞退しました";
+"notice_room_reason" = ". 理由: %@";
+"notice_avatar_url_changed" = "%@はアバターを変更しました";
+"notice_display_name_set" = "%@は表示名を%@に設定しました";
+"notice_display_name_changed_from" = "%@は表示名を%@から%@に変更しました";
+"notice_display_name_removed" = "%@は表示名を削除しました";
+"notice_topic_changed" = "%@はトピックを次のように変更しました: %@";
+"notice_room_name_changed" = "%@は部屋名を次のように変更しました: %@";
+"notice_placed_voice_call" = "%@は電話をかけました";
+"notice_placed_video_call" = "%@はビデオ電話をかけました";
+"notice_answered_video_call" = "%@は通話に応答しました";
+"notice_ended_video_call" = "%@は通話を終了しました";
+"notice_conference_call_request" = "%@はVoIP会議をリクエストしました";
+"notice_conference_call_started" = "VoIP会議が開始されました";
+"notice_conference_call_finished" = "VoIP会議が終了しました";
+// button names
+"ok" = "OK";
+"cancel" = "キャンセル";
+"save" = "保存";
+"leave" = "保存しない";
+"send" = "送信";
+"copy_button_name" = "コピー";
+"resend" = "再送信";
+"redact" = "編集";
+"share" = "共有";
+"set_power_level" = "権限レベル";
+"delete" = "削除";
+"view" = "表示";
+// actions
+"action_logout" = "ログアウト";
+"create_room" = "部屋を作る";
+"login" = "ログイン";
+"create_account" = "アカウントを作成する";
+"membership_invite" = "招待した";
+"membership_leave" = "退出";
+"membership_ban" = "追放";
+"num_members_one" = "%@ ユーザー";
+"num_members_other" = "%@ ユーザー";
+"invite" = "招待";
+"kick" = "追い出す";
+"ban" = "追放";
+"unban" = "追放解除";
+"message_unsaved_changes" = "保存されていない変更があります。 放置すると捨てられます。";
+// Login Screen
+"login_error_already_logged_in" = "ログイン済み";
+"login_error_must_start_http" = "URLは http[s]:// で始まる必要があります";
+// room details dialog screen
+"room_details_title" = "部屋の詳細";
+// contacts list screen
+"invitation_message" = "私はmatrixであなたとチャットしたい。 詳細はウェブサイトhttp://matrix.orgをお尋ねください。";
+// Settings screen
+"settings_title_config" = "構成";
+"settings_title_notifications" = "通知";
+// Notification settings screen
+"notification_settings_disable_all" = "すべての通知を無効にする";
+"notification_settings_enable_notifications" = "通知を有効にする";
+"notification_settings_enable_notifications_warning" = "現在、すべての端末ですべての通知が無効になっています。";
+"notification_settings_global_info" = "通知設定はユーザーアカウントに保存され、デスクトップ通知を含むすべてのクライアント間で共有されます。\n\nルールは順番に適用されます。 一致する最初のルールは、メッセージの結果を定義します。\nだから:単語ごとの通知は、送信者ごとの通知よりも重要な部屋ごとの通知よりも重要です。\n同じ種類の複数のルールの場合、一致するリストの最初のルールが優先されます。";
+"notification_settings_per_word_notifications" = "単語単位の通知";
+"notification_settings_per_word_info" = "単語は大文字と小文字を区別せずに一致させ、*ワイルドカードを含めることができます。 従って:\nfooは、区切り文字で囲まれた文字列foo(例 句読点や空白、行の開始/終了)と一致します。\nfoo*は、fooで始まる単語に一致します。\n*foo*は、3文字のfooを含む単語に一致します。";
+"notification_settings_always_notify" = "常に通知";
+"notification_settings_never_notify" = "決して通知しない";
+"notification_settings_word_to_match" = "一致する単語";
+"notification_settings_highlight" = "Highlight";
+"notification_settings_custom_sound" = "カスタムサウンド";
+"notification_settings_per_room_notifications" = "1部屋あたりの通知";
+"notification_settings_per_sender_notifications" = "送信者ごとの通知";
+"notification_settings_sender_hint" = "@user:domain.com";
+"notification_settings_select_room" = "部屋を選択";
+"notification_settings_other_alerts" = "その他のアラート";
+"notification_settings_contain_my_user_name" = "私のユーザー名を含むメッセージについて音で私に通知してください";
+"notification_settings_contain_my_display_name" = "私の表示名が含まれているメッセージについて音で私に通知する";
+"notification_settings_just_sent_to_me" = "私に送られたメッセージについての音で私に知らせる";
+"notification_settings_invite_to_a_new_room" = "私が新しい部屋に招待されたときに知らせる";
+"notification_settings_people_join_leave_rooms" = "人が部屋に入退室したときに私に通知する";
+"notification_settings_receive_a_call" = "通話を受信したときに通知する";
+"notification_settings_suppress_from_bots" = "ボットからの通知を抑制する";
+"notification_settings_by_default" = "既定値では...";
+"notification_settings_notify_all_other" = "他のすべてのメッセージ/部屋について通知する";
+// gcm section
+"settings_config_home_server" = "ホームサーバー: %@";
+"settings_config_identity_server" = "IDサーバー: %@";
+"settings_config_user_id" = "ユーザーID: %@";
+// call string
+"call_waiting" = "待機中...";
+"call_connecting" = "通話接続中...";
+"call_ended" = "通話終了";
+"call_ring" = "呼び出し中...";
+"incoming_video_call" = "着信ビデオ通話";
+"incoming_voice_call" = "着信音声通話";
+"call_invite_expired" = "期限切れの招待コール";
+// unrecognized SSL certificate
+"ssl_trust" = "信頼";
+"ssl_logout_account" = "ログアウト";
+"ssl_remain_offline" = "無視";
+"ssl_fingerprint_hash" = "指紋 (%@):";
+"ssl_could_not_verify" = "リモートサーバーのIDを確認できませんでした。";
+"ssl_cert_not_trust" = "これは、誰かがあなたのトラフィックを悪意を持って傍受しているか、あなたの電話機がリモートサーバーから提供された証明書を信頼していないことを意味します。";
+"ssl_cert_new_account_expl" = "サーバー管理者がこれが予期されると述べた場合は、以下の指紋が提供された指紋と一致することを確認してください。";
+"ssl_unexpected_existing_expl" = "証明書は、お使いの携帯電話にて信頼されたものから変更されました。 これは非常に珍しいことです。 この新しい証明書に同意しないことをお勧めします。";
+"ssl_expected_existing_expl" = "証明書が以前に信頼されたものから信頼されていないものに変更されました。 サーバーが証明書を更新した可能性があります。 予想される指紋については、サーバー管理者にお問い合わせください。";
+"ssl_only_accept" = "サーバー管理者が上記のものと一致する指紋を発行した場合にのみ、証明書を受け入れます。";
+"unignore" = "無視しない";
+"notice_encryption_enabled_ok" = "%@ がエンドツーエンド暗号化をオンにしました。";
+"notice_encryption_enabled_unknown_algorithm" = "%1$@ がエンドツーエンド暗号化をオンにしました (不明なアルゴリズム %2$@)。";
+"device_details_rename_prompt_title" = "セッション名";
+"account_error_push_not_allowed" = "通知は許可されていません";
+"notice_room_third_party_revoked_invite" = "%@ は %@ の部屋への招待を取り消しました";
+// Notice Events with "You"
+"notice_room_invite_by_you" = "%@を招待しました";
+"notice_room_invite_you" = "%@があなたを招待しました";
+"notice_room_join_by_you" = "参加しました";
+"notice_room_leave_by_you" = "退室しました";
+"notice_room_kick_by_you" = "%@をキックしました";
+"notice_room_unban_by_you" = "%@をBANしました";
+"notice_room_ban_by_you" = "%@をBANしました";
+"notice_avatar_url_changed_by_you" = "アバターを変更しました";
+"notice_display_name_set_by_you" = "表示名を%@に変更しました";
+"notice_display_name_changed_from_by_you" = "表示名を%@から%@に変更しました";
+"notice_display_name_removed_by_you" = "表示名を削除しました";
+"notice_topic_changed_by_you" = "トピックを変更しました: %@";
+"notice_room_name_changed_by_you" = "部屋の名前を変更しました: %@";
+"notice_placed_voice_call_by_you" = "音声通話を開始しました";
+"notice_placed_video_call_by_you" = "ビデオ通話を開始しました";
+"notice_answered_video_call_by_you" = "電話に出ました";
+"notice_ended_video_call_by_you" = "通話を終了しました";
+"notice_conference_call_request_by_you" = "VoIP会議をリクエストしました";
+"notice_room_name_removed_by_you" = "部屋名を削除しました";
+"notice_room_topic_removed_by_you" = "トピックを削除しました";
+"notice_profile_change_redacted_by_you" = "プロフィール %@を更新しました";
+"notice_room_created_by_you" = "部屋を作成しました";
+"notice_encryption_enabled_ok_by_you" = "あなたはエンドツーエンド暗号化をオンにしました。";
+"notice_encryption_enabled_unknown_algorithm_by_you" = "あなたはエンドツーエンド暗号化をオンにしました (不明なアルゴリズム %2$@)。";
+"notice_redaction_by_you" = "イベントを編集しました (id: %@)";
diff --git a/Riot/Modules/MatrixKit/Assets/MatrixKitAssets.bundle/kab.lproj/MatrixKit.strings b/Riot/Modules/MatrixKit/Assets/MatrixKitAssets.bundle/kab.lproj/MatrixKit.strings
new file mode 100644
index 000000000..8749803a1
--- /dev/null
+++ b/Riot/Modules/MatrixKit/Assets/MatrixKitAssets.bundle/kab.lproj/MatrixKit.strings
@@ -0,0 +1,530 @@
+
+
+
+"ssl_expected_existing_expl" = "Aselkin yettwabeddel seg win yettwamanen ɣer win ur nettwaman ara. Ahat aqeddac iɛawed-d aselkin-ines. Nermes anedbal n uqeddac ɣef udsil umḍin yetturaǧun.";
+"ssl_unexpected_existing_expl" = "Aselken yettubeddel deg ubdil n win yettwamanen deg tiliɣri-inek·inem. Aya MAČČI D AYEN IGERRZEN. Issefk UR TETTAQBALEḌ ARA aselkin-a amaynut.";
+"ssl_cert_new_account_expl" = "Ma yella anedbal n uqeddac yenna-d belli aya yettuau, ẓer ma yella adsil umḍin ddaw yemṣada d udsil umḍin i d-mudden nutni.";
+"ssl_cert_not_trust" = "Aya yebɣa ad d-yini yellawin d ugur i tikli-inek·inem s yir udem, neɣ tiliɣri-inek·inem ur yettkil ara ɣef uselkin i as-imudd uqeddac anmeggag.";
+"ssl_could_not_verify" = "D awezɣi ad nsenqed timagit n uqeddac agemmaḍ.";
+"ssl_remain_offline" = "Ttu";
+"ssl_logout_account" = "Tuffɣa";
+
+// unrecognized SSL certificate
+"ssl_trust" = "Ittkel";
+"incoming_voice_call" = "Asiwel s taɣect i d-ikecmen";
+"incoming_video_call" = "Asiwel s tvidyut i d-ikecmen";
+"call_ring" = "Yessawal...";
+"call_ended" = "Asiwel yekfa";
+"notification_settings_notify_all_other" = "Ṭṭef-d ilɣa i meṛṛa iznan/tixxamin";
+"settings_title_notifications" = "Ilɣa";
+
+// Settings screen
+"settings_title_config" = "Tawila";
+
+// members list Screen
+
+// accounts list Screen
+
+// image size selection
+
+// invitation members list Screen
+
+// room creation dialog Screen
+
+// room info dialog Screen
+
+// room details dialog screen
+"room_details_title" = "Talqayt n texxamt";
+"login_error_must_start_http" = "Ilaq ad yebdu URL s http[s]://";
+"ban" = "Agi";
+"kick" = "Suffeɣ";
+"invite" = "Nced";
+"membership_leave" = "Azelmaḍ";
+"membership_invite" = "Yettwancad";
+"create_account" = "Rnu amiḍan";
+"login" = "Isem n useqdac";
+"create_room" = "Rnu taxxamt";
+
+// actions
+"action_logout" = "Tuffɣa";
+"view" = "Wali";
+"delete" = "Kkes";
+"share" = "Bḍu";
+"redact" = "Sfeḍ";
+"resend" = "Ɛawed azen";
+"copy_button_name" = "Nɣel";
+"send" = "Azen";
+"leave" = "Ffeɣ";
+"save" = "Sekles";
+"cancel" = "Sefsex";
+
+// Room Screen
+
+// general errors
+
+// Home Screen
+
+// Last seen time
+
+// call events
+
+/* -*-
+ Automatic localization for en
+
+ The following key/value pairs were extracted from the android i18n file:
+ /console/src/main/res/values/strings.xml.
+*/
+
+
+// titles
+
+// button names
+"ok" = "IH";
+"notice_room_created_by_you_for_dm" = "Terniḍ.";
+"notice_room_created_by_you" = "Aql-ak·akem terniḍ, tsewleḍ taxxamt.";
+"notice_conference_call_request_by_you" = "Tsutreḍ-d asarag VoIP";
+"notice_avatar_url_changed_by_you" = "Tbeddleḍ avatar-inek·inem";
+"notice_room_reject_by_you" = "Tufiḍ tinubga";
+"notice_room_leave_by_you" = "Truḥeḍ";
+"notice_room_join_by_you" = "Terniḍ";
+"notice_conference_call_finished" = "Asarag VoIP yekfa";
+"notice_conference_call_started" = "Asarag VoIP yebda";
+
+// Country picker
+"country_picker_title" = "Fren tamurt";
+"public" = "Azayez";
+"private" = "Amaẓlay";
+"default" = "amezwer";
+"error" = "Tuccḍa";
+"e2e_passphrase_create" = "Rnu tafyirt tuffirt";
+"e2e_passphrase_not_match" = "Tifyar tuffirin ilaq ad mṣadant";
+"e2e_passphrase_empty" = "Tafyirt tuffirt ur ilaq ara ad ilint d tilmawin";
+"e2e_passphrase_confirm" = "Sentem tafyirt tuffirt";
+"e2e_export" = "Sifeḍ";
+
+// E2E export
+"e2e_export_room_keys" = "Sifeḍ tisura n texxamt";
+"e2e_passphrase_enter" = "Sekcem tafyirt tuffirt";
+"e2e_import" = "Kter";
+
+// E2E import
+"e2e_import_room_keys" = "Kter tisura n texxamt";
+"format_time_d" = "d";
+"format_time_h" = "sr";
+
+// Time
+"format_time_s" = "s";
+"group_section" = "Igrawen";
+
+// Groups
+"group_invite_section" = "Inced-d";
+"attachment_cancel_upload" = "Sefsex asali?";
+"attachment_cancel_download" = "Sefsex asider?";
+"room_member_power_level_prompt" = "Ur tettizmireḍ ara ad tesfesxeḍ asnifel-a acku tessebɣaseḍ aseqdac ad yesɛu aswir n tezmert am kečč·kemm.\nTebɣiḍ s tidet?";
+"room_no_conference_call_in_encrypted_rooms" = "Isiwlen isaragen ur ttwasefraken ara deg texxamin yettwawgelhen";
+"room_no_power_to_create_conference_call" = "Tesriḍ tisirag akken ad tebduḍ asarag deg texxamt-a";
+"room_left_for_dm" = "Truḥeḍ";
+"room_left" = "Teǧǧiḍ taxxamt";
+"room_error_timeline_event_not_found_title" = "Asali n yideg n tesnakudt ur yeddi ara";
+"room_error_join_failed_title" = "Anekcum ɣer texxamt ur yeddi ara";
+"account_error_email_wrong_description" = "Tagi ur tettban ara d tansa n yimayl tameɣtut";
+"account_error_email_wrong_title" = "Tansa n yimayl d tarameɣtut";
+"account_msisdn_validation_error" = "Asenqed n wuṭṭun n tilifun ur yeddi ara.";
+"account_msisdn_validation_title" = "Asenqed yettṛaǧu";
+"account_email_validation_message" = "Ma ulac aɣilif, senqed imayl-ik/im syen sit ɣef useɣwen i yellan. Akken ara yemmed waya, sit ad tkemmleḍ.";
+"account_email_validation_title" = "Asenqed yettṛaǧu";
+
+// Account
+"account_save_changes" = "Sekles ibeddilen";
+"room_event_encryption_verify_ok" = "Senqed";
+"room_event_encryption_info_unblock" = "Kkes seg tebdart taberkant";
+"room_event_encryption_info_block" = "Tabdart taberkant";
+"room_event_encryption_info_unverify" = "Ur yettusenqed ara";
+"room_event_encryption_info_device_blocked" = "Deg tebdart taberkant";
+"room_event_encryption_info_device_verified" = "Yettwasenqed";
+"room_event_encryption_info_event_none" = "ulac";
+"device_details_delete_prompt_message" = "Tamahelt-a tesra asentem-nniḍen.\nI ukemmel, ma ulac aɣilif sekcem awal-ik·im uffir.";
+"device_details_delete_prompt_title" = "Asesteb";
+"device_details_rename_prompt_message" = "Isem n tiɣimit tazayezt yettban i yimdanen wukud tettmeslayeḍ";
+
+// Settings
+"settings" = "Iɣewwaren";
+"notice_encrypted_message" = "Izen yettwawgelhen";
+"notice_room_join_rule_invite_by_you_for_dm" = "Tgiḍ aya i tinubga kan.";
+"notice_room_join_rule_invite_by_you" = "Terriḍ taxxamt s tinubga kan.";
+"unignore" = "Ur yettwazgel ara";
+"ignore" = "Ttu";
+"show_details" = "Sken talqayt";
+"cancel_download" = "Sefsex Asider";
+"cancel_upload" = "Sefsex Asali";
+"select_all" = "Fren kulec";
+"reset_to_default" = "Wennez ɣer umezwer";
+"mention" = "Abder";
+"start_video_call" = "Yebda usiwel s tvidyut";
+"start_voice_call" = "Yebda usiwel s taɣect";
+"submit" = "Azen";
+"sign_up" = "Jerred";
+"retry" = "Ɛreḍ tikkelt-nniḍen";
+"dismiss" = "Agi";
+"discard" = "Ignorer";
+"continue" = "Kemmel";
+"close" = "Mdel";
+"back" = "Uɣal ɣer deffir";
+"abort" = "Sefsex";
+"yes" = "Ih";
+
+// Action
+"no" = "Uhu";
+"login_error_resource_limit_exceeded_contact_button" = "Nermes anedbal";
+"login_error_resource_limit_exceeded_message_monthly_active_user" = "Aqeddac-a agejdan yewweḍ ɣer talast n useqdac urmid n wayyur.";
+"login_error_resource_limit_exceeded_message_default" = "Aqeddac-a agejdan iɛedda yiwet seg tlisa-ines tiɣbula.";
+"login_error_resource_limit_exceeded_title" = "Talast n yiɣbula tettuɛedda";
+"login_desktop_device" = "Aniraw";
+"login_tablet_device" = "Taṭablit";
+"login_mobile_device" = "Aziraz";
+"login_leave_fallback" = "Sefex";
+"login_error_login_email_not_yet" = "Aseɣwen n yimayl iɣef mazal ur yettusit ara fell-as";
+"login_error_user_in_use" = "Isem n useqdac-a yettwaseqdac yakan";
+"login_error_limit_exceeded" = "Aṭas n yisuturen i yettwaznen";
+"login_error_not_json" = "Ulac deg-s JSON ameɣtu";
+"login_error_bad_json" = "JSON ur yemsil ara akken iwata";
+"login_error_unknown_token" = "Ajuṭu n unekcum i yettwafernen ur yettwassen ara";
+"login_error_forbidden" = "Isem n uqeddac/awal uffir d arameɣtu";
+"login_email_placeholder" = "Tansa n l'email";
+"login_password_placeholder" = "Mot de passe";
+/* *********************** */
+/* iOS specific */
+/* *********************** */
+
+"matrix" = "Matrix";
+"account_msisdn_validation_message" = "Ad naze-n SMS deg-s tangalt n usenqed. Ttxil-k·m sekcem tangalt-a ddaw.";
+"login_server_url_placeholder" = "URL (e.g. https://matrix.org)";
+"notification_settings_per_room_notifications" = "Ilɣa s texxamt";
+"notification_settings_per_word_notifications" = "Ilɣa s awal";
+"notice_redaction_by_you" = "Tsemsawiḍ aneḍru (asulay: %@)";
+"notice_room_third_party_revoked_invite_by_you_for_dm" = "Tesfesxeḍ tinubga n %@";
+"notice_room_third_party_revoked_invite_for_dm" = "%@ isefsex tinubga n %@";
+"device_details_last_seen" = "Timeẓri taneggarut\n";
+"notice_feedback" = "Aneḍru n timawin (asulay: %@): %@";
+"login_error_registration_is_not_supported" = "Iklasen ur ttusefraken ara akka tura";
+"ssl_only_accept" = "Ur qebbel ara aselkin alamma isuffeɣ-d unedbal n uqeddac adsil umḍin yemṣadan d win yellan ddaw-a.";
+"ssl_fingerprint_hash" = "Adsil umḍin (%@):";
+"call_invite_expired" = "Ancad ɣer usiwel yezri";
+
+// Settings keys
+
+// call string
+"call_waiting" = "Yettraǧu...";
+"settings_config_user_id" = "Asulay n useqdac:%@";
+"notification_settings_other_alerts" = "Ilɣuten-nniḍen";
+"notification_settings_select_room" = "Fren taxxamt";
+"notification_settings_sender_hint" = "@user:domain.com";
+"notification_settings_highlight" = "Asebrureq";
+"notification_settings_never_notify" = "Ur ttelɣu ara akk";
+"notification_settings_always_notify" = "Selɣu yal tikkelt";
+"notification_settings_enable_notifications" = "Rmed ilɣa";
+"num_members_other" = "%@ iseqdacen";
+"num_members_one" = "%@ n useqdac";
+"membership_ban" = "Yettwagdel";
+"notice_profile_change_redacted_by_you" = "Tleqqmeḍ amaɣnu-inek·inem %@";
+"notice_event_redacted_by_you" = " sɣur-k·m";
+"notice_display_name_set_by_you" = "Tesbadud isem n uskan ɣer %@";
+"notice_room_unban_by_you" = "Tgedleḍ %@";
+"notice_room_third_party_registered_invite_by_you" = "Tqebleḍ tinubga n %@";
+"notice_room_third_party_invite_by_you_for_dm" = "Tnecdeḍ-d %@";
+"notice_room_invite_you" = "%@ inced-ik·kem-id";
+
+// Notice Events with "You"
+"notice_room_invite_by_you" = "Tnecdeḍ-d %@";
+"notice_conference_call_request" = "%@ isuter asarag VoIP";
+"notice_ended_video_call" = "%@ iḥbes asiwel";
+"notice_answered_video_call" = "%@ yerra ɣef usiwel";
+"notice_placed_video_call" = "%@ isɛedda siwel s tvidyut";
+"notice_placed_voice_call" = "%@ isɛedda asiwel s taɣect";
+"notice_avatar_url_changed" = "%@ t·ibeddel·t avatar-ines";
+"notice_room_ban" = "%@ igdel %@";
+"notice_room_unban" = "%@ ur yegdil ara %@";
+"notice_room_kick" = "%@ isuffeɣ %@";
+"notice_room_reject" = "%@ yugi tinubga";
+"notice_room_leave" = "%@ azelmaḍ";
+"notice_room_join" = "%@ yerna";
+"notice_room_third_party_registered_invite" = "%@ yeqbel tinnubga n %@";
+"notice_room_third_party_invite_for_dm" = "%@ inced-d %@";
+
+/* -*-
+ Automatic localization for en
+
+ The following key/value pairs were extracted from the android i18n file:
+ /matrix-sdk/src/main/res/values/strings.xml.
+*/
+
+"notice_room_invite" = "%@ inced-d %@";
+"ssl_homeserver_url" = "URL n uqeddac agejdan: %@";
+"user_id_placeholder" = "am.: @bob:amedya.com";
+"network_error_not_reachable" = "Ma ulac aɣilif senqed tuqqna-inek·inem";
+"offline" = "aruqqin";
+
+// Others
+"user_id_title" = "Asulay n useqdac:";
+"format_time_m" = "m";
+"search_searching" = "Anadi la iteddu...";
+
+// Search
+"search_no_results" = "Ulac igmaḍ";
+"contact_local_contacts" = "Inermisen idiganen";
+
+// Contacts
+"contact_mx_users" = "Iseqdacen n Matrix";
+"attachment_e2e_keys_import" = "Kter...";
+"attachment_large" = "Meqqer: %@";
+"attachment_medium" = "Alemmas: %@";
+"attachment_small" = "Mecṭuḥ: %@";
+"message_reply_to_sender_sent_a_file" = "yuzen afaylu.";
+"message_reply_to_sender_sent_a_video" = "yuzen-d tavidyut.";
+
+// Reply to message
+"message_reply_to_sender_sent_an_image" = "Azen tugna.";
+
+// Room
+"room_please_select" = "Ttxil-k·m fren taxxamt";
+"room_creation_participants_title" = "Imttekkiyen:";
+"room_creation_alias_placeholder_with_homeserver" = "(am. +foo%@)";
+"room_creation_name_placeholder" = "(am. lunchGroup)";
+
+// Room creation
+"room_creation_name_title" = "Isem n texxamt:";
+"account_error_msisdn_wrong_description" = "Ur yettban ara wagi d uṭṭun n tiliɣri ameɣtu";
+"account_error_msisdn_wrong_title" = "Yir uṭṭun n tiliɣri";
+"account_error_matrix_session_is_not_opened" = "Tiɣimit n Matrix ur teldi ara";
+"account_email_validation_error" = "Ulamek akk tettwasenqed tansa n yimayl. Ma ulac aɣilif senqed imayl-inek·inem syen sit ɣef useɣwen yellan deg-s. Akken ara tgeḍ aya, sit ɣef kemmel";
+"room_event_encryption_info_verify" = "Senqed...";
+"room_event_encryption_info_device_not_verified" = "UR yettwasenqed ARA";
+"room_event_encryption_info_device_fingerprint" = "Adsil umḍin Ed25519\n";
+"room_event_encryption_info_device_name" = "Isem azayez\n";
+"room_event_encryption_info_event_unencrypted" = "ur yettwawgelhen ara";
+"room_event_encryption_info_event_decryption_error" = "Tuccḍa deg tukksa n uwgelhen\n";
+"room_event_encryption_info_event_algorithm" = "Alguritm\n";
+"room_event_encryption_info_event_fingerprint_key" = "Tasarut n udsil umḍin Ed25519 tettusra\n";
+"room_event_encryption_info_event_identity_key" = "Tasarut n timagit Curve25519\n";
+"room_event_encryption_info_event_user_id" = "Asulay n useqdac\n";
+"room_event_encryption_info_event" = "Talɣut n uneḍru\n";
+
+// Encryption information
+"room_event_encryption_info_title" = "Talɣut n uwgelhen seg yixef ɣer yixef\n\n";
+"device_details_rename_prompt_title" = "Isem n tɣimit";
+"device_details_last_seen_format" = "%@ @ %@\n";
+"device_details_name" = "Isem azayez\n";
+
+// Devices
+"device_details_title" = "Talɣut ɣef tɣimit\n";
+"notification_settings_room_rule_title" = "Taxxamt: '%@'";
+"settings_enter_validation_token_for" = "Sekcem ajiṭun n usentem i %@:";
+"settings_enable_push_notifications" = "Rmed ilɣa n push";
+"room_displayname_more_than_two_members" = "%@ d %@ d wiyaḍ";
+"room_displayname_two_members" = "%@ akked %@";
+
+// room display name
+"room_displayname_empty_room" = "Texxamt tilemt";
+"notice_crypto_unable_to_decrypt" = "**Ukamek yettwakkes uwgelhen: %@**";
+"notice_invalid_attachment" = "taceqquft yeddan d tarameɣtut";
+"notice_file_attachment" = "afaylu yeddan";
+"notice_room_related_groups" = "Igrawen icudden ɣer texxamt-a d: %@";
+"notice_room_join_rule_public" = "%@ yerra taxxamt d tazayazt.";
+"notice_room_created_for_dm" = "%@ yerna.";
+"notice_room_created" = "%@ yerna taxxamt syen iswel-itt.";
+"notice_profile_change_redacted" = "%@ leqqmen amaɣnu-nsen %@";
+"notice_event_redacted_by" = " s %@";
+"notice_event_redacted" = "";
+
+// Events formatter
+"notice_avatar_changed_too" = "(ula d avaṭar yettubeddel)";
+"reject_call" = "Agi asiwel";
+"answer_call" = "Err ɣef usiwel";
+"resend_message" = "Ales tuzna n yizen";
+"capture_media" = "Ṭṭef tawlaft/tavidyut";
+"select_account" = "Fren amiḍan";
+"start_chat" = "Bdu adiwenni";
+"set_admin" = "Sbadu anedbal";
+"submit_code" = "Azen tangalt";
+"login_error_resource_limit_exceeded_message_contact" = "\n\nTtxil-k·m nermes anedbal-ik·im n uqeddac i wakken ad tkemmleḍ aseqdec n uqeddac-a.";
+"register_error_title" = "Ajerred yecceḍ";
+"login_invalid_param" = "Aɣewwar d arameɣtu";
+"login_use_fallback" = "Seqdec asebtar n ufrananeggaru";
+"login_prompt_email_token" = "Ttxil-k sekcem ajuṭu-inek·inem n usentem n yimayl:";
+"login_optional_field" = "d afrayan";
+"login_identity_server_title" = "URL n uqeddac n timagit:";
+"login_home_server_title" = "URL n uqeddac agejdan:";
+
+// Login Screen
+"login_create_account" = "Rnu amiḍan:";
+"notification_settings_per_word_info" = "Awalen i d-yettwafen s war ma nefka azal i umṣada n yisekkilen, i izemren ad yesεu ajukeṛ *. Ihi:\nfoo yufa-d azrir foo zzin-as-d s yijemmaqen n wawalen (am. aisgez, tallunt d tazwara neɣ taggara n yizirig).\nfoo* yufa-d meṛṛa awalen i ibeddun s foo.\n*foo* yufa-d akk awalen ideg llan kraḍ n yisekkilen-a foo.";
+"notification_settings_global_info" = "Iɣewwaren n yilɣa ttuskelsen deg umiḍan-inek·inem n useqdac, ad ttwabḍun gar meṛṛa imsaɣen ara ten-isferken (ula d iɣewwaren n tnarit).\n\nIlugan ttusnasen akken myezwaren; alugan amezwaru i yemṣadan tesbaduy agmuḍ n yizen.\nIhi: Ilɣa s wawal sεan azal ugar n yilɣa s texxamt i yesεan azal ula d nutni ɣef yilɣa s umazan.\nI wugar n yilugan n yiwen wanaw, amezwaru deg tebdart yemṣadan d netta i d tazwart.";
+"local_contacts_access_discovery_warning" = "I usnirem n yinermisen s useqdec yakan n Matrix, %@ yezmer ad yazen tansiwin n yimayl d wuṭṭunen n tiliɣri n udlis-inek·inem n tansiwin ɣer uqeddac-inek·inem n timagit n Matrix i tferneḍ. Ma yella yettusefrak, isefka-inek·inem udmawanen ad ttwagzamen send ad ttwaznen - ttxil-k·m senqed tasertit n tudert tabaḍnit n uqeddac-ik·im n timagit i wugar n telqayt.";
+"local_contacts_access_not_granted" = "I usnirem n yiseqdacen seg yinermisen idiganen, anekcum ɣer yinermisen yettusra maca %@ ur yesεi ara tisirag ad t-iseqdec";
+"e2e_export_prompt" = "Akala-a ad ak·am-imudd tisirag i usifeḍ n tsura n yiznan i d-tremseḍ deg texxamin yettwawgelhen, ɣer ufaylu adigan. Syen tzemreḍ ad tketreḍ afaylu deg umsaɣ-nniḍen n Matrix, i wakken amsaɣ-a ad yizmir ad yekkes awgelhen i yiznan-a.\nAfaylu i d-yettusifḍen ad imudd tisirag i yal win i izemren ad t-iɣer, ad yekkes awgelhen i yiznan yettwawgelhen i tzemreḍ ad twaliḍ. Γef waya ilaq ad t-tḥerzeḍ deg wadeg aɣellsan.";
+"e2e_import_prompt" = "Akala-a ad ak·am-imudd tisirag i wakken ad tketreḍ tisura i d-tsifḍeḍ yakan seg umsaɣ-nniḍen n Matrix. Tzemreḍ mbeεd ad tekkseḍ awgelhen n yal izen iwumi yezmer umsaɣ-nniḍen ad asen-yekkes awgelhen.\nAfaylu i d-yettisifḍen yettwammesten s tefyirt tuffirt. Ilaq ad teskecmeḍ tafyirt tuffirt dagi, i wakken ad tekkseḍ awgelhen i ufaylu.";
+"attachment_e2e_keys_file_prompt" = "Afaylu-a deg-s tisura n uwgelhen ttusifḍent-d seg umsaɣ Matrix.\nTebɣiḍ ad twaliḍ agbur n ufaylu neɣ ad d-tketreḍ tisura i yellan deg-s?";
+"room_error_timeline_event_not_found" = "Asnas yettaεraḍ ad d-isali kra n wagazen ufrinen deg tesnakudt n texxamt, maca ur tt-yifi ara";
+"room_event_encryption_verify_message" = "I usenqed n tɣimit-a ma tzemreḍ ad tettekleḍ fell-as, ttxil-k·m nermes bab-is s useqdec s ubrid-nniḍen (am. s timmad-is neɣ s usiwel) syen steqsi-t ma yella tasarut i yettwali deg yiɣewwaren-is n useqdac n tɣimit-a temṣada d tsarut yellan ddaw:\n\n Isem n tɣimit: %@\n Asulay n texxamt: %@\n Tasarut n texxamt: %@\n\nMa yella mṣadant, sit ɣef tqeffalt n usenqed ddaw. Ma yella ur mṣadant ara, ihi yella win i yettεekkiṛen tiɣimit-a, neɣ ahat tebɣiḍ ad tsiteḍ ɣef tqeffalt n tebdart taberkant deg ubdel.\n\nΓer sdat, akala-a n usenqed-a ad yuɣal yemmed ugar.";
+"call_connecting" = "Asiwel iteddu...";
+"settings_config_identity_server" = "Aqeddac n timagit: %@";
+
+// gcm section
+"settings_config_home_server" = "Aqeddac agejdan: %@";
+"notification_settings_by_default" = "S umezwer...";
+"notification_settings_suppress_from_bots" = "Kkes ilɣa n yiṛubuten";
+"notification_settings_receive_a_call" = "Lɣu-yi-d mi ara yi-d-yaweḍ usiwel";
+"notification_settings_people_join_leave_rooms" = "Lɣu-yi-d mi ara ad d-rnun neɣ ad ǧǧen yimdanen tixxamin";
+"notification_settings_invite_to_a_new_room" = "Lɣu-yi-d mi ara ttunecdeɣ ɣer texxamt tamaynut";
+"notification_settings_just_sent_to_me" = "Lɣu-yi-d s yimesli ɣef yiznan i d-yettwaznen i nekk kan";
+"notification_settings_contain_my_display_name" = "Lɣu-yi-d s yimesli ɣef yiznan ideg yella yisem-iw ameskan";
+"notification_settings_contain_my_user_name" = "Lɣu-yi-d s yimesli ɣef yiznan ideg yella yisem-inu n useqdac";
+"notification_settings_per_sender_notifications" = "Ilɣa s umazan";
+"notification_settings_custom_sound" = "imesli udmawan";
+"notification_settings_word_to_match" = "awala ara d-yettwafen";
+"notification_settings_enable_notifications_warning" = "Meṛṛa ilɣa nsan akka tura ɣef meṛṛa ibenkan.";
+
+// Notification settings screen
+"notification_settings_disable_all" = "Sens meṛṛa ilɣa";
+
+// contacts list screen
+"invitation_message" = "Bɣiɣ ad mmeslayeɣ yid-k·m s Matrix. Ttxil-k·m, rzu ɣer usmel web http://matrix.org i wugar n talɣut.";
+
+// Login Screen
+"login_error_already_logged_in" = "Yeqqen yakan";
+"message_unsaved_changes" = "Llan isenfal ur nettusekles ara. Tuffɣa ad ten-tsefsex.";
+"unban" = "Kkes agdel";
+"notice_room_history_visible_to_members_from_joined_point_by_you_for_dm" = "Terriḍ iznan si sya d asawen ad ttbanen i yal yiwen, segmi ara d-rnun.";
+"notice_room_history_visible_to_members_from_joined_point_by_you" = "Terriḍ azray n texxamt si sya d asawen ad d-yettban i meṛṛa imttekkiyen n texxamt, sgmi ara d-rnun.";
+"notice_room_history_visible_to_members_from_invited_point_by_you_for_dm" = "Terriḍ iznan si sya d asawen ad d-ttbanen i yal yiwen, segmi ara d-ttusnubegten.";
+"notice_room_history_visible_to_members_from_invited_point_by_you" = "Terriḍ azray n texxamt si sya d asawen ad d-yettban i meṛṛa imttekkiyen n texxamt, segmi ara d-ttusnubegten.";
+"notice_room_history_visible_to_members_by_you_for_dm" = "Terriḍ iznan si sya ɣer sdat ad d-ttbanen i meṛṛa imttekkiyen n texxamt.";
+"notice_room_history_visible_to_members_by_you" = "Terriḍ azray n texxamt si sya ɣer sdat ad d-yettban i meṛṛa imttekkiyen n texxamt.";
+"notice_room_history_visible_to_anyone_by_you" = "Terriḍ azray n texxamt si sya ɣer sdat ad yettban i yal yiwen.";
+"notice_encryption_enabled_unknown_algorithm_by_you" = "Tremdeḍ awgelhen seg yixef ɣer yixef (alguritm d arussin %@).";
+"notice_encryption_enabled_ok_by_you" = "Tremdeḍ awgelhen seg yixef ɣer yixef.";
+"notice_room_topic_removed_by_you" = "Tekkseḍ asentel";
+"notice_room_name_removed_by_you_for_dm" = "Tekkseḍ isem";
+"notice_room_name_removed_by_you" = "Tekkseḍ isem n texxamt";
+"notice_ended_video_call_by_you" = "Tekfiḍ asiwel";
+"notice_answered_video_call_by_you" = "Terriḍ ɣef usiwel";
+"notice_placed_video_call_by_you" = "Tseεeddaḍ asiwel s tvidyut";
+"notice_placed_voice_call_by_you" = "Tesεeddaḍ asiwel s umeslaw";
+"notice_room_name_changed_by_you_for_dm" = "Tbeddleḍ isem s %@.";
+"notice_room_name_changed_by_you" = "Tbeddleḍ isem n texxamt s %@.";
+"notice_topic_changed_by_you" = "Tbeddleḍ asentel s \"%@\".";
+"notice_display_name_removed_by_you" = "Tekkzeḍ isem-inek·inem ameskan";
+"notice_display_name_changed_from_by_you" = "Tbeddleḍ isem-inek ameskan seg %@ ɣer %@";
+"notice_room_withdraw_by_you" = "Tesfesxeḍ tinubga n %@";
+"notice_room_kick_by_you" = "Tsuffɣeḍ %@";
+"notice_room_ban_by_you" = "Tgedleḍ %@";
+"notice_room_third_party_revoked_invite_by_you" = "Tekkseḍ tinubga n %@ i wakken ad d-yernu ɣer texxamt";
+"notice_room_third_party_invite_by_you" = "Tuzneḍ tinubga i %@ i wakken ad d-yernu ɣer texxamt";
+"notice_room_name_changed_for_dm" = "%@ ibeddel isem s %@.";
+"notice_room_name_changed" = "%@ ibeddel isem n texxamt s %@.";
+"notice_topic_changed" = "%@ ibeddel asentel s \"%@\".";
+"notice_display_name_removed" = "%@ yekkes isem-is ameskan";
+"notice_display_name_changed_from" = "%@ ibeddel isem-is ameskan seg %@ ɣer %@";
+"notice_display_name_set" = "%@ yesbadu isem-ines ameskan s %@";
+"notice_room_reason" = "Ssebba: %@";
+"notice_room_withdraw" = "%@ isefsex tinubga n %@";
+"notice_room_third_party_revoked_invite" = "%@ yekkes tinubga n %@ i wakken ad d-yernu ɣer texxamt";
+"notice_room_third_party_invite" = "%@ yuzen tinubga i %@ i wakken ad d-yernu ɣer texxamt";
+"language_picker_default_language" = "Amezwer (%@)";
+
+// Language picker
+"language_picker_title" = "Fren tutlayt";
+"local_contacts_access_discovery_warning_title" = "Asnirem n yiseqdacen";
+"microphone_access_not_granted_for_call" = "Isawalen sran ad kecmen ɣer usawaḍ maca %@ ur yesεi ara tisirag ad t-iseqdec";
+
+// Permissions
+"camera_access_not_granted_for_call" = "Isawalen s tvidyut sran anekcum ɣer tkamiṛat maca %@ ur yesεi ara tisirag ad tt-iseqdec";
+"power_level" = "Aswir n tezmert";
+"not_supported_yet" = "Ur yettusefrak ara akka tura";
+"error_common_message" = "Tella-d tuccḍa. Ttxil-kṃ εreḍ tikkelt-nniḍen ticki.";
+"unsent" = "Ur yettwazen ara";
+"attachment_multiselection_original" = "Teɣzi tamirant";
+"attachment_multiselection_size_prompt" = "Tebɣiḍ ad tazneḍ iznan d:";
+"attachment_original" = "Teɣzi tamirant: %@";
+
+// Attachment
+"attachment_size_prompt" = "Tebɣiḍ ad t-tazneḍ d:";
+
+// Room members
+"room_member_ignore_prompt" = "D tidet tebɣiḍ ad teffreḍ meṛṛa iznan i d-yusan sɣur aseqdac-a?";
+"message_reply_to_message_to_reply_to_prefix" = "D tiririt i";
+"message_reply_to_sender_sent_an_audio_file" = "yuzen afaylu ameslaw.";
+"room_error_cannot_load_timeline" = "Asali n tesnakudt ur yeddi ara";
+"room_error_topic_edition_not_authorized" = "Ur tesεiḍ tisirag ad tesnefleḍ asentel n texxamt-a";
+"room_error_name_edition_not_authorized" = "Ur tesεiḍ ara tisirag ad tesnefleḍ isem n texxamt-a";
+"room_error_join_failed_empty_room" = "D awezɣi akka tura ad talseḍ tuɣalin ɣer texxamt tilemt.";
+"room_creation_participants_placeholder" = "(am. @bob:homeserver1; @john:homeserver2...)";
+"room_creation_alias_placeholder" = "(am. #foo:example.org)";
+"room_creation_alias_title" = "Isem yettunefken i texxamt:";
+"account_error_push_not_allowed" = "Ilɣa ur ttusirgen ara";
+"account_error_picture_change_failed" = "Asenfel n tugna yecceḍ";
+"account_error_display_name_change_failed" = "Asenfel n yisem ameskan yecceḍ";
+"account_linked_emails" = "Imaylen yettwacudden";
+"account_link_email" = "Rnu imayl";
+"room_event_encryption_verify_title" = "Senqed tiɣimit\n\n";
+"room_event_encryption_info_device_verification" = "Asenqed\n";
+"room_event_encryption_info_device_id" = "Asulay\n";
+"room_event_encryption_info_device_unknown" = "tiɣimit tarussint\n";
+"room_event_encryption_info_device" = "\nTalɣut n tɣimit n umazan\n";
+"room_event_encryption_info_event_session_id" = "Asulay n tɣimit\n";
+"device_details_identifier" = "Asulay\n";
+"settings_enable_inapp_notifications" = "Rmed ilɣa deg usnas";
+"notice_in_reply_to" = "D tiririt i";
+"notice_sticker" = "astiker";
+"notice_crypto_error_unknown_inbound_session_id" = "Tiɣimit n umazan ur aɣ-d-tuzin ara tisura i yizen-a.";
+"notice_room_history_visible_to_members_from_joined_point_for_dm" = "%@ yerra iznan si sya ɣer sdat ttbanen i yal yiwen, seg wasmi ara d-rnun.";
+"notice_room_history_visible_to_members_from_joined_point" = "%@ yerra azray n texxamt si sya ɣer sdat yettban i meṛṛa imttekkiyen, seg wasmi ara d-rnun.";
+"notice_room_history_visible_to_members_from_invited_point_for_dm" = "%@ yerra azray n texxamt si sya ɣer sdat yettban i yal yiwen, segmi ara d-yettusnubget.";
+"notice_room_history_visible_to_members_from_invited_point" = "%@ yerra azray n texxamt si sya ɣer sdat yettban i meṛṛa imttekkiyen n texxamt, seg wasmi ara d-ttusnubegten.";
+"notice_room_history_visible_to_anyone" = "%@ yerra azray n texxamt si sya ɣer sdat yettban i yal yiwen.";
+"notice_room_history_visible_to_members" = "%@ yerra azray n texxamt si sya ɣer sdat yettban i meṛṛa imttekkiyen n texxmat.";
+"notice_room_history_visible_to_members_for_dm" = "%@ yerra iznan si sya ɣer sdat ttbanen i meṛṛa imttekkiyen n texxamt.";
+"notice_error_unknown_event_type" = "Anaw n uneḍru d arussin";
+"notice_error_unexpected_event" = "Aneḍru ur nettwaṛǧæ ara";
+"notice_error_unsupported_event" = "Aneḍru ur yettusefrak ara";
+"notice_redaction" = "%@ yekkes aneḍru (asulay: %@)";
+"notice_unsupported_attachment" = "taceqquft yeddan ur tettusefrak ara: %@";
+"notice_location_attachment" = "adig yeddan";
+"notice_video_attachment" = "tavidyut yeddan";
+"notice_audio_attachment" = "ameslaw yeddan";
+"notice_image_attachment" = "tugna yeddan";
+"notice_encryption_enabled_unknown_algorithm" = "%1$@ yermed awgelhen seg yixef ɣer yixef (alguritm %2$@ d arussin).";
+"notice_encryption_enabled_ok" = "%@ yermed awgelhen seg yixef ɣer yixef.";
+"notice_room_aliases_for_dm" = "Ismawen yettunefken d: %@";
+"notice_room_aliases" = "Ismawen yettunefken i texxamt d: %@";
+"notice_room_power_level_event_requirement" = "Iswiren n tezmert addayen icudden ɣer yineḍruyen d:";
+"notice_room_power_level_acting_requirement" = "Iswiren n tezmert addayen i yezmer ad yesεu useqdac send asedmer d:";
+"notice_room_power_level_intro" = "Aswir n tezmert n yimttekkiyen n texxamt d:";
+"notice_room_power_level_intro_for_dm" = "Aswir n tezmert n yimttekkiyen d:";
+"notice_room_join_rule_public_by_you_for_dm" = "Terriḍ aya d azayaz.";
+"notice_room_join_rule_public_by_you" = "Terriḍ taxxamt d tazayazt.";
+"notice_event_redacted_reason" = " [ssebba: %@]";
+"notice_room_join_rule_public_for_dm" = "%@ yerra aya d azayaz.";
+"notice_room_join_rule_invite_for_dm" = "%@ yerra aya s tinubga kan.";
+// New
+"notice_room_join_rule_invite" = "%@ yerra taxxamt s tinubga kan.";
+// Old
+"notice_room_join_rule" = "Alugan n tmerna d: %@";
+"notice_room_topic_removed" = "%@ yekkes asentel";
+"notice_room_name_removed" = "%@ yekkes isem n texxamt";
+"notice_room_name_removed_for_dm" = "%@ yekkes isem";
+"end_call" = "Kfu asiwel";
+"invite_user" = "Snubget-d aseqdac n Matrix";
+"attach_media" = "Seddu amidyat seg temkarḍit";
+"set_moderator" = "Sbadu imḍebber";
+"set_power_level" = "Sbadu aswir n tezmert";
+"set_default_power_level" = "Wennez aswir n tezmert";
+"login_error_forgot_password_is_not_supported" = "Tatut n wawal uffir ur yettusefrak ara akka tura";
+"login_error_do_not_support_login_flows" = "Akka tura ur nsefrak ara ula yiwen neɣ meṛṛa aragen yettusbadun s uqeddac-a agejdan";
+"login_error_no_login_flow" = "Tiririt n telɣut n usesteb seg uqeddac-a agejdan ur teddi ara";
+"login_error_title" = "Anekcum yecceḍ";
+"login_email_info" = "Afran n tansa n yiamyl tettaǧǧa iseqdacen-nniḍen ad ak·akem-afen deg Matrix s sshala, rnu ad ak-tmudd abrid ad twennzeḍ awala-ik·im uffir ar sdat.";
+"login_display_name_placeholder" = "Isem yettwaskanen (am. Bob Obson)";
+"login_user_id_placeholder" = "Asulay n Matrix (am. @bob:matrix.org neɣ bob)";
+"login_identity_server_info" = "Matrix yettmuddu-d iqeddacen n timagit i ucuddu n yimaylen, atg. Wuɣur ttuɣalen yisulayen n Matrix. Ala https://matrix.org i yellan akka tura.";
+"login_home_server_info" = "Aqeddac-ik·im agejdan isseklas meṛṛa idiwenniyen-inek·inem d yisefka n umiḍan-inekịnem";
diff --git a/Riot/Modules/MatrixKit/Assets/MatrixKitAssets.bundle/ko.lproj/MatrixKit.strings b/Riot/Modules/MatrixKit/Assets/MatrixKitAssets.bundle/ko.lproj/MatrixKit.strings
new file mode 100644
index 000000000..88e2af12a
--- /dev/null
+++ b/Riot/Modules/MatrixKit/Assets/MatrixKitAssets.bundle/ko.lproj/MatrixKit.strings
@@ -0,0 +1,381 @@
+"view" = "보기";
+"back" = "돌아가기";
+"continue" = "계속";
+"leave" = "떠나기";
+"invite" = "초대";
+"retry" = "다시 시도";
+"cancel" = "취소";
+"save" = "저장";
+"matrix" = "Matrix";
+// Login Screen
+"login_create_account" = "계정 만들기:";
+"login_server_url_placeholder" = "URL (예: https://matrix.org)";
+"login_home_server_title" = "홈서버 URL:";
+"login_identity_server_title" = "ID 서버 URL:";
+"login_user_id_placeholder" = "Matrix ID (예: @bob:matrix.org 혹은 bob)";
+"login_password_placeholder" = "비밀번호";
+"login_optional_field" = "선택";
+"login_email_placeholder" = "이메일 주소";
+"login_error_title" = "로그인 실패";
+"login_error_forbidden" = "잘못된 이름/비밀번호";
+"login_error_user_in_use" = "이 이름은 이미 사용중입니다";
+"login_leave_fallback" = "취소";
+"register_error_title" = "가입 실패";
+"login_error_forgot_password_is_not_supported" = "비밀번호 찾기는 현재 지원하지 않습니다";
+"login_mobile_device" = "모바일";
+"login_tablet_device" = "태블릿";
+"login_desktop_device" = "데스크톱";
+"login_error_resource_limit_exceeded_message_monthly_active_user" = "이 홈서버가 월 간 활성 사용자 한도를 초과했습니다.";
+"login_error_resource_limit_exceeded_message_contact" = "\n\n서비스를 계속 이용하려면 서비스 관리자에게 연락하세요.";
+"login_error_resource_limit_exceeded_contact_button" = "관리자에게 연락하기";
+// Action
+"no" = "아니요";
+"yes" = "네";
+"close" = "닫기";
+"sign_up" = "등록하기";
+"submit" = "제출";
+"submit_code" = "코드 제출";
+"set_moderator" = "중재자로 설정";
+"set_admin" = "관리자로 설정";
+"start_chat" = "대화 시작";
+"abort" = "중단";
+"discard" = "삭제";
+"start_voice_call" = "음성 통화 시작";
+"start_video_call" = "영상 통화 시작";
+"select_account" = "계정 선택";
+"capture_media" = "사진/영상 찍기";
+"invite_user" = "Matrix 사용자 초대";
+"reset_to_default" = "기본으로 되돌리기";
+"resend_message" = "메시지 다시 보내기";
+"select_all" = "모두 선택";
+"cancel_upload" = "업로드 취소";
+"cancel_download" = "다운로드 취소";
+"show_details" = "세부 정보 표시";
+"answer_call" = "전화 받기";
+"reject_call" = "전화 거부";
+"end_call" = "전화 끝내기";
+"ignore" = "무시";
+"unignore" = "무시하지 않기";
+"notice_sticker" = "스티커";
+// room display name
+"room_displayname_empty_room" = "빈 방";
+"room_displayname_two_members" = "%@님과 %@님";
+// Settings
+"settings" = "설정";
+"settings_enable_push_notifications" = "푸시 알림 켜기";
+// Devices
+"device_details_title" = "기기 정보\n";
+"device_details_name" = "공개 이름\n";
+"device_details_identifier" = "ID\n";
+"device_details_rename_prompt_message" = "기기의 공개 이름은 대화하는 사람들에게 보여집니다";
+"device_details_delete_prompt_title" = "확인";
+"device_details_delete_prompt_message" = "이 작업은 추가 확인이 필요합니다.\n계속하려면, 비밀번호를 입력해주세요.";
+// Encryption information
+"room_event_encryption_info_title" = "종단간 암호화 정보\n\n";
+"room_event_encryption_info_event_user_id" = "사용자 ID\n";
+"room_event_encryption_info_event_algorithm" = "알고리즘\n";
+"room_event_encryption_info_event_unencrypted" = "암호화되지 않음";
+"room_event_encryption_info_device" = "\n발신자 기기 정보\n";
+"room_event_encryption_info_device_unknown" = "알 수 없는 기기\n";
+"room_event_encryption_info_device_name" = "공개 이름\n";
+"room_event_encryption_info_device_id" = "ID\n";
+"room_event_encryption_info_device_verification" = "확인\n";
+"room_event_encryption_info_device_fingerprint" = "Ed25519 지문\n";
+"room_event_encryption_info_device_verified" = "확인됨";
+"room_event_encryption_info_device_not_verified" = "확인되지 않음";
+"account_link_email" = "이메일 연결";
+"account_linked_emails" = "이메일 연결함";
+// Others
+"user_id_title" = "사용자 ID:";
+"offline" = "오프라인";
+"error" = "오류";
+"not_supported_yet" = "아직 지원하지 않음";
+"default" = "기본";
+"ssl_homeserver_url" = "홈서버 URL: %@";
+// Country picker
+"country_picker_title" = "나라를 고르세요";
+// Language picker
+"language_picker_title" = "언어를 고르세요";
+"language_picker_default_language" = "기본 (%@)";
+"login_home_server_info" = "당신의 홈서버는 대화와 계정 정보를 저장합니다";
+"login_identity_server_info" = "Matrix는 어떤 이메일이 어떤 Matrix ID에 속하느 지 추적하도록 ID 서버를 제공합니다. 현재는 https://matrix.org만 존재합니다.";
+"login_display_name_placeholder" = "표시 이름 (예: Bob Obson)";
+"login_email_info" = "다른 사용자가 Matrix에서 당신을 더 쉽게 찾고, 이후 비밀번호를 다시 설정할 수 있도록 이메일 주소를 지정하세요.";
+"login_prompt_email_token" = "이메일 확인 토큰을 입럭해주세요:";
+"login_error_no_login_flow" = "이 홈서버에서 확인 정보를 검색하는데 실패함";
+"login_error_do_not_support_login_flows" = "현재 이 홈서버가 정의한 일부 혹은 모든 로그인 흐름을 지원하지 않음";
+"login_error_registration_is_not_supported" = "등록을 현재 지원하지 않음";
+"login_error_unknown_token" = "지정된 접근 토큰이 인식되지 않음";
+"login_error_bad_json" = "잘못된 JSON";
+"login_error_not_json" = "올바른 JSON을 포함하지 않음";
+"login_error_limit_exceeded" = "너무 많은 요청을 보냈습니다";
+"login_error_login_email_not_yet" = "아직 클릭하지 않은 이메일 링크";
+"login_use_fallback" = "대체 페이지 사용";
+"login_invalid_param" = "잘못된 매개변수";
+"login_error_resource_limit_exceeded_title" = "리소스 한도 초과됨";
+"login_error_resource_limit_exceeded_message_default" = "이 홈서버가 리소스 한도를 초과했습니다.";
+"dismiss" = "버리기";
+"set_power_level" = "권한 등급 설정";
+"set_default_power_level" = "권한 등급 다시 설정";
+"mention" = "언급";
+"attach_media" = "라이브러리에서 미디어 첨부";
+// Events formatter
+"notice_avatar_changed_too" = "(아바타도 변경됬습니다)";
+"notice_room_name_removed" = "%@님이 방 이름을 제거했습니다";
+"notice_room_topic_removed" = "%@님이 주제를 제거했습니다";
+"notice_event_redacted" = "<%@ 검열됨>";
+"notice_event_redacted_by" = " 사용자 %@님";
+"notice_event_redacted_reason" = " [이유: %@]";
+"notice_profile_change_redacted" = "%@님이 프로필 %@을(를) 업데이트했습니다";
+"notice_room_created" = "%@님이 방을 만들었습니다";
+"notice_room_join_rule" = "참가 규칙: %@";
+"notice_room_power_level_intro" = "방 구성원의 권한 등급:";
+"notice_room_power_level_acting_requirement" = "사용자가 활동할 수 있는 최소 권한 등급:";
+"notice_room_power_level_event_requirement" = "이벤트와 관련된 최소 권한 등급:";
+"notice_room_aliases" = "방의 별칭: %@";
+"notice_room_related_groups" = "이 방과 관련된 그룹: %@";
+"notice_encrypted_message" = "암호화된 메시지";
+"notice_encryption_enabled" = "%@님이 종단간 암호화를 켰습니다 (알고리즘 %@)";
+"notice_image_attachment" = "사진 첨부";
+"notice_audio_attachment" = "소리 첨부";
+"notice_video_attachment" = "영상 첨부";
+"notice_location_attachment" = "위치 첨부";
+"notice_file_attachment" = "파일 첨부";
+"notice_invalid_attachment" = "잘못된 첨부";
+"notice_unsupported_attachment" = "지원하지 않는 첨부: %@";
+"notice_feedback" = "피드백 이벤트 (ID: %@): %@";
+"notice_redaction" = "%@님이 이벤트를 검열했습니다 (ID: %@)";
+"notice_error_unsupported_event" = "지원하지 않는 이벤트";
+"notice_error_unexpected_event" = "예기치 못한 이벤트";
+"notice_error_unknown_event_type" = "알 수 없는 이벤트 유형";
+"notice_room_history_visible_to_anyone" = "%@님이 이후 방 기록을 누구나 볼 수 있게 했습니다.";
+"notice_room_history_visible_to_members" = "%@님이 이후 방 기록을 모든 방 구성원이 볼 수 있게 했습니다.";
+"notice_room_history_visible_to_members_from_invited_point" = "%@님이 이후 방 기록을 초대된 시점부터 모든 방 구성원이 볼 수 있게 했습니다.";
+"notice_room_history_visible_to_members_from_joined_point" = "%@님이 이후 방 기록을 참가한 시점부터 모든 방 구성원이 볼 수 있게 했습니다.";
+"notice_crypto_unable_to_decrypt" = "** 암호를 복호화할 수 없음: %@ **";
+"notice_crypto_error_unknown_inbound_session_id" = "발신자의 기기에서 이 메시지의 키를 보내지 않았습니다.";
+"notice_in_reply_to" = "관련 대화";
+"room_displayname_more_than_two_members" = "%@님 외 %@명";
+"settings_enable_inapp_notifications" = "인앱 알림 켜기";
+"settings_enter_validation_token_for" = "%@의 확인 토큰을 입력하세요:";
+"notification_settings_room_rule_title" = "방: '%@'";
+"device_details_last_seen" = "마지막으로 본 순간\n";
+"device_details_last_seen_format" = "%@ @ %@\n";
+"room_event_encryption_info_event" = "이벤트 정보\n";
+"room_event_encryption_info_event_identity_key" = "Curve25519 ID 키\n";
+"room_event_encryption_info_event_fingerprint_key" = "Ed25519 핑거프린트 키가 필요함\n";
+"room_event_encryption_info_event_session_id" = "세션 ID\n";
+"room_event_encryption_info_event_decryption_error" = "암호 복호화 오류\n";
+"room_event_encryption_info_event_none" = "없음";
+"room_event_encryption_info_device_blocked" = "블랙리스트 대상";
+"room_event_encryption_info_verify" = "확인 중...";
+"room_event_encryption_info_unverify" = "확인하지 않음";
+"room_event_encryption_info_block" = "블랙리스트";
+"room_event_encryption_info_unblock" = "블랙리스트 제외";
+"room_event_encryption_verify_title" = "기기 확인\n\n";
+"room_event_encryption_verify_message" = "이 기기를 신뢰할 수 있는지 확인하려면, 다른 방법을 사용하여 소유자와 연락해주세요 (예: 현실에서 혹은 전화로) 그리고 이 기기의 사용자 설정에서 볼 수 있는 키가 아래의 키와 일치하는지 물어보세요:\n\n\t기기 이름: %@\n\t기기 ID: %@\n\t기기 키: %@\n\n그것이 맞다면, 아래 확인 버튼을 누르세요. 맞지 않다면, 다른 사람이 이 기기를 가로채고 있는 것이고 블랙리스트에 올려야 합니다.\n\n앞으로 이 확인 절차는 더 정교해질 것입니다.";
+"room_event_encryption_verify_ok" = "확인";
+// Account
+"account_save_changes" = "변경 사항 저장";
+"account_email_validation_title" = "확인 보류 중";
+"account_email_validation_message" = "이메일을 확인하고 거기에 있는 링크를 클릭해주세요. 모두 끝나면, 계속을 클릭하세요.";
+"account_email_validation_error" = "이메일 주소를 확인할 수 없습니다. 이메일을 확인하고 거기에 있는 링크를 클릭해주세요. 모두 끝나면, 계속을 클릭하세요";
+"account_msisdn_validation_title" = "확인 보류 중";
+"account_msisdn_validation_message" = "활성 코드가 있는 SMS를 보냈습니다. 아래에 이 코드를 입력해주세요.";
+"account_msisdn_validation_error" = "전화번호를 확인할 수 없습니다.";
+"account_error_display_name_change_failed" = "표시 이름 변경에 실패함";
+"account_error_picture_change_failed" = "사진 변경에 실패함";
+"account_error_matrix_session_is_not_opened" = "Matrix 세션이 열리지 않았습니다";
+"account_error_email_wrong_title" = "올바르지 않은 이메일 주소";
+"account_error_email_wrong_description" = "올바른 이메일 주소로 보이지 않습니다";
+"account_error_msisdn_wrong_title" = "올바르지 않은 전화번호";
+"account_error_msisdn_wrong_description" = "올바른 전화번호로 보이지 않습니다";
+"account_error_push_not_allowed" = "알림이 허용되지 않음";
+// Room creation
+"room_creation_name_title" = "방 이름:";
+"room_creation_name_placeholder" = "(예: lunchGroup)";
+"room_creation_alias_title" = "방 별칭:";
+"room_creation_alias_placeholder" = "(예: #foo:example.org)";
+"room_creation_alias_placeholder_with_homeserver" = "(예: #foo%@)";
+"room_creation_participants_title" = "참가자:";
+"room_creation_participants_placeholder" = "(예: @bob:homeserver1; @john:homeserver2...)";
+// Room
+"room_please_select" = "방을 선택해주세요";
+"room_error_join_failed_title" = "방 참가에 실패함";
+"room_error_join_failed_empty_room" = "현재 빈 방에 다시 참가할 수 없습니다.";
+"room_error_name_edition_not_authorized" = "이 방 이름을 편집할 권한이 없습니다";
+"room_error_topic_edition_not_authorized" = "이 방 주제를 편집할 권한이 없습니다";
+"room_error_cannot_load_timeline" = "타임라인 불러오기에 실패함";
+"room_error_timeline_event_not_found_title" = "타임라인 위치 불러오기에 실패함";
+"room_error_timeline_event_not_found" = "애플리케이션이 이 방의 타임라인에서 특정 시점을 불러오려 했으나 찾을 수 없었습니다";
+"room_left" = "당신은 방을 떠났습니다";
+"room_no_power_to_create_conference_call" = "이 방에 회의를 시작하려면 초대할 권한이 필요합니다";
+"room_no_conference_call_in_encrypted_rooms" = "암호화된 방에서 회의 전화는 지원되지 않습니다";
+// Reply to message
+"message_reply_to_sender_sent_an_image" = "사진 보내기.";
+"message_reply_to_sender_sent_a_video" = "영상 보내기.";
+"message_reply_to_sender_sent_an_audio_file" = "음성 파일 보내기.";
+"message_reply_to_sender_sent_a_file" = "파일 보내기.";
+"message_reply_to_message_to_reply_to_prefix" = "관련 대화";
+// Room members
+"room_member_ignore_prompt" = "이 사용자의 모든 메시지를 숨기겠습니까?";
+"room_member_power_level_prompt" = "사용자를 자신과 같은 권한 등급으로 승급시키는 변경 사항은 취소할 수 없습니다.\n확신합니까?";
+// Attachment
+"attachment_size_prompt" = "다음으로 보내겠습니까:";
+"attachment_original" = "실제 크기: %@";
+"attachment_small" = "작게: %@";
+"attachment_medium" = "중간: %@";
+"attachment_large" = "크게: %@";
+"attachment_cancel_download" = "다운로드를 취소합니까?";
+"attachment_cancel_upload" = "업로드를 취소합니까?";
+"attachment_multiselection_size_prompt" = "다음으로 사진을 보내겠습니까:";
+"attachment_multiselection_original" = "실제 크기";
+"attachment_e2e_keys_file_prompt" = "이 파일은 Matrix 클라이언트에서 내보낸 암호화 키를 갖고 있습니다.\n파일 내용물을 보거나 갖고 있는 키를 가져오고 싶나요?";
+"attachment_e2e_keys_import" = "가져오기...";
+// Contacts
+"contact_mx_users" = "Matrix 사용자";
+"contact_local_contacts" = "로컬 연락처";
+// Groups
+"group_invite_section" = "초대";
+"group_section" = "그룹";
+// Search
+"search_no_results" = "결과 없음";
+"search_searching" = "검색 중...";
+// Time
+"format_time_s" = "초";
+"format_time_m" = "분";
+"format_time_h" = "시";
+"format_time_d" = "일";
+// E2E import
+"e2e_import_room_keys" = "방 키 가져오기";
+"e2e_import_prompt" = "이 과정으로 다른 Matrix 클라이언트에서 이전에 내보낸 암호화 키를 가져올 수 있습니다. 이 키로 다른 클라이언트에서 복호화할 수 있는 모든 메시지를 복호화할 수 있게 됩니다.\n내보낸 파일은 암호로 보호됩니다. 파일을 복호화하려면 여기에 암호를 입력해야 합니다.";
+"e2e_import" = "가져오기";
+"e2e_passphrase_enter" = "암호 입력";
+// E2E export
+"e2e_export_room_keys" = "방 키 내보내기";
+"e2e_export_prompt" = "이 과정으로 암호화된 방에서 받은 메시지의 키를 로컬 파일로 내보낼 수 있습니다. 그런 다음 이후 다른 Matrix 클라이언트에 파일을 가져올 수 있습니다, 이 키로 메시지를 복호화할 수 있게 됩니다.\n파일을 읽을 수 있는 모든 사용자는 내보낸 파일로 볼 수 있는 암호화된 메시지를 복호화할 수 있으므로, 안전하게 보관해야 합니다.";
+"e2e_export" = "내보내기";
+"e2e_passphrase_confirm" = "암호 확인";
+"e2e_passphrase_empty" = "암호를 입력해주세요";
+"e2e_passphrase_not_match" = "암호가 일치하지 않음";
+"e2e_passphrase_create" = "암호 만들기";
+"unsent" = "보내지지 않음";
+"error_common_message" = "오류가 발생했습니다. 나중에 다시 시도해주세요.";
+"private" = "보안";
+"public" = "공개";
+"power_level" = "권한 등급";
+"network_error_not_reachable" = "네트워크 연결 상태를 확인해주세요";
+"user_id_placeholder" = "예: @bob:homeserver";
+// Permissions
+"camera_access_not_granted_for_call" = "영상 통화를 하려면 카메라에 접근해야 하지만 %@은(는) 사용할 권한이 없습니다";
+"microphone_access_not_granted_for_call" = "전화를 하려면 마이크에 접근해야 하지만 %@은(는) 사용할 권한이 없습니다";
+"local_contacts_access_not_granted" = "로컬 연락처에서 사용자를 검색하려면 연락처에 접근해야 하지만 %@은(는) 사용할 권한이 없습니다";
+"local_contacts_access_discovery_warning_title" = "사용자 검색";
+"local_contacts_access_discovery_warning" = "Matrix를 사용 중인 연락처 사람들을 찾기 위해 %@은(는) 주소록에 있는 이메일 주소와 전화번호를 선택한 Matrix ID 서버로 보낼 수 있습니다. 서버가 지원한다면, 개인 정보는 보내기 전에 해시됩니다 - 더 자세한 정보는 ID 서버의 개인 정보 정책을 확인해주세요.";
+"notice_room_invite" = "%@님이 %@님을 초대했습니다";
+"notice_room_third_party_invite" = "%@님이 %@님에게 방에 참가하라는 초대를 보냈습니다";
+"notice_room_third_party_registered_invite" = "%@님이 %@님의 초대를 수락했습니다";
+"notice_room_join" = "%@님이 참가했습니다";
+"notice_room_leave" = "%@님이 떠났습니다";
+"notice_room_reject" = "%@님이 초대를 거절했습니다";
+"notice_room_kick" = "%@님이 %@님을 추방했습니다";
+"notice_room_unban" = "%@님이 %@님의 출입 금지를 풀었습니다";
+"notice_room_ban" = "%@님이 %@님을 출입 금지했습니다";
+"notice_room_withdraw" = "%@님이 %@님의 초대를 취소했습니다";
+"notice_room_reason" = ". 이유: %@";
+"notice_avatar_url_changed" = "%@님이 아바타를 바꿨습니다";
+"notice_display_name_set" = "%@님이 표시 이름을 %@(으)로 설정했습니다";
+"notice_display_name_changed_from" = "%@님이 표시 이름을 %@에서 %@(으)로 바꿨습니다";
+"notice_display_name_removed" = "%@님이 표시 이름을 제거했습니다";
+"notice_topic_changed" = "%@님이 주제를 다음으로 바꿨습니다: %@";
+"notice_room_name_changed" = "%@님이 방 이름을 다음으로 바꿨습니다: %@";
+"notice_placed_voice_call" = "%@님이 음성 통화를 걸었습니다";
+"notice_placed_video_call" = "%@님이 영상 통화를 걸었습니다";
+"notice_answered_video_call" = "%@님이 전화를 받았습니다";
+"notice_ended_video_call" = "%@님이 전화를 끊었습니다";
+"notice_conference_call_request" = "%@님이 VoIP 회의를 요청했습니다";
+"notice_conference_call_started" = "VoIP 회의가 시작했습니다";
+"notice_conference_call_finished" = "VoIP 회의가 끝났습니다";
+// button names
+"ok" = "예";
+"send" = "보내기";
+"copy_button_name" = "복사";
+"resend" = "다시 보내기";
+"redact" = "감추기";
+"share" = "공유";
+"delete" = "삭제";
+// actions
+"action_logout" = "로그아웃";
+"create_room" = "방 만들기";
+"login" = "로그인";
+"create_account" = "계정 만들기";
+"membership_invite" = "초대받음";
+"membership_leave" = "떠남";
+"membership_ban" = "출입 금지당함";
+"num_members_one" = "%@명의 사용자";
+"num_members_other" = "%@명의 사용자";
+"kick" = "추방";
+"ban" = "출입 금지";
+"unban" = "출입 금지 풀기";
+"message_unsaved_changes" = "저장하지 않은 변경 사항이 있습니다. 떠나게 되면 변경 사항은 버려집니다.";
+// Login Screen
+"login_error_already_logged_in" = "이미 로그인됨";
+"login_error_must_start_http" = "URL은 http[s]://로 시작해야 함";
+// room details dialog screen
+"room_details_title" = "방 세부 사항";
+// contacts list screen
+"invitation_message" = "저는 Matrix로 당신과 대화하고 싶습니다. 자세한 정보는 웹사이트 http://martix.org에 방문해주세요.";
+// Settings screen
+"settings_title_config" = "설정";
+"settings_title_notifications" = "알림";
+// Notification settings screen
+"notification_settings_disable_all" = "모든 알림 끄기";
+"notification_settings_enable_notifications" = "알림 켜기";
+"notification_settings_enable_notifications_warning" = "모든 기기에 모든 알림이 현재부터 꺼집니다.";
+"notification_settings_global_info" = "알림 설정은 사용자 계정에 저장되며 계정을 등록한 모든 클라이언트 간에 공유됩니다 (데스크톱 알림 포함)\n\n규칙은 순서대로 적용됩니다; 일치하는 것의 첫 번째 규칙은 메시지의 결과를 정의하는 것입니다.\n따라서: 발신자 별 알림보다 방 별 알림이, 방 별 알림보다 단어 별 알림이 더 중요합니다.\n동일한 종류의 여러 규칙이 있다면, 목록에서 일치하는 첫 번째 규칙이 우선됩니다.";
+"notification_settings_per_word_notifications" = "단어 별 알림";
+"notification_settings_per_word_info" = "단어는 대소문자를 구분하지 않고, * 와이드카드 기호를 넣을 수 있습니다. 따라서:\nfoo라는 단어는 양끝에 단어가 이어지지 않은 경우 (예: 문장 부호, 공백 또는 문장의 시작과 끝)에 있는 단어를 맞춥니다.\nfoo*는 foo로 시작하는 모든 단어를 맞춥니다.\n*foo*는 foo라는 세 글자를 포함하는 모든 단어를 맞춥니다.";
+"notification_settings_always_notify" = "항상 알림";
+"notification_settings_never_notify" = "절대 알리지 않기";
+"notification_settings_word_to_match" = "맞춰볼 단어";
+"notification_settings_highlight" = "강조";
+"notification_settings_custom_sound" = "맞춤 소리";
+"notification_settings_per_room_notifications" = "방 별 알림";
+"notification_settings_per_sender_notifications" = "발신자 별 알림";
+"notification_settings_sender_hint" = "@user:domain.com";
+"notification_settings_select_room" = "방 선택";
+"notification_settings_other_alerts" = "기타 경고";
+"notification_settings_contain_my_user_name" = "내 사용자 이름이 있는 메시지에 대해 소리로 알림";
+"notification_settings_contain_my_display_name" = "내 표시 이름이 있는 메시지에 대해 소리로 알림";
+"notification_settings_just_sent_to_me" = "나에게만 온 메시지에 대해 소리로 알림";
+"notification_settings_invite_to_a_new_room" = "내가 새 방에 초대받았을 때 알림";
+"notification_settings_people_join_leave_rooms" = "사람들이 참가하고 떠날 때 알림";
+"notification_settings_receive_a_call" = "전화를 받을 때 알림";
+"notification_settings_suppress_from_bots" = "봇의 알림 억제";
+"notification_settings_by_default" = "기본으로 되돌리기...";
+"notification_settings_notify_all_other" = "모든 다른 메시지/방의 알림";
+// gcm section
+"settings_config_home_server" = "홈서버: %@";
+"settings_config_identity_server" = "ID 서버: %@";
+"settings_config_user_id" = "사용자 ID: %@";
+// call string
+"call_waiting" = "대기 중...";
+"call_connecting" = "전화 연결 중...";
+"call_ended" = "전화 종료됨";
+"call_ring" = "전화 중...";
+"incoming_video_call" = "수신 영상 통화";
+"incoming_voice_call" = "수신 음성 통화";
+"call_invite_expired" = "전화 초대가 만료됨";
+// unrecognized SSL certificate
+"ssl_trust" = "신뢰";
+"ssl_logout_account" = "로그아웃";
+"ssl_remain_offline" = "무시";
+"ssl_fingerprint_hash" = "핑거프린트 (%@):";
+"ssl_could_not_verify" = "원격 서버의 ID를 확인할 수 없습니다.";
+"ssl_cert_not_trust" = "누군가가 악의적으로 트래픽을 가로채고 있거나, 휴대 전화가 원격 서버에서 제공한 인증을 신뢰하지 않습니다.";
+"ssl_cert_new_account_expl" = "서버 관리자가 예상된다고 대답했다면, 아래 핑거프린트가 그들이 제공한 핑거프린트와 맞는지 확인하세요.";
+"ssl_unexpected_existing_expl" = "휴대 전화를 인증했던 인증서가 다른 것으로 변경되었습니다. 이것은 매우 비정상적입니다. 이 새로운 인증서를 수락하지 않는 것을 권합니다.";
+"ssl_expected_existing_expl" = "인증서가 신뢰했던 것에서 신뢰하지 않은 것으로 변경되었습니다. 서버가 인증서를 세로 작성했을 수 있습니자. 예상되는 핑거프린트를 위해 서버 관리자에게 연락하세요.";
+"ssl_only_accept" = "위의 것과 일치한 핑거프린트를 서버 관리자가 게시해야 인증서를 수락할 수 있습니다.";
+"notice_room_third_party_revoked_invite" = "%@님이 %@님에게 보낸 초대를 취소했습니다";
+"device_details_rename_prompt_title" = "기기 이름";
diff --git a/Riot/Modules/MatrixKit/Assets/MatrixKitAssets.bundle/lv.lproj/MatrixKit.strings b/Riot/Modules/MatrixKit/Assets/MatrixKitAssets.bundle/lv.lproj/MatrixKit.strings
new file mode 100644
index 000000000..b7e38eef4
--- /dev/null
+++ b/Riot/Modules/MatrixKit/Assets/MatrixKitAssets.bundle/lv.lproj/MatrixKit.strings
@@ -0,0 +1,119 @@
+"matrix" = "Matrix";
+// Login Screen
+"login_create_account" = "Izveidot kontu:";
+"login_server_url_placeholder" = "URL adrese (piemēram, https://matrix.org)";
+"login_home_server_title" = "Mājas serveria URL:";
+"login_home_server_info" = "Tavs mājas serveris glabā visas sarunas un konta datus";
+"login_identity_server_title" = "Identifikācijas servera URL:";
+"view" = "Skatīt";
+"back" = "Atpakaļ";
+"continue" = "Turpināt";
+"leave" = "Atstāt";
+"invite" = "Uzaicināt";
+"retry" = "Atgriezties";
+"cancel" = "Atcelt";
+"save" = "Saglabāt";
+"login_password_placeholder" = "Parole";
+"login_display_name_placeholder" = "Displeja vārds (piem. Bob Obson)";
+"login_email_placeholder" = "Epasta adrese";
+"login_prompt_email_token" = "Lūdzu ievadi savu epasta pārbaudes kodu:";
+"login_error_title" = "Piekļuve neizdevās";
+"login_error_registration_is_not_supported" = "Reģistrācija pašlaik netiek atbalstīta";
+"login_error_forbidden" = "Nederīgs lietotājvārds/parole";
+"login_error_limit_exceeded" = "Pārāk daudz pieprasījumi tikuši izsūtīti";
+"login_error_user_in_use" = "Šis lietotājvārds jau tiek izmantots";
+"login_leave_fallback" = "Atcelt";
+"login_invalid_param" = "Nepareizs parametrs";
+"register_error_title" = "Reģistrācija neizdevās";
+"login_error_resource_limit_exceeded_title" = "Resursu Limits Pārsniegts";
+"yes" = "Jā";
+"close" = "Aizvērt";
+"sign_up" = "Pieteikties";
+"start_chat" = "Sākt čatu";
+"start_voice_call" = "Sākt Balss Zvanu";
+"start_video_call" = "Sākt Video Zvanu";
+"select_account" = "Izvēlies kontu";
+"invite_user" = "Uzaicini matrix Lietotāju";
+"select_all" = "Izvēlēties Visus";
+"cancel_upload" = "Atcelt Augšupielādi";
+"cancel_download" = "Atcelt Lejupielādi";
+"show_details" = "Rādīt Detaļas";
+"answer_call" = "Pacelt Zvanu";
+"reject_call" = "Atteikt Zvanu";
+"end_call" = "Beigt Zvanu";
+"ignore" = "Ignorēt";
+"unignore" = "At-Ignorēt";
+"notice_room_name_removed" = "%@ noņēma istabas nosaukumu";
+"notice_room_created" = "%@ izveidoja istabu";
+"notice_encrypted_message" = "Šifrēts ziņojums";
+"notice_image_attachment" = "attēla pielikums";
+"notice_audio_attachment" = "audio pielikums";
+"notice_video_attachment" = "video pielikums";
+"notice_location_attachment" = "atrašanās vietas pielikums";
+"notice_file_attachment" = "datnes pielikums";
+"notice_invalid_attachment" = "nederīgs pielikums";
+"notice_unsupported_attachment" = "Neatbalstīts pielikums: %@";
+"notice_error_unsupported_event" = "Neatbalstīts notikums";
+"notice_error_unexpected_event" = "Negaidīts notikums";
+"notice_error_unknown_event_type" = "Nezināms notikuma veids";
+"notice_sticker" = "uzlīme";
+"room_displayname_two_members" = "%@ un %@";
+"room_displayname_more_than_two_members" = "%@ un %@ citi";
+"notification_settings_room_rule_title" = "Istaba: '%@'";
+"device_details_identifier" = "ID\n";
+"device_details_last_seen" = "Pēdējoreiz redzēts\n";
+"device_details_rename_prompt_title" = "Sesijas Nosaukums";
+"device_details_delete_prompt_title" = "Autentifikācija";
+"room_event_encryption_info_event_user_id" = "Lietotāja ID\n";
+"room_event_encryption_info_event_identity_key" = "Curve25519 identitātes atslēga\n";
+"room_event_encryption_info_event_algorithm" = "Algoritms\n";
+"room_event_encryption_info_event_session_id" = "Sesijas ID\n";
+"room_event_encryption_info_event_decryption_error" = "Atšifrēšanas kļūda\n";
+"room_event_encryption_info_event_none" = "nav";
+"room_event_encryption_info_device_unknown" = "nezināma sesija\n";
+"room_event_encryption_info_device_name" = "Publiskais Nosaukums\n";
+"room_event_encryption_info_device_id" = "ID\n";
+"room_event_encryption_info_device_verification" = "Verifikācija\n";
+"room_event_encryption_info_device_verified" = "Verificēts";
+"room_event_encryption_info_device_not_verified" = "NAV verificēts";
+"notice_room_name_removed_for_dm" = "%@ noņēma nosaukumu";
+
+// Events formatter
+"notice_avatar_changed_too" = "(avatars arī tika nomainīts)";
+"resume_call" = "Atjaunot";
+"resend_message" = "Atkārtot ziņojuma sūtīšanu";
+"reset_to_default" = "Atietatīt uz noklusējumu";
+"capture_media" = "Uzņemt foto/video";
+"attach_media" = "Pievienot mediju no bibliotēkas";
+"mention" = "Pieminēt";
+"set_admin" = "Norādīt administratoru";
+"set_moderator" = "Norādīt moderatoru";
+"set_default_power_level" = "Nodzēst jaudas līmeni";
+"set_power_level" = "Iestatīt jaudas līmeni";
+"submit_code" = "Iesniegt kodu";
+"submit" = "Iesniegt";
+"dismiss" = "Noraidīt";
+"discard" = "Izmest";
+"abort" = "Pārtraukt";
+
+// Action
+"no" = "Nē";
+"login_error_resource_limit_exceeded_contact_button" = "Sazinieties ar administratoru";
+"login_error_resource_limit_exceeded_message_contact" = "\n\nLai turpinātu lietot šo pakalpojumu, lūdzu, sazinieties ar savu pakalpojuma administratoru.";
+"login_error_resource_limit_exceeded_message_monthly_active_user" = "Šis mājas serveris ir sasniedzis ikmēneša aktīvo lietotāju limitu.";
+"login_error_resource_limit_exceeded_message_default" = "Šis mājas serveris ir sasniedzis vienu no savu resursu limitiem.";
+"login_desktop_device" = "Dators";
+"login_tablet_device" = "Planšete";
+"login_mobile_device" = "Mobilā iekārta";
+"login_error_forgot_password_is_not_supported" = "“Aizmirsu paroli” patlaban nav atbalstīta";
+"login_use_fallback" = "Lietojiet atkāpšanās lappusi";
+"login_error_login_email_not_yet" = "E-pasta saite vel nav noklikšķināta";
+"login_error_not_json" = "Nesatur derīgu JSON";
+"login_error_bad_json" = "Bojāts JSON";
+"login_error_unknown_token" = "Netika atpazīta norādītā pieejas atslēga";
+"login_error_do_not_support_login_flows" = "Mēs patrez neatbalstām jebkādas pieteikšanās plūsmas, kas definētas no šī mājas servera";
+"login_error_no_login_flow" = "Mums neizdevās saņemt autentifikācijas informāciju no šī mājas servera.";
+"login_email_info" = "E-pasta adreses norādīšana ļauj citiem lietotājiem viegli atras tevi Matix, kā arī nodrošinās tev iespēju nākotnē atjaunot savu paroli.";
+"login_optional_field" = "neobligāts";
+"login_user_id_placeholder" = "Matrix ID (piem. @bob:matrix.org vai bobs)";
+"login_identity_server_info" = "Matrix nodrošina identifikācijas serverus, lai atšķirtu, kuri e-pasti, u.c., pieder kuram Matrix ID. Patlaban pastāv vienīgi https://matrix.org";
diff --git a/Riot/Modules/MatrixKit/Assets/MatrixKitAssets.bundle/nb-NO.lproj/MatrixKit.strings b/Riot/Modules/MatrixKit/Assets/MatrixKitAssets.bundle/nb-NO.lproj/MatrixKit.strings
new file mode 100644
index 000000000..7f2347f95
--- /dev/null
+++ b/Riot/Modules/MatrixKit/Assets/MatrixKitAssets.bundle/nb-NO.lproj/MatrixKit.strings
@@ -0,0 +1,544 @@
+
+
+"room_event_encryption_info_device_verification" = "Verifisering\n";
+"notice_display_name_set" = "%@ satte visningsnavnet sitt til %@";
+"room_no_power_to_create_conference_call" = "Du trenger tillatelse til å invitere for å starte en konferanse i dette rommet";
+"room_error_join_failed_empty_room" = "Det er for øyeblikket ikke mulig å bli med i et tomt rom igjen.";
+"room_event_encryption_verify_message" = "For å verifisere at denne økten er til å stole på, vennligst kontakt eieren på andre måter (f.eks. personlig eller en telefonsamtale) og spør dem om nøkkelen de ser i brukerinnstillingene for denne økten samsvarer med nøkkelen nedenfor:\n\n\tØkt-navn: %@\n\tØkt-ID: %@\n\tØkt-nøkkel: %@\n\nHvis den stemmer overens, trykk på verifiserings-knappen nedenfor. Hvis den ikke gjør det, er det noen andre som overvåker denne økten, og du vil sannsynligvis trykke på svartelisteknappen i stedet.\n\nI fremtiden vil denne verifiseringsprosessen bli mer sofistikert.";
+"room_event_encryption_info_block" = "Svarteliste";
+"device_details_delete_prompt_title" = "Autentisering";
+"notice_room_power_level_acting_requirement" = "Minimum tilgangsnivå en bruker må ha før utførelse av handling er:";
+"login_prompt_email_token" = "Vennligst skriv inn valideringstokenet ditt for e-post:";
+"login_server_url_placeholder" = "URL (f.eks. https://matrix.org)";
+
+// Login Screen
+"login_create_account" = "Opprett konto:";
+/* *********************** */
+/* iOS specific */
+/* *********************** */
+
+"matrix" = "Matrix";
+"ssl_cert_not_trust" = "Dette kan bety at noen overvåker trafikken din, eller at telefonen ikke stoler på sertifikatet fra den eksterne serveren.";
+"resend" = "Send på nytt";
+"login_identity_server_title" = "URL til identitetsserver:";
+"login_home_server_info" = "Hjemmeserveren lagrer alle samtalene og kontodataene dine";
+"login_home_server_title" = "Hjemmeserver-URL:";
+"login_identity_server_info" = "Matrix tilbyr identitetsservere for å spore hvilke e-postadresser etc. som tilhører hvilke Matrix-IDer. Bare https://matrix.org eksisterer for øyeblikket.";
+"login_user_id_placeholder" = "Matrix-ID (f.eks. @Bob: matrix.org eller bob)";
+"login_email_info" = "Angi en e-postadresse slik at andre brukere lettere kan finne deg, og samtidig gi deg en måte å tilbakestille passordet ditt senere.";
+"login_display_name_placeholder" = "Visningsnavn (f.eks. Bob Obson)";
+"login_optional_field" = "valgfri";
+"login_password_placeholder" = "Passord";
+"login_error_do_not_support_login_flows" = "Foreløpig støtter vi ikke påloggingsflytene som er definert for denne hjemmeserveren";
+"login_error_no_login_flow" = "Vi kunne ikke hente autentiseringsinformasjon fra denne hjemmeserveren";
+"login_error_title" = "Innlogging feilet";
+"login_email_placeholder" = "E-postadresse";
+"login_error_unknown_token" = "Angitt tilgangstoken ble ikke gjenkjent";
+"login_error_forbidden" = "Ugyldig brukernavn/passord";
+"login_error_registration_is_not_supported" = "Registrering støttes ikke for øyeblikket";
+"login_error_resource_limit_exceeded_message_monthly_active_user" = "Denne hjemmeserveren har nådd grensen for aktive månedlige brukere.";
+"login_error_resource_limit_exceeded_message_default" = "Denne hjemmeserveren har overskredet en av ressursgrensene.";
+"login_error_resource_limit_exceeded_title" = "Ressursgrensen er overskredet";
+"login_desktop_device" = "PC";
+"login_tablet_device" = "Nettbrett";
+"login_mobile_device" = "Mobil";
+"login_error_forgot_password_is_not_supported" = "Glemt passord støttes for øyeblikket ikke";
+"register_error_title" = "Registrering feilet";
+"login_invalid_param" = "Ugyldig parameter";
+"login_leave_fallback" = "Avbryt";
+"login_use_fallback" = "Bruk reserveside";
+"login_error_login_email_not_yet" = "E-postlinken som ikke er klikket på ennå";
+"login_error_user_in_use" = "Dette brukernavnet er allerede brukt";
+"login_error_limit_exceeded" = "Det er sendt for mange forespørsler";
+"login_error_not_json" = "Inneholdt ikke gyldig JSON";
+"login_error_bad_json" = "Feilformatert JSON";
+"set_power_level" = "Bestem tilgangsnivå";
+"submit_code" = "Send inn kode";
+"submit" = "Send inn";
+"sign_up" = "Meld deg på";
+"retry" = "Prøv på nytt";
+"dismiss" = "Ignorer";
+"discard" = "Forkast";
+"continue" = "Fortsett";
+"close" = "Lukk";
+"back" = "Tilbake";
+"abort" = "Avbryt";
+"yes" = "Ja";
+
+// Action
+"no" = "Nei";
+"login_error_resource_limit_exceeded_contact_button" = "Kontakt administrator";
+"login_error_resource_limit_exceeded_message_contact" = "\n\nKontakt tjenesteadministratoren din for å fortsette å bruke denne tjenesten.";
+"set_default_power_level" = "Tilbakestill tilgangsnivå";
+"start_chat" = "Start samtale";
+"set_admin" = "Velg admin";
+"set_moderator" = "Velg moderator";
+"invite_user" = "Inviter bruker";
+"capture_media" = "Ta bilde/video";
+"attach_media" = "Legg ved media fra biblioteket";
+"select_account" = "Velg en konto";
+"mention" = "Nevn";
+"start_video_call" = "Start videoanrop";
+"start_voice_call" = "Start taleanrop";
+"answer_call" = "Svar på anrop";
+"show_details" = "Vis detaljer";
+"cancel_download" = "Avbryt nedlasting";
+"cancel_upload" = "Avbryt opplasting";
+"select_all" = "Velg alle";
+"resend_message" = "Send meldingen på nytt";
+"reset_to_default" = "Tilbakestill til standard";
+"reject_call" = "Avvis anrop";
+"end_call" = "Avslutt samtale";
+// Old
+"notice_room_join_rule" = "Bli-med regelen er: %@";
+"notice_room_created_for_dm" = "%@ ble med.";
+"notice_room_created" = "%@ opprettet og konfigurerte rommet.";
+"notice_profile_change_redacted" = "%@ oppdaterte profilen sin %@";
+"notice_event_redacted_reason" = " [årsak: %@]";
+"notice_event_redacted_by" = " av %@";
+"notice_event_redacted" = "";
+"notice_room_topic_removed" = "%@ fjernet emnet";
+"notice_room_name_removed_for_dm" = "%@ fjernet navnet";
+"notice_room_name_removed" = "%@ fjernet romnavnet";
+
+// Events formatter
+"notice_avatar_changed_too" = "(avatar ble også endret)";
+"unignore" = "Opphev ignorering";
+"ignore" = "Ignorer";
+"resume_call" = "Gjenoppta";
+"notice_room_power_level_intro" = "Medlemmenes tilgangsnivå i rommet er:";
+"notice_room_join_rule_public_by_you_for_dm" = "Du gjorde dette offentlig.";
+"notice_room_join_rule_public_by_you" = "Du gjorde rommet offentlig.";
+"notice_room_join_rule_public_for_dm" = "%@ gjorde dette offentlig.";
+"notice_room_join_rule_public" = "%@ gjorde rommet offentlig.";
+"notice_room_join_rule_invite_by_you_for_dm" = "Du endret til kun inviterte.";
+"notice_room_join_rule_invite_by_you" = "Du endret rommet til kun inviterte.";
+"notice_room_join_rule_invite_for_dm" = "%@ endret til kun inviterte.";
+// New
+"notice_room_join_rule_invite" = "%@ endret rommet til kun inviterte.";
+"notice_room_power_level_intro_for_dm" = "Medlemmenes tilgangsnivå er:";
+"notice_room_power_level_event_requirement" = "Minimum tilgangsnivå relatert til events er:";
+"notice_unsupported_attachment" = "Ikke støttet vedlegg: %@";
+"notice_invalid_attachment" = "ugyldig vedlegg";
+"notice_file_attachment" = "filvedlegg";
+"notice_location_attachment" = "lokasjonsvedlegg";
+"notice_video_attachment" = "videovedlegg";
+"notice_audio_attachment" = "lydvedlegg";
+"notice_image_attachment" = "bildevedlegg";
+"notice_encryption_enabled_unknown_algorithm" = "%1$@ slo på ende-til-ende-kryptering (ukjent algoritme %2$@).";
+"notice_encryption_enabled_ok" = "%@ slo på ende-til-ende-kryptering.";
+"notice_encrypted_message" = "Kryptert melding";
+"notice_room_related_groups" = "Gruppene som er tilknyttet dette rommet er: %@";
+"notice_room_aliases_for_dm" = "Aliasene er: %@";
+"notice_room_aliases" = "Rom-aliasene er: %@";
+"room_event_encryption_info_event" = "Hendelseinformasjon\n";
+
+// Encryption information
+"room_event_encryption_info_title" = "Ende-til-ende-krypteringsinformasjon\n\n";
+"device_details_delete_prompt_message" = "Denne operasjonen krever ekstra godkjenning.\nFor å fortsette, vennligst skriv inn passordet ditt.";
+"device_details_rename_prompt_message" = "En økts offentlige navn er synlig for folk du kommuniserer med";
+"device_details_rename_prompt_title" = "Øktnavn";
+"device_details_last_seen_format" = "%@ @ %@\n";
+"device_details_last_seen" = "Sist sett\n";
+"device_details_identifier" = "ID\n";
+"device_details_name" = "Offentlig navn\n";
+
+// Devices
+"device_details_title" = "Øktinformasjon\n";
+"notification_settings_room_rule_title" = "Rom: '%@'";
+"settings_enter_validation_token_for" = "Angi valideringstoken for %@:";
+"settings_enable_push_notifications" = "Aktiver push-varsler";
+"settings_enable_inapp_notifications" = "Aktiver varsler i appen";
+
+// Settings
+"settings" = "Innstillinger";
+"room_displayname_more_than_two_members" = "%@ og %@ andre";
+"room_displayname_two_members" = "%@ og %@";
+
+// room display name
+"room_displayname_empty_room" = "Tomt rom";
+"notice_in_reply_to" = "Som svar på";
+"notice_sticker" = "klistremerke";
+"notice_crypto_error_unknown_inbound_session_id" = "Avsenderøkten har ikke sendt oss nøklene til denne meldingen.";
+"notice_crypto_unable_to_decrypt" = "** Kan ikke dekryptere: %@ **";
+"notice_room_history_visible_to_members_from_joined_point_for_dm" = "%@ gjorde fremtidige meldinger synlige for alle, fra de ble med.";
+"notice_room_history_visible_to_members_from_invited_point_for_dm" = "%@ gjorde fremtidige meldinger synlige for alle, fra de blir invitert.";
+"notice_room_history_visible_to_anyone" = "%@ gjorde fremtidig romhistorie synlig for alle.";
+"notice_error_unknown_event_type" = "Ukjent hendelsestype";
+"notice_error_unexpected_event" = "Uventet hendelse";
+"notice_error_unsupported_event" = "Ikke støttet hendelse";
+"notice_feedback" = "Tilbakemeldingshendelse (id: %@): %@";
+"notice_room_history_visible_to_members_from_joined_point" = "%@ synliggjorde fremtidig romhistorie for alle medlemmer i rommet, fra det tidspunktet de ble med.";
+"notice_room_history_visible_to_members_from_invited_point" = "%@ synliggjorde fremtidig romhistorie for alle medlemmer i rommet, fra det punktet de er invitert.";
+"notice_room_history_visible_to_members_for_dm" = "%@ gjorde fremtidige meldinger synlige for alle medlemmer i rommet.";
+"notice_room_history_visible_to_members" = "%@ synliggjorde fremtidig romhistorie for alle medlemmer i rommet.";
+"notice_redaction" = "%@ holdt igjen en hendelse (id: %@)";
+"room_event_encryption_info_event_fingerprint_key" = "Benyttet Ed25519 fingeravtrykknøkkel\n";
+"room_event_encryption_info_event_identity_key" = "Curve25519 identitetsnøkkel\n";
+"room_event_encryption_info_event_user_id" = "Bruker-ID\n";
+"room_event_encryption_info_event_session_id" = "Økt-ID\n";
+"room_event_encryption_info_event_algorithm" = "Algoritme\n";
+"account_email_validation_message" = "Sjekk e-posten din og klikk på linken den inneholder. Når dette er gjort, klikker du på fortsett.";
+"account_linked_emails" = "Linkede e-poster";
+"account_link_email" = "Link e-post";
+
+// Account
+"account_save_changes" = "Lagre endringer";
+"room_event_encryption_verify_ok" = "Verifiser";
+"room_event_encryption_verify_title" = "Verifiser økt\n\n";
+"room_event_encryption_info_unblock" = "Fjern svartelisting";
+"room_event_encryption_info_unverify" = "Fjern verifisering";
+"room_event_encryption_info_verify" = "Verifiser...";
+"room_event_encryption_info_device_blocked" = "Svartelistet";
+"room_event_encryption_info_device_not_verified" = "IKKE verifisert";
+"room_event_encryption_info_device_verified" = "Verifisert";
+"room_event_encryption_info_device_fingerprint" = "Ed25519 fingeravtrykk\n";
+"room_event_encryption_info_device_id" = "ID\n";
+"room_event_encryption_info_device_name" = "Offentlig navn\n";
+"room_event_encryption_info_device_unknown" = "ukjent økt\n";
+"room_event_encryption_info_device" = "\nInformasjon om avsenderøkt\n";
+"room_event_encryption_info_event_none" = "ingen";
+"room_event_encryption_info_event_unencrypted" = "ukryptert";
+"room_event_encryption_info_event_decryption_error" = "Dekrypteringsfeil\n";
+"account_email_validation_title" = "Venter på verifisering";
+"room_creation_alias_placeholder" = "(f.eks. #foo:example.org)";
+"room_creation_alias_title" = "Romalias:";
+"room_creation_name_placeholder" = "(f.eks. lunsjgruppe)";
+
+// Room creation
+"room_creation_name_title" = "Romnavn:";
+"account_error_push_not_allowed" = "Varsler ikke tillatt";
+"account_error_msisdn_wrong_description" = "Dette ser ikke ut til å være et gyldig telefonnummer";
+"account_error_msisdn_wrong_title" = "Ugyldig telefonnummer";
+"account_error_email_wrong_description" = "Dette ser ikke ut til å være en gyldig e-postadresse";
+"account_error_email_wrong_title" = "Ugyldig e-postadresse";
+"account_error_matrix_session_is_not_opened" = "Matrix-økt er ikke åpnet";
+"account_error_picture_change_failed" = "Endring av bildet feilet";
+"account_error_display_name_change_failed" = "Endring av visningsnavn feilet";
+"account_msisdn_validation_error" = "Kan ikke bekrefte telefonnummeret.";
+"account_msisdn_validation_message" = "Vi har sendt en SMS med en aktiveringskode. Vennligst skriv inn denne koden nedenfor.";
+"account_email_validation_error" = "Kan ikke bekrefte e-postadressen. Sjekk e-posten din og klikk på lenken den inneholder. Når dette er gjort, klikker du på fortsett";
+"account_msisdn_validation_title" = "Venter på verifisering";
+"room_error_join_failed_title" = "Deltakelse i rommet feilet";
+
+// Room
+"room_please_select" = "Vennligst velg et rom";
+"room_creation_participants_placeholder" = "(f.eks. @bob:hjemmeserver1; @john:hjemmeserver2 ...)";
+"room_creation_participants_title" = "Deltakere:";
+"room_creation_alias_placeholder_with_homeserver" = "(f.eks. #foo%@)";
+"room_member_power_level_prompt" = "Du vil ikke kunne angre denne endringen ettersom du gir brukeren samme tilgangsnivå som deg selv.\nEr du sikker?";
+
+// Room members
+"room_member_ignore_prompt" = "Er du sikker på at du vil skjule alle meldinger fra denne brukeren?";
+"message_reply_to_message_to_reply_to_prefix" = "Som svar på";
+"message_reply_to_sender_sent_a_file" = "sendte en fil.";
+"message_reply_to_sender_sent_an_audio_file" = "sendte en lydfil.";
+"message_reply_to_sender_sent_a_video" = "sendte en video.";
+
+// Reply to message
+"message_reply_to_sender_sent_an_image" = "sendte et bilde.";
+"room_no_conference_call_in_encrypted_rooms" = "Konferansesamtaler støttes ikke i krypterte rom";
+"room_left_for_dm" = "Du forlot";
+"room_left" = "Du forlot rommet";
+"room_error_timeline_event_not_found" = "Applikasjonen prøvde å laste inn et bestemt punkt i tidslinjen til dette rommet, men kunne ikke finne det";
+"room_error_timeline_event_not_found_title" = "Kunne ikke laste tidslinjeposisjonen";
+"room_error_cannot_load_timeline" = "Kunne ikke laste tidslinjen";
+"room_error_topic_edition_not_authorized" = "Du har ikke autorisasjon til å redigere dette romemnet";
+"room_error_name_edition_not_authorized" = "Du har ikke autorisasjon til å redigere dette romnavnet";
+
+// Groups
+"group_invite_section" = "Invitasjoner";
+"contact_local_contacts" = "Lokale kontakter";
+
+// Contacts
+"contact_mx_users" = "Matrix-brukere";
+"attachment_e2e_keys_import" = "Importer...";
+"attachment_e2e_keys_file_prompt" = "Denne filen inneholder krypteringsnøkler eksportert fra en Matrix-klient.\nVil du se filinnholdet eller importere nøklene den inneholder?";
+"attachment_multiselection_original" = "Faktisk størrelse";
+"attachment_multiselection_size_prompt" = "Vil du sende bilder som:";
+"attachment_cancel_upload" = "Avbryte opplastingen?";
+"attachment_cancel_download" = "Avbryte nedlastingen?";
+"attachment_large" = "Stor: %@";
+"attachment_medium" = "Medium: %@";
+"attachment_small" = "Liten: %@";
+"attachment_original" = "Faktisk størrelse: %@";
+
+// Attachment
+"attachment_size_prompt" = "Vil du sende som:";
+"search_searching" = "Søk pågår...";
+
+// Search
+"search_no_results" = "Ingen resultater";
+"group_section" = "Grupper";
+"format_time_h" = "t";
+"format_time_m" = "m";
+
+// Time
+"format_time_s" = "s";
+"e2e_import_prompt" = "Denne prosessen lar deg importere krypteringsnøkler som du tidligere hadde eksportert fra en annen app. Du vil da kunne dekryptere alle meldinger som den andre klienten kan dekryptere.\nEksportfilen er beskyttet med en passordfrase. Du bør angi passordet her for å dekryptere filen.";
+
+// E2E import
+"e2e_import_room_keys" = "Importer romnøkler";
+"format_time_d" = "d";
+"e2e_import" = "Importer";
+"e2e_export_prompt" = "Denne prosessen lar deg eksportere nøklene for meldinger du har mottatt i krypterte rom til en lokal fil. Du vil da kunne importere filen til en annen app i fremtiden, slik at den også kan dekryptere disse meldingene.\nDen eksporterte filen lar alle som kan lese den dekryptere alle krypterte meldinger du kan se, så du bør passe på å lagre den sikkert.";
+
+// E2E export
+"e2e_export_room_keys" = "Eksporter romnøkler";
+"e2e_passphrase_enter" = "Skriv inn passordfrase";
+"e2e_export" = "Eksporter";
+"e2e_passphrase_empty" = "Passordfrase kan ikke være tom";
+"e2e_passphrase_confirm" = "Bekreft passordfrase";
+"power_level" = "Tilgangsnivå";
+"public" = "Offentlig";
+"private" = "Privat";
+"default" = "standard";
+"not_supported_yet" = "Støttes ikke ennå";
+"error_common_message" = "En feil oppstod. Prøv igjen senere.";
+"error" = "Feil";
+"unsent" = "Ikke sendt";
+"offline" = "offline";
+
+// Others
+"user_id_title" = "Bruker-ID:";
+"e2e_passphrase_create" = "Opprett passordfrase";
+"e2e_passphrase_not_match" = "Passordfrase må stemme overens";
+"user_id_placeholder" = "eks: @bob:hjemmeserver";
+"network_error_not_reachable" = "Vennligst kontroller nettverkstilkoblingen";
+"local_contacts_access_discovery_warning" = "For å finne kontakter som allerede bruker løsningen, kan %@ sende e-postadresser og telefonnumre i adresseboken til den valgte identitetsserveren. Der det støttes, blir personlige data indeksert før sending - vennligst sjekk identitetsserverens personvernregler for mer informasjon.";
+"local_contacts_access_discovery_warning_title" = "Finne brukere";
+"local_contacts_access_not_granted" = "Å finne brukere fra lokale kontakter krever tilgang til dine kontakter, men %@ har ikke tillatelse til å bruke dem";
+"microphone_access_not_granted_for_call" = "Samtaler krever tilgang til mikrofonen, men %@ har ikke tillatelse til å bruke den";
+
+// Permissions
+"camera_access_not_granted_for_call" = "Videosamtaler krever tilgang til kameraet, men %@ har ikke tillatelse til å bruke det";
+"ssl_homeserver_url" = "Hjemmeserver-URL:% @";
+
+/* -*-
+ Automatic localization for en
+
+ The following key/value pairs were extracted from the android i18n file:
+ /matrix-sdk/src/main/res/values/strings.xml.
+*/
+
+"notice_room_invite" = "%@ inviterte %@";
+"language_picker_default_language" = "Standard (%@)";
+
+// Language picker
+"language_picker_title" = "Velg språk";
+
+// Country picker
+"country_picker_title" = "Velg et land";
+"notice_room_kick" = "%@ utviste %@";
+"notice_room_reject" = "%@ avviste invitasjonen";
+"notice_room_leave" = "%@ forlot";
+"notice_room_join" = "%@ ble med";
+"notice_room_third_party_registered_invite" = "%@ godtok invitasjonen til %@";
+"notice_room_third_party_invite_for_dm" = "%@ inviterte %@";
+"notice_room_third_party_invite" = "%@ sendte en invitasjon til %@ om å bli med i rommet";
+"notice_room_third_party_revoked_invite_for_dm" = "%@ tilbakekalte invitasjonen til %@ om å bli med i rommet";
+"notice_room_third_party_revoked_invite" = "%@ tilbakekalte invitasjonen til %@ om å bli med i rommet";
+"notice_answered_video_call" = "%@ svarte på anropet";
+"notice_placed_video_call" = "%@ startet et videoanrop";
+"notice_placed_voice_call" = "%@ startet et taleanrop";
+"notice_room_name_changed_for_dm" = "%@ endret navnet til %@.";
+"notice_room_name_changed" = "%@ endret romnavnet til %@.";
+"notice_topic_changed" = "%@ endret emnet til \"%@\".";
+"notice_display_name_removed" = "%@ fjernet visningsnavnet";
+"notice_display_name_changed_from" = "%@ endret visningsnavnet fra %@ til %@";
+"notice_avatar_url_changed" = "%@ byttet avatar";
+"notice_room_reason" = ". Grunnen til: %@";
+"notice_room_withdraw" = "%@ trakk tilbake invitasjonen til %@";
+"notice_room_ban" = "%@ utestengte %@";
+"notice_room_unban" = "%@ omgjorde utestenging %@";
+"notice_declined_video_call" = "%@ avviste anropet";
+"notice_ended_video_call" = "%@ avsluttet samtalen";
+"notice_room_third_party_invite_by_you" = "Du sendte en invitasjon til %@ om å bli med i rommet";
+"notice_room_invite_you" = "%@ inviterte deg";
+
+// Notice Events with "You"
+"notice_room_invite_by_you" = "Du inviterte %@";
+"notice_conference_call_finished" = "VoIP-konferansen avsluttet";
+"notice_conference_call_started" = "VoIP-konferansen startet";
+"notice_conference_call_request" = "%@ ba om en VoIP-konferanse";
+"notice_room_kick_by_you" = "Du utviste %@";
+"notice_room_reject_by_you" = "Du avviste invitasjonen";
+"notice_room_leave_by_you" = "Du forlot";
+"notice_room_join_by_you" = "Du ble med";
+"notice_room_third_party_revoked_invite_by_you_for_dm" = "Du tilbakekalte invitasjonen til %@";
+"notice_room_third_party_revoked_invite_by_you" = "Du tilbakekalte invitasjonen til %@ om å bli med i rommet";
+"notice_room_third_party_registered_invite_by_you" = "Du godtok invitasjonen til %@";
+"notice_room_third_party_invite_by_you_for_dm" = "Du inviterte %@";
+"notice_room_unban_by_you" = "Du omgjorde utestenging av %@";
+"notice_room_ban_by_you" = "Du utestengte %@";
+"notice_answered_video_call_by_you" = "Du svarte på anropet";
+"notice_placed_video_call_by_you" = "Du startet et videoanrop";
+"notice_placed_voice_call_by_you" = "Du startet et taleanrop";
+"notice_room_name_changed_by_you_for_dm" = "Du endret navnet til %@.";
+"notice_room_name_changed_by_you" = "Du endret romnavnet til %@.";
+"notice_topic_changed_by_you" = "Du endret emnet til \"%@\".";
+"notice_display_name_removed_by_you" = "Du fjernet visningsnavnet ditt";
+"notice_display_name_changed_from_by_you" = "Du endret visningsnavnet ditt fra %@ til %@";
+"notice_display_name_set_by_you" = "Du satte visningsnavnet ditt til %@";
+"notice_room_withdraw_by_you" = "Du trakk tilbake invitasjonen til %@";
+"notice_avatar_url_changed_by_you" = "Du byttet avatar";
+"notice_declined_video_call_by_you" = "Du avviste anropet";
+"notice_ended_video_call_by_you" = "Du avsluttet samtalen";
+"login" = "Innlogging";
+"create_room" = "Opprett rom";
+
+// actions
+"action_logout" = "Logg ut";
+"view" = "Visning";
+"delete" = "Slett";
+"share" = "Del";
+"redact" = "Fjern";
+"copy_button_name" = "Kopier";
+"send" = "Send";
+"leave" = "Forlat";
+"save" = "Lagre";
+"cancel" = "Avbryt";
+
+// Room Screen
+
+// general errors
+
+// Home Screen
+
+// Last seen time
+
+// call events
+
+/* -*-
+ Automatic localization for en
+
+ The following key/value pairs were extracted from the android i18n file:
+ /console/src/main/res/values/strings.xml.
+*/
+
+
+// titles
+
+// button names
+"ok" = "OK";
+"notice_room_history_visible_to_members_from_joined_point_by_you_for_dm" = "Du gjorde fremtidige meldinger synlige for alle, fra de ble med.";
+"notice_room_history_visible_to_members_from_joined_point_by_you" = "Du gjorde fremtidig romhistorie synlig for alle medlemmer i rommet, fra det tidspunktet de ble med.";
+"notice_room_history_visible_to_members_from_invited_point_by_you_for_dm" = "Du gjorde fremtidige meldinger synlige for alle, fra de blir invitert.";
+"notice_room_history_visible_to_members_from_invited_point_by_you" = "Du gjorde fremtidig romhistorie synlig for alle medlemmer i rommet, fra det tidspunktet de er invitert.";
+"notice_room_history_visible_to_members_by_you_for_dm" = "Du gjorde fremtidige meldinger synlige for alle medlemmer i rommet.";
+"notice_room_history_visible_to_members_by_you" = "Du gjorde fremtidig romhistorie synlig for alle medlemmer i rommet.";
+"notice_room_history_visible_to_anyone_by_you" = "Du gjorde fremtidig romhistorie synlig for alle.";
+"notice_redaction_by_you" = "Du fjernet en hendelse (id: %@)";
+"notice_encryption_enabled_unknown_algorithm_by_you" = "Du slo på ende-til-ende-kryptering (ukjent algoritme %@).";
+"notice_encryption_enabled_ok_by_you" = "Du slo på ende-til-ende-kryptering.";
+"notice_room_created_by_you_for_dm" = "Du ble med.";
+"notice_room_created_by_you" = "Du opprettet og konfigurerte rommet.";
+"notice_profile_change_redacted_by_you" = "Du oppdaterte profilen din %@";
+"notice_event_redacted_by_you" = " av deg";
+"notice_room_topic_removed_by_you" = "Du fjernet emnet";
+"notice_room_name_removed_by_you_for_dm" = "Du fjernet navnet";
+"notice_room_name_removed_by_you" = "Du fjernet romnavnet";
+"notice_conference_call_request_by_you" = "Du ba om en VoIP-konferanse";
+"kick" = "Utvis";
+"invite" = "Inviter";
+"num_members_other" = "%@ brukere";
+"num_members_one" = "%@ bruker";
+"membership_ban" = "Utestengt";
+"membership_leave" = "Forlot";
+"membership_invite" = "Invitert";
+"create_account" = "Opprett konto";
+"notification_settings_global_info" = "Varslingsinnstillinger lagres i brukerkontoen din og deles mellom alle klienter som støtter dem (inkludert skrivebordsvarsler).\n\nRegler brukes i rekkefølge; den første regelen som samsvarer definerer resultatet for meldingen.\nSå: Varsler per ord er viktigere enn varsler per rom som er viktigere enn varsler per avsender.\nVed flere regler av samme type benyttes den første i listen som samsvarer.";
+"notification_settings_enable_notifications_warning" = "Alle varsler er for øyeblikket deaktivert for alle enheter.";
+"notification_settings_enable_notifications" = "Aktiver varsler";
+
+// Notification settings screen
+"notification_settings_disable_all" = "Deaktiver alle varsler";
+"settings_title_notifications" = "Varsler";
+
+// Settings screen
+"settings_title_config" = "Konfigurasjon";
+
+// contacts list screen
+"invitation_message" = "Jeg vil gjerne chatte med deg med Matrix. Besøk nettstedet https://matrix.org for å få mer informasjon.";
+
+// members list Screen
+
+// accounts list Screen
+
+// image size selection
+
+// invitation members list Screen
+
+// room creation dialog Screen
+
+// room info dialog Screen
+
+// room details dialog screen
+"room_details_title" = "Romdetaljer";
+"login_error_must_start_http" = "URL må starte med http[s]://";
+
+// Login Screen
+"login_error_already_logged_in" = "Allerede logget inn";
+"message_unsaved_changes" = "Endringer er ikke lagret. Hvis du avslutter, forkastes de.";
+"unban" = "Omgjør utestenging";
+"ban" = "Utesteng";
+"notification_settings_receive_a_call" = "Varsle meg når jeg mottar et anrop";
+"notification_settings_people_join_leave_rooms" = "Varsle meg når folk blir med eller forlater rom";
+"notification_settings_invite_to_a_new_room" = "Varsle meg når jeg blir invitert til et nytt rom";
+"notification_settings_just_sent_to_me" = "Varsle meg med lyd om meldinger sendt bare til meg";
+"notification_settings_contain_my_display_name" = "Varsle meg med lyd om meldinger som inneholder visningsnavnet mitt";
+"notification_settings_contain_my_user_name" = "Varsle meg med lyd om meldinger som inneholder brukernavnet mitt";
+"notification_settings_other_alerts" = "Andre varsler";
+"notification_settings_select_room" = "Velg et rom";
+"notification_settings_sender_hint" = "@bruker:domene.com";
+"notification_settings_per_sender_notifications" = "Varsler per avsender";
+"notification_settings_per_room_notifications" = "Varsler per rom";
+"notification_settings_custom_sound" = "Egendefinert lyd";
+"notification_settings_highlight" = "Fremhev";
+"notification_settings_word_to_match" = "ord som skal samsvare";
+"notification_settings_never_notify" = "Aldri varsle";
+"notification_settings_always_notify" = "Alltid varsle";
+"notification_settings_per_word_info" = "Ord samsvarer uten å ta hensyn til store eller små bokstaver, og kan inneholde et * jokertegn. Så:\nfoo samsvarer med strengen foo omgitt av ordavgrensere (f.eks. tegnsetting og mellomrom eller start/slutt på linjen).\nfoo* samsvarer med et slikt ord som begynner foo.\n*foo* samsvarer med et hvilket som helst ord som inkluderer de tre bokstavene foo.";
+"notification_settings_per_word_notifications" = "Varsler per ord";
+"notification_settings_suppress_from_bots" = "Blokker meldinger fra roboter";
+"notification_settings_notify_all_other" = "Varsle for alle andre meldinger/rom";
+"notification_settings_by_default" = "Som standard ...";
+"incoming_video_call" = "Innkommende videoanrop";
+"call_ended" = "Samtale avsluttet";
+"call_ringing" = "Ringer…";
+
+// Settings keys
+
+// call string
+"call_connecting" = "Kobler til…";
+"settings_config_user_id" = "Bruker-ID: %@";
+"settings_config_identity_server" = "Identitetsserver: %@";
+
+// gcm section
+"settings_config_home_server" = "Hjemmeserver: %@";
+"incoming_voice_call" = "Innkommende taleanrop";
+"call_invite_expired" = "Anropsinvitasjon utløpt";
+
+// unrecognized SSL certificate
+"ssl_trust" = "Tillit";
+"call_transfer_to_user" = "Overfør til %@";
+"call_consulting_with_user" = "Rådfører seg med %@";
+"call_video_with_user" = "Videosamtale med %@";
+"call_voice_with_user" = "Taleanrop med %@";
+"call_more_actions_dialpad" = "Tastaturet";
+"call_more_actions_transfer" = "Overfør";
+"call_more_actions_audio_use_device" = "Bruk enhetslyd";
+"call_more_actions_audio_use_headset" = "Bruk hodetelefonlyd";
+"call_more_actions_change_audio_device" = "Bytt lydenhet";
+"call_more_actions_unhold" = "Gjenoppta";
+"call_more_actions_hold" = "Hold";
+"call_holded" = "Du holdt samtalen";
+"call_remote_holded" = "%@ satte samtalen på vent";
+"ssl_unexpected_existing_expl" = "Sertifikatet har endret seg fra et som telefonen din klarerte. Dette er VELDIG UVANLIG. Det anbefales derfor at du IKKE godtar dette nye sertifikatet.";
+"ssl_cert_new_account_expl" = "Hvis serveradministratoren har sagt at dette forventes, må du forsikre deg om at fingeravtrykket nedenfor samsvarer med fingeravtrykket du har fått.";
+"ssl_could_not_verify" = "Kunne ikke bekrefte identiteten til den eksterne serveren.";
+"ssl_fingerprint_hash" = "Fingeravtrykk (%@):";
+"ssl_remain_offline" = "Ignorer";
+"ssl_logout_account" = "Logg ut";
+"ssl_expected_existing_expl" = "Sertifikatet er endret fra et tidligere klarert til et som ikke er klarert. Serveren kan ha fornyet sertifikatet. Kontakt serveradministratoren for forventet fingeravtrykk.";
+"ssl_only_accept" = "Godta KUN sertifikatet hvis serveradministratoren har publisert et fingeravtrykk som samsvarer med det over.";
diff --git a/Riot/Modules/MatrixKit/Assets/MatrixKitAssets.bundle/nl.lproj/MatrixKit.strings b/Riot/Modules/MatrixKit/Assets/MatrixKitAssets.bundle/nl.lproj/MatrixKit.strings
new file mode 100644
index 000000000..6dfa3be48
--- /dev/null
+++ b/Riot/Modules/MatrixKit/Assets/MatrixKitAssets.bundle/nl.lproj/MatrixKit.strings
@@ -0,0 +1,534 @@
+/*
+ Copyright 2017 Vector Creations 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.
+ */
+
+/* *********************** */
+/* iOS specific */
+/* *********************** */
+
+"matrix" = "Matrix";
+// Login Screen
+"login_create_account" = "Account aanmaken:";
+"login_server_url_placeholder" = "URL (bv. https://matrix.org)";
+"login_home_server_title" = "Thuisserver-URL:";
+"login_home_server_info" = "Uw thuisserver slaat al uw gespreks- en accountgegevens op";
+"login_identity_server_title" = "Identiteitsserver-URL:";
+"login_identity_server_info" = "Matrix verstrekt identiteitsservers om te achterhalen welke e-mailadressen enz. bij welke Matrix-ID’s horen. Tot nu toe bestaat alleen https://matrix.org.";
+"login_user_id_placeholder" = "Matrix-ID (bv. @jan:matrix.org of jan)";
+"login_password_placeholder" = "Wachtwoord";
+"login_optional_field" = "optioneel";
+"login_display_name_placeholder" = "Weergavenaam (bv. Jan Janssens)";
+"login_email_info" = "Door een e-mailadres in te voeren kunnen andere gebruikers u eenvoudiger op Matrix vinden, verder geeft het u een manier om uw wachtwoord in de toekomst te wijzigen.";
+"login_email_placeholder" = "E-mailadres";
+"login_prompt_email_token" = "Voer uw e-mailadres-validatiebewijs in:";
+"login_error_title" = "Aanmelden Mislukt";
+"login_error_no_login_flow" = "Ophalen van authenticatie-informatie van deze thuisserver is mislukt";
+"login_error_do_not_support_login_flows" = "Momenteel bieden we geen ondersteuning voor sommige of alle aanmeldingsmethoden van deze thuisserver";
+"login_error_registration_is_not_supported" = "Registratie wordt momenteel niet ondersteund";
+"login_error_forbidden" = "Ongeldige gebruikersnaam/wachtwoord";
+"login_error_unknown_token" = "Het gespecificeerde toegangsbewijs is niet herkend";
+"login_error_bad_json" = "Ongeldige JSON";
+"login_error_not_json" = "Bevat geen geldige JSON";
+"login_error_limit_exceeded" = "Er zijn te veel verzoeken verzonden";
+"login_error_user_in_use" = "Deze gebruikersnaam is al in gebruik";
+"login_error_login_email_not_yet" = "De koppeling in de e-mail is nog niet geopend";
+"login_use_fallback" = "Terugvalpagina gebruiken";
+"login_leave_fallback" = "Annuleren";
+"login_invalid_param" = "Ongeldige parameter";
+"register_error_title" = "Registratie Mislukt";
+"login_error_forgot_password_is_not_supported" = "Wachtwoord vergeten wordt momenteel nog niet ondersteund";
+// Action
+"no" = "Nee";
+"yes" = "Ja";
+"abort" = "Afbreken";
+"back" = "Terug";
+"close" = "Sluiten";
+"continue" = "Verdergaan";
+"discard" = "Verwerpen";
+"dismiss" = "Sluiten";
+"retry" = "Opnieuw proberen";
+"sign_up" = "Aanmelden";
+"submit" = "Versturen";
+"submit_code" = "Code versturen";
+"set_power_level" = "Machtsniveau instellen";
+"set_default_power_level" = "Machtsniveau opnieuw instellen";
+"set_moderator" = "Tot moderator benoemen";
+"set_admin" = "Tot beheerder benoemen";
+"start_chat" = "Gesprek beginnen";
+"start_voice_call" = "Spraakoproep beginnen";
+"start_video_call" = "Video-oproep beginnen";
+"mention" = "Vermelden";
+"select_account" = "Selecteer een account";
+"attach_media" = "Media van de bibliotheek bijvoegen";
+"capture_media" = "Foto/video maken";
+"invite_user" = "Matrix-gebruiker uitnodigen";
+"reset_to_default" = "Standaardwaarden herstellen";
+"resend_message" = "Bericht opnieuw versturen";
+"select_all" = "Alles selecteren";
+"cancel_upload" = "Upload annuleren";
+"cancel_download" = "Download annuleren";
+"show_details" = "Details weergeven";
+"answer_call" = "Oproep beantwoorden";
+"reject_call" = "Oproep afwijzen";
+"end_call" = "Ophangen";
+"ignore" = "Negeren";
+"unignore" = "Stoppen met negeren";
+// Events formatter
+"notice_avatar_changed_too" = "(avatar is ook veranderd)";
+"notice_room_name_removed" = "%@ heeft de gespreksnaam verwijderd";
+"notice_room_topic_removed" = "%@ heeft het onderwerp verwijderd";
+"notice_event_redacted" = "";
+"notice_event_redacted_by" = " door %@";
+"notice_event_redacted_reason" = " [reden: %@]";
+"notice_profile_change_redacted" = "%@ heeft zijn/haar profiel bijgewerkt %@";
+"notice_room_created" = "%@ heeft de kamer aangemaakt en ingesteld.";
+"notice_room_join_rule" = "De toetredingsregel is: %@";
+"notice_room_power_level_intro" = "De machtsniveaus van de gespreksleden zijn:";
+"notice_room_power_level_acting_requirement" = "De minimale machtsniveaus waarover een gebruiker moet beschikken vooraleer deze kan handelen zijn:";
+"notice_room_power_level_event_requirement" = "De minimale machtsniveaus gerelateerd aan gebeurtenissen zijn:";
+"notice_room_aliases" = "De gespreksbijnamen zijn: %@";
+"notice_encrypted_message" = "Versleuteld bericht";
+"notice_encryption_enabled" = "%@ heeft eind-tot-eind-versleuteling aangezet (%@-algoritme)";
+"notice_image_attachment" = "afbeeldingsbijlage";
+"notice_audio_attachment" = "audiobijlage";
+"notice_video_attachment" = "videobijlage";
+"notice_location_attachment" = "locatiebijlage";
+"notice_file_attachment" = "bestandsbijlage";
+"notice_invalid_attachment" = "ongeldige bijlage";
+"notice_unsupported_attachment" = "Niet-ondersteunde bijlage: %@";
+"notice_feedback" = "Feedbackgebeurtenis (id: %@): %@";
+"notice_redaction" = "%@ een gebeurtenis verwijderd (id: %@)";
+"notice_error_unsupported_event" = "Niet-ondersteunde gebeurtenis";
+"notice_error_unexpected_event" = "Onverwachte gebeurtenis";
+"notice_error_unknown_event_type" = "Onbekend gebeurtenistype";
+"notice_room_history_visible_to_anyone" = "%@ heeft de toekomstige gespreksgeschiedenis voor iedereen zichtbaar gemaakt.";
+"notice_room_history_visible_to_members" = "%@ heeft de toekomstige gespreksgeschiedenis voor alle gespreksleden zichtbaar gemaakt.";
+"notice_room_history_visible_to_members_from_invited_point" = "%@ heeft de toekomstige gespreksgeschiedenis zichtbaar gemaakt voor alle gespreksleden, vanaf het moment dat ze uitgenodigd zijn.";
+"notice_room_history_visible_to_members_from_joined_point" = "%@ heeft de toekomstige gespreksgeschiedenis zichtbaar gemaakt voor alle gespreksleden, vanaf het moment dat ze toetreden.";
+"notice_crypto_unable_to_decrypt" = "** Kan niet ontsleutelen: %@ **";
+"notice_crypto_error_unknown_inbound_session_id" = "De sessie van de afzender heeft ons geen sleutels voor dit bericht gestuurd.";
+// room display name
+"room_displayname_empty_room" = "Leeg gesprek";
+"room_displayname_two_members" = "%@ en %@";
+"room_displayname_more_than_two_members" = "%@ en %@ anderen";
+// Settings
+"settings" = "Instellingen";
+"settings_enable_inapp_notifications" = "In-app-meldingen inschakelen";
+"settings_enable_push_notifications" = "Pushmeldingen inschakelen";
+"settings_enter_validation_token_for" = "Voer validatiebewijs voor %@ in:";
+"notification_settings_room_rule_title" = "Gesprek: ‘%@’";
+// Devices
+"device_details_title" = "Sessie-informatie\n";
+"device_details_name" = "Publieke naam\n";
+"device_details_identifier" = "ID\n";
+"device_details_last_seen" = "Laatst gezien\n";
+"device_details_last_seen_format" = "%@ @ %@\n";
+"device_details_rename_prompt_message" = "De publieke naam van een sessie is zichtbaar voor de personen waarmee u communiceert";
+"device_details_delete_prompt_title" = "Authenticatie";
+"device_details_delete_prompt_message" = "Deze handeling vereist bijkomende authenticatie.\nVoer uw wachtwoord in om verder te gaan.";
+// Encryption information
+"room_event_encryption_info_title" = "Informatie over eind-tot-eind-versleuteling\n\n";
+"room_event_encryption_info_event" = "Gebeurtenisinformatie\n";
+"room_event_encryption_info_event_user_id" = "Gebruikers-ID\n";
+"room_event_encryption_info_event_identity_key" = "Curve25519-identiteitssleutel\n";
+"room_event_encryption_info_event_fingerprint_key" = "Geclaimde Ed25519-vingerafdrukssleutel\n";
+"room_event_encryption_info_event_algorithm" = "Algoritme\n";
+"room_event_encryption_info_event_session_id" = "Sessie-ID\n";
+"room_event_encryption_info_event_decryption_error" = "Ontsleutelingsfout\n";
+"room_event_encryption_info_event_unencrypted" = "onversleuteld";
+"room_event_encryption_info_event_none" = "geen";
+"room_event_encryption_info_device" = "\nInformatie over sessie van afzender\n";
+"room_event_encryption_info_device_unknown" = "onbekende sessie\n";
+"room_event_encryption_info_device_name" = "Publieke naam\n";
+"room_event_encryption_info_device_id" = "ID\n";
+"room_event_encryption_info_device_verification" = "Verificatie\n";
+"room_event_encryption_info_device_fingerprint" = "Ed25519-vingerafdruk\n";
+"room_event_encryption_info_device_verified" = "Geverifieerd";
+"room_event_encryption_info_device_not_verified" = "NIET geverifieerd";
+"room_event_encryption_info_device_blocked" = "Geblokkeerd";
+"room_event_encryption_info_verify" = "Verifiëren…";
+"room_event_encryption_info_unverify" = "Ontverifiëren";
+"room_event_encryption_info_block" = "Blokkeren";
+"room_event_encryption_info_unblock" = "Deblokkeren";
+"room_event_encryption_verify_title" = "Sessie verifiëren\n\n";
+"room_event_encryption_verify_message" = "Om te verifiëren dat deze sessie vertrouwd kan worden, neemt u contact op met de eigenaar van de sessie op een andere manier (bv. persoonlijk of door te bellen) en vraagt u hem/haar of de sleutel die hij/zij in de gebruikersinstellingen ziet overeenkomt met de onderstaande sleutel:\n\n\tSessienaam: %@\n\tSessie-ID: %@\n\tSessiesleutel: %@\n\nAls het overeenkomt, klikt u hieronder op de knop ‘Verifiëren’. Als het niet overeenkomt, onderschept iemand anders deze sessie en drukt u in plaats daarvan op de knop ‘Blokkeren’.\n\nIn de toekomst zal dit verificatieproces verbeterd worden.";
+"room_event_encryption_verify_ok" = "Verifiëren";
+// Account
+"account_save_changes" = "Wijzigingen opslaan";
+"account_link_email" = "E-mailadres koppelen";
+"account_linked_emails" = "Gekoppelde e-mailadressen";
+"account_email_validation_title" = "Verificatie in afwachting";
+"account_email_validation_message" = "Bekijk uw e-mail en open de koppeling erin. Wanneer dit gedaan is, tikt u op verder gaan.";
+"account_email_validation_error" = "Kan het e-mailadres niet verifiëren. Bekijk uw e-mail en open de koppeling erin. Wanneer dit gedaan is, tikt u op verder gaan";
+"account_msisdn_validation_title" = "Verificatie in afwachting";
+"account_msisdn_validation_message" = "We hebben een sms met een activatiecode verstuurd. Voer deze code hieronder in.";
+"account_msisdn_validation_error" = "Kan het telefoonnummer niet verifiëren.";
+"account_error_display_name_change_failed" = "Wijzigen van weergavenaam is mislukt";
+"account_error_picture_change_failed" = "Wijzigen van afbeelding is mislukt";
+"account_error_matrix_session_is_not_opened" = "Matrix-sessie is niet geopend";
+"account_error_email_wrong_title" = "Ongeldig e-mailadres";
+"account_error_email_wrong_description" = "Het ziet er niet naar uit dat dit een geldig e-mailadres is";
+"account_error_msisdn_wrong_title" = "Ongeldig telefoonnummer";
+"account_error_msisdn_wrong_description" = "Het ziet er niet naar uit dat dit een geldig telefoonnummer is";
+// Room creation
+"room_creation_name_title" = "Gespreksnaam:";
+"room_creation_name_placeholder" = "(bv. lunchGroep)";
+"room_creation_alias_title" = "Gespreksbijnaam:";
+"room_creation_alias_placeholder" = "(bv. #foo:voorbeeld.org)";
+"room_creation_alias_placeholder_with_homeserver" = "(bv. #foo%@)";
+"room_creation_participants_title" = "Deelnemers:";
+"room_creation_participants_placeholder" = "(bv. @jan:thuisserver1; @joep:thuisserver2…)";
+// Room
+"room_please_select" = "Selecteer een gesprek";
+"room_error_join_failed_title" = "Toetreden tot het gesprek is mislukt";
+"room_error_join_failed_empty_room" = "Het is momenteel niet mogelijk om tot een leeg gesprek toe te treden.";
+"room_error_name_edition_not_authorized" = "U bent niet bevoegd om de naam van dit gesprek te wijzigen";
+"room_error_topic_edition_not_authorized" = "U bent niet bevoegd om het onderwerp van dit gesprek te wijzigen";
+"room_error_cannot_load_timeline" = "Laden van tijdslijn is mislukt";
+"room_error_timeline_event_not_found_title" = "Laden van tijdslijnpositie is mislukt";
+"room_error_timeline_event_not_found" = "De app heeft geprobeerd een specifiek punt in de tijdslijn van dit gesprek te laden, maar kon het niet vinden";
+"room_left" = "U heeft het gesprek verlaten";
+"room_no_power_to_create_conference_call" = "U heeft toestemming nodig om een vergadering in dit groepsgesprek te starten";
+"room_no_conference_call_in_encrypted_rooms" = "Vergadergesprekken worden niet ondersteund in versleutelde gesprekken";
+// Room members
+"room_member_ignore_prompt" = "Weet u zeker dat u alle berichten van deze gebruiker wilt verbergen?";
+"room_member_power_level_prompt" = "U kunt deze veranderingen niet ongedaan maken aangezien u de gebruiker tot hetzelfde niveau als uzelf promoveert.\nWeet u het zeker?";
+// Attachment
+"attachment_size_prompt" = "Wilt u het versturen als:";
+"attachment_original" = "Werkelijke grootte (%@)";
+"attachment_small" = "Klein (~%@)";
+"attachment_medium" = "Middel (~%@)";
+"attachment_large" = "Groot (~%@)";
+"attachment_cancel_download" = "Download annuleren?";
+"attachment_cancel_upload" = "Upload annuleren?";
+"attachment_multiselection_size_prompt" = "Wilt u afbeeldingen versturen als:";
+"attachment_multiselection_original" = "Werkelijke grootte";
+"attachment_e2e_keys_file_prompt" = "Dit bestand bevat versleutelingssleutels die uit een Matrix-client geëxporteerd zijn.\nWilt u de bestandsinhoud bekijken of de sleutels die het bevat importeren?";
+"attachment_e2e_keys_import" = "Bezig met importeren…";
+// Contacts
+"contact_mx_users" = "Matrix-gebruikers";
+"contact_local_contacts" = "Lokale contacten";
+// Search
+"search_no_results" = "Geen resultaten";
+// Time
+"format_time_s" = "s";
+"format_time_m" = "m";
+"format_time_h" = "u";
+"format_time_d" = "d";
+// E2E import
+"e2e_import_room_keys" = "Gesprekssleutels importeren";
+"e2e_import_prompt" = "Dit proces maakt het mogelijk om versleutelingssleutels die u eerder had geëxporteerd vanaf een andere Matrix-cliënt te importeren. Daarna kunt u alle berichten ontsleutelen die de andere cliënt ook kon ontsleutelen.\nHet exporteerbestand is beschermd met een wachtwoord. Voer hier het wachtwoord in om het bestand te ontsleutelen.";
+"e2e_import" = "Importeren";
+"e2e_passphrase_enter" = "Voer wachtwoord in";
+// E2E export
+"e2e_export_room_keys" = "Gesprekssleutels exporteren";
+"e2e_export_prompt" = "Dit proces maakt het mogelijk om de sleutels voor berichten die u heeft ontvangen in versleutelde gesprekken te exporteren naar een lokaal bestand. Daarna kunt u het bestand in de toekomst in een andere Matrix-cliënt importeren, zodat die cliënt ook deze berichten zal kunnen ontsleutelen.\nHet geëxporteerde bestand zal iedereen die het kan lezen de mogelijkheid bieden om de versleutelde berichten die u kunt zien te ontsleutelen, dus wees voorzichtig en bewaar het op een veilige plaats.";
+"e2e_export" = "Exporteren";
+"e2e_passphrase_confirm" = "Bevestig wachtwoord";
+"e2e_passphrase_empty" = "Wachtwoord mag niet leeg zijn";
+"e2e_passphrase_not_match" = "Wachtwoorden moeten overeenkomen";
+// Others
+"user_id_title" = "Gebruikers-ID:";
+"offline" = "offline";
+"unsent" = "Niet verstuurd";
+"error" = "Fout";
+"not_supported_yet" = "Nog niet ondersteund";
+"default" = "standaard";
+"private" = "Privé";
+"public" = "Publiek";
+"power_level" = "Machtsniveau";
+"network_error_not_reachable" = "Controleer uw netwerkverbinding";
+"user_id_placeholder" = "bv: @jan:thuisserver";
+"ssl_homeserver_url" = "Thuisserver-URL: %@";
+// Permissions
+"camera_access_not_granted_for_call" = "Video-oproepen vereisen toegang tot de camera, maar %@ heeft hier geen toestemming voor";
+"microphone_access_not_granted_for_call" = "Oproepen vereisen toegang tot de camera, maar %@ heeft hier geen toestemming voor";
+"local_contacts_access_not_granted" = "Gebruikers zoeken op basis van uw lokale contacten vereist toegang tot die contacten, maar %@ heeft hier geen toestemming voor";
+"local_contacts_access_discovery_warning_title" = "Gebruikers zoeken";
+"local_contacts_access_discovery_warning" = "Om contacten te vinden die Matrix al gebruiken, kan %@ de e-mailadressen en telefoonnummers in uw adresboek naar uw gekozen Matrix-identiteitsserver sturen. Waar ondersteund worden de persoonlijke gegevens gehasht vóór het versturen - bekijk het privacybeleid van uw identiteitsserver voor meer informatie.";
+// Country picker
+"country_picker_title" = "Kies een land";
+/* -*-
+ Automatic localization for en
+
+ The following key/value pairs were extracted from the android i18n file:
+ /matrix-sdk/src/main/res/values/strings.xml.
+*/
+
+"notice_room_invite" = "%@ heeft %@ uitgenodigd";
+"notice_room_third_party_invite" = "%@ heeft een uitnodiging gestuurd naar %@ om tot het gesprek toe te treden";
+"notice_room_third_party_registered_invite" = "%@ heeft de uitnodiging voor %@ aanvaard";
+"notice_room_join" = "%@ is tot de kamer toegetreden";
+"notice_room_leave" = "%@ heeft de kamer verlaten";
+"notice_room_reject" = "%@ heeft de uitnodiging geweigerd";
+"notice_room_kick" = "%@ heeft %@ uit de kamer gezet";
+"notice_room_unban" = "%@ heeft %@ ontbannen";
+"notice_room_ban" = "%@ heeft %@ verbannen";
+"notice_room_withdraw" = "%@ heeft de uitnodiging van %@ ingetrokken";
+"notice_room_reason" = ". Reden: %@";
+"notice_avatar_url_changed" = "%@ heeft zijn/haar avatar veranderd";
+"notice_display_name_set" = "%@ heeft zijn/haar weergavenaam veranderd naar %@";
+"notice_display_name_changed_from" = "%@ heeft zijn/haar weergavenaam veranderd van %@ naar %@";
+"notice_display_name_removed" = "%@ heeft zijn/haar weergavenaam verwijderd";
+"notice_topic_changed" = "%@ heeft het onderwerp veranderd naar \"%@\".";
+"notice_room_name_changed" = "%@ heeft de gespreksnaam veranderd naar %@.";
+"notice_placed_voice_call" = "%@ heeft een spraakoproep gestart";
+"notice_placed_video_call" = "%@ heeft een video-oproep gestart";
+"notice_answered_video_call" = "%@ heeft de oproep beantwoord";
+"notice_ended_video_call" = "%@ heeft opgehangen";
+"notice_conference_call_request" = "%@ heeft een VoIP-vergadering aangevraagd";
+"notice_conference_call_started" = "VoIP-vergadering gestart";
+"notice_conference_call_finished" = "VoIP-vergadering beëindigd";
+// Room Screen
+
+// general errors
+
+// Home Screen
+
+// Last seen time
+
+// call events
+
+/* -*-
+ Automatic localization for en
+
+ The following key/value pairs were extracted from the android i18n file:
+ /console/src/main/res/values/strings.xml.
+*/
+
+// titles
+
+// button names
+"ok" = "Oké";
+"cancel" = "Annuleren";
+"save" = "Opslaan";
+"leave" = "Verlaten";
+"send" = "Versturen";
+"copy_button_name" = "Kopiëren";
+"resend" = "Opnieuw versturen";
+"redact" = "Verwijderen";
+"share" = "Delen";
+"delete" = "Verwijderen";
+"view" = "Bekijken";
+// actions
+"action_logout" = "Afmelden";
+"create_room" = "Gesprek aanmaken";
+"login" = "Aanmelden";
+"create_account" = "Account aanmaken";
+"membership_invite" = "Uitgenodigd";
+"membership_leave" = "Verlaten";
+"membership_ban" = "Verbannen";
+"num_members_one" = "%@ gebruiker";
+"num_members_other" = "%@ gebruikers";
+"invite" = "Uitnodigen";
+"kick" = "Er uit zetten";
+"ban" = "Verbannen";
+"unban" = "Ontbannen";
+"message_unsaved_changes" = "Er zijn onopgeslagen wijzigingen. Verlaten zal ze verwijderen.";
+// Login Screen
+"login_error_already_logged_in" = "Reeds aangemeld";
+"login_error_must_start_http" = "URL moet beginnen met http[s]://";
+// members list Screen
+
+// accounts list Screen
+
+// image size selection
+
+// invitation members list Screen
+
+// room creation dialog Screen
+
+// room info dialog Screen
+
+// room details dialog screen
+"room_details_title" = "Kamerdetails";
+// contacts list screen
+"invitation_message" = "Ik wil graag praten via Matrix. Bezoek de website https://matrix.org voor meer informatie.";
+// Settings screen
+"settings_title_config" = "Configuratie";
+"settings_title_notifications" = "Meldingen";
+// Notification settings screen
+"notification_settings_disable_all" = "Alle meldingen uitschakelen";
+"notification_settings_enable_notifications" = "Meldingen inschakelen";
+"notification_settings_enable_notifications_warning" = "Alle meldingen zijn momenteel voor alle apparaten uitgeschakeld.";
+"notification_settings_global_info" = "Meldingsinstellingen worden op uw account opgeslagen en gedeeld met alle cliënten die dat ondersteunen (inclusief bureaubladmeldingen).\n\nRegels worden in volgorde toegepast; de eerste regel die overeenkomt bepaalt de uitkomst van een bericht.\nDus: per-woord-meldingen zijn belangrijker dan per-gespreks-meldingen, die op hun beurt weer belangrijker zijn dan per-afzender-meldingen.\nVoor meerdere regels van hetzelfde type geldt dat de eerste in de lijst die overeenkomt de prioriteit heeft.";
+"notification_settings_per_word_notifications" = "Per-woord-meldingen";
+"notification_settings_per_word_info" = "Woorden komen niet hoofdlettergevoelig met elkaar overeen en kunnen een *-wildcard bevatten. Dus:\nfoo komt overeen met de tekenreeks ‘foo’, die omgeven wordt door woordscheidingstekens (zoals punctuatie en spaties, of het begin of einde van een regel).\nfoo* komt overeen met elk woord dat met ‘foo’ begint.\n*foo* komt overeen met elk woord dat de drie letters ‘foo’ bevat.";
+"notification_settings_always_notify" = "Altijd melden";
+"notification_settings_never_notify" = "Nooit melden";
+"notification_settings_word_to_match" = "woord om mee overeen te komen";
+"notification_settings_highlight" = "Markeren";
+"notification_settings_custom_sound" = "Aangepast geluid";
+"notification_settings_per_room_notifications" = "Per-gespreks-meldingen";
+"notification_settings_per_sender_notifications" = "Per-afzender-meldingen";
+"notification_settings_sender_hint" = "@gebruiker:domein.com";
+"notification_settings_select_room" = "Selecteer een gesprek";
+"notification_settings_other_alerts" = "Andere meldingen";
+"notification_settings_contain_my_user_name" = "Meld mij met geluid over berichten die mijn gebruikersnaam bevatten";
+"notification_settings_contain_my_display_name" = "Meld mij met geluid over berichten die mijn weergavenaam bevatten";
+"notification_settings_just_sent_to_me" = "Meld mij met geluid over berichten die alleen naar mij gestuurd zijn";
+"notification_settings_invite_to_a_new_room" = "Meld mij wanneer ik in een nieuw gesprek uitgenodigd word";
+"notification_settings_people_join_leave_rooms" = "Meld mij wanneer mensen het gesprek verlaten of betreden";
+"notification_settings_receive_a_call" = "Meld mij wanneer ik een oproep ontvang";
+"notification_settings_suppress_from_bots" = "Meldingen van robots onderdrukken";
+"notification_settings_by_default" = "Standaard…";
+"notification_settings_notify_all_other" = "Melden voor alle andere berichten/gesprekken";
+// gcm section
+"settings_config_home_server" = "Thuisserver: %@";
+"settings_config_identity_server" = "Identiteitsserver: %@";
+"settings_config_user_id" = "Gebruikers-ID: %@";
+// Settings keys
+
+// call string
+"call_waiting" = "In afwachting…";
+"call_connecting" = "Verbinden…";
+"call_ended" = "Oproep beëindigd";
+"call_ring" = "Bellen…";
+"incoming_video_call" = "Inkomende video-oproep";
+"incoming_voice_call" = "Inkomende spraakoproep";
+"call_invite_expired" = "Oproepuitnodiging verlopen";
+// unrecognized SSL certificate
+"ssl_trust" = "Vertrouwen";
+"ssl_logout_account" = "Afmelden";
+"ssl_remain_offline" = "Negeren";
+"ssl_fingerprint_hash" = "Vingerafdruk (%@):";
+"ssl_could_not_verify" = "Kan de identiteit van de externe server niet bepalen.";
+"ssl_cert_not_trust" = "Dit kan betekenen dat iemand kwaadwillig uw verkeer onderschept, of dat uw telefoon het certificaat dat door de externe server wordt geleverd niet vertrouwt.";
+"ssl_cert_new_account_expl" = "Als de serverbeheerder heeft gezegd dat dit de bedoeling is, wees er dan zeker van dat de vingerafdruk hieronder overeenkomt met de vingerafdruk die door hen wordt geleverd.";
+"ssl_unexpected_existing_expl" = "Het certificaat is veranderd van één dat door uw telefoon werd vertrouwd naar een ander. Dit is HEEL ONGEBRUIKELIJK. Het wordt aangeraden om dit nieuwe certificaat NIET TE AANVAARDEN.";
+"ssl_expected_existing_expl" = "Het certificaat is veranderd van een vertrouwd naar een onvertrouwd certificaat. De server heeft misschien zijn certificaat vernieuwd. Contacteer de serverbeheerder voor de verwachte vingerafdruk.";
+"ssl_only_accept" = "Aanvaard het certificaat alleen als de serverbeheerder een vingerafdruk heeft gepubliceerd die overeenkomt met degene hierboven.";
+"search_searching" = "Bezig met zoeken…";
+// Language picker
+"language_picker_title" = "Kies een taal";
+"language_picker_default_language" = "Standaard (%@)";
+"login_mobile_device" = "Mobiel";
+"login_tablet_device" = "Tablet";
+"login_desktop_device" = "Desktop";
+"notice_room_related_groups" = "De groepen die geassocieerd zijn met dit gesprek zijn: %@";
+// Groups
+"group_invite_section" = "Uitnodigingen";
+"group_section" = "Groepen";
+"notice_sticker" = "sticker";
+"notice_in_reply_to" = "In antwoord op";
+"error_common_message" = "Er is een fout opgetreden. Probeer het later opnieuw.";
+"login_error_resource_limit_exceeded_title" = "Bronlimiet Overschreden";
+"login_error_resource_limit_exceeded_message_default" = "Deze thuisserver heeft één of meerdere van zijn bronlimieten overschreden.";
+"login_error_resource_limit_exceeded_message_monthly_active_user" = "Deze thuisserver heeft zijn limiet voor maandelijks actieve gebruikers bereikt.";
+"login_error_resource_limit_exceeded_message_contact" = "\n\nGelieve contact op te nemen met uw dienstbeheerder om deze dienst te blijven gebruiken.";
+"login_error_resource_limit_exceeded_contact_button" = "Beheerder contacteren";
+// Reply to message
+"message_reply_to_sender_sent_an_image" = "heeft een afbeelding gestuurd.";
+"message_reply_to_sender_sent_a_video" = "heeft een video gestuurd.";
+"message_reply_to_sender_sent_an_audio_file" = "heeft een audiobestand gestuurd.";
+"message_reply_to_sender_sent_a_file" = "heeft een bestand gestuurd.";
+"message_reply_to_message_to_reply_to_prefix" = "In antwoord op";
+"e2e_passphrase_create" = "Wachtwoord aanmaken";
+"account_error_push_not_allowed" = "Meldingen niet toegestaan";
+"notice_room_third_party_revoked_invite" = "%@ heeft de uitnodiging voor %@ om tot het gesprek toe te treden ingetrokken";
+"device_details_rename_prompt_title" = "Sessienaam";
+"notice_encryption_enabled_ok" = "%@ heeft eind-tot-eind-versleuteling ingeschakeld.";
+"notice_encryption_enabled_unknown_algorithm" = "%1$@ heeft eind-tot-eind-versleuteling ingeschakeld (onbekend algoritme %2$@).";
+"notice_room_name_removed_for_dm" = "%@ heeft de naam verwijderd";
+"notice_room_ban_by_you" = "U heeft %@ verbannen";
+"notice_room_unban_by_you" = "U heeft %@ ontbannen";
+"notice_room_history_visible_to_members_from_joined_point_by_you_for_dm" = "U heeft toekomstige berichten voor iedereen zichtbaar gemaakt vanaf het moment dat zij deelnemen aan het gesprek.";
+"notice_room_history_visible_to_members_from_joined_point_by_you" = "U heeft de toekomstige gespreksgeschiedenis zichtbaar gemaakt voor alle gespreksleden, vanaf het moment dat ze toegetreden zijn.";
+"notice_room_history_visible_to_members_from_invited_point_by_you_for_dm" = "U heeft toekomstige berichten voor iedereen zichtbaar gemaakt vanaf het moment dat zij zijn uitgenodigd.";
+"notice_room_history_visible_to_members_from_invited_point_by_you" = "U heeft de toekomstige gespreksgeschiedenis zichtbaar gemaakt voor alle gespreksleden, vanaf het moment dat ze uitgenodigd zijn.";
+"notice_room_history_visible_to_members_by_you_for_dm" = "U heeft toekomstige berichten voor alle gespreksleden zichtbaar gemaakt.";
+"notice_room_history_visible_to_members_by_you" = "U heeft de toekomstige gespreksgeschiedenis voor alle gespreksleden zichtbaar gemaakt.";
+"notice_room_history_visible_to_anyone_by_you" = "U heeft de toekomstige gespreksgeschiedenis voor iedereen zichtbaar gemaakt.";
+"notice_redaction_by_you" = "U heeft een gebeurtenis bewerkt (ID: %@)";
+"notice_encryption_enabled_unknown_algorithm_by_you" = "U heeft eind-tot-eind-versleuteling ingeschakeld (onbekend algoritme %@).";
+"notice_encryption_enabled_ok_by_you" = "U heeft eind-tot-eind-versleuteling ingeschakeld.";
+"notice_room_created_by_you_for_dm" = "U bent toegetreden.";
+"notice_room_created_by_you" = "U heeft de kamer aangemaakt en ingesteld.";
+"notice_profile_change_redacted_by_you" = "U heeft uw profiel %@ bijgewerkt";
+"notice_event_redacted_by_you" = " door u";
+"notice_room_topic_removed_by_you" = "U heeft het onderwerp verwijderd";
+"notice_room_name_removed_by_you_for_dm" = "U heeft de naam verwijderd";
+"notice_room_name_removed_by_you" = "U heeft de gespreksnaam verwijderd";
+"notice_conference_call_request_by_you" = "U heeft een VoIP-vergadering aangevraagd";
+"notice_ended_video_call_by_you" = "U heeft opgehangen";
+"notice_answered_video_call_by_you" = "U heeft de oproep beantwoord";
+"notice_placed_video_call_by_you" = "U heeft een spraakoproep gestart";
+"notice_placed_voice_call_by_you" = "U heeft een spraakoproep gestart";
+"notice_room_name_changed_by_you_for_dm" = "U heeft de gespreksnaam veranderd naar %@.";
+"notice_room_name_changed_by_you" = "U heeft de gespreksnaam veranderd naar %@.";
+"notice_topic_changed_by_you" = "U heeft het onderwerp veranderd naar \"%@\".";
+"notice_display_name_removed_by_you" = "U heeft uw weergavenaam verwijderd";
+"notice_display_name_changed_from_by_you" = "U heeft uw weergavenaam veranderd van %@ naar %@";
+"notice_display_name_set_by_you" = "U heeft uw weergavenaam veranderd naar %@";
+"notice_avatar_url_changed_by_you" = "U heeft uw profielfoto veranderd";
+"notice_room_withdraw_by_you" = "U heeft %@'s uitnodiging teruggetrokken";
+"notice_room_kick_by_you" = "U heeft %@ verwijderd uit het gesprek";
+"notice_room_reject_by_you" = "U heeft de uitnodiging geweigerd";
+"notice_room_leave_by_you" = "U heeft het gesprek verlaten";
+"notice_room_join_by_you" = "U bent toegetreden";
+"notice_room_third_party_revoked_invite_by_you_for_dm" = "U heeft %@'s uitnodiging ingetrokken";
+"notice_room_third_party_revoked_invite_by_you" = "U heeft de uitnodiging aan %@ om aan het gesprek deel te nemen ingetrokken";
+"notice_room_third_party_registered_invite_by_you" = "U heeft de uitnodiging van %@ aanvaard";
+"notice_room_third_party_invite_by_you_for_dm" = "U heeft %@ uitgenodigd";
+"notice_room_third_party_invite_by_you" = "U heeft %@ uitgenodigd om aan het gesprek deel te nemen";
+"notice_room_invite_you" = "%@ heeft u uitgenodigd";
+
+// Notice Events with "You"
+"notice_room_invite_by_you" = "U heeft %@ uitgenodigd";
+"notice_room_name_changed_for_dm" = "%@ heeft de gespreksnaam veranderd naar %@.";
+"notice_room_third_party_revoked_invite_for_dm" = "%@ heeft %@'s uitnodiging ingetrokken";
+"notice_room_third_party_invite_for_dm" = "%@ heeft %@ uitgenodigd";
+"room_left_for_dm" = "U heeft het gesprek verlaten";
+"notice_room_history_visible_to_members_from_joined_point_for_dm" = "%@ heeft toekomstige berichten voor iedereen zichtbaar gemaakt vanaf het moment dat zij deelnemen aan het gesprek.";
+"notice_room_history_visible_to_members_from_invited_point_for_dm" = "%@ heeft toekomstige berichten voor iedereen zichtbaar gemaakt vanaf het moment dat zij zijn uitgenodigd.";
+"notice_room_history_visible_to_members_for_dm" = "%@ heeft toekomstige berichten voor alle gespreksleden zichtbaar gemaakt.";
+"notice_room_aliases_for_dm" = "De bijnamen zijn: %@";
+"notice_room_join_rule_public_by_you_for_dm" = "U maakte dit publiekelijk.";
+"notice_room_join_rule_public_by_you" = "U heeft de kamer publiekelijk gemaakt.";
+"notice_room_join_rule_public_for_dm" = "%@ maakte dit publiekelijk.";
+"notice_room_join_rule_public" = "%@ heeft de kamer publiekelijk gemaakt.";
+"notice_room_created_for_dm" = "%@ is toegetreden.";
+"notice_room_power_level_intro_for_dm" = "Het machtsniveau van de gespreksleden is:";
+"notice_room_join_rule_invite_by_you_for_dm" = "U maakte dit gesprek alleen op uitnodiging.";
+"notice_room_join_rule_invite_by_you" = "U heeft het toegangsbeleid gewijzigd naar alleen genodigden.";
+// New
+"notice_room_join_rule_invite" = "%@ heeft het toegangsbeleid gewijzigd naar alleen genodigden.";
+"notice_room_join_rule_invite_for_dm" = "%@ heeft dit gesprek alleen op uitnodiging gemaakt.";
+"call_more_actions_dialpad" = "Kiestoetsen";
+"call_more_actions_transfer" = "Doorschakelen";
+"call_more_actions_audio_use_device" = "Ingebouwde Luidspreker";
+"call_more_actions_audio_use_headset" = "Audio-koptelefoon gebruiken";
+"call_more_actions_change_audio_device" = "Audio-apparaat wisselen";
+"call_more_actions_unhold" = "Hervatten";
+"call_more_actions_hold" = "Vasthouden";
+"call_holded" = "U heeft de oproep in de wacht";
+"call_remote_holded" = "%@ heeft de oproep in de wacht";
+"notice_declined_video_call_by_you" = "U heeft de oproep afgewezen";
+"notice_declined_video_call" = "%@ heeft de oproep afgewezen";
+"resume_call" = "Hervatten";
+"call_consulting_with_user" = "In de wacht bij %@";
+"call_transfer_to_user" = "Doorverbinden met %@";
+"call_video_with_user" = "Video-oproep met %@";
+"call_voice_with_user" = "Spraakoproep met %@";
+"call_ringing" = "Bellen…";
+"e2e_passphrase_too_short" = "Wachtwoord is te kort (hij moet minimaal %d tekens lang zijn)";
+"microphone_access_not_granted_for_voice_message" = "Spraakberichten vereisen toegang tot de Microfoon maar %@ heeft geen toestemming om het te gebruiken";
+"message_reply_to_sender_sent_a_voice_message" = "heeft een spraakbericht gestuurd.";
+"attachment_large_with_resolution" = "Groot %@ (~%@)";
+"attachment_medium_with_resolution" = "Middel %@ (~%@)";
+"attachment_small_with_resolution" = "Klein %@ (~%@)";
+"attachment_size_prompt_message" = "U kunt dit uitzetten in uw instellingen.";
+"attachment_size_prompt_title" = "Bevestig de afmeting om te versturen";
+"room_displayname_all_other_participants_left" = "%@ (vertrok)";
diff --git a/Riot/Modules/MatrixKit/Assets/MatrixKitAssets.bundle/pl.lproj/MatrixKit.strings b/Riot/Modules/MatrixKit/Assets/MatrixKitAssets.bundle/pl.lproj/MatrixKit.strings
new file mode 100644
index 000000000..d90ed3c37
--- /dev/null
+++ b/Riot/Modules/MatrixKit/Assets/MatrixKitAssets.bundle/pl.lproj/MatrixKit.strings
@@ -0,0 +1,498 @@
+"matrix" = "Matrix";
+// Login Screen
+"login_create_account" = "Stwórz konto:";
+"login_server_url_placeholder" = "URL (np. https://matrix.org)";
+"login_identity_server_title" = "URL serwera tożsamości:";
+"login_password_placeholder" = "Hasło";
+"login_optional_field" = "opcjonalne";
+"login_email_placeholder" = "Adres e-mail";
+"login_error_forbidden" = "Nieprawidłowa nazwa użytkownika/hasło";
+"login_error_unknown_token" = "Wprowadzony token dostępu nie został rozpoznany";
+"login_error_bad_json" = "Uszkodzony JSON";
+"login_error_not_json" = "Nie zawiera prawidłowego JSON";
+"login_error_limit_exceeded" = "Wysłano zbyt wiele żądań";
+"login_error_user_in_use" = "Ta nazwa użytkownika jest już używana";
+"login_error_login_email_not_yet" = "Nie kliknięto odnośnika z wiadomości e-mail";
+"login_leave_fallback" = "Anuluj";
+"login_error_resource_limit_exceeded_title" = "Przekroczono limit dostępu do zasobów";
+"login_error_resource_limit_exceeded_message_default" = "Ten serwer przekroczył jeden z limitów dostępu do zasobów.";
+"login_error_resource_limit_exceeded_message_monthly_active_user" = "Ten serwer osiągnął miesięczny limit aktywnych użytkowników.";
+"login_error_resource_limit_exceeded_message_contact" = "\n\nSkontaktuj się z administratorem Twojego serwera domowego, aby korzystać dalej z tej usługi.";
+"login_error_resource_limit_exceeded_contact_button" = "Skontaktuj się z administratorem";
+// Action
+"no" = "Nie";
+"yes" = "Tak";
+"back" = "Powrót";
+"close" = "Zamknij";
+"continue" = "Kontynuuj";
+"dismiss" = "Zamknij";
+"retry" = "Ponów";
+"submit" = "Wyślij";
+"login_home_server_title" = "URL serwera domowego:";
+"login_display_name_placeholder" = "Wyświetlana nazwa (np. Bob Obson)";
+"login_invalid_param" = "Nieprawidłowy parametr";
+"login_tablet_device" = "Tablet";
+"discard" = "Odrzuć";
+"start_chat" = "Rozpocznij rozmowę";
+"start_voice_call" = "Rozpocznij połączenie głosowe";
+"start_video_call" = "Rozpocznij połączenie wideo";
+"mention" = "Wspomnij";
+"capture_media" = "Zrób zdjęcie/film";
+"resend_message" = "Wyślij ponownie wiadomość";
+"select_all" = "Zaznacz wszystko";
+"cancel_upload" = "Anuluj wysyłanie";
+"cancel_download" = "Anuluj pobieranie";
+"ignore" = "Ignoruj";
+"unignore" = "Przestań ignorować";
+// Events formatter
+"notice_avatar_changed_too" = "(awatar też został zmieniony)";
+"notice_room_name_removed" = "%@ usunął(-ęła) nazwę pokoju";
+"notice_room_topic_removed" = "%@ usunął(-ęła) temat pokoju";
+"notice_event_redacted" = "";
+"notice_event_redacted_by" = " przez %@";
+"notice_event_redacted_reason" = " [powód: %@]";
+"notice_profile_change_redacted" = "%@ zaktualizował(-a) swój profil %@";
+"notice_encrypted_message" = "Wiadomość zaszyfrowana";
+"notice_encryption_enabled" = "%@ włączył(a) szyfrowanie end-to-end (algorytm %@)";
+"ssl_only_accept" = "Akceptuj certyfikat TYLKO wtedy gdy administrator opublikował odcisk palca pasujący do tego powyżej.";
+"ssl_unexpected_existing_expl" = "Certyfikat zmienił stan z zaufanego na niezaufany. Jest to NIEZWYKLE RZADKIE. Zalecane jest NIE AKCEPTOWANIE nowego certyfikatu.";
+"ssl_cert_not_trust" = "Może to oznaczać że ktoś zakłóca twoje połączenie, lub Twój telefon nie ufa certyfikatowi dostarczonemu przez zdalny serwer.";
+"ssl_could_not_verify" = "Nie można zweryfikować tożsamości serwera.";
+"ssl_fingerprint_hash" = "Fingerprint (%@):";
+"ssl_remain_offline" = "Ignoruj";
+"ssl_logout_account" = "Wyloguj";
+// unrecognized SSL certificate
+"ssl_trust" = "Ufaj";
+"incoming_voice_call" = "Przychodzące połączenie głosowe";
+"incoming_video_call" = "Przychodzące połączenie wideo";
+"call_ring" = "Dzwonię…";
+"call_ended" = "Zakończono połączenie";
+"call_connecting" = "Łączenie…";
+"settings_config_user_id" = "ID użytkownika: %@";
+"settings_config_identity_server" = "Serwer tożsamości: %@";
+// gcm section
+"settings_config_home_server" = "Serwer Domowy: %@";
+"notification_settings_notify_all_other" = "Powiadamiaj o wszystkich innych wiadomośsciach/pokojach";
+"notification_settings_select_room" = "Wybierz pokój";
+"notification_settings_sender_hint" = "@user:domain.com";
+"notification_settings_enable_notifications" = "Włącz powiadomienia";
+// Notification settings screen
+"notification_settings_disable_all" = "Wyłącz wszystkie powiadomienia";
+"settings_title_notifications" = "Powiadomienia";
+// room details dialog screen
+"room_details_title" = "Szczegóły pokoju";
+"login_error_must_start_http" = "URL musi zaczynać się od http[s]://";
+"select_account" = "Wybierz konto";
+"show_details" = "Pokaż szczegóły";
+"end_call" = "Zakończ rozmowę";
+"login_error_title" = "Logowanie nie powiodło się";
+"login_error_registration_is_not_supported" = "Rejestracja nie jest obecnie wspierana";
+"register_error_title" = "Rejestracja nie powiodła się";
+"abort" = "Przerwij";
+"sign_up" = "Zarejestruj się";
+"submit_code" = "Prześlij kod";
+"attach_media" = "Dołącz zawartość multimedialną z Biblioteki";
+"reset_to_default" = "Przywróć ustawienia domyślne";
+"notice_room_created" = "%@ stworzył(-a) i skonfigurował (-a) pokój.";
+"notice_audio_attachment" = "załącznik (audio)";
+"notice_video_attachment" = "załącznik (wideo)";
+"notice_invalid_attachment" = "nieprawidłowy załącznik";
+"notice_unsupported_attachment" = "Niewspierany załącznik: %@";
+"notice_room_history_visible_to_members" = "%@ uczynił(-a) przyszłą historię pokoju widoczną dla wszystkich uczestników pokoju.";
+"notice_room_history_visible_to_members_from_invited_point" = "%@ uczynił(-a) przyszłą historię pokoju widoczną dla wszystkich uczestników pokoju, od momentu ich zaproszenia.";
+"notice_room_history_visible_to_members_from_joined_point" = "%@ uczynił(-a) przyszłą historię pokoju widoczną dla wszystkich uczestników pokoju, od momentu ich dołączenia.";
+"notice_crypto_error_unknown_inbound_session_id" = "Sesja nadawcy nie wysłała kluczy do wiadomości dla bieżącej sesji.";
+"notice_sticker" = "naklejka";
+"notice_in_reply_to" = "W odpowiedzi do";
+// room display name
+"room_displayname_empty_room" = "Pusty pokój";
+"room_displayname_two_members" = "%@ i %@";
+"room_displayname_more_than_two_members" = "%@ i %@ innych";
+// Settings
+"settings" = "Ustawienia";
+"notification_settings_room_rule_title" = "Pokój: '%@'";
+// Devices
+"device_details_title" = "Informacje o sesji\n";
+"device_details_name" = "Publiczna nazwa\n";
+"device_details_identifier" = "ID\n";
+"device_details_last_seen" = "Ostatnio widziany(-a)\n";
+"device_details_last_seen_format" = "%@ @ %@\n";
+"device_details_rename_prompt_message" = "Publiczna nazwa sesji jest widoczna dla osób z którymi się komunikujesz";
+"device_details_delete_prompt_title" = "Uwierzytelnienie";
+"device_details_delete_prompt_message" = "To działanie wymaga dodatkowego uwierzytelnienia.\nWprowadź hasło, aby kontynuować.";
+// Encryption information
+"room_event_encryption_info_title" = "Informacje o szyfrowaniu end-to-end\n\n";
+"room_event_encryption_info_event" = "Informacje o zdarzeniu\n";
+"room_event_encryption_info_event_user_id" = "ID użytkownika\n";
+"room_event_encryption_info_event_identity_key" = "Klucz tożsamości Curve25519\n";
+"room_event_encryption_info_event_algorithm" = "Algorytm\n";
+"room_event_encryption_info_event_decryption_error" = "Błąd deszyfrowania\n";
+"room_event_encryption_info_device" = "\nInformacje o sesji nadawcy\n";
+"room_event_encryption_info_device_unknown" = "nieznana sesja\n";
+"room_event_encryption_info_device_name" = "Publiczna nazwa\n";
+"room_event_encryption_info_device_id" = "ID\n";
+"notification_settings_never_notify" = "Nigdy nie powiadamiaj";
+"notification_settings_always_notify" = "Zawsze powiadamiaj";
+// Settings screen
+"settings_title_config" = "Konfiguracja";
+"ban" = "Zbanuj";
+"unban" = "Odbanuj";
+"kick" = "Wyproś";
+"invite" = "Zaproś";
+"num_members_other" = "%@ użytkowników";
+"num_members_one" = "%@ użytkownik";
+"create_account" = "Stwórz konto";
+"create_room" = "Utwórz pokój";
+// actions
+"action_logout" = "Wyloguj";
+"view" = "Podgląd";
+"delete" = "Usuń";
+"set_power_level" = "Ustaw poziom uprawnień";
+"share" = "Udostępnij";
+"redact" = "Usuń";
+"resend" = "Wyślij ponownie";
+"copy_button_name" = "Kopiuj";
+"send" = "Wyślij";
+"leave" = "Opuść";
+"save" = "Zapisz";
+"cancel" = "Anuluj";
+// button names
+"ok" = "OK";
+"notice_conference_call_finished" = "Zakończono konferencję VoIP";
+"notice_conference_call_started" = "Rozpoczęto konferencję VoIP";
+"notice_conference_call_request" = "%@ zaprasza do konferencji VoIP";
+"set_default_power_level" = "Resetuj poziom uprawnień";
+"notice_file_attachment" = "załącznik (plik)";
+"notice_room_history_visible_to_anyone" = "%@ uczynił(-a) przyszłą historię pokoju widoczną dla każdego.";
+"notice_crypto_unable_to_decrypt" = "** Nie można odszyfrować: %@ **";
+"ssl_cert_new_account_expl" = "Jeśli administrator serwera oświadczył, że jest to oczekiwane, upewnij się, że poniższy odcisk palca odpowiada odciskowi palca dostarczonemu przez niego.";
+"notification_settings_by_default" = "Domyślnie…";
+"notification_settings_suppress_from_bots" = "Ogranicz powiadomienia od botów";
+"notification_settings_custom_sound" = "Dźwięk niestandardowy";
+"login_home_server_info" = "Twój serwer domowy przechowuje wszystkie Twoje rozmowy i dane konta";
+"login_user_id_placeholder" = "Identyfikator Matrix (np. @bob:matrix.org lub bob)";
+"search_searching" = "Wyszukiwanie...";
+
+// Search
+"search_no_results" = "Brak wyników";
+"group_section" = "Grupy";
+
+// Groups
+"group_invite_section" = "Zaproszenia";
+"attachment_e2e_keys_import" = "Importuj...";
+"attachment_multiselection_original" = "Rzeczywisty rozmiar";
+"attachment_multiselection_size_prompt" = "Czy chcesz wysłać obrazy jako:";
+"attachment_cancel_upload" = "Przerwać wgrywanie?";
+"attachment_cancel_download" = "Przerwać pobieranie?";
+"membership_ban" = "Zbanowany(-a)";
+"login_desktop_device" = "Komputer";
+"login_mobile_device" = "Smartphone";
+"login_error_forgot_password_is_not_supported" = "Przywracanie hasła nie jest obecnie obsługiwane";
+"login_prompt_email_token" = "Wprowadź token weryfikacyjny wysłany na e-mail:";
+"room_event_encryption_info_event_session_id" = "ID sesji\n";
+"room_event_encryption_info_event_fingerprint_key" = "Odebrany klucz Ed25519 fingerprint\n";
+"device_details_rename_prompt_title" = "Nazwa sesji";
+"settings_enter_validation_token_for" = "Wprowadź token weryfikacyjny dla %@:";
+"settings_enable_push_notifications" = "Włącz powiadomienia push";
+"settings_enable_inapp_notifications" = "Włącz powiadomienia aplikacyjne";
+"notice_room_history_visible_to_members_from_joined_point_for_dm" = "%@ uczynił(-a) przyszłą historię pokoju widoczną dla wszystkich uczestników pokoju, od momentu ich dołączenia.";
+"notice_room_history_visible_to_members_from_invited_point_for_dm" = "%@ uczynił(-a) przyszłą historię pokoju widoczną dla wszystkich uczestników pokoju, od momentu ich zaproszenia.";
+"notice_room_history_visible_to_members_for_dm" = "%@ uczynił(-a) przyszłą historię pokoju widoczną dla wszystkich uczestników pokoju.";
+"notice_error_unknown_event_type" = "Nieznany typ zdarzenia";
+"notice_error_unexpected_event" = "Niespodziewane zdarzenie";
+"notice_error_unsupported_event" = "Nieobsługiwane zdarzenie";
+"notice_redaction" = "%@ zredagował(-a) zdarzenie (id: %@)";
+"notice_feedback" = "Opis zdarzenia (id: %@): %@";
+"notice_location_attachment" = "załącznik (lokalizacja)";
+"notice_image_attachment" = "załącznik (obraz)";
+"notice_encryption_enabled_unknown_algorithm" = "%1$@ włączył(-a) szyfrowanie end-to-end (nierozpoznany algorytm %2$@).";
+"notice_encryption_enabled_ok" = "%@ włączył(-a) szyfrowanie end-to-end.";
+"notice_room_related_groups" = "Społeczności powiązane z tym pokojem: %@";
+"notice_room_aliases_for_dm" = "Aliasy pokoju: %@";
+"notice_room_aliases" = "Aliasy pokoju: %@";
+"notice_room_power_level_event_requirement" = "Minimalny poziom uprawnień związany ze zdarzeniami:";
+"notice_room_power_level_acting_requirement" = "Minimalny poziom uprawnień uczestnika pokoju, aby mógł podjąć działania:";
+"notice_room_power_level_intro_for_dm" = "Poziom uprawnień uczestników pokoju:";
+"notice_room_power_level_intro" = "Poziom uprawnień uczestników pokoju:";
+"notice_room_join_rule_public_by_you_for_dm" = "Ustawiłeś(-aś) widoczność tego pokoju jako 'pokój publiczny'.";
+"notice_room_join_rule_public_by_you" = "Ustawiłeś(-aś) widoczność tego pokoju jako 'pokój publiczny'.";
+"notice_room_join_rule_public_for_dm" = "%@ ustawił(-a) widoczność tego pokoju jako 'pokój publiczny'.";
+"notice_room_join_rule_public" = "%@ ustawił(-a) widoczność tego pokoju jako 'pokój publiczny'.";
+"notice_room_join_rule_invite_by_you_for_dm" = "Ustawiłeś(-aś) dostępność tego pokoju na 'Tylko osoby, które zostały zaproszone'.";
+"notice_room_join_rule_invite_by_you" = "Ustawiłeś(-aś) dostępność tego pokoju na 'Tylko osoby, które zostały zaproszone'.";
+"notice_room_join_rule_invite_for_dm" = "%@ ustawił(-a) dostępność tego pokoju na 'Tylko osoby, które zostały zaproszone'.";
+// New
+"notice_room_join_rule_invite" = "%@ ustawił(-a) dostępność tego pokoju na 'Tylko osoby, które zostały zaproszone'.";
+// Old
+"notice_room_join_rule" = "Reguła dołączenia to: %@";
+"notice_room_created_for_dm" = "%@ stworzył(-a) pokój.";
+"notice_room_name_removed_for_dm" = "%@ usunął(-ęła) nazwę pokoju";
+"resume_call" = "Wznów";
+"reject_call" = "Odrzuć połączenie";
+"answer_call" = "Odbierz połączenie";
+"invite_user" = "Zaproś użytkownika Matrix";
+"set_admin" = "Nadaj uprawnienia Administratora";
+"set_moderator" = "Nadaj uprawnienia Moderatora";
+"login_use_fallback" = "Użyj strony zastępczej";
+"login_error_do_not_support_login_flows" = "Obecnie nie obsługujemy żadnego lub wszystkich przepływów logowania zdefiniowanych przez ten Serwer Domowy";
+"login_error_no_login_flow" = "Nie udało się pobrać informacji uwierzytelniających z tego serwera domowego";
+"login_email_info" = "Podanie adresu e-mail pozwala innym użytkownikom na znalezienie Ciebie w sieci Matrix oraz pozwala na zresetowanie hasła.";
+"login_identity_server_info" = "Matrix zapewnia serwery tożsamości do śledzenia, które e-maile itp. Należą do których identyfikatorów Matrix. Obecnie istnieje tylko https://matrix.org.";
+"ssl_expected_existing_expl" = "Certyfikat zmienił się z wcześniej zaufanego na taki, który nie jest zaufany. Serwer mógł odnowić swój certyfikat. Skontaktuj się z administratorem serwera, aby uzyskać oczekiwany odcisk palca.";
+"call_transfer_to_user" = "Transfer do %@";
+"call_consulting_with_user" = "Konsultacje z %@";
+"call_video_with_user" = "Połączenie wideo z %@";
+"call_voice_with_user" = "Połączenie głosowe z %@";
+"call_more_actions_dialpad" = "Klawiatura numeryczna";
+"call_more_actions_transfer" = "Transferuj";
+"call_more_actions_audio_use_device" = "Wyjście audio";
+"call_more_actions_audio_use_headset" = "Użyj zestawu głosowego";
+"call_more_actions_change_audio_device" = "Zmień urządzenie dźwiękowe";
+"call_more_actions_unhold" = "Wznów";
+"call_more_actions_hold" = "Wstrzymaj";
+"call_holded" = "Wstrzymałeś połączenie";
+"call_remote_holded" = "%@ wstrzymał(-a) połączenie";
+"call_invite_expired" = "Zaproszenie do rozmowy wygasło";
+"call_ringing" = "Dzwonię…";
+"notification_settings_receive_a_call" = "Powiadom mnie, gdy ktoś do mnie dzwoni";
+"notification_settings_people_join_leave_rooms" = "Powiadamiaj mnie, gdy ktoś dołącza do pokoju lub go opuszcza";
+"notification_settings_invite_to_a_new_room" = "Powiadamiaj mnie, gdy jestem zaproszony do nowego pokoju";
+"notification_settings_just_sent_to_me" = "Powiadamiaj mnie dźwiękiem o wiadomościach wysłanych tylko do mnie";
+"notification_settings_contain_my_display_name" = "Powiadamiaj mnie dźwiękiem o wiadomościach zawierających moją nazwę wyświetlaną";
+"notification_settings_contain_my_user_name" = "Powiadamiaj mnie dźwiękiem o wiadomościach zawierających moją nazwę użytkownika";
+"notification_settings_other_alerts" = "Inne Alarmy";
+"notification_settings_per_sender_notifications" = "Powiadomienia per-nadawca";
+"notification_settings_per_room_notifications" = "Powiadomienia per-pokój";
+"notification_settings_highlight" = "Wyróżnienie";
+"notification_settings_word_to_match" = "słowo do dopasowania";
+"notification_settings_per_word_info" = "Słowa dopasowują wielkość liter bez uwzględniania wielkości liter i mogą zawierać * symbol wieloznaczny. Więc:\nfoo dopasowuje ciąg foo otoczony ogranicznikami słów (np. interpunkcja i białe spacje lub początek / koniec linii).\nfoo* odpowiada każdemu słowu zaczynającemu się foo.\n*foo* pasuje do każdego takiego słowa, które zawiera 3 litery foo.";
+"notification_settings_per_word_notifications" = "Powiadomienia według słów";
+"notification_settings_global_info" = "Ustawienia powiadomień są zapisywane na koncie użytkownika i są udostępniane wszystkim klientom, które je obsługują.\n\nReguły powiadomień są stosowane w kolejności; pierwsza pasująca reguła określa wynik wiadomości.\nTak więc: powiadomienia według słów są ważniejsze niż powiadomienia dotyczące pokoju, które są ważniejsze niż powiadomienia według nadawcy.\nW przypadku wielu reguł tego samego typu pierwszeństwo ma pierwsza na liście pasująca reguła.";
+"notification_settings_enable_notifications_warning" = "Wszystkie powiadomienia są obecnie wyłączone dla wszystkich urządzeń.";
+
+// contacts list screen
+"invitation_message" = "Chciałbym z Tobą porozmawiać za pomocą sieci Matrix. Odwiedź witrynę http://matrix.org, aby uzyskać więcej informacji.";
+
+// Login Screen
+"login_error_already_logged_in" = "Jesteś już zalogowany(-a)";
+"message_unsaved_changes" = "Istnieją niezapisane zmiany. Opuszczenie spowoduje ich odrzucenie.";
+"membership_leave" = "Odszedł(-a)";
+"membership_invite" = "Zaproszony(-a)";
+"login" = "Zaloguj się";
+"notice_room_history_visible_to_members_from_joined_point_by_you_for_dm" = "Sprawiłeś(-aś), że przyszłe wiadomości będą widoczne tylko dla uczestników pokoju od momentu, gdy dołączyli.";
+"notice_room_history_visible_to_members_from_joined_point_by_you" = "Sprawiłeś(-aś), że przyszłe wiadomości będą widoczne tylko dla uczestników pokoju od momentu, gdy dołączyli.";
+"notice_room_history_visible_to_members_from_invited_point_by_you_for_dm" = "Sprawiłeś(-aś), że przyszłe wiadomości będą widoczne dla wszystkich uczestników pokoju od momentu, gdy zostali zaproszeni.";
+"notice_room_history_visible_to_members_from_invited_point_by_you" = "Sprawiłeś(-aś), że przyszłe wiadomości będą widoczne dla wszystkich uczestników pokoju od momentu, gdy zostali zaproszeni.";
+"notice_room_history_visible_to_members_by_you_for_dm" = "Sprawiłeś(-aś), że przyszłe wiadomości będą widoczne dla wszystkich uczestników pokoju.";
+"notice_room_history_visible_to_members_by_you" = "Sprawiłeś(-aś), że przyszłe wiadomości będą widoczne dla wszystkich uczestników pokoju.";
+"notice_room_history_visible_to_anyone_by_you" = "Sprawiłeś(-aś), że przyszłe wiadomości będą widoczne dla każdego.";
+"notice_redaction_by_you" = "Zredagowałeś(-aś) zdarzenie (id: %@)";
+"notice_encryption_enabled_unknown_algorithm_by_you" = "Włączyłeś(-aś) szyfrowanie end-to-end (nierozpoznany algorytm %@).";
+"notice_encryption_enabled_ok_by_you" = "Włączyłeś(-aś) szyfrowanie end-to-end.";
+"notice_room_created_by_you_for_dm" = "Utworzyłeś(-aś) i skonfigurowałeś(-aś) pokój.";
+"notice_room_created_by_you" = "Utworzyłeś(-aś) i skonfigurowałeś(-aś) pokój.";
+"notice_profile_change_redacted_by_you" = "Zaktualizowałeś(-aś) swój profil %@";
+"notice_event_redacted_by_you" = " przez Ciebie";
+"notice_room_topic_removed_by_you" = "Usunąłeś(-aś) temat";
+"notice_room_name_removed_by_you_for_dm" = "Usunąłeś(-aś) nazwę pokoju";
+"notice_room_name_removed_by_you" = "Usunąłeś(-aś) nazwę pokoju";
+"notice_conference_call_request_by_you" = "Poprosiłeś(-aś) o konferencję VoIP";
+"notice_declined_video_call_by_you" = "Odrzuciłeś(-aś) połączenie";
+"notice_ended_video_call_by_you" = "Zakończyłeś(-aś) połączenie";
+"notice_answered_video_call_by_you" = "Odebrałeś(-aś) połączenie";
+"notice_placed_video_call_by_you" = "Nawiązałeś(-aś) rozmowę wideo";
+"notice_placed_voice_call_by_you" = "Nawiązałeś(-aś) połączenie głosowe";
+"notice_room_name_changed_by_you_for_dm" = "Zmieniłeś(-aś) nazwę na %@.";
+"notice_room_name_changed_by_you" = "Zmieniłeś(-aś) nazwę pokoju na %@.";
+"notice_topic_changed_by_you" = "Zmieniłeś(-aś) temat na \"%@\".";
+"notice_display_name_removed_by_you" = "Usunąłeś(-aś) swoją wyświetlaną nazwę";
+"notice_display_name_changed_from_by_you" = "Zmieniłeś(-aś) wyświetlaną nazwę z %@ na %@";
+"notice_display_name_set_by_you" = "Zmieniłeś(-aś) wyświetlaną nazwę na %@";
+"notice_avatar_url_changed_by_you" = "Zmieniłeś(-aś) swój awatar";
+"notice_room_withdraw_by_you" = "Wycofałeś(-aś) zaproszenie do pokoju dla %@";
+"notice_room_ban_by_you" = "Zbanowałeś(-aś) %@";
+"notice_room_unban_by_you" = "Odbanowałeś(-aś) %@";
+"notice_room_kick_by_you" = "Wyprosiłeś(-aś) %@";
+"notice_room_reject_by_you" = "Odrzuciłeś(-aś) zaproszenie";
+"notice_room_leave_by_you" = "Opuściłeś(-aś) pokój";
+"notice_room_join_by_you" = "Dołączyłeś(-aś) do pokoju";
+"notice_room_third_party_revoked_invite_by_you_for_dm" = "Odrzuciłeś(-aś) zaproszenie %@";
+"notice_room_third_party_revoked_invite_by_you" = "Odrzuciłeś(-aś) zaproszenie do pokoju od %@";
+"notice_room_third_party_registered_invite_by_you" = "Przyjąłeś(-aś) zaproszenie od %@";
+"notice_room_third_party_invite_by_you_for_dm" = "Zaprosiłeś(-aś) %@";
+"notice_room_third_party_invite_by_you" = "Wysłałeś(-aś) zaproszenie do pokoju dla %@";
+"notice_room_invite_you" = "%@ zaprosił(-a) Ciebie";
+
+// Notice Events with "You"
+"notice_room_invite_by_you" = "Zaprosiłeś(-aś) %@";
+"notice_declined_video_call" = "%@ odrzucił(-a) połączenie";
+"notice_ended_video_call" = "%@ zakończył(-a) połączenie";
+"notice_answered_video_call" = "%@ odebrał(-a) połączenie";
+"notice_placed_video_call" = "%@ nawiązał(-a) połączenie wideo";
+"notice_placed_voice_call" = "%@ nawiązał(-a) połączenie głosowe";
+"notice_room_name_changed_for_dm" = "%@ zmienił(-a) nazwę na %@.";
+"notice_room_name_changed" = "%@ zmienił(-a) nazwę pokoju na %@.";
+"notice_topic_changed" = "%@ zmienił(-a) temat na \"%@\".";
+"notice_display_name_removed" = "%@ usunął(-ęła) swoją wyświetlaną nazwę";
+"notice_display_name_changed_from" = "%@ zmienił(-a) swoją wyświetlaną nazwę z %@ na %@";
+"notice_display_name_set" = "%@ zmienił(-a) swoją wyświetlaną nazwę na %@";
+"notice_avatar_url_changed" = "%@ zmienił(-a) swój awatar";
+"notice_room_reason" = ". Powód: %@";
+"notice_room_withdraw" = "%@ wycofał(-a) zaproszenie %@";
+"notice_room_ban" = "%@ zbanował(-a) %@";
+"notice_room_unban" = "%@ odbanował(-a) %@";
+"notice_room_kick" = "%@ wyprosiła(-a) %@";
+"notice_room_reject" = "%@ odrzucił(-a) zaproszenie";
+"notice_room_leave" = "%@ opuścił(-a) pokój";
+"notice_room_join" = "%@ dołączył(-a)";
+"notice_room_third_party_revoked_invite_for_dm" = "%@ odrzucił(-a) zaproszenie do pokoju od %@";
+"notice_room_third_party_registered_invite" = "%@ przyjął(-ęła) zaproszenie od %@";
+"notice_room_third_party_revoked_invite" = "%@ odrzucił(-a) zaproszenie do pokoju od %@";
+"notice_room_third_party_invite_for_dm" = "%@ zaprosił(-a) %@";
+"notice_room_third_party_invite" = "%@ wysłał(-a) zaproszenie do pokoju dla %@";
+
+/* -*-
+ Automatic localization for en
+
+ The following key/value pairs were extracted from the android i18n file:
+ /matrix-sdk/src/main/res/values/strings.xml.
+*/
+
+"notice_room_invite" = "%@ zaprosił(-a) %@";
+"language_picker_default_language" = "Domyślny (%@)";
+
+// Language picker
+"language_picker_title" = "Wybierz język";
+
+// Country picker
+"country_picker_title" = "Wybierz kraj";
+"local_contacts_access_discovery_warning" = "Aby znaleźć kontakty, które są użytkownikami sieci Matrix, %@ może wysłać adresy e-mail i numery telefonów z Twojej książki adresowej do wybranego serwera tożsamości Matrix. Jeśli serwer na to pozwala, dane osobowe są szyfrowane przed wysłaniem - zapoznaj się z polityką prywatności Twojego serwera tożsamości, aby uzyskać więcej informacji.";
+"local_contacts_access_discovery_warning_title" = "Odkrywanie użytkowników";
+"local_contacts_access_not_granted" = "Wyszukiwanie użytkowników sieci Matrix na podstawie lokalnych kontaktów wymaga dostępu do Twoich kontaktów, ale %@ nie ma uprawnień, które umożliwiłyby ich użycie";
+"microphone_access_not_granted_for_call" = "Połączenia głosowe wymagają dostępu do mikrofonu, ale %@ nie ma pozwolenia na jego używanie";
+
+// Permissions
+"camera_access_not_granted_for_call" = "Rozmowy wideo wymagają dostępu do kamery, ale %@ nie ma pozwolenia na jej używanie";
+"ssl_homeserver_url" = "URL Serwera Domowego: %@";
+"user_id_placeholder" = "np.: @bob:homeserver";
+"network_error_not_reachable" = "Sprawdź połączenie internetowe";
+"power_level" = "Poziom uprawnień";
+"public" = "Publiczny";
+"private" = "Prywatny";
+"default" = "domyślny";
+"not_supported_yet" = "Jeszcze nie obsługiwane";
+"error_common_message" = "Wystąpił błąd. Spróbuj ponownie później.";
+"error" = "Błąd";
+"unsent" = "Niewysłane";
+"offline" = "offline";
+
+// Others
+"user_id_title" = "ID użytkownika:";
+"e2e_passphrase_create" = "Utwórz hasło";
+"e2e_passphrase_not_match" = "Hasła muszą się zgadzać";
+"e2e_passphrase_empty" = "Hasło nie może być puste";
+"e2e_passphrase_confirm" = "Potwierdź hasło";
+"e2e_export" = "Eksport";
+"e2e_export_prompt" = "Ten proces umożliwia wyeksportowanie do pliku lokalnego kluczy wiadomości odebranych w zaszyfrowanych pokojach. Dzięki temu będziesz mógł w przyszłości zaimportować plik do innego klienta Matrix i odszyfrować te wiadomości.\nWyeksportowany plik pozwoli każdemu, kto może go odczytać, na odszyfrowanie wszelkich zaszyfrowanych wiadomości, które widzisz, więc powinieneś zadbać o jego bezpieczeństwo.";
+
+// E2E export
+"e2e_export_room_keys" = "Eksportuj klucze pokoju";
+"e2e_passphrase_enter" = "Wprowadź hasło";
+"e2e_import" = "Importuj";
+"e2e_import_prompt" = "Ten proces umożliwia zaimportowanie kluczy szyfrowania, które zostały wcześniej wyeksportowane z innego klienta Matrix. Będziesz mógł odszyfrować wszystkie wiadomości, które inny klient mógłby odszyfrować. \nPlik eksportu jest chroniony hasłem. Wprowadź hasło, aby odszyfrować plik.";
+
+// E2E import
+"e2e_import_room_keys" = "Importuj klucze pokoju";
+"format_time_d" = "d";
+"format_time_h" = "h";
+"format_time_m" = "m";
+
+// Time
+"format_time_s" = "s";
+"contact_local_contacts" = "Lokalne Kontakty";
+
+// Contacts
+"contact_mx_users" = "Użytkownicy Matrix";
+"attachment_e2e_keys_file_prompt" = "Ten plik zawiera klucze szyfrowania wyeksportowane z klienta Matrix.\nChcesz przejrzeć zawartość pliku czy zaimportować zawarte w nim klucze?";
+"attachment_large" = "Duży (%@)";
+"attachment_medium" = "Średni (%@)";
+"attachment_small" = "Mały (%@)";
+"attachment_original" = "Rzeczywisty rozmiar (%@)";
+
+// Attachment
+"attachment_size_prompt" = "Czy chcesz wysłać jako:";
+"room_member_power_level_prompt" = "Nie będzie można cofnąć tej zmiany, ponieważ nadajesz użytkownikowi uprawnienia równoważne do Twoich. \nCzy jesteś pewny(-a)?";
+
+// Room members
+"room_member_ignore_prompt" = "Czy na pewno chcesz ukryć wszystkie wiadomości od tego użytkownika?";
+"message_reply_to_message_to_reply_to_prefix" = "W odpowiedzi na";
+"message_reply_to_sender_sent_a_file" = "wysłał(-a) plik.";
+"message_reply_to_sender_sent_a_video" = "wysłał(-a) plik wideo.";
+"message_reply_to_sender_sent_an_audio_file" = "wysłał(-a) plik audio.";
+
+// Reply to message
+"message_reply_to_sender_sent_an_image" = "wysłał(-a) obraz.";
+"room_no_conference_call_in_encrypted_rooms" = "Połączenia konferencyjne nie są obsługiwane w zaszyfrowanych pokojach";
+"room_no_power_to_create_conference_call" = "Nie masz uprawnień do rozpoczęcia konferencji w tym pokoju";
+"room_left_for_dm" = "Opuściłeś(-aś) pokój";
+"room_left" = "Opuściłeś(-aś) pokój";
+"room_error_timeline_event_not_found" = "Aplikacja próbowała załadować określony punkt na osi czasu tego pokoju, ale nie mogła go znaleźć";
+"room_error_timeline_event_not_found_title" = "Nie udało się załadować pozycji na osi czasu";
+"room_error_cannot_load_timeline" = "Nie udało się załadować osi czasu";
+"room_error_topic_edition_not_authorized" = "Nie masz uprawnień do edytowania tematu tego pokoju";
+"room_error_name_edition_not_authorized" = "Nie masz uprawnień do edytowania nazwy tego pokoju";
+"room_error_join_failed_empty_room" = "Obecnie nie jest możliwe ponowne dołączenie do pustego pokoju.";
+"room_error_join_failed_title" = "Nie udało się dołączyć do pokoju";
+
+// Room
+"room_please_select" = "Wybierz pokój";
+"room_creation_participants_placeholder" = "(np. @bob:homeserver1; @john:homeserver2...)";
+"room_creation_participants_title" = "Uczestnicy:";
+"room_creation_alias_placeholder_with_homeserver" = "(np. #foo%@)";
+"room_creation_alias_placeholder" = "(np. #foo:example.org)";
+"room_creation_alias_title" = "Alias pokoju:";
+"room_creation_name_placeholder" = "(np. grupa obiadowa)";
+
+// Room creation
+"room_creation_name_title" = "Nazwa pokoju:";
+"account_error_push_not_allowed" = "Powiadomienia nie są dozwolone";
+"account_error_msisdn_wrong_description" = "To nie wygląda na prawidłowy numer telefonu";
+"account_error_msisdn_wrong_title" = "Nieprawidłowy numer telefonu";
+"account_error_email_wrong_description" = "To nie wygląda na prawidłowy adres e-mail";
+"account_error_email_wrong_title" = "Niepoprawny adres e-mail";
+"account_error_matrix_session_is_not_opened" = "Sesja Matrix nie jest otwarta";
+"account_error_picture_change_failed" = "Zmiana obrazu nie powiodła się";
+"account_error_display_name_change_failed" = "Zmiana wyświetlanej nazwy nie powiodła się";
+"account_msisdn_validation_error" = "Nie można zweryfikować numeru telefonu.";
+"account_msisdn_validation_message" = "Wysłaliśmy SMS-a z kodem aktywacyjnym. Wpisz otrzymany kod poniżej.";
+"account_msisdn_validation_title" = "Oczekiwanie na weryfikację";
+"account_email_validation_error" = "Nie można zweryfikować adresu e-mail. Sprawdź swoją skrzynkę e-mail i kliknij zawarte w niej łącze. Gdy to zrobisz, kliknij kontynuuj";
+"account_email_validation_message" = "Sprawdź swoją skrzynkę e-mail i kliknij zawarte w niej łącze. Gdy to zrobisz, kliknij kontynuuj.";
+"account_email_validation_title" = "Oczekiwanie na weryfikację";
+"account_linked_emails" = "Połączone adresy e-mail";
+"account_link_email" = "Połącz adres e-mail";
+
+// Account
+"account_save_changes" = "Zapisz zmiany";
+"room_event_encryption_verify_ok" = "Zweryfikuj";
+"room_event_encryption_verify_message" = "Aby sprawdzić, czy tej sesji można zaufać, skontaktuj się z jej właścicielem w inny sposób (np. osobiście lub telefonicznie) i zapytaj, czy klucz, który widzą w swoich ustawieniach użytkownika dla tej sesji, odpowiada kluczowi poniżej:\n\nNazwa sesji: %@\nIdentyfikator sesji: %@\nKlucz sesji: %@\n\nJeżeli klucze są identyczne, naciśnij przycisk weryfikacji poniżej. Jeżeli klucze się różnią, to oznacza to, że ktoś inny mógł przechwycić tę sesję — w takim przypadku naciśnij przycisk zablokuj.\n\nW przyszłości proces weryfikacji będzie bardziej wyrafinowany.";
+"room_event_encryption_verify_title" = "Zweryfikuj sesję\n\n";
+"room_event_encryption_info_unblock" = "Odblokuj";
+"room_event_encryption_info_block" = "Zablokuj";
+"room_event_encryption_info_unverify" = "Cofnij weryfikację";
+"room_event_encryption_info_verify" = "Zweryfikuj...";
+"room_event_encryption_info_device_blocked" = "Zablokowany";
+"room_event_encryption_info_device_not_verified" = "NIE zweryfikowano";
+"room_event_encryption_info_device_verified" = "Zweryfikowano";
+"room_event_encryption_info_device_fingerprint" = "Ed25519 fingerprint\n";
+"room_event_encryption_info_device_verification" = "Weryfikacja\n";
+"room_event_encryption_info_event_none" = "brak";
+"room_event_encryption_info_event_unencrypted" = "niezaszyfrowane";
+"e2e_passphrase_too_short" = "Hasło jest zbyt krótkie (Hasło musi składać się z co najmniej %d znaków)";
+"microphone_access_not_granted_for_voice_message" = "Wiadomości głosowe wymagają dostępu do mikrofonu ale %@ nie posiada uprawnień do użycia go";
+"message_reply_to_sender_sent_a_voice_message" = "wysłał(-a) wiadomość głosową.";
+"attachment_large_with_resolution" = "Duży %@ (~%@)";
+"attachment_medium_with_resolution" = "Średni %@ (~%@)";
+"attachment_small_with_resolution" = "Mały %@ (~%@)";
+"attachment_size_prompt_message" = "Możesz to wyłączyć w ustawieniach.";
+"attachment_size_prompt_title" = "Potwierdź rozmiar, który chcesz wysłać";
diff --git a/Riot/Modules/MatrixKit/Assets/MatrixKitAssets.bundle/pt_BR.lproj/MatrixKit.strings b/Riot/Modules/MatrixKit/Assets/MatrixKitAssets.bundle/pt_BR.lproj/MatrixKit.strings
new file mode 100644
index 000000000..19b5d5cc3
--- /dev/null
+++ b/Riot/Modules/MatrixKit/Assets/MatrixKitAssets.bundle/pt_BR.lproj/MatrixKit.strings
@@ -0,0 +1,479 @@
+"matrix" = "Matrix";
+// Login Screen
+"login_create_account" = "Criar conta:";
+"login_server_url_placeholder" = "URL (e.g. https://matrix.org)";
+"login_home_server_title" = "URL de Servidorcasa:";
+"login_home_server_info" = "Seu servidorcasa armazena todas as suas conversas e dados de conta";
+"login_identity_server_title" = "URL de servidor de identidade:";
+"login_identity_server_info" = "Matrix provê servidores de identidade para rastrear quais emails, etc. pertencem a quais IDs Matrix. Somente https://matrix.org existe atualmente.";
+"login_user_id_placeholder" = "ID Matrix (e.g. @bob:matrix.org ou bob)";
+"login_password_placeholder" = "Senha";
+"login_optional_field" = "opcional";
+"login_display_name_placeholder" = "Nome de exibição (e.g. Bob Obson)";
+"login_email_info" = "Especificar um endereço de email permite que outras(os) usuárias(os) encontrem você mais facilmente, e vai dar a você uma forma de resettar sua senha no futuro.";
+"login_email_placeholder" = "Endereço de email";
+"login_prompt_email_token" = "Por favor entre seu token de validação de email:";
+"login_error_title" = "Login Falhou";
+"login_error_no_login_flow" = "Nós falhamos para recuperar informação de autenticação deste servidorcasa";
+"view" = "Visualizar";
+"login_error_do_not_support_login_flows" = "Atualmente nós não suportamos qualquer ou todos os fluxos de login definidos por este servidorcasa";
+"back" = "Voltar";
+"continue" = "Continuar";
+"leave" = "Sair";
+"invite" = "Convidar";
+"retry" = "Retentar";
+"cancel" = "Cancelar";
+"save" = "Salvar";
+"login_error_registration_is_not_supported" = "Registro não é suportado atualmente";
+"login_error_forbidden" = "Nome de usuária(o)/senha inválidos";
+"login_error_unknown_token" = "O token de acesso especificado não foi reconhecido";
+"login_error_bad_json" = "JSON malformado";
+"login_error_not_json" = "Não continha JSON válido";
+"login_error_limit_exceeded" = "Requisições demais têm sido enviadas";
+"login_error_user_in_use" = "Este nome de usuária(o) já é usado";
+"login_desktop_device" = "Desktop";
+// Action
+"no" = "Não";
+"yes" = "Sim";
+"abort" = "Abortar";
+"close" = "Fechar";
+"discard" = "Descartar";
+"dismiss" = "Dispensar";
+"sign_up" = "Fazer signup";
+"submit" = "Submeter";
+"submit_code" = "Submeter código";
+"set_default_power_level" = "Resettar Nível de Poder";
+"set_moderator" = "Definir Moderador(a)";
+"set_admin" = "Definir Admin";
+"start_chat" = "Começar Chat";
+"start_voice_call" = "Começar Chamada de Voz";
+"start_video_call" = "Começar Chamada de Vídeo";
+"mention" = "Mencionar";
+"select_account" = "Selecionar uma conta";
+"attach_media" = "Anexar Mídia desde Biblioteca";
+"capture_media" = "Tirar Foto/Vídeo";
+"invite_user" = "Convidar Usuária(o) matrix";
+"reset_to_default" = "Resettar para default";
+"resend_message" = "Reenviar a mensagem";
+"select_all" = "Selecionar Todas";
+"cancel_upload" = "Cancelar Upload";
+"cancel_download" = "Cancelar Download";
+"show_details" = "Mostrar Detalhes";
+"answer_call" = "Atender Chamada";
+"reject_call" = "Rejeitar Chamada";
+"end_call" = "Terminar Chamada";
+"ignore" = "Ignorar";
+"unignore" = "Designorar";
+"login_error_forgot_password_is_not_supported" = "Esqueceu senha não é suportada atualmente";
+"login_error_login_email_not_yet" = "O link de email que ainda não tem sido clicado";
+"login_use_fallback" = "Usar página de fallback";
+"login_leave_fallback" = "Cancelar";
+"login_invalid_param" = "Parâmetro inválido";
+"register_error_title" = "Registro Falhou";
+"login_tablet_device" = "Tablet";
+"login_error_resource_limit_exceeded_title" = "Limite de Recursos Excedido";
+"login_error_resource_limit_exceeded_message_default" = "Este servidorcasa tem excedido um de seus limites de recursos.";
+"login_error_resource_limit_exceeded_message_monthly_active_user" = "Este servidorcasa tem atingido seu limite de Usuárias(os) Mensalmente Ativos.";
+"login_error_resource_limit_exceeded_message_contact" = "\n\nPor favor contacte seu/sua administrador(a) de serviço para continuar usando este serviço.";
+"login_error_resource_limit_exceeded_contact_button" = "Contactar Administrador(a)";
+// Events formatter
+"notice_avatar_changed_too" = "(avatar foi mudado também)";
+"notice_room_name_removed" = "%@ removeu o nome da sala";
+"notice_room_topic_removed" = "%@ removeu o tópico";
+"notice_event_redacted_by" = " por %@";
+"notice_event_redacted_reason" = " [razão: %@]";
+"login_mobile_device" = "Celular";
+"notice_event_redacted" = "";
+"notice_profile_change_redacted" = "%@ atualizou perfil dela(e) %@";
+"notice_room_created" = "%@ criou e configurou a sala.";
+"notice_room_join_rule" = "A regra de se juntar é: %@";
+"notice_room_power_level_intro" = "Os níveis de poder de membros da sala são:";
+"notice_room_power_level_acting_requirement" = "Os níveis de poder mínimos que um/a usuária(o) deve ter antes de agir são:";
+"notice_room_power_level_event_requirement" = "Os níveis mínimos de poder relacionados a eventos são:";
+"notice_room_aliases" = "Os aliases da sala são: %@";
+"notice_room_related_groups" = "Os grupos associados a esta sala são: %@";
+"notice_encrypted_message" = "Mensagem encriptada";
+"set_power_level" = "Definir Nível de Poder";
+"power_level" = "Nível de Poder";
+"notice_encryption_enabled" = "%@ ativou a criptografia de ponta a ponta (algorithm %@)";
+"notice_image_attachment" = "anexo de imagem";
+"notice_audio_attachment" = "anexo de áudio";
+"notice_video_attachment" = "anexo de vídeo";
+"notice_location_attachment" = "anexo de local";
+"notice_file_attachment" = "anexo de arquivo";
+"notice_invalid_attachment" = "anexo inválido";
+"notice_unsupported_attachment" = "Anexo insuportado: %@";
+"notice_feedback" = "Evento de feedback (id: %@): %@";
+"notice_redaction" = "%@ redigiu um evento (id: %@)";
+"notice_error_unsupported_event" = "Evento insuportado";
+"notice_error_unexpected_event" = "Evento não-esperado";
+"notice_error_unknown_event_type" = "Tipo de evento desconhecido";
+"notice_room_history_visible_to_anyone" = "%@ fez histórico da sala futuro visível para qualquer pessoa.";
+"notice_room_history_visible_to_members" = "%@ fez histórico da sala futuro visível para todos os membros da sala.";
+"notice_room_history_visible_to_members_from_invited_point" = "%@ fez histórico da sala futuro visível para todos os membros da sala, do ponto que foram convidados.";
+"notice_room_history_visible_to_members_from_joined_point" = "%@ fez histórico da sala futuro visível para todos os membros da sala, do ponto que se juntaram.";
+"notice_crypto_unable_to_decrypt" = "** Incapaz de decriptar: %@ **";
+"notice_crypto_error_unknown_inbound_session_id" = "A sessão do/da enviador(a) não nos tem enviado as chaves para esta mensagem.";
+"notice_sticker" = "sticker";
+"notice_in_reply_to" = "Em resposta a";
+// room display name
+"room_displayname_empty_room" = "Sala vazia";
+"room_displayname_two_members" = "%@ e %@";
+"room_displayname_more_than_two_members" = "%@ e %@ outros";
+// Settings
+"settings" = "Configurações";
+"settings_enable_inapp_notifications" = "Habilitar notificações Em-App";
+"settings_enable_push_notifications" = "Habilitar notificações push";
+"settings_enter_validation_token_for" = "Entrar token de validação para %@:";
+"notification_settings_room_rule_title" = "Sala: '%@'";
+// Devices
+"device_details_title" = "Informação de sessão\n";
+"device_details_name" = "Nome Público\n";
+"device_details_identifier" = "ID\n";
+"device_details_last_seen" = "Visto por último\n";
+"device_details_last_seen_format" = "%@ @ %@\n";
+"device_details_rename_prompt_message" = "O nome público de uma sessão é visível para pessoas com quem você se comunica";
+"device_details_delete_prompt_title" = "Autenticação";
+"device_details_delete_prompt_message" = "Esta operação requer autenticação adicional.\nPara continuar, por favor entre sua senha.";
+// Encryption information
+"room_event_encryption_info_title" = "Informação de encriptação ponta-a-ponta\n\n";
+"room_event_encryption_info_event" = "Informação de evento\n";
+"room_event_encryption_info_event_user_id" = "ID de usuária(o)\n";
+"room_event_encryption_info_event_algorithm" = "Algoritmo\n";
+"room_event_encryption_info_event_session_id" = "ID de sessão\n";
+"room_event_encryption_info_event_unencrypted" = "não-encriptado";
+"room_event_encryption_info_event_none" = "nenhuma";
+"room_event_encryption_info_device" = "\nInformação de sessão de enviador(a)\n";
+"room_event_encryption_info_device_unknown" = "sessão desconhecida\n";
+"room_event_encryption_info_device_name" = "Nome Público\n";
+"room_event_encryption_info_device_id" = "ID\n";
+"room_event_encryption_info_device_verification" = "Verificação\n";
+"room_event_encryption_info_event_decryption_error" = "Erro de decriptação\n";
+"room_event_encryption_info_device_verified" = "Verificado";
+"room_event_encryption_info_device_not_verified" = "NÃO verificado";
+"room_event_encryption_info_device_blocked" = "Na lista negra";
+"room_event_encryption_info_verify" = "Verificar...";
+"room_event_encryption_info_unverify" = "Desverificar";
+"room_event_encryption_info_block" = "Adicionar à lista negra";
+"room_event_encryption_info_unblock" = "Remover da lista negra";
+"room_event_encryption_verify_title" = "Verificar sessão\n\n";
+"room_event_encryption_verify_message" = "Para verificar que esta sessão pode ser confiada, por favor contacte a/o dona(o) dela usando alguma outro meio (e.g. em pessoa ou uma chamada de telefone) e pergunte-lhe se a chave que ela/ele vê em suas Configurações de Usuária(o) para esta sessão bate com a chave abaixo:\n\n\tNome de sessão: %@\n\tID de sessão: %@\n\tChave de sessão: %@\n\nSe ela bate, pressione o botão verificar abaixo. Se não bate, então alguma outra pessoa está interceptando esa sessão e você provavelmente quer pressionar o botão adicionar à lista negra em vez disso.\n\nNo futuro este processo de verificação vai ser mais sofisticado.";
+"room_event_encryption_verify_ok" = "Verificar";
+// Account
+"account_save_changes" = "Salvar mudanças";
+"account_link_email" = "Linkar Email";
+"account_linked_emails" = "Emails linkados";
+"account_email_validation_title" = "Verificação Pendendo";
+"account_email_validation_message" = "Por favor cheque seu email e clique no link que ele contém. Uma vez que isto seja feito, clique em continuar.";
+"account_email_validation_error" = "Incapaz de verificar endereço de email. Por favor cheque seu email e clique no link que ele contém. Uma vez que isto seja feito, clique em continuar";
+"account_msisdn_validation_title" = "Verificação Pendendo";
+"account_msisdn_validation_message" = "Nós temos enviado um SMS com um código de ativação. Por favor entre este código abaixo.";
+"account_msisdn_validation_error" = "Incapaz de verificar número de telefone.";
+"room_event_encryption_info_event_identity_key" = "Chave de identidade Curve25519\n";
+"room_event_encryption_info_event_fingerprint_key" = "Chave de impressão digital Ed25519 clamada\n";
+"room_event_encryption_info_device_fingerprint" = "Impressão digital Ed25519\n";
+"account_error_display_name_change_failed" = "Mudança de nome de exibição falhou";
+"account_error_picture_change_failed" = "Mudança de imagem falhou";
+"account_error_matrix_session_is_not_opened" = "Sessão Matrix não está aberta";
+"account_error_email_wrong_title" = "Endereço de Email Inválido";
+"account_error_email_wrong_description" = "Isto não parece ser um endereço de email válido";
+"account_error_msisdn_wrong_title" = "Número de Telefone Inválido";
+"account_error_msisdn_wrong_description" = "Isto não parece ser um número de telefone válido";
+// Room creation
+"room_creation_name_title" = "Nome de sala:";
+"room_creation_name_placeholder" = "(e.g. grupoDeAlmoço)";
+"room_creation_alias_title" = "Alias de sala:";
+"room_creation_participants_title" = "Participantes:";
+// Room
+"room_please_select" = "Por favor selecione uma sala";
+"room_error_join_failed_title" = "Falha para se juntar a sala";
+"room_error_join_failed_empty_room" = "Não é atualmente possível se juntar a uma sala vazia.";
+"room_error_name_edition_not_authorized" = "Você não está autorizada(o) a editar o nome deste sala";
+"room_error_topic_edition_not_authorized" = "Você não está autorizada(o) a editar o tópico desta sala";
+"room_error_cannot_load_timeline" = "Falha para carregar timeline";
+"room_error_timeline_event_not_found_title" = "Falha para carregar posição de timeline";
+"room_error_timeline_event_not_found" = "O aplicativo estava tentando carregar um ponto específico na timeline desta sala mas foi incapaz de o encontrar";
+"room_left" = "Você saiu da sala";
+"room_no_power_to_create_conference_call" = "Você precisa de permissão para convidar para começar uma conferência nesta sala";
+"room_no_conference_call_in_encrypted_rooms" = "Chamadas de conferência não são suportadas em salas encriptadas";
+// Reply to message
+"message_reply_to_sender_sent_an_image" = "enviou uma imagem.";
+"message_reply_to_sender_sent_a_video" = "enviou um vídeo.";
+"room_creation_alias_placeholder" = "(e.g. #foo:exemplo.org)";
+"room_creation_alias_placeholder_with_homeserver" = "(e.g. #foo%@)";
+"room_creation_participants_placeholder" = "(e.g. @bob:servidorcasa1; @john:servidorcasa2...)";
+"message_reply_to_sender_sent_an_audio_file" = "enviou um arquivo de áudio.";
+"message_reply_to_sender_sent_a_file" = "enviou um arquivo.";
+"message_reply_to_message_to_reply_to_prefix" = "Em resposta a";
+// Room members
+"room_member_ignore_prompt" = "Você tem certeza que você quer esconder todas as mensagens desta(e) usuária(o)?";
+"room_member_power_level_prompt" = "Você não vai ser capaz de desfazer esta mudança como você está promovendo a/o usuária(o) para ter o mesmo nível de poder que você mesma(o).\nVocê tem certeza?";
+// Attachment
+"attachment_size_prompt" = "Você quer enviar como:";
+"attachment_original" = "Tamanho de Verdade (%@)";
+"attachment_small" = "Pequeno (~%@)";
+"attachment_medium" = "Médio (~%@)";
+"attachment_large" = "Grande (~%@)";
+"attachment_cancel_download" = "Cancelar o download?";
+"attachment_cancel_upload" = "Cancelar o upload?";
+"attachment_multiselection_size_prompt" = "Você quer enviar imagens como:";
+"attachment_multiselection_original" = "Tamanho de Verdade";
+"attachment_e2e_keys_file_prompt" = "Este arquivo contém chaves de encriptação exportadas de um cliente Matrix.\nVocê quer visualizar o conteúdo do arquivo ou importar as chaves que ele contém?";
+"attachment_e2e_keys_import" = "Importar...";
+// Contacts
+"contact_mx_users" = "Usuárias(os) Matrix";
+"contact_local_contacts" = "Contatos Locais";
+// Groups
+"group_invite_section" = "Convites";
+"group_section" = "Grupos";
+// Search
+"search_no_results" = "Nenhum Resultado";
+"search_searching" = "Pesquisa em Progresso...";
+// Time
+"format_time_s" = "s";
+"format_time_m" = "m";
+"format_time_h" = "h";
+"format_time_d" = "d";
+// E2E import
+"e2e_import_room_keys" = "Importar chaves de sala";
+"e2e_import_prompt" = "Esse processo permite a você importar chaves de encriptação que você tinha previamente exportado de um outro cliente Matrix. Você vai então ser capaz de descriptar quaisquer mensagens que o outro cliente podia decriptar.\nO arquivo de exportação é protegido com uma frasepasse. Você deveria inserir a frasepasse aqui, para decriptar o arquivo.";
+"e2e_import" = "Importar";
+"e2e_passphrase_enter" = "Entrar frasepasse";
+// E2E export
+"e2e_export_room_keys" = "Exportar chaves de sala";
+"e2e_export_prompt" = "Este processo permite a você exportar as chaves para mensagens que você recebeu em salas encriptadas para um arquivo local. Você vai então ser capaz importar o arquivo para um outro cliente Matrix no futuro, para que o cliente também possa decriptar estas mensagens.\nO arquivo exportado vai permitir a qualquer pessoa que o possa ler decriptar quaisquer mensagens encriptadas que você pode ver, então você deveria ser cuidadosa(o) para mantê-lo protegido.";
+"e2e_export" = "Exportar";
+"e2e_passphrase_confirm" = "Confirmar frasepasse";
+"e2e_passphrase_empty" = "Frasepasse não deve estar vazia";
+"e2e_passphrase_not_match" = "Frasepasses devem corresponder";
+"e2e_passphrase_create" = "Criar frasepasse";
+// Others
+"user_id_title" = "ID de usuária(o):";
+"offline" = "offline";
+"unsent" = "Não-enviado";
+"error" = "Erro";
+"error_common_message" = "Um erro ocorreu. Por favor tente de novo mais tarde.";
+"not_supported_yet" = "Não suportado ainda";
+"default" = "padrão";
+"private" = "Privado";
+"public" = "Público";
+"network_error_not_reachable" = "Por favor cheque sua conectividade de rede";
+"user_id_placeholder" = "ex: @bob:servidorcasa";
+"ssl_homeserver_url" = "URL de servidorcasa: %@";
+// Permissions
+"camera_access_not_granted_for_call" = "Chamadas de vídeo requerem acesso à Câmera mas %@ não tem permissão para usá-la";
+"microphone_access_not_granted_for_call" = "Chamadas requerem acesso ao Microfone mas %@ não tem permissão para usá-lo";
+"local_contacts_access_not_granted" = "Descoberta de usuárias(os) desde contatos locais requer acesso a seus contatos mas %@ não tem permissão para usá-los";
+"local_contacts_access_discovery_warning_title" = "Descoberta de usuárias(os)";
+"local_contacts_access_discovery_warning" = "Para descobrir contatos já usando Matrix, %@ pode enviar endereços de email e números de telefone em seu livro de endereços para seu servidor de identidade Matrix escolhido. Onde suportado, dados pessoais são hashados antes do envio - por favor cheque a política de privacidade de seu servidor de identidade para mais detalhes.";
+// Country picker
+"country_picker_title" = "Escolha um país";
+// Language picker
+"language_picker_title" = "Escolha uma língua";
+"language_picker_default_language" = "Default (%@)";
+"notice_room_invite" = "%@ convidou %@";
+"notice_room_third_party_invite" = "%@ enviou um convite para %@ para se juntar à sala";
+"notice_room_third_party_registered_invite" = "%@ aceitou o convite para %@";
+"notice_room_join" = "%@ juntou-se";
+"notice_room_leave" = "%@ saiu";
+"notice_room_reject" = "%@ rejeitou o convite";
+"notice_room_kick" = "%@ expulsou %@";
+"notice_room_unban" = "%@ desbaniu %@";
+"notice_room_ban" = "%@ baniu %@";
+"notice_room_withdraw" = "%@ retirou o convite de %@";
+"notice_room_reason" = ". Razão: %@";
+"notice_avatar_url_changed" = "%@ mudou o avatar dela(e)";
+"notice_display_name_set" = "%@ definiu o nome de exibição dela(e) para %@";
+"notice_display_name_changed_from" = "%@ mudou o nome de exibição dela(e) de %@ para %@";
+"notice_display_name_removed" = "%@ removeu o nome de exibição dela(e)";
+"notice_topic_changed" = "%@ mudou o tópico para \"%@\".";
+"notice_room_name_changed" = "%@ mudou o nome da sala para %@.";
+"notice_placed_voice_call" = "%@ começou uma chamada de voz";
+"notice_placed_video_call" = "%@ começou uma chamada de vídeo";
+"notice_answered_video_call" = "%@ atendeu a chamada";
+"notice_ended_video_call" = "%@ terminou a chamada";
+"notice_conference_call_request" = "%@ requisitou uma conferência de VoIP";
+"notice_conference_call_started" = "Conferência de VoIP começada";
+"notice_conference_call_finished" = "Conferência de VoIP terminada";
+// button names
+"ok" = "OK";
+"send" = "Enviar";
+"copy_button_name" = "Copiar";
+"resend" = "Reenviar";
+"redact" = "Remover";
+"share" = "Compartilhar";
+"delete" = "Deletar";
+// actions
+"action_logout" = "Fazer logout";
+"create_room" = "Criar Sala";
+"login" = "Fazer login";
+"create_account" = "Criar Conta";
+"membership_invite" = "Convidada(o)";
+"membership_leave" = "Saiu";
+"membership_ban" = "Banida(o)";
+"num_members_one" = "%@ usuária(o)";
+"num_members_other" = "%@ usuárias(os)";
+"kick" = "Expulsar";
+"ban" = "Banir";
+"unban" = "Des-banir";
+"message_unsaved_changes" = "Existem mudanças não-salvas. Sair vai descartá-las.";
+// Login Screen
+"login_error_already_logged_in" = "Login já feito";
+"login_error_must_start_http" = "URL deve começar com http[s]://";
+// room details dialog screen
+"room_details_title" = "Detalhes de Sala";
+// contacts list screen
+"invitation_message" = "Eu gostaria de conversar com você com matrix. Por favor, visite o website http://matrix.org para ter mais informação.";
+// Settings screen
+"settings_title_config" = "Configuração";
+"settings_title_notifications" = "Notificações";
+// Notification settings screen
+"notification_settings_disable_all" = "Desabilitar todas as notificações";
+"notification_settings_enable_notifications" = "Habilitar notificações";
+"notification_settings_enable_notifications_warning" = "Todas as notificações estão atualmente desabilitadas para todos os dispositivos.";
+"notification_settings_global_info" = "Configurações de notificação são salvas em sua conta de usuária(o) e são compartilhadas entre todos os clientes que as suportam (incluindo notificações de desktop).\n\nRegras são aplicadas em ordem; a primeira regra que corresponde define o resultado da mensagem.\nEntão: Notificações per-palavra são mais importantes que notificações per-sala que são mais importantes que notificações per-enviador(a).\nPara múltiplas regras do mesmo tipo, a primeira na lista que corresponde leva prioridade.";
+"notification_settings_per_word_notifications" = "Notificações per-palavra";
+"notification_settings_per_word_info" = "Palavras correspondem insensivelmente a maiúsculas e minúsculas, e podem incluir um wildcard *. Então:\nfoo corresponde a string foo rodeado por delimitadores de palavras (e.g., pontuação e whitespace ou início/fim de linha).\nfoo* corresponde a qualquer palavra que começa foo.\n*foo* corresponde a qualquer palavra que inclui as 3 letras foo.";
+"notification_settings_always_notify" = "Sempre notificar";
+"notification_settings_never_notify" = "Nunca notificar";
+"notification_settings_word_to_match" = "palavra para corresponder";
+"notification_settings_highlight" = "Destacar";
+"notification_settings_custom_sound" = "Som personalizado";
+"notification_settings_per_room_notifications" = "Notificações per-sala";
+"notification_settings_per_sender_notifications" = "Notificações per-enviador(a)";
+"notification_settings_sender_hint" = "@usuarix:dominio.com";
+"notification_settings_select_room" = "Selecionar uma sala";
+"notification_settings_other_alerts" = "Outros Alertas";
+"notification_settings_contain_my_user_name" = "Notificar-me com som sobre mensagens que contêm meu nome de usuária(o)";
+"notification_settings_contain_my_display_name" = "Notificar-me com som sobre mensagens que contêm meu nome de exibição";
+"notification_settings_just_sent_to_me" = "Notificar-me com som sobre mensagens enviadas apenas para mim";
+"notification_settings_invite_to_a_new_room" = "Notificar-me quando eu sou convidada(o) para uma nova sala";
+"notification_settings_people_join_leave_rooms" = "Notificar-me quando pessoas se juntam ou saem de salas";
+"notification_settings_receive_a_call" = "Notificar-me quando eu recebo uma chamada";
+"notification_settings_suppress_from_bots" = "Suprimir notificações de bots";
+"notification_settings_by_default" = "Por default...";
+"notification_settings_notify_all_other" = "Notificar para todas as outras mensagens/salas";
+// gcm section
+"settings_config_home_server" = "Servidorcasa: %@";
+"settings_config_identity_server" = "Servidor de identidade: %@";
+"settings_config_user_id" = "ID de usuária(o): %@";
+// call string
+"call_waiting" = "Aguardando...";
+"call_connecting" = "Conectando…";
+"call_ended" = "Chamada terminada";
+"call_ring" = "Chamando...";
+"incoming_video_call" = "Chamada de Vídeo Entrante";
+"incoming_voice_call" = "Chamada de Voz Entrante";
+"call_invite_expired" = "Convite de Chamada Expirado";
+// unrecognized SSL certificate
+"ssl_trust" = "Confiar";
+"ssl_logout_account" = "Fazer logout";
+"ssl_remain_offline" = "Ignorar";
+"ssl_fingerprint_hash" = "Impressão digital (%@):";
+"ssl_could_not_verify" = "Não foi possível verificar identidade de servidor remoto.";
+"ssl_cert_not_trust" = "Isto pode significar que alguém está interceptando maliciosamente seu tráfico, ou que seu telefone não confia no certificado provido pelo servidor remoto.";
+"ssl_cert_new_account_expl" = "Se o/a administrador(a) do servidor tem dito que isto é esperado, assegure-se que a impressão digital abaixo bate com a impressão digital provida por ele(a).";
+"ssl_unexpected_existing_expl" = "O certificado tem mudado de um que esta confiado por seu telefone. Isto é ALTAMENTE INCOMUM. É recomendado que você NÃO ACEITE este novo certificado.";
+"ssl_expected_existing_expl" = "O certificado tem sido mudado de um previamente confiado para um que não é confiado. O servidor pode ter renovado o certificado dele. Contacte o/a administrador(a) do servidor a impressão digital esperada.";
+"ssl_only_accept" = "SOMENTE aceite o certificado se o/a administrador(a) do servidor tem publicado uma impressão digital que corresponde à acima.";
+"notice_encryption_enabled_ok" = "%@ ativou encriptação ponta-a-ponta.";
+"notice_encryption_enabled_unknown_algorithm" = "%1$@ ativou encriptação ponta-a-ponta (algoritmo não-reconhecido %2$@).";
+"device_details_rename_prompt_title" = "Nome da Sessão";
+"account_error_push_not_allowed" = "Notificações não permitidas";
+"notice_room_third_party_revoked_invite" = "%@ revogou o convite para %@ para se juntar à sala";
+// Notice Events with "You"
+"notice_room_invite_by_you" = "Você convidou %@";
+"notice_room_invite_you" = "%@ convidou você";
+"notice_room_third_party_invite_by_you" = "Você enviou um convite para %@ para se juntar à sala";
+"notice_room_third_party_registered_invite_by_you" = "Você aceitou o convite para %@";
+"notice_room_third_party_revoked_invite_by_you" = "Você revogou o convite para que %@ se junte à sala";
+"notice_room_join_by_you" = "Você juntou-se";
+"notice_room_leave_by_you" = "Você saiu";
+"notice_room_reject_by_you" = "Você rejeitou o convite";
+"notice_room_kick_by_you" = "Você expulsou %@";
+"notice_room_unban_by_you" = "Você desbaniu %@";
+"notice_room_ban_by_you" = "Você baniu %@";
+"notice_room_withdraw_by_you" = "Você retirou o convite de %@";
+"notice_avatar_url_changed_by_you" = "Você mudou seu avatar";
+"notice_display_name_set_by_you" = "Você definiu seu nome de exibição para %@";
+"notice_display_name_changed_from_by_you" = "Você mudou seu nome de exibição de %@ para %@";
+"notice_display_name_removed_by_you" = "Você removeu seu nome de exibição";
+"notice_topic_changed_by_you" = "Você mudou o tópico para \"%@\".";
+"notice_room_name_changed_by_you" = "Você mudou o nome da sala para %@.";
+"notice_placed_voice_call_by_you" = "Você começou uma chamada de voz";
+"notice_placed_video_call_by_you" = "Você começou uma chamada de vídeo";
+"notice_answered_video_call_by_you" = "Você atendeu a chamada";
+"notice_ended_video_call_by_you" = "Você terminou a chamada";
+"notice_conference_call_request_by_you" = "Você requisitou uma conferência de VoIP";
+"notice_room_name_removed_by_you" = "Você removeu o nome da sala";
+"notice_room_topic_removed_by_you" = "Você removeu o tópico";
+"notice_event_redacted_by_you" = " por você";
+"notice_profile_change_redacted_by_you" = "Você atualizou seu perfil %@";
+"notice_room_created_by_you" = "Você criou e configurou a sala.";
+"notice_encryption_enabled_ok_by_you" = "Você ativou encriptação ponta-a-ponta.";
+"notice_encryption_enabled_unknown_algorithm_by_you" = "Você ativou a encriptação ponta-a-ponta (algoritmo irreconhecido %@).";
+"notice_redaction_by_you" = "Você redigiu um evento (id: %@)";
+"notice_room_history_visible_to_anyone_by_you" = "Você fez histórico da sala futuro visível para qualquer pessoa.";
+"notice_room_history_visible_to_members_by_you" = "Você fez histórico da sala futuro visível para todos os membros da sala.";
+"notice_room_history_visible_to_members_from_invited_point_by_you" = "Você fez histórico de sala futuro visível para todos os membros da sala, do ponto que são convidados.";
+"notice_room_history_visible_to_members_from_joined_point_by_you" = "Você fez histórico da sala futuro visível para todos os membros da sala, do momento que se juntaram.";
+"notice_room_name_removed_for_dm" = "%@ removeu o nome";
+"notice_room_created_for_dm" = "%@ juntou-se.";
+// New
+"notice_room_join_rule_invite" = "%@ fez a sala somente convite.";
+"notice_room_join_rule_invite_for_dm" = "%@ fez isto somente convite.";
+"notice_room_join_rule_invite_by_you" = "Você fez a sala somente convite.";
+"notice_room_join_rule_invite_by_you_for_dm" = "Você fez isto somente convite.";
+"notice_room_join_rule_public" = "%@ fez a sala pública.";
+"notice_room_join_rule_public_for_dm" = "%@ fez isto público.";
+"notice_room_join_rule_public_by_you" = "Você fez a sala pública.";
+"notice_room_join_rule_public_by_you_for_dm" = "Você fez isto público.";
+"notice_room_power_level_intro_for_dm" = "Os níveis de poder de membros são:";
+"notice_room_aliases_for_dm" = "Os aliases são: %@";
+"notice_room_history_visible_to_members_for_dm" = "%@ fez mensagens futuras visíveis para todos os membros da sala.";
+"notice_room_history_visible_to_members_from_invited_point_for_dm" = "%@ fez mensagens futuras visíveis para todas as pessoas, do ponto que são convidadas.";
+"notice_room_history_visible_to_members_from_joined_point_for_dm" = "%@ fez mensagens futuras visíveis para todas as pessoas, do ponto que se juntaram.";
+"room_left_for_dm" = "Você saiu";
+"notice_room_third_party_invite_for_dm" = "%@ convidou %@";
+"notice_room_third_party_revoked_invite_for_dm" = "%@ revogou o convite de %@";
+"notice_room_name_changed_for_dm" = "%@ mudou o nome para %@.";
+"notice_room_third_party_invite_by_you_for_dm" = "Você convidou %@";
+"notice_room_third_party_revoked_invite_by_you_for_dm" = "Você revogou o convite de %@";
+"notice_room_name_changed_by_you_for_dm" = "Você mudou o nome para %@.";
+"notice_room_name_removed_by_you_for_dm" = "Você removeu o nome";
+"notice_room_created_by_you_for_dm" = "Você juntou-se.";
+"notice_room_history_visible_to_members_by_you_for_dm" = "Você fez mensagens futuras visíveis para todos os membros da sala.";
+"notice_room_history_visible_to_members_from_invited_point_by_you_for_dm" = "Você fez mensagens futuras visíveis para todas as pessoas, do ponto que são convidadas.";
+"notice_room_history_visible_to_members_from_joined_point_by_you_for_dm" = "Você fez mensagens futuras visíveis para todas as pessoas, do ponto que se juntaram.";
+"call_more_actions_dialpad" = "Pad de disco";
+"call_more_actions_unhold" = "Retomar";
+"call_more_actions_hold" = "Pôr em espera";
+"call_holded" = "Você pôs a chamada em espera";
+"call_remote_holded" = "%@ pôs a chamada em espera";
+"notice_declined_video_call_by_you" = "Você declinou a chamada";
+"notice_declined_video_call" = "%@ declinou a chamada";
+"resume_call" = "Retomar";
+"call_more_actions_transfer" = "Transferir";
+"call_more_actions_audio_use_device" = "Falante de Dispositivo";
+"call_more_actions_audio_use_headset" = "Usar Áudio de Auscultador";
+"call_more_actions_change_audio_device" = "Mudar Dispositivo de Áudio";
+"call_video_with_user" = "Chamada de vídeo com %@";
+"call_voice_with_user" = "Chamada de voz com %@";
+"call_ringing" = "Tocando…";
+"call_transfer_to_user" = "Transferir para %@";
+"call_consulting_with_user" = "Consultando com %@";
+"e2e_passphrase_too_short" = "Frasepasse curta demais (Ela deve ser a um mínimo %d caracteres em comprimento)";
+"microphone_access_not_granted_for_voice_message" = "Mensagens de voz requerem acesso ao Microfone mas %@ não tem permissão para usá-lo";
+"message_reply_to_sender_sent_a_voice_message" = "enviou uma mensagem de voz.";
+"attachment_large_with_resolution" = "Grande %@ (~%@)";
+"attachment_medium_with_resolution" = "Médio %@ (~%@)";
+"attachment_small_with_resolution" = "Pequeno %@ (~%@)";
+"attachment_size_prompt_message" = "Você pode desligar isto em configurações.";
+"attachment_size_prompt_title" = "Confirmar tamanho para enviar";
+"room_displayname_all_other_participants_left" = "%@ (Saiu)";
+"auth_reset_password_error_not_found" = "Não encontrado";
+"auth_reset_password_error_unauthorized" = "Não-autorizada(o)";
+"auth_username_in_use" = "Nome de usuária(o) em uso";
+"auth_invalid_user_name" = "Nome de usuária(o) inválido";
+"rename" = "Renomear";
+"room_displayname_all_other_members_left" = "%@ (Saiu)";
diff --git a/Riot/Modules/MatrixKit/Assets/MatrixKitAssets.bundle/ru.lproj/MatrixKit.strings b/Riot/Modules/MatrixKit/Assets/MatrixKitAssets.bundle/ru.lproj/MatrixKit.strings
new file mode 100644
index 000000000..53403ae12
--- /dev/null
+++ b/Riot/Modules/MatrixKit/Assets/MatrixKitAssets.bundle/ru.lproj/MatrixKit.strings
@@ -0,0 +1,478 @@
+"matrix" = "Matrix";
+// Login Screen
+"login_create_account" = "Регистрация:";
+"login_server_url_placeholder" = "URL (например https://matrix.org)";
+"login_home_server_title" = "URL домашнего сервера:";
+"login_home_server_info" = "Ваш домашний сервер хранит все ваши разговоры и данные о аккаунте";
+"login_identity_server_title" = "Cервер идентификации URL:";
+"login_identity_server_info" = "Серверы идентификации Matrix обеспечивают определение соответствия между email и Matrix ID. В настоящее время существует только https://matrix.org.";
+"login_user_id_placeholder" = "Matrix ID (например, @bob:matrix.org или bob)";
+"login_password_placeholder" = "Пароль";
+"login_optional_field" = "не обязательно";
+"login_display_name_placeholder" = "Отображаемое имя (например, Иван Петров)";
+"login_email_info" = "Укажите адрес электронной почты, чтобы другие пользователи могли легко находить вас в Matrix. Кроме того, вы сможете при необходимости восстановить свой пароль.";
+"login_email_placeholder" = "Адрес электронной почты";
+"login_prompt_email_token" = "Введите токен подтверждения адреса электронной почты:";
+"login_error_title" = "Не удалось войти";
+"login_error_no_login_flow" = "Не удалось получить информацию для аутентификации с этого домашнего сервера";
+"login_error_registration_is_not_supported" = "Регистрация в настоящее время не поддерживается";
+"login_error_forbidden" = "Неверное имя пользователя или пароль";
+"login_error_unknown_token" = "Указанный токен доступа не распознан";
+"login_error_bad_json" = "Поврежденный JSON";
+"login_error_not_json" = "Не содержит допустимый JSON";
+"login_error_limit_exceeded" = "Отправлено слишком много запросов";
+"login_error_user_in_use" = "Это имя пользователя уже используется";
+"login_error_login_email_not_yet" = "Вы не перешли по высланной в email ссылке";
+"login_leave_fallback" = "Отмена";
+"login_invalid_param" = "Недопустимый параметр";
+"register_error_title" = "Регистрация не удалась";
+// Action
+"no" = "Нет";
+"yes" = "Да";
+"abort" = "Отменить";
+"back" = "Назад";
+"close" = "Закрыть";
+"continue" = "Продолжить";
+"discard" = "Отказаться";
+"dismiss" = "Отклонить";
+"retry" = "Попробовать снова";
+"sign_up" = "Зарегистрироваться";
+"submit" = "Отправить";
+"submit_code" = "Отправить код";
+"set_default_power_level" = "Сбросить уровень доступа";
+"set_moderator" = "Сделать модератором";
+"set_admin" = "Сделать администратором";
+"start_chat" = "Начать чат";
+"start_voice_call" = "Начать голосовой вызов";
+"start_video_call" = "Начать видеовызов";
+"mention" = "Упоминание";
+"select_account" = "Выберите аккаунт";
+"attach_media" = "Прикрепить файл из библиотеки";
+"capture_media" = "Сделать фото/видео";
+"invite_user" = "Пригласить пользователя matrix";
+"reset_to_default" = "Восстановить по умолчанию";
+"resend_message" = "Отправить сообщение снова";
+"select_all" = "Выбрать все";
+"cancel_upload" = "Отменить отправку";
+"cancel_download" = "Отменить загрузку";
+"show_details" = "Показать детали";
+"answer_call" = "Ответить на вызов";
+"reject_call" = "Отклонить вызов";
+"end_call" = "Завершить вызов";
+"ignore" = "Игнорировать";
+"unignore" = "Перестать игнорировать";
+// Events formatter
+"notice_avatar_changed_too" = "(аватар также изменился)";
+"notice_room_name_removed" = "%@ удалил(а) название комнаты";
+"notice_room_topic_removed" = "%@ удалил(а) тему";
+"notice_event_redacted_reason" = " [причина: %@]";
+"notice_profile_change_redacted" = "%@ обновил(а) свой профиль %@";
+"notice_room_created" = "%@ создал(а) и настроил(а) комнату.";
+"notice_room_power_level_intro" = "Уровень доступа членов комнаты:";
+"notice_room_power_level_acting_requirement" = "Минимальные уровни доступа пользователя для совершения действия:";
+"notice_room_power_level_event_requirement" = "Минимальные уровни доступа, связанные с событиями:";
+"notice_encrypted_message" = "Зашифрованное сообщение";
+"notice_encryption_enabled" = "%@ включил(а) сквозное шифрование (алгоритм %@)";
+"notice_image_attachment" = "прикрепленное изображение";
+"notice_audio_attachment" = "прикрепленное аудио";
+"notice_video_attachment" = "прикрепленное видео";
+"notice_location_attachment" = "прикрепленное местоположение";
+"notice_file_attachment" = "прикрепленный файл";
+"notice_invalid_attachment" = "недопустимое вложение";
+"notice_unsupported_attachment" = "Неподдерживаемое вложение: %@";
+"notice_redaction" = "%@ отредактировал(а) событие (id: %@)";
+"notice_error_unsupported_event" = "Неподдерживаемое событие";
+"notice_error_unexpected_event" = "Непредвиденное событие";
+"notice_error_unknown_event_type" = "Неизвестный тип события";
+"notice_room_history_visible_to_anyone" = "%@ сделал(а) будущую историю комнату видимой всем.";
+"notice_room_history_visible_to_members" = "%@ сделал(а) будущую историю комнаты видимой всем членам комнаты.";
+"notice_room_history_visible_to_members_from_invited_point" = "%@ сделал(а) будущую историю комнаты видимой всем членам комнаты с момента их приглашения.";
+"notice_room_history_visible_to_members_from_joined_point" = "%@ сделал(а) будущую историю комнаты видимой всем членам комнаты с момента их входа.";
+"notice_crypto_unable_to_decrypt" = "** Не удалось расшифровать: %@ **";
+"notice_crypto_error_unknown_inbound_session_id" = "Сессия отправителя не отправила вам ключи для этого сообщения.";
+// room display name
+"room_displayname_empty_room" = "Пустая комната";
+"room_displayname_two_members" = "%@ и %@";
+"room_displayname_more_than_two_members" = "%@ и %u другие";
+// Settings
+"settings" = "Настройки";
+"settings_enable_inapp_notifications" = "Включить уведомления в приложении";
+"settings_enable_push_notifications" = "Включить push-уведомления";
+"settings_enter_validation_token_for" = "Введите токен подтверждения для %@:";
+"notification_settings_room_rule_title" = "Комната: '%@'";
+// Devices
+"device_details_title" = "Информация о сессии\n";
+"device_details_name" = "Публичное имя\n";
+"device_details_identifier" = "ID\n";
+"device_details_last_seen" = "Последняя активность\n";
+"device_details_last_seen_format" = "%@ @ %@\n";
+"device_details_rename_prompt_message" = "Публичные имена сессий видны людям, с которыми вы общаетесь";
+"device_details_delete_prompt_title" = "Аутентификация";
+"room_event_encryption_info_event" = "Информация о событии\n";
+"room_event_encryption_info_event_user_id" = "ID пользователя\n";
+"room_event_encryption_info_event_algorithm" = "Алгоритм\n";
+"room_event_encryption_info_event_session_id" = "ID сессии\n";
+"room_event_encryption_info_event_decryption_error" = "Ошибка расшифровки\n";
+"room_event_encryption_info_event_unencrypted" = "незашифровано";
+"room_event_encryption_info_device" = "\nИнформация о сессии отправителя\n";
+"room_event_encryption_info_device_unknown" = "неизвестная сессия\n";
+"room_event_encryption_info_device_name" = "Публичное имя\n";
+"room_creation_participants_title" = "Участники:";
+// Room
+"room_please_select" = "Пожалуйста, выберите комнату";
+"room_error_join_failed_title" = "Не удалось войти в комнату";
+"room_left" = "Вы покинули комнату";
+// Room members
+"room_member_ignore_prompt" = "Вы уверены, что хотите скрыть все сообщения этого пользователя?";
+"room_member_power_level_prompt" = "Вы не сможете отменить это действие, поскольку пользователь получит такой же уровень доступа, как и у вас. \nВы уверены?";
+"power_level" = "Уровень доступа";
+// Country picker
+"country_picker_title" = "Выберите страну";
+// Language picker
+"language_picker_title" = "Выберите язык";
+"language_picker_default_language" = "По умолчанию (%@)";
+"notice_room_join" = "%@ вошел(ла)";
+"notice_room_leave" = "%@ вышел(ла)";
+"notice_room_reject" = "%@ отклонил(а) приглашение";
+"notice_room_kick" = "%@ выкинул(а) %@";
+"notice_room_unban" = "%@ разблокировал(а) %@";
+"notice_room_ban" = "%@ заблокировал(а) %@";
+"notice_room_withdraw" = "%@ отозвал(а) приглашение %@";
+"notice_room_reason" = ". Причина: %@";
+"notice_avatar_url_changed" = "%@ изменил(а) свой аватар";
+"notice_display_name_set" = "%@ сделал(а) своим отображаемым именем %@";
+"notice_display_name_changed_from" = "%@ изменил(а) свое отображаемое имя с %@ на %@";
+"notice_display_name_removed" = "%@ удалил(а) свое отображаемое имя";
+"notice_topic_changed" = "%@ изменил(а) тему на \"%@\".";
+"notice_room_name_changed" = "%@ изменил(а) название комнаты на %@.";
+"notice_placed_voice_call" = "%@ совершил(а) голосовой вызов";
+"notice_placed_video_call" = "%@ совершил(а) видео вызов";
+"notice_answered_video_call" = "%@ ответил(а) на вызов";
+"notice_ended_video_call" = "%@ завершил(а) вызов";
+"notice_conference_call_request" = "%@ запросил(а) голосовую конференцию";
+"notice_conference_call_started" = "голосовая конференция началась";
+"notice_conference_call_finished" = "голосовая конференция завершилась";
+// button names
+"ok" = "OK";
+"cancel" = "Отмена";
+"save" = "Сохранить";
+"leave" = "Покинуть";
+"send" = "Отправить";
+"copy_button_name" = "Скопировать";
+"resend" = "Переотправить";
+"redact" = "Удалить";
+"share" = "Поделиться";
+"set_power_level" = "Установить уровень мощности";
+"delete" = "Удалить";
+// actions
+"action_logout" = "Выйти";
+"create_room" = "Создать комнату";
+"login" = "Войти";
+"create_account" = "Создать аккаунт";
+"membership_invite" = "Приглашен";
+"membership_leave" = "Покинул";
+"membership_ban" = "Заблокирован";
+"num_members_one" = "%@ пользователь";
+"num_members_other" = "%@ пользователей";
+"invite" = "Пригласить";
+"kick" = "Выкинуть";
+"ban" = "Заблокировать";
+"unban" = "Разблокировать";
+// Room creation
+"room_creation_name_title" = "Название комнаты:";
+"login_use_fallback" = "Использовать резервную страницу";
+"login_error_do_not_support_login_flows" = "В настоящее время мы не поддерживаем потоки авторизации, определенных данным домашним сервером";
+"login_error_forgot_password_is_not_supported" = "\"Забытый пароль\" в настоящее время не поддерживается";
+"notice_event_redacted" = "<отредактировано%@>";
+"notice_event_redacted_by" = " от %@";
+"notice_room_join_rule" = "Правило присоединения: %@";
+"device_details_delete_prompt_message" = "Для этой операции требуется дополнительная аутентификация.\nЧтобы продолжить, введите свой пароль.";
+// Encryption information
+"room_event_encryption_info_title" = "Сведения о сквозном шифровании\n\n";
+"room_event_encryption_info_event_identity_key" = "Ключ идентификации Curve25519\n";
+"room_event_encryption_info_event_fingerprint_key" = "Требуемый ключ цифрового отпечатка Ed25519\n";
+"room_event_encryption_info_event_none" = "никого нет";
+"room_event_encryption_info_device_id" = "Идентификатор устройства\n";
+"room_event_encryption_info_device_verification" = "Верификация\n";
+"room_event_encryption_info_device_fingerprint" = "Отпечаток Ed25519\n";
+"room_event_encryption_info_device_verified" = "Подтверждено";
+"room_event_encryption_info_device_not_verified" = "НЕ подтверждено";
+"room_event_encryption_info_device_blocked" = "В черном списке";
+"room_event_encryption_info_verify" = "Проверка...";
+"room_event_encryption_info_unverify" = "Отменить верификацию";
+"room_event_encryption_info_block" = "Черный список";
+"room_event_encryption_info_unblock" = "Удалить из черного списка";
+"room_event_encryption_verify_title" = "Проверить сессию\n\n";
+"room_event_encryption_verify_message" = "Для верификации сессии, пожалуйста, свяжитесь с владельцем используя другие методы коммуникации (например, лично или по телефону) и попросите его подтвердить, что он видит такой же ключ как написанный ниже:\n\n\tИмя сессии: %@\n\tИдентификатор сессии: %@\n\tКлюч сессии: %@\n\nЕсли совпадают, то нажмите кнопку верификации ниже. Если нет, значит кто-то перехватил эту сессию и вы, скорее всего, захотите внести его в черный список.\n\nВ будущем процесс верификации будет усложнен.";
+"room_event_encryption_verify_ok" = "Подтвердить";
+// Account
+"account_save_changes" = "Сохранить изменения";
+"account_linked_emails" = "Связанные адреса электронной почты";
+"account_link_email" = "Связанный адрес электронной почты";
+"account_email_validation_title" = "Ожидание проверки";
+"account_email_validation_message" = "Проверьте свою электронную почту и нажмите на содержащуюся ссылку. После этого нажмите кнопку Продолжить.";
+"account_email_validation_error" = "Не удалось проверить адрес электронной почты. Проверьте свою электронную почту и нажмите на содержащуюся ссылку. Когда это будет сделано, нажмите Продолжить";
+"account_msisdn_validation_title" = "Ожидание проверки";
+"account_msisdn_validation_message" = "Мы отправили SMS с кодом активации. Введите этот код в поле ниже.";
+"account_msisdn_validation_error" = "Не удалось проверить номер телефона.";
+"account_error_display_name_change_failed" = "Не удалось изменить отображаемое имя";
+"account_error_picture_change_failed" = "Не удалось изменить аватар";
+"account_error_matrix_session_is_not_opened" = "Сессия Matrix не открыта";
+"account_error_email_wrong_title" = "Неверный адрес электронной почты";
+"account_error_email_wrong_description" = "Похоже это недействительный адрес электронной почты";
+"account_error_msisdn_wrong_title" = "Некорректный номер телефона";
+"account_error_msisdn_wrong_description" = "Это недействительный номер телефона";
+"room_creation_name_placeholder" = "(напр. lunchGroup)";
+"room_creation_alias_title" = "Псевдоним комнаты:";
+"room_creation_alias_placeholder" = "(напр. #foo:primer.ru)";
+"room_creation_alias_placeholder_with_homeserver" = "(напр. #foo%@)";
+"room_creation_participants_placeholder" = "(напр. @boris:homeserver1; @ivan:homeserver2...)";
+"room_error_join_failed_empty_room" = "В настоящее время невозможно присоединиться в пустую комнату.";
+"room_error_name_edition_not_authorized" = "У вас нет прав на редактирование названия этой комнаты";
+"room_error_topic_edition_not_authorized" = "У вас нет прав редактировать тему этой комнаты";
+"room_error_cannot_load_timeline" = "Не удалось загрузить хронологию";
+"room_error_timeline_event_not_found_title" = "Не удалось загрузить метку из хронологии";
+"room_error_timeline_event_not_found" = "Приложение пыталось загрузить конкретную позицию хронологии этой комнаты, но не смогло ее найти";
+"room_no_power_to_create_conference_call" = "Вы должны иметь право выдачи приглашений, чтобы начать конференцию в этой комнате";
+"room_no_conference_call_in_encrypted_rooms" = "Групповые вызовы не поддерживаются в зашифрованных комнатах";
+// Attachment
+"attachment_size_prompt" = "Вы хотите отправить как:";
+"attachment_original" = "Фактический размер (%@)";
+"attachment_small" = "Маленький (%@)";
+"attachment_medium" = "Средний (%@)";
+"attachment_large" = "Большой (%@)";
+"attachment_cancel_download" = "Отменить загрузку?";
+"attachment_cancel_upload" = "Отменить отправку?";
+"attachment_multiselection_size_prompt" = "Вы хотите отправить изображения как:";
+"attachment_multiselection_original" = "Фактический размер";
+"attachment_e2e_keys_file_prompt" = "Этот файл содержит ключи шифрования, экспортированные из клиента Matrix.\nВы хотите просмотреть содержимое файла или импортировать содержащиеся в нем ключи?";
+"attachment_e2e_keys_import" = "Импорт...";
+// Contacts
+"contact_mx_users" = "Пользователи Matrix";
+"contact_local_contacts" = "Локальные контакты";
+// Search
+"search_no_results" = "Нет результатов";
+// Time
+"format_time_s" = "с";
+"format_time_m" = "м";
+"format_time_h" = "ч";
+"format_time_d" = "д";
+// E2E import
+"e2e_import_room_keys" = "Импорт ключей комнаты";
+"e2e_import_prompt" = "Этот процесс позволит вам импортировать ключи шифрования, которые вы экспортировали ранее из клиента Matrix. Это позволит вам расшифровать историю чата.\nФайл защищен парольной фразой. Введите ее для расшифровки файла.";
+"e2e_import" = "Импорт";
+"e2e_passphrase_enter" = "Введите парольную фразу";
+// E2E export
+"e2e_export_room_keys" = "Экспорт ключей комнаты";
+"notice_feedback" = "Обратная связь (id: %@): %@";
+"e2e_export_prompt" = "Этот процесс позволяет вам экспортировать ключи для сообщений, которые вы получили в комнатах с шифрованием, в локальный файл. Вы сможете импортировать эти ключи в другой клиент Matrix чтобы расшифровать эти сообщения.\nЭкспортированный файл позволит любому пользователю расшифровать и зашифровать сообщения, которые вы видите, поэтому вы должны быть крайне осторожны и держать файл в надежном месте.";
+"e2e_export" = "Экспорт";
+"e2e_passphrase_confirm" = "Подтвердить парольную фразу";
+"e2e_passphrase_empty" = "Парольная фраза не может быть пустой";
+"e2e_passphrase_not_match" = "Парольные фразы должны совпадать";
+// Others
+"user_id_title" = "Идентификатор пользователя:";
+"offline" = "не в сети";
+"unsent" = "Не отправлено";
+"error" = "Ошибка";
+"not_supported_yet" = "Пока не поддерживается";
+"default" = "по умолчанию";
+"network_error_not_reachable" = "Проверьте подключение к сети";
+"user_id_placeholder" = "напр.: @boris:homeserver";
+"ssl_homeserver_url" = "URL-адрес домашнего сервера: %@";
+// Permissions
+"camera_access_not_granted_for_call" = "Для видеозвонков требуется доступ к камере, но %@ не имеет разрешения на ее использование";
+"microphone_access_not_granted_for_call" = "Для звонков требуется доступ к микрофону, но %@ не имеет разрешения на его использование";
+"local_contacts_access_not_granted" = "Поиск пользователей из локальных контактов требует доступа к вашим контактам, но %@ не имеет разрешения на их использование";
+"local_contacts_access_discovery_warning_title" = "Обнаружение пользователей";
+"local_contacts_access_discovery_warning" = "Чтобы обнаружить контакты, уже использующие Matrix, %@ может отправлять адреса электронной почты и номера телефонов в вашей адресной книге на выбранный вами сервер идентификации Matrix. Там, где это поддерживается, личные данные перед отправкой хэшируются - пожалуйста, ознакомьтесь с политикой конфиденциальности вашего сервера идентификации для более подробной информации.";
+"notice_room_invite" = "%@ пригласил(а) %@";
+"notice_room_third_party_invite" = "%@ отправил(а) приглашение для %@ войти в комнату";
+"notice_room_third_party_registered_invite" = "%@ принял(а) приглашение от %@";
+"notice_room_aliases" = "Псевдонимы комнаты: %@";
+"private" = "Приватный";
+"public" = "Публичный";
+"view" = "Просмотр";
+"message_unsaved_changes" = "Имеются несохраненные изменения. Они будут потеряны.";
+// Login Screen
+"login_error_already_logged_in" = "Уже вошли";
+"login_error_must_start_http" = "URL-адрес должен начинаться с http[s]://";
+// room details dialog screen
+"room_details_title" = "Информация о комнате";
+// contacts list screen
+"invitation_message" = "Я бы хотел поговорить с вами в Matrix. Пожалуйста, посетите веб-сайт https://matrix.org для получения дополнительной информации.";
+// Settings screen
+"settings_title_config" = "Конфигурация";
+"settings_title_notifications" = "Уведомления";
+// Notification settings screen
+"notification_settings_disable_all" = "Отключить все уведомления";
+"notification_settings_enable_notifications" = "Включить уведомления";
+"notification_settings_enable_notifications_warning" = "Все уведомления отключены для всех устройств.";
+"notification_settings_global_info" = "Настройки уведомлений сохраняются в учетной записи и распространяются на все клиенты, которые их поддерживают (включая настольный компьютер).\n\nПравила применяются по порядку; будет использовано первое подходящее.\nТаким образом: уведомления для сообщений приоритетней уведомлений для комнат, которые, в свою очередь, приоритетней уведомлений для отправителей.\nДля нескольких правил одинакового типа будет использован первый по счету.";
+"notification_settings_always_notify" = "Всегда уведомлять";
+"notification_settings_never_notify" = "Никогда не уведомлять";
+"notification_settings_word_to_match" = "соответствие слов";
+"notification_settings_custom_sound" = "Пользовательский звук";
+"notification_settings_sender_hint" = "@user:domain.com";
+"notification_settings_select_room" = "Выберите комнату";
+"notification_settings_other_alerts" = "Другие предупреждения";
+"notification_settings_contain_my_user_name" = "Уведомлять звуком о сообщениях, содержащих мое имя пользователя";
+"notification_settings_contain_my_display_name" = "Уведомлять звуком о сообщениях, содержащих мое отображаемое имя";
+"notification_settings_just_sent_to_me" = "Уведомлять звуком о сообщениях, отправленных только мне";
+"notification_settings_invite_to_a_new_room" = "Уведомлять меня о приглашении в новую комнату";
+"notification_settings_people_join_leave_rooms" = "Уведомлять, когда пользователи заходят или выходят из комнат";
+"notification_settings_receive_a_call" = "Уведомлять о получении звонка";
+"notification_settings_suppress_from_bots" = "Подавлять уведомления от ботов";
+"notification_settings_by_default" = "По умолчанию...";
+"notification_settings_notify_all_other" = "Уведомлять о всех других сообщениях/комнатах";
+// gcm section
+"settings_config_home_server" = "Домашний сервер: %@";
+"settings_config_identity_server" = "Сервер идентификации: %@";
+"settings_config_user_id" = "Идентификатор пользователя: %@";
+// call string
+"call_waiting" = "Ожидание...";
+"call_connecting" = "Соединение…";
+"call_ended" = "Вызов завершен";
+"call_ring" = "Вызов...";
+"incoming_video_call" = "Входящий видеовызов";
+"incoming_voice_call" = "Входящий голосовой вызов";
+"call_invite_expired" = "Срок действия приглашения на звонок истек";
+// unrecognized SSL certificate
+"ssl_trust" = "Доверять";
+"ssl_logout_account" = "Выход";
+"ssl_remain_offline" = "Игнорировать";
+"ssl_could_not_verify" = "Не удалось проверить подлинность удаленного сервера.";
+"ssl_cert_not_trust" = "Это может означать, что кто-то злонамеренно перехватывает ваш трафик или что ваш телефон не доверяет сертификату, предоставленному удаленным сервером.";
+"ssl_unexpected_existing_expl" = "Сертификат изменился у пользователя, который был доверенным для вашего устройства. Это ОЧЕНЬ НЕОБЫЧНО. Рекомендуется НЕ ПРИНИМАТЬ новый сертификат.";
+"ssl_only_accept" = "Примите сертификат ТОЛЬКО в том случае, если администратор сервера опубликовал отпечаток, соответствующий приведенному выше.";
+"ssl_fingerprint_hash" = "Отпечаток (%@):";
+"notification_settings_per_word_notifications" = "Уведомления по одному слову";
+"notification_settings_per_word_info" = "Слова нечувствительны к регистру и могут содержать * спецсимвол. Так:\nFoo соответствует строке foo, окруженной разделителями слов (например, пунктуацией и пробелом или началом/концом строки).\nFoo * соответствует любому такому слову, которое начинается с foo.\n* Foo * соответствует любому такому слову, которое включает в себя 3 буквы foo.";
+"notification_settings_highlight" = "Подсветка";
+"notification_settings_per_room_notifications" = "Уведомления для каждой комнаты";
+"notification_settings_per_sender_notifications" = "Уведомления для отдельного собеседника";
+"ssl_cert_new_account_expl" = "Если администратор сервера сказал, что это ожидается, убедитесь, что отпечаток ниже соответствует предоставленному им отпечатку.";
+"ssl_expected_existing_expl" = "Сертификат изменился с ранее доверенного на один, которому не доверяют. Возможно, сервер обновил свой сертификат. Обратитесь к администратору сервера за ожидаемым отпечатком.";
+"search_searching" = "Выполняется поиск...";
+"login_mobile_device" = "Мобильное устройство";
+"login_tablet_device" = "Планшет";
+"login_desktop_device" = "Компьютер";
+"notice_room_related_groups" = "Группы, связанные с этой комнатой: %@";
+// Groups
+"group_invite_section" = "Приглашения";
+"group_section" = "Группы";
+"notice_sticker" = "стикер";
+"notice_in_reply_to" = "В ответ на";
+"error_common_message" = "Произошла ошибка. Пожалуйста, повторите попытку позже.";
+// Reply to message
+"message_reply_to_sender_sent_an_image" = "отправил(а) изображение.";
+"message_reply_to_sender_sent_a_video" = "отправил(а) видео.";
+"message_reply_to_sender_sent_an_audio_file" = "отправил(а) аудиофайл.";
+"message_reply_to_sender_sent_a_file" = "отправил(а) файл.";
+"message_reply_to_message_to_reply_to_prefix" = "В ответ на";
+"login_error_resource_limit_exceeded_title" = "Превышен лимит ресурса";
+"login_error_resource_limit_exceeded_contact_button" = "Связаться с администратором";
+"login_error_resource_limit_exceeded_message_default" = "Этот сервер превысил один из лимитов ресурсов.";
+"login_error_resource_limit_exceeded_message_monthly_active_user" = "Этот сервер достиг ежемесячного лимита активных пользователей.";
+"login_error_resource_limit_exceeded_message_contact" = "\n\nПожалуйста свяжитесь с вашим администратором что бы продолжить пользоваться этим сервисом.";
+"e2e_passphrase_create" = "Создать парольную фразу";
+"account_error_push_not_allowed" = "Уведомления не разрешены";
+"notice_room_third_party_revoked_invite" = "%@ отозвал приглашение для %@ на вступление в комнату";
+"device_details_rename_prompt_title" = "Имя Сессии";
+"notice_encryption_enabled_ok" = "%@ включил сквозное шифрование.";
+"notice_encryption_enabled_unknown_algorithm" = "%1$@ включил сквозное шифрование (нераспознанный алгоритм %2$@).";
+// Notice Events with "You"
+"notice_room_invite_by_you" = "Вы пригласили %@";
+"notice_room_invite_you" = "%@ пригласил Вас";
+"notice_room_third_party_invite_by_you" = "Вы отправили приглашение %@ вступить в комнату";
+"notice_room_third_party_registered_invite_by_you" = "Вы приняли приглашение для @%";
+"notice_room_third_party_revoked_invite_by_you" = "Вы отозвали приглашение для %@ вступить в комнату";
+"notice_room_join_by_you" = "Вы вошли";
+"notice_room_leave_by_you" = "Вы вышли";
+"notice_room_reject_by_you" = "Вы отклонили приглашение";
+"notice_room_kick_by_you" = "Вы исключили %@";
+"notice_room_unban_by_you" = "Вы разбанили %@";
+"notice_room_ban_by_you" = "Вы забанили %@";
+"notice_avatar_url_changed_by_you" = "Вы сменили свой аватар";
+"notice_display_name_set_by_you" = "Вы установили своё отображаемое имя как %@";
+"notice_display_name_changed_from_by_you" = "Вы сменили своё отображаемое имя с %@ на %@";
+"notice_display_name_removed_by_you" = "Вы удалили своё отображаемое имя";
+"notice_topic_changed_by_you" = "Вы сменили тему на \"%@\".";
+"notice_room_name_changed_by_you" = "Вы сменили имя комнаты на %@.";
+"notice_room_withdraw_by_you" = "Вы отозвали приглашение %@";
+"notice_placed_voice_call_by_you" = "Вы начали звонок";
+"notice_placed_video_call_by_you" = "Вы начали видеозвонок";
+"notice_answered_video_call_by_you" = "Вы ответили на звонок";
+"notice_ended_video_call_by_you" = "Вы закончили звонок";
+"notice_conference_call_request_by_you" = "Вы запросили VoIP конференцию";
+"notice_room_name_removed_by_you" = "Вы удалили название комнаты";
+"notice_room_topic_removed_by_you" = "Вы удалили эту тему";
+"notice_event_redacted_by_you" = " вами";
+"notice_profile_change_redacted_by_you" = "Вы обновили свой профиль %@";
+"notice_room_created_by_you" = "Вы создали и настроили комнату.";
+"notice_encryption_enabled_ok_by_you" = "Вы активировали сквозное шифрование.";
+"notice_encryption_enabled_unknown_algorithm_by_you" = "Вы активировали сквозное шифрование (неопознанный алгоритм %@).";
+"notice_redaction_by_you" = "Вы отредактировали событие (id: %@)";
+"notice_room_history_visible_to_anyone_by_you" = "Вы сделали будущую историю комнаты видимой для всех.";
+"notice_room_history_visible_to_members_by_you" = "Вы сделали будущую историю комнаты видимой для всех членов комнаты.";
+"notice_room_history_visible_to_members_from_invited_point_by_you" = "Вы сделали будущую историю комнаты видимой для всех членов комнаты, с того момента, как они приглашены.";
+"notice_room_history_visible_to_members_from_joined_point_by_you" = "Вы сделали будущую историю комнаты видимой для всех членов комнаты, с того момента, как они присоединились.";
+// New
+"notice_room_join_rule_invite" = "%@ сделал(а) комнату доступной только по приглашению.";
+"notice_room_join_rule_invite_by_you" = "Вы сделали комнату только по приглашению.";
+"notice_room_join_rule_public" = "%@ сделал(а) комнату публичной.";
+"notice_room_join_rule_public_by_you" = "Вы сделали комнату публичной.";
+"notice_room_name_removed_for_dm" = "%@ удалил(а) название";
+"notice_room_created_for_dm" = "%@ вошёл(ла).";
+"notice_room_join_rule_invite_for_dm" = "%@ сделал(а) доступ только по приглашению.";
+"notice_room_join_rule_invite_by_you_for_dm" = "Вы сделали доступ только по приглашению.";
+"notice_room_join_rule_public_for_dm" = "%@ сделал(а) комнату публичной.";
+"notice_room_join_rule_public_by_you_for_dm" = "Вы сделали комнату публичной.";
+"notice_room_power_level_intro_for_dm" = "Уровень доступа членов комнаты:";
+"notice_room_aliases_for_dm" = "Псевдонимы: %@";
+"notice_room_history_visible_to_members_for_dm" = "%@ сделал(а) будущую историю сообщений видимой всем членам комнаты.";
+"notice_room_history_visible_to_members_from_invited_point_for_dm" = "%@ сделал(а) будущие сообщения видимыми для всех с момента их приглашения.";
+"notice_room_history_visible_to_members_from_joined_point_for_dm" = "%@ сделал(а) будущие сообщения видимыми для всех с момента их присоединения.";
+"room_left_for_dm" = "Вы вышли";
+"notice_room_third_party_invite_for_dm" = "%@ пригласил(а) %@";
+"notice_room_third_party_revoked_invite_for_dm" = "%@ отозвал(а) приглашение %@";
+"notice_room_name_changed_for_dm" = "%@ изменил(а) название на %@.";
+"notice_room_third_party_invite_by_you_for_dm" = "Вы пригласили %@";
+"notice_room_third_party_revoked_invite_by_you_for_dm" = "Вы отозвали приглашение %@";
+"notice_room_name_changed_by_you_for_dm" = "Вы сменили название на %@.";
+"notice_room_name_removed_by_you_for_dm" = "Вы удалили название";
+"notice_room_created_by_you_for_dm" = "Вы вошли.";
+"notice_room_history_visible_to_members_by_you_for_dm" = "Вы сделали будущие сообщения видимыми для всех участников комнаты.";
+"notice_room_history_visible_to_members_from_invited_point_by_you_for_dm" = "Вы сделали будущие сообщения видимыми для всех с момента их приглашения.";
+"notice_room_history_visible_to_members_from_joined_point_by_you_for_dm" = "Вы сделали будущие сообщения видимыми для всех с момента их присоединения.";
+"call_more_actions_dialpad" = "Панель набора номера";
+"call_more_actions_transfer" = "Перевод";
+"call_more_actions_audio_use_device" = "Динамик устройства";
+"call_more_actions_audio_use_headset" = "Использовать звук гарнитуры";
+"call_more_actions_change_audio_device" = "Сменить аудиоустройство";
+"call_more_actions_unhold" = "Возобновить";
+"call_more_actions_hold" = "Удерживать";
+"call_holded" = "Вы поставили вызов на удержание";
+"call_remote_holded" = "%@ поставил(а) вызов на удержание";
+"notice_declined_video_call_by_you" = "Вы отменили вызов";
+"notice_declined_video_call" = "%@ отменил(а) этот вызов";
+"resume_call" = "Возобновить";
+"call_transfer_to_user" = "Передача с %@";
+"call_consulting_with_user" = "Консультация с %@";
+"call_video_with_user" = "Видеовызов с %@";
+"call_voice_with_user" = "Голосовой вызов с %@";
+"call_ringing" = "Звонок…";
+"microphone_access_not_granted_for_voice_message" = "Голосовые сообщения требуют доступа к микрофону, но у %@ нет разрешения на его использование";
+"e2e_passphrase_too_short" = "Слишком короткая парольная фраза (Длина парольной фразы должна быть не менее %d символов)";
+"message_reply_to_sender_sent_a_voice_message" = "отправил(а) голосовое сообщение.";
+"attachment_large_with_resolution" = "Большой %@ (~%@)";
+"attachment_medium_with_resolution" = "Средний %@ (~%@)";
+"attachment_small_with_resolution" = "Маленький %@ (~%@)";
+"attachment_size_prompt_message" = "Это можно отключить в настройках.";
+"attachment_size_prompt_title" = "Подтвердите размер для отправки";
+"auth_reset_password_error_not_found" = "Не найдено";
+"auth_reset_password_error_unauthorized" = "Неавторизованный";
+"auth_invalid_user_name" = "Недопустимое имя пользователя";
+"auth_username_in_use" = "Имя пользователя занято";
+"rename" = "Переименовать";
+"room_displayname_all_other_members_left" = "%@ (Вышел)";
diff --git a/Riot/Modules/MatrixKit/Assets/MatrixKitAssets.bundle/si.lproj/MatrixKit.strings b/Riot/Modules/MatrixKit/Assets/MatrixKitAssets.bundle/si.lproj/MatrixKit.strings
new file mode 100644
index 000000000..5efc3e3d5
--- /dev/null
+++ b/Riot/Modules/MatrixKit/Assets/MatrixKitAssets.bundle/si.lproj/MatrixKit.strings
@@ -0,0 +1,25 @@
+
+
+"invite" = "ආරාධනා";
+"settings_title_notifications" = "දැනුම්දීම්";
+"notification_settings_sender_hint" = "@පරිශීලක:වසම.ලංකා";
+"login_password_placeholder" = "මුරපදය";
+"login_leave_fallback" = "අවලංගු කරන්න";
+"login_email_placeholder" = "වි-තැපැල් ලිපිනය";
+"login_identity_server_title" = "අනන්යතා සේවාදායකයේ ඒ.ස.නි.:";
+"login_home_server_info" = "ඔබගේ මූලික සේවාදායකය ඔබගේ සියලු සංවාද සහ ගිණුමේ දත්ත ගබඩා කරයි";
+"login_home_server_title" = "මූලික සේවාදායකයේ ඒ.ස.නි.:";
+"login_server_url_placeholder" = "ඒ.ස.නි.(URL) (උදා. https://matrix.org)";
+
+// Login Screen
+"login_create_account" = "ගිණුම සාදන්න:";
+/* *********************** */
+/* iOS specific */
+/* *********************** */
+
+"matrix" = "මැට්රික්ස්";
+"save" = "සුරකින්න";
+"cancel" = "අවලංගු කරන්න";
+"leave" = "හැරයන්න";
+"continue" = "ඉදිරියට";
+"back" = "ආපසු";
diff --git a/Riot/Modules/MatrixKit/Assets/MatrixKitAssets.bundle/sq.lproj/MatrixKit.strings b/Riot/Modules/MatrixKit/Assets/MatrixKitAssets.bundle/sq.lproj/MatrixKit.strings
new file mode 100644
index 000000000..48323eea3
--- /dev/null
+++ b/Riot/Modules/MatrixKit/Assets/MatrixKitAssets.bundle/sq.lproj/MatrixKit.strings
@@ -0,0 +1,477 @@
+"matrix" = "Matrix";
+// Login Screen
+"login_create_account" = "Krijo llogari:";
+"login_server_url_placeholder" = "URL (p.sh. https://matrix.org)";
+"login_home_server_title" = "URL Shërbyesi Home:";
+"login_identity_server_title" = "URL Shërbyesi Identitetesh:";
+"login_password_placeholder" = "Fjalëkalim";
+"login_optional_field" = "opsionale";
+"login_email_placeholder" = "Adresë email";
+"login_prompt_email_token" = "Ju lutemi, jepni token-in tuaj të vleftësimit të email-it:";
+"login_error_title" = "Dështoi Hyrja";
+"login_error_registration_is_not_supported" = "Hëpërhë regjistrimet nuk mbulohen";
+"login_error_forbidden" = "Emër përdoruesi/fjalëkalim i pavlefshëm";
+"login_error_unknown_token" = "Token-i i hyrjeve i dhënë nuk u njoh";
+"login_error_bad_json" = "JSON e keqformuar";
+"login_error_not_json" = "S’përmbante JSON të vlefshëm";
+"login_error_user_in_use" = "Ky emër përdoruesi është i përdorur tashmë";
+"login_use_fallback" = "Përdor faqe fallback";
+"login_leave_fallback" = "Anuloje";
+"login_invalid_param" = "Parametër i pavlefshëm";
+"register_error_title" = "Regjistrimi Dështoi";
+"login_error_forgot_password_is_not_supported" = "Harrimi i fjalëkalimeve hëpërhë s’mbulohet";
+"login_mobile_device" = "Celular";
+"login_tablet_device" = "Tablet";
+"login_desktop_device" = "Desktop";
+// Action
+"no" = "Jo";
+"yes" = "Po";
+"abort" = "Ndërprite";
+"back" = "Mbrapsht";
+"close" = "Mbylle";
+"continue" = "Vazhdo";
+"discard" = "Hidhe Tej";
+"dismiss" = "Mos e merr parasysh";
+"retry" = "Riprovo";
+"sign_up" = "Regjistrohuni";
+"submit" = "Parashtroje";
+"set_default_power_level" = "Rikthe Shkallët e Pushtetit Te Parazgjedhja";
+"set_moderator" = "Caktojeni Moderator";
+"set_admin" = "Caktojeni Përgjegjës";
+"start_chat" = "Filloni Fjalosje";
+"start_voice_call" = "Nis Thirrje Audio";
+"start_video_call" = "Nis Thirrje Video";
+"mention" = "Përmendje";
+"select_account" = "Përzgjidhni një llogari";
+"attach_media" = "Bashkëngjitni media nga Mediateka";
+"capture_media" = "Bëni Foto/Video";
+"invite_user" = "Ftoni Përdorues matrix";
+"reset_to_default" = "Riktheje te parazgjedhjet";
+"resend_message" = "Ridërgoje mesazhin";
+"select_all" = "Përzgjidhe Krejt";
+"cancel_upload" = "Anuloje Ngarkimin";
+"cancel_download" = "Anuloje Shkarkimin";
+"show_details" = "Shfaq Hollësi";
+"answer_call" = "Përgjigjuni Thirrjes";
+"reject_call" = "Hidhe poshtë Thirrjen";
+"end_call" = "Përfundoje Thirrjen";
+"ignore" = "Shpërfille";
+"unignore" = "Hiqja shpërfilljen";
+// Events formatter
+"notice_avatar_changed_too" = "(u ndryshua edhe avatari)";
+"notice_room_name_removed" = "%@ hoqi emrin e dhomës";
+"notice_room_topic_removed" = "%@ hoqi temën";
+"notice_event_redacted" = "";
+"notice_event_redacted_by" = " nga %@";
+"notice_event_redacted_reason" = " [arsye: %@]";
+"notice_profile_change_redacted" = "%@ përditësoi profilin e vet %@";
+"notice_room_created" = "%@ krijoi dhe formësoi dhomën.";
+"notice_room_join_rule" = "Rregulli për pjesëmarrje është: %@";
+"notice_room_power_level_intro" = "Shkallët e pushtetit të anëtarëve të dhomës janë:";
+"notice_room_aliases" = "Aliaset e dhomës janë: %@";
+"notice_room_related_groups" = "Grupet përshoqëruar kësaj dhome janë: %@";
+"notice_encrypted_message" = "Mesazhi i fshehtëzuar";
+"notice_encryption_enabled" = "%@ aktivizoi fshehtëzimin skaj-më-skaj (algoritëm %@)";
+"notice_image_attachment" = "bashkëngjitje figurash";
+"notice_audio_attachment" = "bashkëngjitje audio";
+"notice_video_attachment" = "bashkëngjitje videosh";
+"notice_file_attachment" = "bashkëngjitje kartelash";
+"notice_invalid_attachment" = "bashkëngjitje e pavlefshme";
+"notice_unsupported_attachment" = "Bashkëngjitje e pambuluar: %@";
+"notice_error_unsupported_event" = "Akt i pambuluar";
+"notice_error_unexpected_event" = "Akt i papritur";
+"notice_error_unknown_event_type" = "Lloj i panjohur akti";
+"notice_crypto_unable_to_decrypt" = "** S’arrihet të shfshehtëzohet: %@ **";
+// room display name
+"room_displayname_empty_room" = "Dhomë e zbrazët";
+"room_displayname_more_than_two_members" = "%@ dhe %@ të tjerë";
+// Settings
+"settings" = "Rregullime";
+"settings_enable_inapp_notifications" = "Aktivizoni njoftime Aplikacioni";
+"settings_enable_push_notifications" = "Aktivizoni njoftime push";
+"settings_enter_validation_token_for" = "Jepni token vleftësimi për %@:";
+"notification_settings_room_rule_title" = "Dhomë: '%@'";
+// Devices
+"device_details_title" = "Të dhëna sesioni\n";
+"device_details_name" = "Emër Publik\n";
+"device_details_identifier" = "ID\n";
+"device_details_last_seen" = "Parë së fundi më\n";
+"device_details_last_seen_format" = "%@ @ %@\n";
+"device_details_rename_prompt_message" = "Emri publik i një sesioni është i dukshëm për persona me të cilët komunikoni";
+"device_details_delete_prompt_title" = "Mirëfilltësim";
+// Encryption information
+"room_event_encryption_info_title" = "Të dhëna fshehtëzimi skaj-më-skaj\n\n";
+"room_event_encryption_info_event" = "Të dhëna akti\n";
+"room_event_encryption_info_event_user_id" = "ID Përdoruesi\n";
+"room_event_encryption_info_event_identity_key" = "Kyç identiteti Curve25519\n";
+"room_event_encryption_info_event_algorithm" = "Algoritëm\n";
+"room_event_encryption_info_event_session_id" = "ID sesioni\n";
+"room_event_encryption_info_event_decryption_error" = "Gabim shfshehtëzimi\n";
+"room_event_encryption_info_event_unencrypted" = "të pafshehtëzuara";
+"room_event_encryption_info_event_none" = "asnjë";
+"room_event_encryption_info_device" = "\nTë dhëna sesioni dërguesi\n";
+"room_event_encryption_info_device_unknown" = "sesion i panjohur\n";
+"room_event_encryption_info_device_name" = "Emër Publik\n";
+"room_event_encryption_info_device_id" = "ID\n";
+"room_event_encryption_info_device_verification" = "Verifikim\n";
+"room_event_encryption_info_device_fingerprint" = "Shenja gishtash Ed25519\n";
+"room_event_encryption_info_device_verified" = "E verifikuar";
+"room_event_encryption_info_device_not_verified" = "JO e verifikuar";
+"room_event_encryption_info_device_blocked" = "Në Listë të Zezë";
+"room_event_encryption_info_verify" = "Verifikoni…";
+"room_event_encryption_info_block" = "Listë e zezë";
+"room_event_encryption_verify_title" = "Verifiko sesionin\n\n";
+"room_event_encryption_verify_ok" = "Verifikoje";
+// Account
+"account_save_changes" = "Ruaji ndryshimet";
+"account_link_email" = "Lidhni Email";
+"account_linked_emails" = "Email-e të lidhur";
+"account_email_validation_title" = "Verifikim Në Pritje të Miratimit";
+"account_msisdn_validation_title" = "Verifikim Në Pritje të Miratimit";
+"account_msisdn_validation_error" = "S’arrihet të verifikohet numër telefoni.";
+"account_error_display_name_change_failed" = "Ndryshimi i emrit të shfaqjes dështoi";
+"account_error_picture_change_failed" = "Ndryshimi i fotos dështoi";
+"account_error_matrix_session_is_not_opened" = "Sesioni Matrix s’është hapur";
+"account_error_email_wrong_title" = "Adresë Email e Pavlefshme";
+"account_error_email_wrong_description" = "Kjo s’duket se është adresë email e vlefshme";
+"account_error_msisdn_wrong_title" = "Numër Telefoni i Pavlefshëm";
+"account_error_msisdn_wrong_description" = "Ky s’duket të jetë numër telefoni i vlefshëm";
+// Room creation
+"room_creation_name_title" = "Emër dhome:";
+"room_creation_name_placeholder" = "(p.sh., Grupiiçajit)";
+"room_creation_alias_title" = "Alias dhome:";
+"room_creation_alias_placeholder" = "(p.sh. #kot:shembull.org)";
+"room_creation_alias_placeholder_with_homeserver" = "(p.sh. #kot%@)";
+"room_creation_participants_title" = "Pjesëmarrës:";
+// Room
+"room_please_select" = "Ju lutemi, përzgjidhni një dhomë";
+"room_error_join_failed_title" = "S’u arrit të hyhej në dhomë";
+"room_error_name_edition_not_authorized" = "S’jeni i autorizuar të përpunoni emrin e kësaj dhome";
+"room_error_topic_edition_not_authorized" = "S’jeni i autorizuar ta përpunoni temën e kësaj dhome";
+"room_error_cannot_load_timeline" = "S’u arrit të ngarkohej rrjedha kohore";
+"room_error_timeline_event_not_found_title" = "S’u arrit të ngarkohej pozicion rrjedhe kohore";
+"room_left" = "Dolët prej dhomës";
+// Attachment
+"attachment_size_prompt" = "Doni të dërgohet si:";
+"attachment_original" = "Madhësi Faktike (%@)";
+"attachment_small" = "E vogël (~ %@)";
+"attachment_medium" = "Mesatare (~%@)";
+"attachment_large" = "E madhe (~ %@)";
+"attachment_cancel_download" = "Të anulohet shkarkimi?";
+"attachment_cancel_upload" = "Të anulohet ngarkimin?";
+"attachment_multiselection_size_prompt" = "Doni të dërgoni figura si:";
+"attachment_multiselection_original" = "Madhësi Faktike";
+"attachment_e2e_keys_import" = "Importoni…";
+// Contacts
+"contact_mx_users" = "Përdorues të Matrix-it";
+"contact_local_contacts" = "Kontakte Vendore";
+// Groups
+"group_invite_section" = "Ftesa";
+"group_section" = "Grupe";
+// Search
+"search_no_results" = "S’ka Përfundime";
+"search_searching" = "Kërkim në ecuri e sipër…";
+// Time
+"format_time_s" = "s";
+"format_time_m" = "m";
+"format_time_h" = "h";
+"format_time_d" = "d";
+// E2E import
+"e2e_import_room_keys" = "Importo kyçe dhome";
+"e2e_import" = "Importo";
+"e2e_passphrase_enter" = "Jepni frazëkalimin";
+// E2E export
+"e2e_export_room_keys" = "Eksporto kyçe dhome";
+"e2e_export" = "Eksporto";
+"e2e_passphrase_confirm" = "Ripohoni frazëkalimin";
+"e2e_passphrase_empty" = "Frazëkalimi s’duhet të jetë i zbrazët";
+"e2e_passphrase_not_match" = "Frazëkalimet duhet të përputhen";
+// Others
+"user_id_title" = "ID Përdoruesi:";
+"offline" = "jo në linjë";
+"error" = "Gabim";
+"not_supported_yet" = "S’mbulohen ende";
+"default" = "parazgjedhje";
+"private" = "Private";
+"public" = "Publike";
+"power_level" = "Shkallë Pushteti";
+"user_id_placeholder" = "p.sh.: @beni:homeserver";
+"ssl_homeserver_url" = "URL Shërbyesi Home: %@";
+"local_contacts_access_discovery_warning_title" = "Zbulim përdoruesish";
+// Country picker
+"country_picker_title" = "Zgjidhni një vend";
+// Language picker
+"language_picker_title" = "Zgjidhni gjuhë";
+"language_picker_default_language" = "Parazgjedhje (%@)";
+"notice_room_invite" = "%@ ftoi %@";
+"notice_room_third_party_invite" = "%@ dërgoi një ftesë për %@ që të vijë te dhoma";
+"notice_room_third_party_registered_invite" = "%@ e pranoi ftesën për %@";
+"notice_room_join" = "%@ u bë pjesë";
+"notice_room_leave" = "%@ doli";
+"notice_room_reject" = "%@ hodhi tej ftesën";
+"notice_room_kick" = "%@ përzuri %@";
+"notice_room_ban" = "%@ dëboi %@";
+"notice_room_withdraw" = "%@ e tërhoqi ftesën për %@";
+"notice_room_reason" = ". Arsye: %@";
+"notice_avatar_url_changed" = "%@ ndryshoi avatarin e vet";
+"notice_display_name_set" = "%@ caktoi emrin e vet të ekranit si %@";
+"notice_display_name_changed_from" = "%@ ndryshoi emrin e vet të ekranit nga %@ në %@";
+"notice_display_name_removed" = "%@ hoqi emrin e vet të ekranit";
+"notice_topic_changed" = "%@ ndryshoi temën në \"%@\".";
+"notice_room_name_changed" = "%@ ndryshoi emrin e dhomës në %@.";
+"notice_placed_voice_call" = "%@ bëri një thirrje zanore";
+"notice_placed_video_call" = "%@ bëri një thirrje video";
+"notice_answered_video_call" = "%@ iu përgjigj thirrjes";
+"notice_ended_video_call" = "%@ e përfundoi thirrjen";
+"notice_conference_call_request" = "%@ kërkoi një konferencë VoIP";
+"notice_conference_call_started" = "Konferenca VoIP filloi";
+"notice_conference_call_finished" = "Konferenca VoIP përfundoi";
+// button names
+"ok" = "OK";
+"cancel" = "Anuloje";
+"save" = "Ruaje";
+"leave" = "Dilni";
+"send" = "Dërgoje";
+"copy_button_name" = "Kopjoje";
+"resend" = "Ridërgoje";
+"set_power_level" = "Caktoni Shkallë Pushteti";
+"delete" = "Fshije";
+"view" = "Shiheni";
+// actions
+"action_logout" = "Dalje";
+"create_room" = "Krijo Dhomë";
+"login" = "Hyrje";
+"create_account" = "Krijo Llogari";
+"membership_invite" = "I ftuar";
+"membership_leave" = "I ikur";
+"membership_ban" = "I dëbuar";
+"num_members_one" = "%@ përdorues";
+"num_members_other" = "%@ përdorues";
+"invite" = "Ftoje";
+"kick" = "Përzëre";
+"ban" = "Dëboje";
+"unban" = "Hiqja dëbimin";
+"message_unsaved_changes" = "Ka ndryshime të paruajtura. Ikja do të shkaktojë hedhjen tej të tyre.";
+// Login Screen
+"login_error_already_logged_in" = "Tashmë i futur";
+"login_error_must_start_http" = "URL-ja duhet të fillojë me http[s]://";
+// room details dialog screen
+"room_details_title" = "Hollësi Dhome";
+// Settings screen
+"settings_title_config" = "Formësim";
+"settings_title_notifications" = "Njoftime";
+// Notification settings screen
+"notification_settings_disable_all" = "Çaktivizoji krejt njoftimet";
+"notification_settings_enable_notifications" = "Aktivizo njoftimet";
+"notification_settings_enable_notifications_warning" = "Krejt njoftimet hëpërhë janë çaktivizuar për krejt pajisjet.";
+"notification_settings_always_notify" = "Njoftomë përherë";
+"notification_settings_never_notify" = "Mos njofto kurrë";
+"notification_settings_word_to_match" = "fjalë për përputhje";
+"notification_settings_highlight" = "Theksoje";
+"notification_settings_custom_sound" = "Tingull vetjak";
+"notification_settings_sender_hint" = "@përdorues:përkatësi.com";
+"notification_settings_select_room" = "Përzgjidhni një dhomë";
+"notification_settings_other_alerts" = "Sinjalizime të Tjera";
+"notification_settings_contain_my_user_name" = "Njoftomë me tingull mbi mesazhe që përmbajnë emrin tim";
+"notification_settings_contain_my_display_name" = "Njoftomë me tingull mbi mesazhe që përmbajnë emrin tim të shfaqjes";
+"notification_settings_just_sent_to_me" = "Njoftomë me tingull mbi mesazhe dërguar vetëm për mua";
+"notification_settings_suppress_from_bots" = "Ndaloji njoftimet nga robotë";
+"notification_settings_by_default" = "Si parazgjedhje…";
+"notification_settings_notify_all_other" = "Njoftim për krejt mesazhet/dhomat e tjera";
+// gcm section
+"settings_config_home_server" = "Shërbyes home: %@";
+"settings_config_identity_server" = "Shërbyes identitetesh: %@";
+"settings_config_user_id" = "ID Përdoruesi: %@";
+// call string
+"call_waiting" = "Po pritet…";
+"call_ended" = "Thirrja përfundoi";
+"call_ring" = "Po thirret…";
+"incoming_video_call" = "Thirrje Video Ardhëse";
+"incoming_voice_call" = "Thirrje Audio Ardhëse";
+"call_invite_expired" = "Ftesa Për Thirrje Skadoi";
+// unrecognized SSL certificate
+"ssl_trust" = "Besoje";
+"ssl_logout_account" = "Dalje";
+"ssl_remain_offline" = "Shpërfille";
+"ssl_fingerprint_hash" = "Shenja Gishtash (%@):";
+"ssl_could_not_verify" = "S’u verifikua dot identiteti i shërbyesit të largët.";
+"submit_code" = "Parashtroni kod";
+"notice_location_attachment" = "bashkëngjitje vendndodhjeje";
+"notice_redaction" = "%@ përpunoi një veprimtari (id: %@)";
+"notice_sticker" = "ngjitës";
+"unsent" = "Të padërguar";
+"network_error_not_reachable" = "Ju lutemi, kontrolloni aftësinë e lidhjes në rrjetin tuaj";
+"share" = "Ndajeni me të tjerët";
+"login_home_server_info" = "Shërbyesi juaj home depoziton krejt të dhënat e bisedave dhe llogarive tuaja";
+"login_identity_server_info" = "Matrix ofron shërbyes identiteti për të ndjekur se cilat email-e, etj, u përkasin ID-ve Matrix IDs. Hëpërhë ekziston vetëm https://matrix.org.";
+"login_user_id_placeholder" = "ID Matrix (p.sh. @poku:matrix.org ose poku)";
+"login_display_name_placeholder" = "Emër në ekran (p.sh. Mane Trimi)";
+"login_email_info" = "Përcaktimi i një adrese email i lejon përdoruesit e tjerë t’ju gjejnë më lehtë në Matrix, dhe do t’ju japë një rrugë për ricaktimin e fjalëkalimit tuaj në të ardhmen.";
+"login_error_no_login_flow" = "Dështuam në marrje të dhënash mirëfilltësimi nga ky Shërbyes Home";
+"login_error_do_not_support_login_flows" = "Hëpërhë nuk mbulojmë ndonjë ose krejt rrjedhat e hyrjeve të përkufizuara nga ky Shërbyes Home";
+"login_error_limit_exceeded" = "Janë dërguar shumë kërkesa";
+"login_error_login_email_not_yet" = "Lidhja email që s’është klikuar ende";
+"notice_room_power_level_acting_requirement" = "Shkallët minimum të pushtetit që duhet të ketë një përdorues përpara se të veprojë, janë:";
+"notice_room_power_level_event_requirement" = "Shkallët minimum të pushtetit që lidhen me aktet janë:";
+"notice_room_history_visible_to_anyone" = "%@ e bëri historikun e ardhshëm të dhomës të dukshëm për këdo.";
+"notice_room_history_visible_to_members" = "%@ e bëri historikun e ardhshëm të dhomës të dukshëm për krejt anëtarët e dhomës.";
+"notice_room_history_visible_to_members_from_invited_point" = "%@ e bëri historikun e ardhshëm të dhomës të dukshëm për krejt anëtarët e dhomës, prej çastit kur janë ftuar.";
+"notice_room_history_visible_to_members_from_joined_point" = "%@ e bëri historikun e ardhshëm të dhomës të dukshëm për krejt anëtarët e dhomës, prej çastit kur morën pjesë.";
+"notice_crypto_error_unknown_inbound_session_id" = "Sesioni i dërguesit nuk na ka dërguar kyçet për këtë mesazh.";
+"notice_in_reply_to" = "Në përgjigje të";
+"device_details_delete_prompt_message" = "Ky veprim lyp mirëfilltësim shtesë.\nQë të vazhdohet, ju lutemi, jepni fjalëkalimin tuaj.";
+"room_event_encryption_info_unverify" = "Hiqi verifikimin";
+"room_event_encryption_info_unblock" = "Hiqe nga listë e zezë";
+"room_event_encryption_verify_message" = "Që të verifikohet se ky sesion mund të besohet, ju lutemi, lidhuni me të zotin e tij duke përdorur ndonjë rrugë tjetër (p.sh., personalisht apo përmes një thirrjeje telefonike) dhe kërkojini nëse kyçi që sheh te Rregullimet e veta të Përdoruesit për këtë sesion përputhet me kyçin më poshtë:\n\n\tEmër sesioni: %@\n\tID sesioni: %@\n\tKyç sesioni: %@\n\nNëse përputhet, shtypni më poshtë butonin e verifikimit. Nëse jo, atëherë dikush tjetër është duke përgjuar këtë pajisje dhe do të donit më mirë të shtypnit butonin e kalimit në listë të zezë.\n\nNë të ardhmen ky proces verifikimi do të jetë më i sofistikuar.";
+"account_email_validation_message" = "Ju lutemi, kontrolloni email-in tuaj dhe klikoni mbi lidhjen që përmban. Pasi të jetë bërë kjo, klikoni që të vazhdohet.";
+"account_email_validation_error" = "S’arrihet të verifikohet adresë email. Ju lutemi, kontrolloni email-in tuaj dhe klikoni mbi lidhjen që përmban. Pasi të jetë bërë kjo, klikoni që të vazhdohet";
+"account_msisdn_validation_message" = "Kemi dërguar një SMS me një kod aktivizimi. Ju lutemi, jepeni këtë kod më poshtë.";
+"room_creation_participants_placeholder" = "(p.sh. @mane:homeserver1; @taku:homeserver2...)";
+"room_error_join_failed_empty_room" = "Hëpërhë s’është e mundur të hyhet në një dhomë të zbrazët.";
+"room_error_timeline_event_not_found" = "Aplikacioni u rrek të ngarkonte një pikë të dhënë prej rrjedhës kohore në këtë dhomë, por s’qe në gjendje ta gjente";
+"room_no_power_to_create_conference_call" = "Ju duhen leje për ftesa, që të nisni një konferencë në këtë dhomë";
+"room_no_conference_call_in_encrypted_rooms" = "Thirrjet konferencë nuk mbulohen në dhoma të fshehtëzuara";
+// Room members
+"room_member_ignore_prompt" = "Doni të fshihen krejt mesazhet nga ky përdorues?";
+"room_member_power_level_prompt" = "S’do të jeni në gjendje ta zhbëni këtë ndryshim, ngaqë po e promovoni përdoruesin të ketë të njëjtën shkallë pushteti si ju vetë.\nJeni i sigurt?";
+"attachment_e2e_keys_file_prompt" = "Kjo kartelë përmban kyçe fshehtëzimi të eksportur nga një klient Matrix.\nDoni të shihni lëndën e kartelës apo të importoni kyçet që ajo përmban?";
+"e2e_import_prompt" = "Ky proces ju lejon të importoni kyçe fshehtëzimi që keni eksportuar më parë nga një tjetër klient Matrix. Mandej do të jeni në gjendje të shfshehtëzoni çfarëdo mesazhesh që mund të shfshehtëzojë ai klient tjetër.\nKartela e eksportit është e mbrojtur me një frazëkalim. Që të shfshehtëzoni kartelën, duhet ta jepni frazëkalimin këtu.";
+"e2e_export_prompt" = "Ky proces ju lejon të eksportoni te një kartelë vendore kyçet për mesazhe që keni marrë në dhoma të fshehtëzuara. Mandej do të jeni në gjendje ta importoni kartelën te një tjetër klient Matrix në të ardhmen, që kështu ai klient të jetë në gjendje t’i fshehtëzojë këto mesazhe.\nKartela e eksportuar do t’i lejojë, cilitdo që mund ta lexojë, të shfshehtëzojë çfarëdo mesazhesh të fshehtëzuar që mund të shihni ju, ndaj duhet të bëni kujdes ta mbani të parrezikuar.";
+"error_common_message" = "Ndodhi një gabim. Ju lutemi, riprovoni më vonë.";
+// Permissions
+"camera_access_not_granted_for_call" = "Thirrjet video lypin përdorim të Kamerës, por %@ s’ka leje për ta përdorur";
+"microphone_access_not_granted_for_call" = "Thirrjet lypin përdorim të Mikrofonit, por %@ s’ka leje ta përdorë atë";
+"local_contacts_access_not_granted" = "Zbulimi i përdoruesve nga kontaktet vendore lyp hyrje te kontaktet tuaja, por %@ s’ka leje t’i përdorë ato";
+"local_contacts_access_discovery_warning" = "Që të mund të zbulojë kontakte që përdorin tashmë Matrix-in, %@ mund të dërgojë adresa email dhe numra telefonash nga libri juaj i adresave te shërbyesi juaj i zgjedhur i identiteteve Matrix. Kur kjo mbulohet, të dhënat personale fshehtëzohen, përpara dërgimit - për më tepër hollësi, ju lutemi kontrolloni rregulla privatësie të shërbyesit tuaj të identiteteve.";
+"notice_room_unban" = "%@ i hoqi dëbimin %@";
+"redact" = "Hiqe";
+// contacts list screen
+"invitation_message" = "Do të doja të bisedoja me ju me Matrix. Për të pasur më tepër itë dhëna, ju lutem, vizitoni sajtin http://matrix.org.";
+"notification_settings_global_info" = "Rregullimet mbi njoftimet ruhen te llogaria juaj e përdoruesit dhe ndahen me krejt klientët që i mbulojnë ato (përfshi njoftimet në desktop).\n\nRregullat zbatohen sipas një radhe; rregulli i parë që ka përputhje përcakton lëndën për mesazhin.\nKështu: njoftimet sipas fjalësh janë më të rëndësishme se njoftimet sipas dhomash të cilat janë më të rëndësishme se njoftimet sipas dërguesish.\nFor multiple rules of the same kind, the first one in the list that matches takes priority.";
+"notification_settings_per_word_notifications" = "Njoftime sipas fjale";
+"notification_settings_per_word_info" = "Për fjalët përputhjet gjenden pa marrë parasysh shkrimin me të madhe apo të vogël, dhe mund të përfshijnë një shenjë të gjithëpushtetshme *. Kështu:\nkot përputhet me vargun kot të rrethuar nga përkufizues fjalësh (p.sh. shenja pikësimi apo hapësira, ose fillim/fund rreshti).\nkot* përputhet me çfarëdo fjale që fillon me kot.\n*kot* përputhet me çfarëdo fjale që përfshin 3 shkronjat kot.";
+"notification_settings_per_room_notifications" = "Njoftime sipas dhome";
+"notification_settings_per_sender_notifications" = "Njoftime sipas dërguesi";
+"notification_settings_invite_to_a_new_room" = "Njoftomë kur ftohem në një dhomë të re";
+"notification_settings_people_join_leave_rooms" = "Njoftomë kur vijnë ose ikin persona nga dhoma";
+"notification_settings_receive_a_call" = "Njoftomë kur marr një thirrje";
+"call_connecting" = "Po lidhet…";
+"ssl_cert_not_trust" = "Kjo mund të ishte shenjë se dikush po përgjon me dashakeqësi trafikun tuaj, ose se telefoni juaj nuk i beson dëshmisë së furnizuar nga shërbyesi i largët.";
+"ssl_cert_new_account_expl" = "Nëse përgjegjësi i shërbyesit ka thënë se kjo është e pritshme, sigurohuni që shenjat e gishtave më poshtë përputhen me shenjat e gishtave të furnizuara prej tyre.";
+"ssl_unexpected_existing_expl" = "Dëshmia ka ndryshuar nga ajo që qe besuar nga telefoni juaj. Kjo është SHUMË E PAZAKONTË. Këshillohet që TË MOS E PRANONI këtë dëshmi të re.";
+"ssl_expected_existing_expl" = "Dëshmia ka ndryshuar nga një e besueshme dikur në një që nuk besohet. Shërbyesi mund të ketë rinovuar dëshminë e tij. Lidhuni me përgjegjësin e shërbyesit për shenjat e pritshme të gishtave.";
+"ssl_only_accept" = "Pranojeni dëshminë VETËM nëse përgjegjësi i shërbyesit ka publikuar shenja gishtash që përputhen me ato më sipër.";
+"login_error_resource_limit_exceeded_title" = "U tejkalua Kufi Burimesh";
+"login_error_resource_limit_exceeded_message_default" = "Ky shërbyes home ka tejkaluar një nga kufijtë mbi burimet.";
+"login_error_resource_limit_exceeded_message_monthly_active_user" = "Ky shërbyes home ka tejkaluar kufirin Përdorues Aktivë Mujorë.";
+"login_error_resource_limit_exceeded_message_contact" = "\n\nJu lutemi, që të vazhdoni të përdorni këtë shërbim, lidhuni me përgjegjësin e shërbimit tuaj.";
+"login_error_resource_limit_exceeded_contact_button" = "Lidhuni Me Përgjegjësin";
+"room_displayname_two_members" = "%@ dhe %@";
+// Reply to message
+"message_reply_to_sender_sent_an_image" = "dërgoi një figurë.";
+"message_reply_to_sender_sent_a_video" = "dërgoi një video.";
+"message_reply_to_sender_sent_an_audio_file" = "dërgoi një kartelë audio.";
+"message_reply_to_sender_sent_a_file" = "dërgoi një kartelë.";
+"message_reply_to_message_to_reply_to_prefix" = "Në përgjigje të";
+"e2e_passphrase_create" = "Krijoni frazëkalim";
+"room_event_encryption_info_event_fingerprint_key" = "U pretendua për kyç Ed25519 shenjash gishtash\n";
+"notice_feedback" = "Akt dhënieje përshtypjesh (id: %@): %@";
+"account_error_push_not_allowed" = "Nuk lejohen njoftime";
+"notice_room_third_party_revoked_invite" = "%@ shfuqizoi ftesën për pjesëmarrje në dhomë për %@";
+"device_details_rename_prompt_title" = "Emër Sesioni";
+"notice_encryption_enabled_ok" = "%@ aktivizoi fshehtëzimin skaj-më-skaj.";
+"notice_encryption_enabled_unknown_algorithm" = "%1$@ aktivizoi fshehtëzimin skaj-më-skaj (algoritëm që s’është njohur %2$@).";
+// Notice Events with "You"
+"notice_room_invite_by_you" = "Ftuat %@";
+"notice_room_invite_you" = "Ju ftoi %@";
+"notice_room_third_party_invite_by_you" = "I dërguat %@ një ftesë të marrë pjesë te dhoma";
+"notice_room_third_party_registered_invite_by_you" = "Pranuat ftesën nga %@";
+"notice_room_third_party_revoked_invite_by_you" = "Shfuqizuat ftesën për ardhje në dhomë për %@";
+"notice_room_join_by_you" = "Erdhët";
+"notice_room_leave_by_you" = "Ikët";
+"notice_room_reject_by_you" = "Hodhët poshtë ftesën";
+"notice_room_kick_by_you" = "Përzutë %@";
+"notice_room_unban_by_you" = "Hoqët dëbimin për %@";
+"notice_room_ban_by_you" = "Dëbuat %@";
+"notice_room_withdraw_by_you" = "Tërhoqët mbrapsht ftesën për %@";
+"notice_avatar_url_changed_by_you" = "Ndryshuat avatarin tuaj";
+"notice_display_name_set_by_you" = "Caktuat si emrin tuaj në ekran %@";
+"notice_display_name_changed_from_by_you" = "Ndryshuat emrin tuaj në ekran nga %@ në %@";
+"notice_display_name_removed_by_you" = "Hoqët emrin tuaj në ekran";
+"notice_topic_changed_by_you" = "E ndryshuat temën në \"%@\".";
+"notice_room_name_changed_by_you" = "E ndryshuat emrin e dhomës në \"%@\".";
+"notice_placed_voice_call_by_you" = "Bëtë një thirrje zanore";
+"notice_placed_video_call_by_you" = "Bëtë një thirrje video";
+"notice_answered_video_call_by_you" = "Iu përgjigjët thirrjes";
+"notice_ended_video_call_by_you" = "E përfunduat thirrjen";
+"notice_conference_call_request_by_you" = "Kërkuat një konferencë VoIP";
+"notice_room_name_removed_by_you" = "Hoqët emrin e dhomës";
+"notice_room_topic_removed_by_you" = "Hoqët temën";
+"notice_event_redacted_by_you" = " nga ju";
+"notice_profile_change_redacted_by_you" = "Përditësuat profilin tuaj %@";
+"notice_room_created_by_you" = "Krijuat dhe formësuat dhomën.";
+"notice_encryption_enabled_ok_by_you" = "Aktivizuat fshehtëzimin skaj-më-skaj.";
+"notice_encryption_enabled_unknown_algorithm_by_you" = "Aktivizuat fshehtëzim skaj-më-skaj (algoritëm %@ i papranuar).";
+"notice_redaction_by_you" = "Redaktuat një akt (id: %@)";
+"notice_room_history_visible_to_anyone_by_you" = "E bëtë historikun e ardhshëm të dhomës të dukshëm për këdo.";
+"notice_room_history_visible_to_members_by_you" = "E bëtë historikun e ardhshëm të dhomës të dukshëm për krejt anëtarët e dhomës.";
+"notice_room_history_visible_to_members_from_invited_point_by_you" = "E bëtë historikun e ardhshëm të dhomës të dukshëm për krejt anëtarët e dhomës, nga çasti që janë ftuar.";
+"notice_room_history_visible_to_members_from_joined_point_by_you" = "E bëtë historikun e ardhshëm të dhomës të dukshëm për krejt anëtarët e dhomës, që nga çasti që bëhen pjesë e dhomës.";
+// New
+"notice_room_join_rule_invite" = "%@ e bëri dhomën vetëm me ftesa.";
+"notice_room_join_rule_invite_by_you" = "E bëtë dhomën vetëm me ftesa.";
+"notice_room_join_rule_public" = "%@ e bëri dhomën publike.";
+"notice_room_join_rule_public_by_you" = "E bëtë dhomën publike.";
+"notice_room_name_removed_for_dm" = "%@ hoqi emrin";
+"notice_room_created_for_dm" = "%@ hyri.";
+"notice_room_join_rule_invite_for_dm" = "%@ e bëri këtë “vetëm me ftesa”.";
+"notice_room_join_rule_invite_by_you_for_dm" = "E bëtë këtë “vetëm me ftesa”.";
+"notice_room_join_rule_public_for_dm" = "%@ e bëri këtë publike.";
+"notice_room_join_rule_public_by_you_for_dm" = "E bëtë këtë publike.";
+"notice_room_power_level_intro_for_dm" = "Shkallë pushteti që kanë anëtarët:";
+"notice_room_aliases_for_dm" = "Aliaset janë: %@";
+"notice_room_history_visible_to_members_for_dm" = "%@ i bëri mesazhet e ardhshëm të dukshëm për krejt anëtarët e dhomës.";
+"notice_room_history_visible_to_members_from_invited_point_for_dm" = "%@ i bëri mesazhet e ardhshëm të dukshëm për këdo, që nga çasti që ftohen.";
+"notice_room_history_visible_to_members_from_joined_point_for_dm" = "%@ i bëri mesazhet e ardhshëm të dukshëm për këdo, që nga çasti bëhen pjesë e bisedës.";
+"room_left_for_dm" = "Dolët";
+"notice_room_third_party_invite_for_dm" = "%@ ftoi %@";
+"notice_room_third_party_revoked_invite_for_dm" = "%@ shfuqizoi ftesën për %@";
+"notice_room_name_changed_for_dm" = "%@ ndryshoi emrin në %@.";
+"notice_room_third_party_invite_by_you_for_dm" = "Ftuat %@";
+"notice_room_third_party_revoked_invite_by_you_for_dm" = "Shfuqizuar ftesën për %@";
+"notice_room_name_changed_by_you_for_dm" = "Ndryshuat emrin në %@.";
+"notice_room_name_removed_by_you_for_dm" = "Hoqët emrin";
+"notice_room_created_by_you_for_dm" = "Hytë.";
+"notice_room_history_visible_to_members_by_you_for_dm" = "I bëtë mesazhet e ardhshëm të dukshëm për krejt anëtarët e dhomës.";
+"notice_room_history_visible_to_members_from_invited_point_by_you_for_dm" = "I bëtë mesazhet e ardhshëm të dukshëm për këdo, nga çasti që ftohen.";
+"notice_room_history_visible_to_members_from_joined_point_by_you_for_dm" = "I bëtë mesazhet e ardhshëm të dukshëm për këdo, nga çasti që hyjnë në bisedë.";
+"resume_call" = "Rimerre";
+"call_holded" = "E mbajtët pezull thirrjen";
+"notice_declined_video_call" = "%@ e hodhi poshtë thirrjen";
+"notice_declined_video_call_by_you" = "E hodhët poshtë thirrjen";
+"call_remote_holded" = "%@ e mbajti pezull thirrjen";
+"call_more_actions_hold" = "Mbaje pezull";
+"call_more_actions_unhold" = "Rimerre";
+"call_more_actions_change_audio_device" = "Ndryshoni Pajisje Audio";
+"call_more_actions_audio_use_headset" = "Përdorni Kufje dhe Mikrofon";
+"call_more_actions_audio_use_device" = "Altoparlant Pajisjeje";
+"call_more_actions_transfer" = "Shpërngule";
+"call_more_actions_dialpad" = "Tastierë numerike";
+"call_transfer_to_user" = "Shpërngulje te %@";
+"call_consulting_with_user" = "Konsultim me %@";
+"call_video_with_user" = "Thirrje me video me %@";
+"call_voice_with_user" = "Thirrje me zë me %@";
+"call_ringing" = "Po i bihet ziles…";
+"e2e_passphrase_too_short" = "Frazëkalim shumë i shkurtër (Duhet të jetë e pakta %d shenja i gjatë)";
+"microphone_access_not_granted_for_voice_message" = "Mesazhet zanorë lypin përdorim të Mikrofonit, por %@ s’ka leje përdorimi të tij";
+"message_reply_to_sender_sent_a_voice_message" = "dërgoi një mesazh zanor.";
+"attachment_large_with_resolution" = "E madhe %@ (~%@)";
+"attachment_medium_with_resolution" = "Mesatare %@ (~%@)";
+"attachment_small_with_resolution" = "E vogël %@ (~%@)";
+"attachment_size_prompt_message" = "Këtë mund ta çaktivizoni te rregullimet.";
+"attachment_size_prompt_title" = "Ripohoni madhësi për dërgim";
+"auth_reset_password_error_not_found" = "S’u gjet";
+"auth_reset_password_error_unauthorized" = "I paautorizuar";
+"auth_username_in_use" = "Emër përdoruesi i përdorur";
+"auth_invalid_user_name" = "Emër i pavlefshëm përdoruesi";
+"rename" = "Riemërtojeni";
diff --git a/Riot/Modules/MatrixKit/Assets/MatrixKitAssets.bundle/sv.lproj/MatrixKit.strings b/Riot/Modules/MatrixKit/Assets/MatrixKitAssets.bundle/sv.lproj/MatrixKit.strings
new file mode 100644
index 000000000..317d36810
--- /dev/null
+++ b/Riot/Modules/MatrixKit/Assets/MatrixKitAssets.bundle/sv.lproj/MatrixKit.strings
@@ -0,0 +1,472 @@
+"matrix" = "Matrix";
+// Login Screen
+"login_create_account" = "Skapa konto:";
+"login_server_url_placeholder" = "URL (t.ex. https://matrix.org)";
+"login_home_server_title" = "Hemserver-URL:";
+"login_home_server_info" = "Din hemserver lagrar alla dina konversationer och din kontodata";
+"login_identity_server_title" = "Identitetsserver-URL:";
+"login_identity_server_info" = "Matrix tillhandahåller identitetsservrar för att spåra vilka e-postadresser o.s.v. som tillhör vilka Matrix-ID:n. Endast https://matrix.org finns för närvarande.";
+"login_user_id_placeholder" = "Matrix-ID (t.ex. @bob:matrix.org eller bob)";
+"login_password_placeholder" = "Lösenord";
+"login_optional_field" = "valfritt";
+"login_display_name_placeholder" = "Visningsnamn (t.ex. Sven Svensson)";
+"login_email_info" = "Genom att ange en e-postadress kan andra användare hitta dig på Matrix lättare, och det ger dig ett sätt att återställa ditt lösenord i framtiden.";
+"login_email_placeholder" = "E-postadress";
+"login_prompt_email_token" = "Ange din e-postvalideringstoken:";
+"login_error_title" = "Inloggning misslyckades";
+"login_error_no_login_flow" = "Vi misslyckades att hämta autentiseringsinformation från den här hemservern";
+"login_error_do_not_support_login_flows" = "För närvarande stöder vi inte några eller alla inloggningsflöden som har definierats av den här hemservern";
+"login_error_registration_is_not_supported" = "Registrering stöds inte för närvarande";
+"login_error_forbidden" = "Ogiltigt användarnamn eller lösenord";
+"login_error_unknown_token" = "Den åtkomsttoken som specificerades kändes inte igen";
+"login_error_bad_json" = "Felformaterad JSON";
+"login_error_not_json" = "Innehöll inte giltig JSON";
+"login_error_limit_exceeded" = "För många förfrågningar har skickats";
+"login_error_user_in_use" = "Det här användarnamnet har redan använts";
+"login_error_login_email_not_yet" = "E-postlänken har inte klickats än";
+"login_use_fallback" = "Använd reservsida";
+"login_leave_fallback" = "Avbryt";
+"login_invalid_param" = "Ogiltig parameter";
+"register_error_title" = "Registrering misslyckades";
+"login_error_forgot_password_is_not_supported" = "Lösenordsåterställning stöds ännu inte";
+"login_mobile_device" = "Mobil";
+"login_tablet_device" = "Surfplatta";
+"login_desktop_device" = "Skrivbord";
+"login_error_resource_limit_exceeded_title" = "Resursgräns överskriden";
+"login_error_resource_limit_exceeded_message_default" = "Hemservern har överskridit en av sina resursgränser.";
+"login_error_resource_limit_exceeded_message_monthly_active_user" = "Den här hemservern har nått sin gräns för aktiva användare per månad.";
+"login_error_resource_limit_exceeded_message_contact" = "\n\nVänligen kontakta din tjänstadministratör för att fortsätta använda den här tjänsten.";
+"login_error_resource_limit_exceeded_contact_button" = "Kontakta administratör";
+// Action
+"no" = "Nej";
+"yes" = "Ja";
+"abort" = "Avbryt";
+"back" = "Tillbaka";
+"close" = "Stäng";
+"continue" = "Fortsätt";
+"discard" = "Släng";
+"dismiss" = "Avvisa";
+"retry" = "Försök igen";
+"sign_up" = "Bli medlem";
+"submit" = "Skicka";
+"submit_code" = "Skicka kod";
+"set_power_level" = "Sätt behörighetsnivå";
+"set_default_power_level" = "Återställ behörighetsnivå";
+"set_moderator" = "Sätt till moderator";
+"set_admin" = "Sätt till administratör";
+"start_chat" = "Starta chatt";
+"start_voice_call" = "Starta röstsamtal";
+"start_video_call" = "Starta videosamtal";
+"mention" = "Nämn";
+"select_account" = "Välj ett konto";
+"attach_media" = "Bifoga media från biblioteket";
+"capture_media" = "Ta bild/video";
+"invite_user" = "Bjud in Matrixanvändare";
+"reset_to_default" = "Återställ till standard";
+"resend_message" = "Skicka meddelandet igen";
+"select_all" = "Välj allt";
+"cancel_upload" = "Avbryt uppladdning";
+"cancel_download" = "Avbryt nedladdning";
+"show_details" = "Visa detaljer";
+"answer_call" = "Svara på samtal";
+"reject_call" = "Avvisa samtal";
+"end_call" = "Avsluta samtal";
+"ignore" = "Ignorera";
+"unignore" = "Avignorera";
+// Events formatter
+"notice_avatar_changed_too" = "(avataren ändrades också)";
+"notice_room_name_removed" = "%@ tog bort rumsnamnet";
+"notice_room_topic_removed" = "%@ tog bort ämnet";
+"notice_event_redacted" = "";
+"notice_event_redacted_by" = " av %@";
+"notice_event_redacted_reason" = " [anledning: %@]";
+"notice_profile_change_redacted" = "%@ uppdaterade sin profil %@";
+"notice_room_created" = "%@ skapade och konfigurerade rummet.";
+"notice_room_join_rule" = "Regeln för att gå med är: %@";
+"notice_room_power_level_intro" = "Behörighetsnivå för rumsmedlemmar är:";
+"notice_room_power_level_acting_requirement" = "Den minimala behörighetsnivån en användare behöver har innan den kan göra något är:";
+"notice_room_power_level_event_requirement" = "Den minimala behörighetsnivån relaterad till händelser är:";
+"notice_room_aliases" = "Rumsaliasen är: %@";
+"notice_room_related_groups" = "Grupperna associerade med det här rummet är: %@";
+"notice_encrypted_message" = "Krypterat meddelande";
+"notice_encryption_enabled_ok" = "%@ aktiverade totalsträckskryptering.";
+"notice_encryption_enabled_unknown_algorithm" = "%1$@ aktiverade totalsträckskryptering (okänd algoritm %2$@).";
+"notice_image_attachment" = "bildbilaga";
+"notice_audio_attachment" = "ljudbilaga";
+"notice_video_attachment" = "videobilaga";
+"notice_location_attachment" = "positionsbilaga";
+"notice_file_attachment" = "filbilaga";
+"notice_invalid_attachment" = "ogiltig bilaga";
+"notice_unsupported_attachment" = "Ostödd bilaga: %@";
+"notice_feedback" = "Återkopplingshändelse (id: %@): %@";
+"notice_redaction" = "%@ dolde en händelse (id: %@)";
+"notice_error_unsupported_event" = "Ostödd händelse";
+"notice_error_unexpected_event" = "Oväntad händelse";
+"notice_error_unknown_event_type" = "Okänd händelsetyp";
+"notice_room_history_visible_to_anyone" = "%@ gjorde framtida rumshistorik synlig för alla.";
+"notice_room_history_visible_to_members" = "%@ gjorde framtida rumshistorik synlig för alla rumsmedlemmar.";
+"notice_room_history_visible_to_members_from_invited_point" = "%@ gjorde framtida rumshistorik synlig för alla rumsmedlemmar från när de bjöds in.";
+"notice_room_history_visible_to_members_from_joined_point" = "%@ gjorde framtida rumshistorik synlig för alla rumsmedlemmar från när de gick med.";
+"notice_crypto_unable_to_decrypt" = "** Kunde inte avkryptera: %@ **";
+"notice_crypto_error_unknown_inbound_session_id" = "Avsändarens session har inte gett oss nycklarna för det här meddelandet.";
+"notice_sticker" = "dekal";
+"notice_in_reply_to" = "Svar på";
+// room display name
+"room_displayname_empty_room" = "Tomt rum";
+"room_displayname_two_members" = "%@ och %@";
+"room_displayname_more_than_two_members" = "%@ och %@ till";
+// Settings
+"settings" = "Inställningar";
+"settings_enable_inapp_notifications" = "Aktivera aviseringar i appen";
+"settings_enable_push_notifications" = "Aktivera pushnotiser";
+"settings_enter_validation_token_for" = "Ange valideringstoken för &@:";
+"notification_settings_room_rule_title" = "Rum: '%@'";
+// Devices
+"device_details_title" = "Sessionsinformation\n";
+"device_details_name" = "Offentligt namn\n";
+"device_details_identifier" = "ID\n";
+"device_details_last_seen" = "Senast sedd\n";
+"device_details_last_seen_format" = "%@ @ %@\n";
+"device_details_rename_prompt_title" = "Sessionsnamn";
+"device_details_rename_prompt_message" = "En sessions offentliga namn är synligt för personer du kommunicerar med";
+"device_details_delete_prompt_title" = "Autentisering";
+"device_details_delete_prompt_message" = "Den här handlingen kräver ytterligare autentisering.\nFör att fortsätta, vänligen ange ditt lösenord.";
+// Encryption information
+"room_event_encryption_info_title" = "Totalsträckskrypteringsinformation\n\n";
+"room_event_encryption_info_event" = "Händelseinformation\n";
+"room_event_encryption_info_event_user_id" = "Användar-ID\n";
+"room_event_encryption_info_event_identity_key" = "Curve25519-identitetsnyckel\n";
+"room_event_encryption_info_event_fingerprint_key" = "Hävdad Ed25519-fingeravtrycksnyckel\n";
+"room_event_encryption_info_event_algorithm" = "Algoritm\n";
+"room_event_encryption_info_event_session_id" = "Sessions-ID\n";
+"room_event_encryption_info_event_decryption_error" = "Avkrypteringsfel\n";
+"room_event_encryption_info_event_unencrypted" = "okrypterad";
+"room_event_encryption_info_event_none" = "ingen";
+"room_event_encryption_info_device" = "\nAvsändarens sessionsinformation\n";
+"room_event_encryption_info_device_unknown" = "Okänd session\n";
+"room_event_encryption_info_device_name" = "Offentligt namn\n";
+"room_event_encryption_info_device_id" = "ID\n";
+"room_event_encryption_info_device_verification" = "Verifiering\n";
+"room_event_encryption_info_device_fingerprint" = "Ed25519-fingeravtryck\n";
+"room_event_encryption_info_device_verified" = "Verifierad";
+"room_event_encryption_info_device_not_verified" = "INTE verifierad";
+"room_event_encryption_info_device_blocked" = "Svartlistad";
+"room_event_encryption_info_verify" = "Verifiera…";
+"room_event_encryption_info_unverify" = "Avverifiera";
+"room_event_encryption_info_block" = "Svartlista";
+"room_event_encryption_info_unblock" = "Avsvartlista";
+"room_event_encryption_verify_title" = "Verifiera session\n\n";
+"room_event_encryption_verify_message" = "För att verifiera att denna session går att lita på, vänligen kontakta ägaren på annat sätt (t.ex. personligen eller med ett telefonsamtal) och fråga dem om nyckeln de ser i sina användarinställningar för den här sessionen matchar nyckeln nedan:\n\n\tSessionsnamn: %@\n\tSessions-ID: %@\n\tSessionsnyckel: %@\n\nOm de matchar, tryck på verifieringsknappen nedan. Om de inte gör det så betyder det att någon annan snappar upp denna session och du vill antagligen trycka på svartlistknappen istället.\n\nI framtiden kommer denna verifieringsprocess att bli mer sofistikerad.";
+"room_event_encryption_verify_ok" = "Verifiera";
+// Account
+"account_save_changes" = "Spara ändringar";
+"account_link_email" = "Länka e-post";
+"account_linked_emails" = "Länkade e-postadresser";
+"account_email_validation_title" = "Avvaktar verifiering";
+"account_email_validation_message" = "Vänligen kolla din e-post och klicka på länken den innehåller. När detta är klart, klicka fortsätt.";
+"account_email_validation_error" = "Kunde inte verifiera e-postadressen. Kontrollera din e-post och klicka på länken den innehåller. När detta är klart, klicka på fortsätt";
+"account_msisdn_validation_title" = "Avvaktar verifiering";
+"account_msisdn_validation_message" = "Vi har skickat ett SMS med en aktiveringskod. Ange den koden nedan.";
+"account_msisdn_validation_error" = "Kunde inte verifiera telefonnummer.";
+"account_error_display_name_change_failed" = "Byte av visningsnamn misslyckades";
+"account_error_picture_change_failed" = "Byte av bild misslyckades";
+"account_error_matrix_session_is_not_opened" = "Matrixsession har inte öppnats";
+"account_error_email_wrong_title" = "Ogiltig e-postadress";
+"account_error_email_wrong_description" = "Det här ser inte ut som en giltig e-postadress";
+"account_error_msisdn_wrong_title" = "Ogiltigt telefonnummer";
+"account_error_msisdn_wrong_description" = "Det här ser inte ut som ett giltigt telefonnummer";
+"account_error_push_not_allowed" = "Aviseringar tillåts inte";
+// Room creation
+"room_creation_name_title" = "Rumsnamn:";
+"room_creation_name_placeholder" = "(t.ex. lunchgrupp)";
+"room_creation_alias_title" = "Rumsalias:";
+"room_creation_alias_placeholder" = "(t.ex. #foo:exempel.org)";
+"room_creation_alias_placeholder_with_homeserver" = "(t.ex. #foo%@)";
+"room_creation_participants_title" = "Deltagare:";
+"room_creation_participants_placeholder" = "(t.ex. @sven:hemserver1; @anna:hemserver2…)";
+// Room
+"room_please_select" = "Vänligen välj ett rum";
+"room_error_join_failed_title" = "Misslyckades att gå med i rum";
+"room_error_join_failed_empty_room" = "Det går för närvarande inte att gå med i ett tomt rum.";
+"room_error_name_edition_not_authorized" = "Du är inte auktoriserad att redigera det här rummets namn";
+"room_error_topic_edition_not_authorized" = "Du är inte auktoriserad att ändra det här rummets ämne";
+"room_error_cannot_load_timeline" = "Misslyckades att ladda tidslinjen";
+"room_error_timeline_event_not_found_title" = "Misslyckades att ladda position på tidslinjen";
+"room_error_timeline_event_not_found" = "Appen försökte ladda en viss punkt i detta rums tidslinje men kunde inte hitta den";
+"room_left" = "Du lämnade rummet";
+"room_no_power_to_create_conference_call" = "Du behöver behörighet att bjuda in personer för att starta ett gruppsamtal i det här rummet";
+"room_no_conference_call_in_encrypted_rooms" = "Gruppsamtal stöds inte i krypterade rum";
+// Reply to message
+"message_reply_to_sender_sent_an_image" = "skickade en bild.";
+"message_reply_to_sender_sent_a_video" = "skickade en video.";
+"message_reply_to_sender_sent_an_audio_file" = "skickade en ljudfil.";
+"message_reply_to_sender_sent_a_file" = "skickade en fil.";
+"message_reply_to_message_to_reply_to_prefix" = "Svar på";
+// Room members
+"room_member_ignore_prompt" = "Är du säker på att du vill dölja alla meddelande från den här användaren?";
+"room_member_power_level_prompt" = "Du kommer inte att kunna ångra denna ändring eftersom du befordrar användaren till samma behörighetsnivå som dig själv.\nÄr du säker?";
+// Attachment
+"attachment_size_prompt" = "Vill du skicka som:";
+"attachment_original" = "Faktisk storlek (%@)";
+"attachment_small" = "Liten (%@)";
+"attachment_medium" = "Mellan (%@)";
+"attachment_large" = "Stor (%@)";
+"attachment_cancel_download" = "Avbryt nedladdningen?";
+"attachment_cancel_upload" = "Avbryt uppladdningen?";
+"attachment_multiselection_size_prompt" = "Vill du skicka bilder som:";
+"attachment_multiselection_original" = "Faktisk storlek";
+"attachment_e2e_keys_file_prompt" = "Den här filen innehåller krypteringsnycklar som har exporteras från en Matrixklient.\nVill du visa filinnehållet eller importera nycklarna som den innehåller?";
+"attachment_e2e_keys_import" = "Importera…";
+// Contacts
+"contact_mx_users" = "Matrixanvändare";
+"contact_local_contacts" = "Lokala kontakter";
+// Groups
+"group_invite_section" = "Inbjudningar";
+"group_section" = "Grupper";
+// Search
+"search_no_results" = "Inga resultat";
+"search_searching" = "Sökning pågår…";
+// Time
+"format_time_s" = "s";
+"format_time_m" = "m";
+"format_time_h" = "t";
+"format_time_d" = "d";
+// E2E import
+"e2e_import_room_keys" = "Importera rumsnycklar";
+"e2e_import_prompt" = "Denna process låter dig importera krypteringsnycklar som du tidigare har exporterat från en annan Matrixklient. Du kommer då kunna avkryptera alla meddelanden som den andra klienten kan avkryptera.\nExportfilen är skyddad med en lösenfras. Du bör ange lösenfrasen här för att avkryptera filen.";
+"e2e_import" = "Importera";
+"e2e_passphrase_enter" = "Ange lösenfras";
+// E2E export
+"e2e_export_room_keys" = "Exportera rumsnycklar";
+"e2e_export_prompt" = "Denna process låter dig exportera nycklarna för meddelanden som du har fått i krypterade rum till en lokal fil. Du kommer då att kunna importera filen till en annan Matrixklient i framtiden, så att klienten också kan avkryptera dessa meddelanden.\nDen exporterade filen tillåter alla som kan läsa den att avkryptera alla krypterade meddelanden som du kan se, så du bör vara noga med att hålla den säker.";
+"e2e_export" = "Exportera";
+"e2e_passphrase_confirm" = "Bekräfta lösenfras";
+"e2e_passphrase_empty" = "Lösenfrasen får inte var tom";
+"e2e_passphrase_not_match" = "Lösenfraserna måste matcha";
+"e2e_passphrase_create" = "Skapa lösenfras";
+// Others
+"user_id_title" = "Användar-ID:";
+"offline" = "offline";
+"unsent" = "Oskickad";
+"error" = "Fel";
+"error_common_message" = "Ett fel inträffade. Försök igen senare.";
+"not_supported_yet" = "Stöds inte än";
+"default" = "förval";
+"private" = "Privat";
+"public" = "Offentlig";
+"power_level" = "Behörighetsnivå";
+"network_error_not_reachable" = "Vänligen kolla din nätverksuppkoppling";
+"user_id_placeholder" = "t.ex.: @sven:hemserver";
+"ssl_homeserver_url" = "Hemserver-URL: %@";
+// Permissions
+"camera_access_not_granted_for_call" = "Videosamtal kräver åtkomst till kameran men %@ har inte behörighet att använda den";
+"microphone_access_not_granted_for_call" = "Samtal kräver åtkomst till mikrofonen men %@ har inte behörighet att använda den";
+"local_contacts_access_not_granted" = "Upptäckt av användare från lokala kontakter kräver åtkomst till dina kontakter men %@ har inte behörighet att komma åt dem";
+"local_contacts_access_discovery_warning_title" = "Användarupptäckt";
+"local_contacts_access_discovery_warning" = "För att upptäcka kontakter som redan använder Matrix kan %@ skicka e-postadresser och telefonnummer i din adressbok till din valda Matrixidentitetsserver. Där det stöds hashas personuppgifter innan de skickas - kontrollera din identitetsservers integritetspolicy för mer information.";
+// Country picker
+"country_picker_title" = "Välj ett land";
+// Language picker
+"language_picker_title" = "Välj ett språk";
+"language_picker_default_language" = "Förval (%@)";
+"notice_room_invite" = "%@ bjöd in %@";
+"notice_room_third_party_invite" = "%@ skickade bjöd in %@ att gå med i rummet";
+"notice_room_third_party_registered_invite" = "%@ accepterade inbjudan för %@";
+"notice_room_third_party_revoked_invite" = "%@ drog tillbaka inbjudan för %@ att gå med i rummet";
+"notice_room_join" = "%@ gick med";
+"notice_room_leave" = "%@ lämnade";
+"notice_room_reject" = "%@ avvisade inbjudan";
+"notice_room_kick" = "%@ kickade %@";
+"notice_room_unban" = "%@ avbannade %@";
+"notice_room_ban" = "%@ bannade %@";
+"notice_room_withdraw" = "%@ drog tillbaka inbjudan för %@";
+"notice_room_reason" = ". Anledning: %@";
+"notice_avatar_url_changed" = "%@ bytte sin avatar";
+"notice_display_name_set" = "%@ satte sitt visningsnamn till %@";
+"notice_display_name_changed_from" = "%@ bytte sitt visningsnamn från %@ till %@";
+"notice_display_name_removed" = "%@ tog bort sitt visningsnamn";
+"notice_topic_changed" = "%@ bytte ämnet till \"%@\".";
+"notice_room_name_changed" = "%@ bytte rummets namn till %@.";
+"notice_placed_voice_call" = "%@ startade ett röstsamtal";
+"notice_placed_video_call" = "%@ startade ett videosamtal";
+"notice_answered_video_call" = "%@ svarade på samtalet";
+"notice_ended_video_call" = "%@ avslutade samtalet";
+"notice_conference_call_request" = "%@ begärde ett VoIP-gruppsamtal";
+"notice_conference_call_started" = "VoIP-gruppsamtal startat";
+"notice_conference_call_finished" = "VoIP-gruppsamtal avslutat";
+// Notice Events with "You"
+"notice_room_invite_by_you" = "Du bjöd in %@";
+"notice_room_invite_you" = "%@ bjöd in dig";
+"notice_room_third_party_invite_by_you" = "Du bjöd in %@ att gå med i rummet";
+"notice_room_third_party_registered_invite_by_you" = "Du accepterade inbjudan för %@";
+"notice_room_third_party_revoked_invite_by_you" = "Du drog tillbaka inbjudan för %@ att gå med i rummet";
+"notice_room_join_by_you" = "Du gick med";
+"notice_room_leave_by_you" = "Du lämnade";
+"notice_room_reject_by_you" = "Du avvisade inbjudan";
+"notice_room_kick_by_you" = "Du kickade %@";
+"notice_room_unban_by_you" = "Du avbannade %@";
+"notice_room_ban_by_you" = "Du bannade %@";
+"notice_room_withdraw_by_you" = "Du drog tillbaka inbjudan för %@";
+"notice_avatar_url_changed_by_you" = "Du ändrade din avatar";
+"notice_display_name_set_by_you" = "Du bytte ditt visningsnamn till %@";
+"notice_display_name_changed_from_by_you" = "Du bytte ditt visningsnamn från %@ till %@";
+"notice_display_name_removed_by_you" = "Du tog bort ditt visningsnamn";
+"notice_topic_changed_by_you" = "Du bytte ämnet till \"%@\".";
+"notice_room_name_changed_by_you" = "Du bytte rummets namn till %@.";
+"notice_placed_voice_call_by_you" = "Du startade ett röstsamtal";
+"notice_placed_video_call_by_you" = "Du startade ett videosamtal";
+"notice_answered_video_call_by_you" = "Du svarade på samtalet";
+"notice_ended_video_call_by_you" = "Du avslutade samtalet";
+"notice_conference_call_request_by_you" = "Du begärde ett VoIP-gruppsamtal";
+"notice_room_name_removed_by_you" = "Du tog bort rummets namn";
+"notice_room_topic_removed_by_you" = "Du tog bort ämnet";
+"notice_event_redacted_by_you" = " av dig";
+"notice_profile_change_redacted_by_you" = "Du uppdaterade din profil %@";
+"notice_room_created_by_you" = "Du skapade och konfigurerade rummet.";
+"notice_encryption_enabled_ok_by_you" = "Du aktiverade totalsträckskryptering.";
+"notice_encryption_enabled_unknown_algorithm_by_you" = "Du aktiverade totalsträckskryptering (okänd algoritm %@).";
+"notice_redaction_by_you" = "Du dolde en händelse (id: %@)";
+"notice_room_history_visible_to_anyone_by_you" = "Du gjorde framtida rumshistorik synlig för alla.";
+"notice_room_history_visible_to_members_by_you" = "Du gjorde framtida rumshistorik synlig för alla rumsmedlemmar.";
+"notice_room_history_visible_to_members_from_invited_point_by_you" = "Du gjorde framtida rumshistorik synlig för alla rumsmedlemmar från när de bjöds in.";
+"notice_room_history_visible_to_members_from_joined_point_by_you" = "Du gjorde framtida rumshistorik synlig för alla rumsmedlemmar från när de gick med.";
+// button names
+"ok" = "OK";
+"cancel" = "Avbryt";
+"save" = "Spara";
+"leave" = "Lämna";
+"send" = "Skicka";
+"copy_button_name" = "Kopiera";
+"resend" = "Skicka igen";
+"redact" = "Ta bort";
+"share" = "Dela";
+"delete" = "Radera";
+"view" = "Visa";
+// actions
+"action_logout" = "Logga ut";
+"create_room" = "Skapa rum";
+"login" = "Logga in";
+"create_account" = "Skapa konto";
+"membership_invite" = "Inbjuden";
+"membership_leave" = "Lämnade";
+"membership_ban" = "Bannade";
+"num_members_one" = "%@ användare";
+"num_members_other" = "%@ användare";
+"invite" = "Bjud in";
+"kick" = "Kicka";
+"ban" = "Banna";
+"unban" = "Avbanna";
+"message_unsaved_changes" = "Det finns osparade ändringar. Att lämna kommer att slänga dem.";
+// Login Screen
+"login_error_already_logged_in" = "Redan inloggad";
+"login_error_must_start_http" = "URL:en måste börja på http[s]://";
+// room details dialog screen
+"room_details_title" = "Rumsdetaljer";
+// contacts list screen
+"invitation_message" = "Jag vill chatta med dig på Matrix. Besök sidan https://matrix.org för mer information.";
+// Settings screen
+"settings_title_config" = "Konfiguration";
+"settings_title_notifications" = "Aviseringar";
+// Notification settings screen
+"notification_settings_disable_all" = "Inaktivera alla aviseringar";
+"notification_settings_enable_notifications" = "Aktivera aviseringar";
+"notification_settings_enable_notifications_warning" = "Alla aviseringar är för närvarande inaktiverade för alla enheter.";
+"notification_settings_global_info" = "Aviseringsinställningar sparas i ditt användarkonto och delas mellan alla klienter som stöder dem (inklusive skrivbordsaviseringar).\n\nRegler tillämpas i ordning; den första regeln som matchar definierar resultatet för meddelandet.\nSå: Aviseringar per ord är viktigare än aviseringar per rum som är viktigare än aviseringar per avsändare.\nFör flera regler av samma slag prioriteras den första i listan som matchar.";
+"notification_settings_per_word_notifications" = "Aviseringar per ord";
+"notification_settings_per_word_info" = "Matchning av ord är inte skiftlägeskänsligt, och kan innehålla ett jokertecken (*). Så:\nfoo matchar strängen foo omgiven av ordavgränsare (t.ex. skiljetecken och mellanslag eller start/slut på rad).\nfoo* matchar alla ord som börjar foo.\n*foo* matchar alla ord som innehåller de tre bokstäverna foo.";
+"notification_settings_always_notify" = "Avisera alltid";
+"notification_settings_never_notify" = "Avisera aldrig";
+"notification_settings_word_to_match" = "ord att matcha";
+"notification_settings_highlight" = "Markera";
+"notification_settings_custom_sound" = "Anpassade ljud";
+"notification_settings_per_room_notifications" = "Aviseringar per rum";
+"notification_settings_per_sender_notifications" = "Aviseringar per avsändare";
+"notification_settings_sender_hint" = "@användare:domän.com";
+"notification_settings_select_room" = "Välj ett rum";
+"notification_settings_other_alerts" = "Andra larm";
+"notification_settings_contain_my_user_name" = "Avisera mig med ett ljud om meddelande som innehåller mitt användarnamn";
+"notification_settings_contain_my_display_name" = "Avisera mig med ett ljud om meddelande som innehåller mitt visningsnamn";
+"notification_settings_just_sent_to_me" = "Avisera med ett ljud om meddelanden skickade till bara mig";
+"notification_settings_invite_to_a_new_room" = "Avisera mig när jag bjuds in till ett nytt rum";
+"notification_settings_people_join_leave_rooms" = "Avisera mig när personer går med i eller lämnar rum";
+"notification_settings_receive_a_call" = "Avisera mig när jag får ett samtal";
+"notification_settings_suppress_from_bots" = "Dämpa aviseringar från bottar";
+"notification_settings_by_default" = "Som förval…";
+"notification_settings_notify_all_other" = "Avisera för alla andra meddelanden/rum";
+// gcm section
+"settings_config_home_server" = "Hemserver: %@";
+"settings_config_identity_server" = "Identitetsserver: %@";
+"settings_config_user_id" = "Användar-ID: %@";
+// call string
+"call_waiting" = "Väntar…";
+"call_connecting" = "Ansluter…";
+"call_ended" = "Samtal avslutat";
+"call_ring" = "Ringer…";
+"incoming_video_call" = "Inkommande videosamtal";
+"incoming_voice_call" = "Inkommande röstsamtal";
+"call_invite_expired" = "Samtalsinbjudan har löpt ut";
+// unrecognized SSL certificate
+"ssl_trust" = "Lita";
+"ssl_logout_account" = "Logga ut";
+"ssl_remain_offline" = "Ignorera";
+"ssl_fingerprint_hash" = "Fingeravtryck (%@):";
+"ssl_could_not_verify" = "Kunde inte verifiera fjärrserverns identitet.";
+"ssl_cert_not_trust" = "Det kan betyda att någon genskjuter din trafik eller att din telefon inte litar på certifikatet från fjärrservern.";
+"ssl_cert_new_account_expl" = "Om serveradministratören har sagt att detta förväntas, kolla att fingeravtrycket nedan matchar det fingeravtryck som de tillhandahåller.";
+"ssl_unexpected_existing_expl" = "Certifikatet har ändrats från ett som din telefon litade på. Detta är MYCKET OVANLIGT. Det rekommenderas att du INTE ACCEPTERAR detta nya certifikat.";
+"ssl_expected_existing_expl" = "Certifikatet har ändrats från ett som din telefon litade på till ett som inte är betrott. Servern kan ha förnyat sitt certifikat. Kontakta serveradministratören för det förväntade fingeravtrycket.";
+"ssl_only_accept" = "Acceptera ENDAST certifikatet om serveradministratören har publicerat ett fingeravtryck som matchar ovanstående.";
+"notice_room_name_removed_for_dm" = "%@ tog bort namnet";
+"notice_room_created_for_dm" = "%@ gick med.";
+// New
+"notice_room_join_rule_invite" = "%@ ändrade rummet till endast inbjudna.";
+"notice_room_join_rule_invite_for_dm" = "%@ ändrade detta till endast inbjudna.";
+"notice_room_join_rule_invite_by_you" = "Du ändrade rummet till endast inbjudna.";
+"notice_room_join_rule_invite_by_you_for_dm" = "Du ändrade detta till endast inbjudna.";
+"notice_room_join_rule_public" = "%@ gjorde rummet offentligt.";
+"notice_room_join_rule_public_for_dm" = "%@ gjorde detta offentligt.";
+"notice_room_join_rule_public_by_you" = "Du gjorde rummet offentligt.";
+"notice_room_join_rule_public_by_you_for_dm" = "Du gjorde detta offentligt.";
+"notice_room_power_level_intro_for_dm" = "Behörighetsnivå för medlemmar är:";
+"notice_room_aliases_for_dm" = "Aliasen är: %@";
+"notice_room_history_visible_to_members_for_dm" = "%@ gjorde framtida meddelanden synliga för alla rumsmedlemmar.";
+"notice_room_history_visible_to_members_from_invited_point_for_dm" = "%@ gjorde framtida meddelanden synliga för alla från när de bjöds in.";
+"notice_room_history_visible_to_members_from_joined_point_for_dm" = "%@ gjorde framtida meddelanden synliga för alla från när de gick med.";
+"room_left_for_dm" = "Du lämnade";
+"notice_room_third_party_invite_for_dm" = "%@ bjöd in %@";
+"notice_room_third_party_revoked_invite_for_dm" = "%@ drog tillbaka inbjudan för %@";
+"notice_room_name_changed_for_dm" = "%@ bytte namnet till %@.";
+"notice_room_third_party_invite_by_you_for_dm" = "Du bjöd in %@";
+"notice_room_third_party_revoked_invite_by_you_for_dm" = "Du drog tillbaka inbjudan för %@";
+"notice_room_name_changed_by_you_for_dm" = "Du bytte namnet till %@.";
+"notice_room_name_removed_by_you_for_dm" = "Du tog bort namnet";
+"notice_room_created_by_you_for_dm" = "Du gick med.";
+"notice_room_history_visible_to_members_by_you_for_dm" = "Du gjorde framtida meddelanden synliga för alla rumsmedlemmar.";
+"notice_room_history_visible_to_members_from_invited_point_by_you_for_dm" = "Du gjorde framtida meddelanden synliga för alla från när de bjöds in.";
+"notice_room_history_visible_to_members_from_joined_point_by_you_for_dm" = "Du gjorde framtida meddelanden synliga för alla från när de gick med.";
+"call_more_actions_dialpad" = "Knappsats";
+"call_more_actions_transfer" = "Överför";
+"call_more_actions_audio_use_device" = "Enhetshögtalare";
+"call_more_actions_audio_use_headset" = "Använd headsetljud";
+"call_more_actions_change_audio_device" = "Byt ljudenhet";
+"call_more_actions_unhold" = "Återuppta";
+"call_more_actions_hold" = "Parkera";
+"call_holded" = "Du parkerade samtalet";
+"call_remote_holded" = "%@ parkerade samtalet";
+"notice_declined_video_call_by_you" = "Du avslog samtalet";
+"notice_declined_video_call" = "%@ avslog samtalet";
+"resume_call" = "Återuppta";
+"call_transfer_to_user" = "Överför till %@";
+"call_consulting_with_user" = "Rådfrågar %@";
+"call_video_with_user" = "Videosamtal med %@";
+"call_voice_with_user" = "Röstsamtal med %@";
+"call_ringing" = "Ringer…";
+"e2e_passphrase_too_short" = "Lösenfras för kort (den måste vara minst %d tecken långt)";
+"microphone_access_not_granted_for_voice_message" = "Röstmeddelanden kräver åtkomst till mikrofonen, men %@ har inte behörighet att använda den";
+"message_reply_to_sender_sent_a_voice_message" = "skickade ett röstmeddelande.";
+"attachment_large_with_resolution" = "Stor %@ (~%@)";
+"attachment_medium_with_resolution" = "Mellan %@ (~%@)";
+"attachment_small_with_resolution" = "Liten %@ (~%@)";
+"attachment_size_prompt_message" = "Du kan stänga av detta i inställningarna.";
+"attachment_size_prompt_title" = "Bekräfta storlek att skicka";
+"room_displayname_all_other_participants_left" = "%@ (Kvar)";
diff --git a/Riot/Modules/MatrixKit/Assets/MatrixKitAssets.bundle/szl.lproj/MatrixKit.strings b/Riot/Modules/MatrixKit/Assets/MatrixKitAssets.bundle/szl.lproj/MatrixKit.strings
new file mode 100644
index 000000000..8b1378917
--- /dev/null
+++ b/Riot/Modules/MatrixKit/Assets/MatrixKitAssets.bundle/szl.lproj/MatrixKit.strings
@@ -0,0 +1 @@
+
diff --git a/Riot/Modules/MatrixKit/Assets/MatrixKitAssets.bundle/tzm.lproj/MatrixKit.strings b/Riot/Modules/MatrixKit/Assets/MatrixKitAssets.bundle/tzm.lproj/MatrixKit.strings
new file mode 100644
index 000000000..f1a0a2a8f
--- /dev/null
+++ b/Riot/Modules/MatrixKit/Assets/MatrixKitAssets.bundle/tzm.lproj/MatrixKit.strings
@@ -0,0 +1,14 @@
+
+
+
+"notice_event_redacted_by" = " Sɣur %@";
+"close" = "Rgel";
+"yes" = "Yah";
+
+// Action
+"no" = "Uhu";
+/* *********************** */
+/* iOS specific */
+/* *********************** */
+
+"matrix" = "Matrix";
diff --git a/Riot/Modules/MatrixKit/Assets/MatrixKitAssets.bundle/uk.lproj/MatrixKit.strings b/Riot/Modules/MatrixKit/Assets/MatrixKitAssets.bundle/uk.lproj/MatrixKit.strings
new file mode 100644
index 000000000..28bb81299
--- /dev/null
+++ b/Riot/Modules/MatrixKit/Assets/MatrixKitAssets.bundle/uk.lproj/MatrixKit.strings
@@ -0,0 +1,521 @@
+
+
+"resend" = "Повторно надіслати";
+"delete" = "Видалити";
+"share" = "Поділитися";
+"redact" = "Вилучити";
+"copy_button_name" = "Копіювати";
+"send" = "Надіслати";
+"register_error_title" = "Поле реєстрації";
+"login_invalid_param" = "Недійсний параметр";
+"save" = "Зберегти";
+"login_leave_fallback" = "Скасувати";
+"cancel" = "Скасувати";
+"submit" = "Надіслати";
+"sign_up" = "Зареєструватися";
+"retry" = "Повторити";
+"unban" = "Розблокувати";
+"ban" = "Заблокувати";
+"kick" = "Викинути";
+"invite" = "Запросити";
+"leave" = "Вийти";
+"discard" = "Відхилити";
+"continue" = "Продовжити";
+"close" = "Закрити";
+"back" = "Назад";
+"view" = "Вигляд";
+"login_server_url_placeholder" = "URL (наприклад, https://matrix.org)";
+
+// Login Screen
+"login_create_account" = "Створіть обліковий запис:";
+/* *********************** */
+/* iOS specific */
+/* *********************** */
+
+"matrix" = "Matrix";
+
+// Events formatter
+"notice_avatar_changed_too" = "(аватар теж змінено)";
+"unignore" = "Не нехтувати";
+"ignore" = "Нехтувати";
+"resume_call" = "Продовжити";
+"end_call" = "Завершити виклик";
+"reject_call" = "Відхилити виклик";
+"answer_call" = "Відповісти на виклик";
+"show_details" = "Показати подробиці";
+"cancel_download" = "Скасувати завантаження";
+"cancel_upload" = "Скасувати вивантаження";
+"select_all" = "Вибрати всі";
+"resend_message" = "Повторити надсилання повідомлення";
+"reset_to_default" = "Скинути до типових";
+"invite_user" = "Запросити користувача matrix";
+"capture_media" = "Зробити знімок/зафільмувати";
+"attach_media" = "Долучити медіа з бібліотеки";
+"select_account" = "Вибрати обліковий запис";
+"mention" = "Згадати";
+"start_video_call" = "Розпочати відеовиклик";
+"start_voice_call" = "Розпочати голосовий виклик";
+"start_chat" = "Почати бесіду";
+"set_admin" = "Призначити адміністратора";
+"set_moderator" = "Призначити модератора";
+"set_default_power_level" = "Скинути рівень повноважень";
+"set_power_level" = "Визначити рівень повноважень";
+"submit_code" = "Надіслати код";
+"dismiss" = "Відхилити";
+"abort" = "Перервати";
+"yes" = "Так";
+
+// Action
+"no" = "Ні";
+"login_error_resource_limit_exceeded_contact_button" = "Зв'язатися з адміністратором";
+"login_error_resource_limit_exceeded_message_contact" = "\n\nЗверніться до адміністратора своєї служби, щоб продовжувати користуватися нею.";
+"login_error_resource_limit_exceeded_message_monthly_active_user" = "Цей домашній сервер досяг свого місячного обмеження активних користувачів.";
+"login_error_resource_limit_exceeded_message_default" = "Цей домашній сервер перевищив одне з обмежень ресурсів.";
+"login_error_resource_limit_exceeded_title" = "Обмеження ресурсів перевищено";
+"login_desktop_device" = "Комп'ютер";
+"login_tablet_device" = "Планшет";
+"login_mobile_device" = "Мобільний";
+"login_error_forgot_password_is_not_supported" = "Відновлення пароля зараз не підтримується";
+"login_use_fallback" = "Застосувати запасну сторінку";
+"login_error_login_email_not_yet" = "Посилання на електронну адресу, на яке ще не клацнули";
+"login_error_user_in_use" = "Це ім'я користувача вже використовується";
+"login_error_limit_exceeded" = "Було надіслано забагато запитів";
+"login_error_not_json" = "Не містить дійсного JSON";
+"login_error_bad_json" = "Неправильний синтаксис JSON";
+"login_error_unknown_token" = "Вказаний ключ доступу не було розпізнано";
+"login_error_forbidden" = "Не правильне ім'я користувача/пароль";
+"login_error_registration_is_not_supported" = "На цей час реєстрація не підтримується";
+"login_error_no_login_flow" = "Не вдалося отримати дані автентифікації з цього домашнього сервера";
+"login_error_title" = "Не вдалося увійти";
+"login_prompt_email_token" = "Введіть ключ підтвердження електронної пошти:";
+"login_email_placeholder" = "Адреса е-пошти";
+"login_email_info" = "Вкажіть адресу електронної пошти, щоб інші користувачі могли легше знаходити вас на Matrix і надання вам можливості відновити пароль у майбутньому.";
+"login_display_name_placeholder" = "Видиме ім'я (наприклад Bob Obson)";
+"login_optional_field" = "необов'язково";
+"login_password_placeholder" = "Пароль";
+"login_user_id_placeholder" = "Ідентифікатор Matrix (наприклад @bob:matrix.org або bob)";
+"login_identity_server_info" = "Matrix забезпечує сервери ідентифікації для відстеження, до яких ідентифікаторів Matrix, які електронні листи належать. Наразі доступно лише для https://matrix.org.";
+"login_identity_server_title" = "URL-адреса сервера ідентифікації:";
+"login_home_server_info" = "Ваш домашній сервер зберігає всі ваші розмови та дані облікового запису";
+"login_home_server_title" = "URL-адреса домашнього сервера:";
+"notice_room_power_level_intro_for_dm" = "Рівні повноважень учасників:";
+"notice_room_power_level_intro" = "Рівні повноважень учасників кімнати:";
+"notice_room_join_rule_public_by_you_for_dm" = "Ви дозволяєте доступ всім.";
+"notice_room_join_rule_public_by_you" = "Ви дозволяєте доступ до кімнати всім.";
+"notice_room_join_rule_public_for_dm" = "%@ дозволяє доступ всім.";
+"notice_room_join_rule_public" = "%@ дозволяє доступ до кімнати всім.";
+"notice_room_join_rule_invite_by_you_for_dm" = "Ви забороняєте доступ всім, окрім запрошених.";
+"notice_room_join_rule_invite_by_you" = "Ви забороняєте доступ до кімнати всім, окрім запрошених.";
+// New
+"notice_room_join_rule_invite" = "%@ забороняє доступ до кімнати всім, окрім запрошених.";
+"notice_room_join_rule_invite_for_dm" = "%@ забороняє доступ всім, окрім запрошених.";
+// Old
+"notice_room_join_rule" = "Правило приєднання: %@";
+"notice_room_created_for_dm" = "%@ приєднується.";
+"notice_room_created" = "%@ створює і налаштовує кімнату.";
+"notice_profile_change_redacted" = "%@ оновлює свій профіль %@";
+"notice_event_redacted_reason" = " [причина: %@]";
+"notice_event_redacted_by" = " від %@";
+"notice_event_redacted" = "<змінено%@>";
+"notice_room_topic_removed" = "%@ вилучає тему";
+"notice_room_name_removed_for_dm" = "%@ вилучає назву";
+"notice_room_name_removed" = "%@ вилучає назву кімнати";
+"login_error_do_not_support_login_flows" = "Наразі ми не підтримуємо один або кілька потоків авторизації, визначених цим домашнім сервером";
+"notification_settings_never_notify" = "Ніколи не сповіщати";
+"notification_settings_always_notify" = "Завжди сповіщати";
+
+// members list Screen
+
+// accounts list Screen
+
+// image size selection
+
+// invitation members list Screen
+
+// room creation dialog Screen
+
+// room info dialog Screen
+
+// room details dialog screen
+"room_details_title" = "Подробиці про кімнату";
+"login_error_must_start_http" = "URL-адреса повинна починатися з http[s]://";
+"num_members_other" = "%@ користувачів";
+"num_members_one" = "%@ користувач";
+"membership_invite" = "Запрошено";
+"create_account" = "Створити обліковий запис";
+"login" = "Увійти";
+"create_room" = "Створити кімнату";
+"call_video_with_user" = "Відеовиклик з %@";
+"call_voice_with_user" = "Голосовий виклик з %@";
+"ssl_fingerprint_hash" = "Відбиток (%@):";
+"ssl_remain_offline" = "Нехтувати";
+"ssl_logout_account" = "Вийти";
+
+// actions
+"action_logout" = "Вийти";
+
+// Room Screen
+
+// general errors
+
+// Home Screen
+
+// Last seen time
+
+// call events
+
+/* -*-
+ Automatic localization for en
+
+ The following key/value pairs were extracted from the android i18n file:
+ /console/src/main/res/values/strings.xml.
+*/
+
+
+// titles
+
+// button names
+"ok" = "Гаразд";
+"notice_declined_video_call_by_you" = "Ви відхилили виклик";
+"notice_ended_video_call_by_you" = "Ви завершили виклик";
+"notice_avatar_url_changed_by_you" = "Ви змінили свій аватар";
+"notice_room_kick_by_you" = "Ви викинули %@";
+"notice_room_invite_you" = "%@ запрошує вас";
+
+// Notice Events with "You"
+"notice_room_invite_by_you" = "Ви запросили %@";
+"notice_room_third_party_invite_by_you_for_dm" = "Ви запросили %@";
+"notice_room_reason" = ". Причина: %@";
+"notice_room_third_party_registered_invite" = "%@ приймає запрошення від %@";
+"notice_room_third_party_invite_for_dm" = "%@ запрошує %@";
+
+/* -*-
+ Automatic localization for en
+
+ The following key/value pairs were extracted from the android i18n file:
+ /matrix-sdk/src/main/res/values/strings.xml.
+*/
+
+"notice_room_invite" = "%@ запрошує %@";
+"ssl_homeserver_url" = "URL-адреса домашнього сервера: %@";
+"user_id_placeholder" = "наприклад: @bob:homeserver";
+"network_error_not_reachable" = "Перевірте під'єднання до мережі";
+"power_level" = "Рівень повноважень";
+"public" = "Загальнодоступний";
+"private" = "Приватний";
+"default" = "типово";
+"not_supported_yet" = "Поки що не підтримується";
+"error_common_message" = "Сталася помилка. Повторіть спробу пізніше.";
+"error" = "Помилка";
+"membership_ban" = "Заблоковано";
+"notice_room_ban_by_you" = "Ви заблокували %@";
+"notice_room_unban_by_you" = "Ви розблокували %@";
+"notice_room_ban" = "%@ блокує %@";
+"notice_room_unban" = "%@ розблоковує %@";
+"call_invite_expired" = "Запрошення на виклик не чинне";
+"incoming_voice_call" = "Вхідний голосовий виклик";
+"incoming_video_call" = "Вхідний відеовиклик";
+"call_ended" = "Виклик завершено";
+"call_ringing" = "Виклик…";
+
+// Settings keys
+
+// call string
+"call_connecting" = "З'єднання…";
+"settings_config_user_id" = "ID користувача: %@";
+"settings_config_identity_server" = "Сервер ідентифікації: %@";
+
+// gcm section
+"settings_config_home_server" = "Домашній сервер: %@";
+"notification_settings_other_alerts" = "Інші попередження";
+"notification_settings_select_room" = "Вибрати кімнату";
+"notification_settings_sender_hint" = "@user:domain.com";
+"notification_settings_enable_notifications_warning" = "Наразі всі сповіщення вимкнено для всіх пристроїв.";
+"notification_settings_enable_notifications" = "Увімкнути сповіщення";
+
+// Notification settings screen
+"notification_settings_disable_all" = "Вимкнути сповіщення";
+"settings_title_notifications" = "Сповіщення";
+
+// Settings
+"settings" = "Налаштування";
+"room_displayname_more_than_two_members" = "%@ і %@ інших";
+"room_displayname_two_members" = "%@ і %@";
+
+// room display name
+"room_displayname_empty_room" = "Порожня кімната";
+"notice_sticker" = "наліпка";
+"notice_unsupported_attachment" = "Непідтримуване вкладення: %@";
+"notice_file_attachment" = "прикріплений файл";
+"notice_location_attachment" = "прикріплене місцеперебування";
+"notice_video_attachment" = "прикріплене відео";
+"notice_audio_attachment" = "прикріплене аудіо";
+"notice_image_attachment" = "прикріплене зображення";
+"notice_encryption_enabled_unknown_algorithm" = "%1$@ вмикає наскрізне шифрування (нерозпізнаний алгоритм %2$@).";
+"notice_encryption_enabled_ok" = "%@ вмикає наскрізне шифрування.";
+"notice_encrypted_message" = "Зашифроване повідомлення";
+"notice_avatar_url_changed" = "%@ змінює свій аватар";
+
+// Settings screen
+"settings_title_config" = "Конфігурація";
+
+// Others
+"user_id_title" = "ID користувача:";
+"e2e_export" = "Експорт";
+"e2e_import" = "Імпорт";
+"search_searching" = "Триває пошук...";
+
+// Search
+"search_no_results" = "Немає результатів";
+"group_section" = "Групи";
+
+// Groups
+"group_invite_section" = "Запрошення";
+"contact_local_contacts" = "Локальні контакти";
+
+// Contacts
+"contact_mx_users" = "Користувачі Matrix";
+"attachment_e2e_keys_import" = "Імпорт...";
+"attachment_multiselection_original" = "Справжній розмір";
+"attachment_multiselection_size_prompt" = "Хочете надіслати зображення як:";
+"attachment_cancel_upload" = "Скасувати вивантаження?";
+"attachment_cancel_download" = "Скасувати завантаження?";
+"attachment_large" = "Великий (~%@)";
+"attachment_medium" = "Середній (~%@)";
+"attachment_small" = "Маленький (~%@)";
+"attachment_original" = "Справжній розмір (%@)";
+
+// Attachment
+"attachment_size_prompt" = "Бажаєте надіслати:";
+"message_reply_to_sender_sent_a_file" = "надсилає файл.";
+"message_reply_to_sender_sent_an_audio_file" = "надсилає звуковий файл.";
+"message_reply_to_sender_sent_a_voice_message" = "надсилає голосове повідомлення.";
+"message_reply_to_sender_sent_a_video" = "надсилає відео.";
+
+// Reply to message
+"message_reply_to_sender_sent_an_image" = "надсилає зображення.";
+
+// E2E import
+"e2e_import_room_keys" = "Імпорт ключів кімнати";
+"format_time_d" = "д";
+"format_time_h" = "год";
+"format_time_m" = "хв";
+
+// Time
+"format_time_s" = "с";
+"room_error_join_failed_title" = "Не вдалося приєднатися до кімнати";
+
+// Room
+"room_please_select" = "Виберіть кімнату";
+"room_creation_participants_placeholder" = "(наприклад @bob:homeserver1; @john:homeserver2...)";
+"room_creation_participants_title" = "Учасники:";
+"room_creation_name_placeholder" = "(наприклад lunchGroup)";
+
+// Room creation
+"room_creation_name_title" = "Назва кімнати:";
+"room_event_encryption_info_device_fingerprint" = "Відбиток Ed25519\n";
+"room_event_encryption_info_event_unencrypted" = "незашифровано";
+"room_event_encryption_info_event_decryption_error" = "Помилка розшифрування\n";
+"room_event_encryption_info_event_session_id" = "ID сеансу\n";
+"room_event_encryption_info_event_algorithm" = "Алгоритм\n";
+"room_event_encryption_info_event_identity_key" = "Ключ ідентифікації Curve25519\n";
+"room_event_encryption_info_event_user_id" = "ID користувача\n";
+"room_event_encryption_info_event" = "Відомості про подію\n";
+
+// Encryption information
+"room_event_encryption_info_title" = "Відомості про наскрізне шифрування\n\n";
+"device_details_rename_prompt_title" = "Назва сеансу";
+"device_details_last_seen_format" = "%@ @ %@\n";
+"device_details_identifier" = "ID\n";
+"room_event_encryption_info_device_id" = "ID\n";
+"room_event_encryption_info_device_name" = "Загальнодоступна назва\n";
+"device_details_name" = "Загальнодоступна назва\n";
+
+// Devices
+"device_details_title" = "Відомості про сеанс\n";
+"notification_settings_room_rule_title" = "Кімната: '%@'";
+"notice_in_reply_to" = "У відповідь на";
+"message_reply_to_message_to_reply_to_prefix" = "У відповідь на";
+"notice_crypto_unable_to_decrypt" = "** Не вдалося розшифрувати: %@ **";
+"notice_error_unknown_event_type" = "Невідомий тип події";
+"notice_error_unexpected_event" = "Неочікувана подія";
+"notice_error_unsupported_event" = "Непідтримувана подія";
+"notice_invalid_attachment" = "неприпустиме вкладення";
+"notice_room_history_visible_to_members_from_joined_point_for_dm" = "%@ робить майбутню історію повідомлень видимою всім від часу їхнього приєднання.";
+"notice_room_history_visible_to_members_from_joined_point" = "%@ робить майбутню історію кімнати видимою всім учасникам кімнати від часу їхнього приєднання.";
+"notice_room_history_visible_to_members_from_invited_point_for_dm" = "%@ робить майбутню історію повідомлень видимою всім від часу їхнього запрошення.";
+"notice_room_history_visible_to_members_from_invited_point" = "%@ робить майбутню історію кімнати видимою усім учасникам кімнати від часу їхнього запрошення.";
+"notice_room_history_visible_to_members_for_dm" = "%@ робить майбутню історію повідомлень видимою усім учасникам кімнати.";
+"notice_room_history_visible_to_members" = "%@ робить майбутню історію кімнати видимою усім учасникам кімнати.";
+"notice_room_history_visible_to_anyone" = "%@ робить майбутню історію кімнати видимою усім.";
+"notice_redaction" = "%@ редагує подію (id: %@)";
+"notice_feedback" = "Подія відгуку (id: %@): %@";
+"notice_room_related_groups" = "Групи пов'язані з цією кімнатою: %@";
+"notice_room_power_level_acting_requirement" = "Мінімальний рівень повноважень користувача для виконання дії:";
+"notification_settings_by_default" = "Типово...";
+"membership_leave" = "Виходить";
+"notice_redaction_by_you" = "Ви відредагували подію (id: %@)";
+"notice_encryption_enabled_unknown_algorithm_by_you" = "Ви ввімкнули наскрізне шифрування (нерозпізнаний алгоритм %@).";
+"notice_encryption_enabled_ok_by_you" = "Ви ввімкнули наскрізне шифрування.";
+"notice_room_created_by_you_for_dm" = "Ви приєдналися.";
+"notice_room_third_party_revoked_invite_by_you_for_dm" = "Ви відкликали запрошення для %@";
+"notice_room_third_party_revoked_invite_by_you" = "Ви відкликали запрошення приєднатися до кімнати для %@";
+"notice_room_third_party_registered_invite_by_you" = "Ви прийняли запрошення для %@";
+"notice_room_withdraw_by_you" = "Ви анулювали запрошення для %@";
+"notice_room_reject_by_you" = "Ви відхилили запрошення";
+"notice_room_third_party_invite_by_you" = "Ви надіслали запрошення приєднатися до кімнати для %@";
+"notice_room_name_changed_for_dm" = "%@ змінює назву на %@.";
+"notice_room_name_changed" = "%@ змінює назву кімнати на %@.";
+"notice_topic_changed" = "%@ змінює тему на «%@».";
+"notice_display_name_removed" = "%@ вилучає своє показуване ім'я";
+"notice_display_name_changed_from" = "%@ змінює своє показуване ім'я з %@ на %@";
+"notice_display_name_set" = "%@ встановлює своїм показуваним іменем %@";
+"notice_room_withdraw" = "%@ анульовує запрошення для %@";
+"notice_room_kick" = "%@ викидає %@";
+"notice_room_reject" = "%@ відхиляє запрошення";
+"notice_room_leave" = "%@ виходить";
+"notice_room_join" = "%@ приєднується";
+"notice_room_third_party_revoked_invite_for_dm" = "%@ відкликає запрошення для %@";
+"notice_room_third_party_revoked_invite" = "%@ відкликає запрошення приєднатися до кімнати для %@";
+"notice_room_third_party_invite" = "%@ надсилає запрошення приєднатися до кімнати для %@";
+"microphone_access_not_granted_for_voice_message" = "Для голосових повідомлень потрібен доступ до мікрофона, але %@ не має дозволу на його використання";
+"microphone_access_not_granted_for_call" = "Для викликів потрібен доступ до мікрофона, але %@ не має дозволу на його використання";
+
+// Permissions
+"camera_access_not_granted_for_call" = "Для відеовикликів потрібен доступ до камери, але %@ не має дозволу на її використання";
+"language_picker_default_language" = "Типово (%@)";
+
+// Language picker
+"language_picker_title" = "Виберіть мову";
+
+// Country picker
+"country_picker_title" = "Виберіть країну";
+"notice_room_join_by_you" = "Ви приєдналися";
+"notice_room_leave_by_you" = "Ви вийшли";
+"room_left_for_dm" = "Ви вийшли";
+"room_left" = "Ви вийшли з кімнати";
+"call_more_actions_unhold" = "Продовжити";
+"call_more_actions_change_audio_device" = "Змінити звуковий пристрій";
+"call_more_actions_audio_use_device" = "Гучномовець пристрою";
+"call_more_actions_transfer" = "Переведення";
+"call_more_actions_dialpad" = "Номеронабирач";
+"call_transfer_to_user" = "Передавання до %@";
+
+// unrecognized SSL certificate
+"ssl_trust" = "Довіряти";
+
+// Account
+"account_save_changes" = "Зберегти зміни";
+"room_event_encryption_info_unblock" = "Видалити з чорного списку";
+"room_event_encryption_info_block" = "Чорний список";
+"room_event_encryption_info_device_blocked" = "У чорному списку";
+"room_event_encryption_info_device_unknown" = "невідомий сеанс\n";
+"room_event_encryption_info_device" = "\nВідомості про сеанс відправника\n";
+"device_details_delete_prompt_title" = "Автентифікація";
+"settings_enter_validation_token_for" = "Введіть токен підтвердження для %@:";
+"settings_enable_push_notifications" = "Увімкнути push-сповіщення";
+"settings_enable_inapp_notifications" = "Увімкнути сповіщення в застосунку";
+"room_displayname_all_other_participants_left" = "%@ (виходить)";
+"notice_crypto_error_unknown_inbound_session_id" = "Сеанс відправника не надіслав нам ключі для цього повідомлення.";
+"notice_room_power_level_event_requirement" = "Найнижчий рівень повноважень пов'язаний з подією:";
+"notification_settings_global_info" = "Налаштування сповіщень зберігаються у вашому обліковому записі й спільні для всіх клієнтів, які їх підтримують (включно зі сповіщеннями стільниці).\n\nПравила застосовуються по черзі; спочатку надсилається повідомлення першого збігу з правилом.\nОтже: сповіщення для кожного слова важливіші за сповіщення для кожної кімнати, які важливіші за сповіщення від кожного відправника.\nДля кількох правил одного виду важливіше перше у списку.";
+"notification_settings_per_word_notifications" = "Сповіщення для кожного слова";
+"notification_settings_per_word_info" = "Слова збігу не чутливі до регістру й можуть містити символ *. Так:\nfoo збігається з рядком foo, оточеним роздільниками слів (наприклад, розділовими знаками та пробілами або початком/кінцем рядка).\nfoo* збігається з будь-яким таким словом, яке починається з foo.\n*foo* збігається з будь-яким таким словом, яке містить три букви foo.";
+"notification_settings_word_to_match" = "слово збігу";
+"notification_settings_highlight" = "Підсвічування";
+"notification_settings_per_room_notifications" = "Сповіщення від кожної кімнати";
+"notification_settings_per_sender_notifications" = "Сповіщення про кожного відправника";
+"notification_settings_contain_my_user_name" = "Сповіщати звуком про повідомлення, що містять моє ім'я користиувача";
+"notification_settings_contain_my_display_name" = "Сповіщати звуком про повідомлення, що містять моє показуване ім'я";
+"notification_settings_just_sent_to_me" = "Сповіщати звуком про надіслані лише мені повідомлення";
+"notification_settings_invite_to_a_new_room" = "Сповіщати про запрошення до нових кімнат";
+"notification_settings_people_join_leave_rooms" = "Сповіщати, коли люди приєднуються чи виходять з кімнат";
+"notification_settings_receive_a_call" = "Сповіщати про виклики";
+"notification_settings_suppress_from_bots" = "Приховувати сповіщень від ботів";
+"notification_settings_notify_all_other" = "Сповіщати про всі інші повідомлення/кімнати";
+"notification_settings_custom_sound" = "Власний звук";
+"account_error_push_not_allowed" = "Сповіщення не дозволені";
+"account_error_msisdn_wrong_description" = "Це не схоже на правильний номер телефону";
+"account_error_msisdn_wrong_title" = "Неправильний номер телефону";
+"account_error_email_wrong_description" = "Це не схоже на правильну адресу е-пошти";
+"account_error_email_wrong_title" = "Неправильна адреса е-пошти";
+"account_error_matrix_session_is_not_opened" = "Сеанс Matrix не відкрито";
+"account_error_picture_change_failed" = "Не вдалося змінити зображення";
+"account_error_display_name_change_failed" = "Не вдалося змінити показуване ім'я";
+"account_msisdn_validation_error" = "Не вдалося перевірити номер телефону.";
+"account_email_validation_title" = "Очікування перевірки";
+"account_msisdn_validation_title" = "Очікування перевірки";
+"account_msisdn_validation_message" = "Ми надіслали СМС із кодом активації. Введіть цей код унизу.";
+"account_linked_emails" = "Пов'язані адреси е-пошти";
+"account_link_email" = "Пов'язати е-пошту";
+"room_event_encryption_verify_ok" = "Звірити";
+"room_event_encryption_info_device_not_verified" = "НЕ звірений";
+"room_event_encryption_info_device_verified" = "Звірений";
+"room_event_encryption_verify_title" = "Звірити сеанс\n\n";
+"room_event_encryption_info_unverify" = "Скасувати перевірку";
+"room_event_encryption_info_verify" = "Звірити...";
+"room_event_encryption_info_device_verification" = "Перевірка\n";
+"room_event_encryption_info_event_none" = "нічого";
+"room_event_encryption_info_event_fingerprint_key" = "Потрібен ключ цифрового відбитка Ed25519\n";
+"device_details_delete_prompt_message" = "Виконання цієї дії вимагає додаткової автентифікації.\nЩоб продовжити, введіть свій пароль.";
+"device_details_rename_prompt_message" = "Загальнодоступну назву сеансу бачать люди, з якими ви спілкуєтесь";
+"device_details_last_seen" = "Останні відвідини\n";
+"notice_room_aliases_for_dm" = "Псевдоніми: %@";
+"notice_room_aliases" = "Псевдо кімнати: %@";
+"unsent" = "Не надіслано";
+"offline" = "не в мережі";
+"e2e_passphrase_create" = "Створити парольну фразу";
+"e2e_passphrase_not_match" = "Парольні фрази повинні збігатися";
+"e2e_passphrase_too_short" = "Парольна фраза закоротка (Її довжина повинна складати принаймні %d символів)";
+
+// E2E export
+"e2e_export_room_keys" = "Експорт ключів кімнати";
+"e2e_passphrase_enter" = "Введіть парольну фразу";
+"e2e_passphrase_empty" = "Парольна фраза не повинна бути порожньою";
+"e2e_passphrase_confirm" = "Підтвердити парольну фразу";
+"notice_room_history_visible_to_members_from_joined_point_by_you_for_dm" = "Ви зробили майбутні повідомлення кімнати видимими будь-кому від часу їхнього приєднання.";
+"notice_room_history_visible_to_members_from_joined_point_by_you" = "Ви зробили майбутню історію кімнати видимою усім учасникам кімнати від часу їхнього приєднання.";
+"notice_room_history_visible_to_members_from_invited_point_by_you_for_dm" = "Ви зробили майбутні повідомлення кімнати видимими будь-кому від часу запрошення їх.";
+"notice_room_history_visible_to_members_from_invited_point_by_you" = "Ви зробили майбутню історію кімнати видимою усім учасникам кімнати від часу запрошення їх.";
+"notice_room_history_visible_to_members_by_you_for_dm" = "Ви зробили майбутні повідомлення кімнати видимими усім учасникам кімнати.";
+"notice_room_history_visible_to_members_by_you" = "Ви зробили майбутню історію кімнати видимою усім учасникам кімнати.";
+"notice_room_history_visible_to_anyone_by_you" = "Ви зробили майбутню історію кімнати видимою будь-кому.";
+"notice_room_created_by_you" = "Ви створили й сконфігурували кімнату.";
+"notice_profile_change_redacted_by_you" = "Ви оновили свій профіль %@";
+"notice_event_redacted_by_you" = " вами";
+"notice_room_topic_removed_by_you" = "Ви вилучили тему";
+"notice_room_name_removed_by_you_for_dm" = "Ви вилучили назву";
+"notice_room_name_removed_by_you" = "Ви вилучили назву кімнати";
+"notice_conference_call_request_by_you" = "Ви запитали VoIP-конференцію";
+"notice_answered_video_call_by_you" = "Ви відповіли на виклик";
+"notice_placed_video_call_by_you" = "Ви розпочали відеовиклик";
+"notice_placed_voice_call_by_you" = "Ви розпочали голосовий виклик";
+"notice_room_name_changed_by_you_for_dm" = "Ви змінили назву на %@.";
+"notice_room_name_changed_by_you" = "Ви змінили назву кімнати на %@.";
+"notice_topic_changed_by_you" = "Ви змінили тему на «%@».";
+"notice_display_name_removed_by_you" = "Ви вилучили показуване ім'я";
+"notice_display_name_changed_from_by_you" = "Ви змінили показуване ім'я з %@ на %@";
+"notice_display_name_set_by_you" = "Ви вказали показуваним іменем %@";
+"notice_conference_call_finished" = "VoIP-конференція завершилася";
+"notice_conference_call_started" = "VoIP-конференція розпочалася";
+"notice_conference_call_request" = "%@ запитує VoIP-конференцію";
+"notice_declined_video_call" = "%@ відхиляє виклик";
+"notice_ended_video_call" = "%@ завершує виклик";
+"notice_answered_video_call" = "%@ відповідає на виклик";
+"notice_placed_video_call" = "%@ здійснює відеовиклик";
+"notice_placed_voice_call" = "%@ здійснює голосовий виклик";
+
+// Room members
+"room_member_ignore_prompt" = "Ви впевнені, що хочете сховати всі повідомлення від цього користувача?";
+"room_no_conference_call_in_encrypted_rooms" = "Кімнати з шифруванням не підтримують конференцвиклики";
+"room_error_topic_edition_not_authorized" = "Ви не маєте повноважень змінювати тему цієї кімнати";
+"room_error_name_edition_not_authorized" = "Ви не маєте повноважень змінювати назву цієї кімнати";
+"room_error_join_failed_empty_room" = "На цю мить неможливо приєднатися до порожньої кімнати.";
+"room_creation_alias_placeholder_with_homeserver" = "(напр. #foo%@)";
+"room_creation_alias_placeholder" = "(напр. #foo:example.org)";
+"account_email_validation_message" = "Перевірте свою електронну пошту та натисніть на посилання у ній. Після цього натисніть кнопку Продовжити.";
+"room_displayname_all_other_members_left" = "%@ (виходять)";
+"auth_username_in_use" = "Ім'я користувача зайняте";
+"rename" = "Перейменувати";
+"room_creation_alias_title" = "Псевдоніми кімнати:";
+"account_email_validation_error" = "Не вдалося перевірити адресу електронної пошти. Перевірте свою електронну пошту та натисніть на посилання в ній. Після цього натисніть продовжити";
diff --git a/Riot/Modules/MatrixKit/Assets/MatrixKitAssets.bundle/vi.lproj/MatrixKit.strings b/Riot/Modules/MatrixKit/Assets/MatrixKitAssets.bundle/vi.lproj/MatrixKit.strings
new file mode 100644
index 000000000..6f7fbb694
--- /dev/null
+++ b/Riot/Modules/MatrixKit/Assets/MatrixKitAssets.bundle/vi.lproj/MatrixKit.strings
@@ -0,0 +1,359 @@
+"matrix" = "Matrix";
+// Login Screen
+"login_create_account" = "Tạo tài khoản:";
+"login_server_url_placeholder" = "URL (e.g. https://matrix.org)";
+"login_home_server_title" = "Máy chủ nhà:";
+"login_home_server_info" = "Máy chủ nhà của bạn lưu trữ tất cả dữ liệu cần giữ gìn và tài khoản";
+"login_identity_server_title" = "Máy chủ xác thực:";
+"login_identity_server_info" = "Matrix cung cấp máy chủ xác thực để theo dõi email nào thuộc về Matrix IDs nào. Chỉ https://matrix.org hiện đang tồn tại.";
+"login_user_id_placeholder" = "Matrix ID (ví dụ: @bob:matrix.org hoặc bob)";
+"login_password_placeholder" = "Mật khẩu";
+"login_optional_field" = "không bắt buộc";
+"login_display_name_placeholder" = "Tên hiển thị (ví dụ: Bob Obson)";
+"login_email_info" = "Định nghĩa địa chỉ email cho phép người dùng khác tìm thấy bạn trên Matrix dễ dàng hơn và giúp bạn đặt lại mật khẩu của bạn sau này.";
+"login_email_placeholder" = "Địa chỉ email";
+"login_prompt_email_token" = "Vui lòng nhập mã xác nhận hợp lệ email của bạn:";
+"login_error_title" = "Đăng nhập thất bại";
+"login_error_no_login_flow" = "Chúng tôi không thể truy xuất thông tin xác thực từ Home Server này";
+"login_error_do_not_support_login_flows" = "Hiện tại chúng tôi không hỗ trợ bất cứ luồng đăng nhập được định nghĩa nào từ Home Server này";
+"login_error_registration_is_not_supported" = "Đăng kí hiện đang không được hỗ trợ";
+"login_error_forbidden" = "Sai tên đăng nhập/mật khẩu";
+"login_error_unknown_token" = "Mã truy cập được chỉ định không được công nhận";
+"login_error_bad_json" = "Sai định dạng JSON";
+"login_error_not_json" = "Không chứa một JSON hợp lệ";
+"login_error_limit_exceeded" = "Quá nhiều yêu cầu đã được gửi";
+"login_error_user_in_use" = "Tên đăng nhập này đã được sử dụng";
+"login_error_login_email_not_yet" = "Liên kết email chưa được nhấp vào";
+"login_use_fallback" = "Sử dụng dự phòng";
+"login_leave_fallback" = "Huỷ";
+"login_invalid_param" = "Tham số không hợp lệ";
+"register_error_title" = "Đăng kí thất bại";
+"login_error_forgot_password_is_not_supported" = "Chức năng quên mật khẩu chưa hỗ trợ";
+"login_mobile_device" = "Di động";
+"login_tablet_device" = "Máy tính bảng";
+"login_desktop_device" = "Máy tính bàn";
+// Action
+"no" = "Không";
+"yes" = "Có";
+"abort" = "Huỷ bỏ";
+"back" = "Trở về";
+"close" = "Đóng";
+"continue" = "Tiếp tục";
+"discard" = "Huỷ bỏ";
+"dismiss" = "Bỏ qua";
+"retry" = "Thử lại";
+"sign_up" = "Đăng kí";
+"submit" = "Gửi đi";
+"submit_code" = "Gửi mã lên";
+"set_default_power_level" = "Thiết lập lại cấp độ";
+"set_moderator" = "Đặt người kiểm duyệt";
+"set_admin" = "Đặt người quản trị";
+"start_chat" = "Bắt đầu chat";
+"start_voice_call" = "Bắt đầu cuộc gọi thoại";
+"start_video_call" = "Bắt đầu cuộc gọi video";
+"mention" = "Đề cập đến";
+"select_account" = "Chọn một tài khoản";
+"attach_media" = "Đính kèm Media từ thư viện";
+"capture_media" = "Chụp ảnh/quay video";
+"invite_user" = "Mời người dùng Matrix";
+"reset_to_default" = "Thiết lập lại về mặc định";
+"resend_message" = "Gửi lại tin nhắn";
+"select_all" = "Chọn tất cả";
+"cancel_upload" = "Huỷ tải lên";
+"cancel_download" = "Huỷ tải xuống";
+"show_details" = "Xem chi tiết";
+"answer_call" = "Trả lời cuộc gọi";
+"reject_call" = "Từ chối cuộc gọi";
+"end_call" = "Kết thúc cuộc gọi";
+"ignore" = "Bỏ qua";
+"unignore" = "Huỷ bỏ qua";
+// Events formatter
+"notice_avatar_changed_too" = "(hình đại diện cũng đã được thay đổi)";
+"notice_room_name_removed" = "%@ bị gỡ bỏ khỏi tên của phòng";
+"notice_room_topic_removed" = "%@ bị gỡ bỏ khỏi chủ đề của phòng";
+"notice_event_redacted" = "";
+"notice_event_redacted_by" = " bởi %@";
+"notice_event_redacted_reason" = " [lí do: %@]";
+"notice_profile_change_redacted" = "%@ cập nhật hồ sơ của họ %@";
+"notice_room_created" = "%@ đã tạo phòng";
+"notice_room_join_rule" = "Quy tắc tham gia là: %@";
+"notice_room_power_level_intro" = "Công suất của thành viên trong phòng:";
+"notice_room_power_level_acting_requirement" = "Mức công suất tối thiểu mà người dùng phải có trước khi hoạt động là:";
+"notice_room_power_level_event_requirement" = "Mức công suất tối thiểu liên quan đến sự kiện là:";
+"notice_room_aliases" = "Các bí danh phòng là:% @";
+"notice_encrypted_message" = "Tin nhắn đã được mã hoá";
+"notice_encryption_enabled" = "%@ đã bật mã hoá end-to-end (thuật toán %@)";
+"notice_image_attachment" = "hình ảnh đính kèm";
+"notice_audio_attachment" = "âm thanh đính kèm";
+"notice_video_attachment" = "video đính kèm";
+"notice_location_attachment" = "địa điểm đính kèm";
+"notice_file_attachment" = "tập tin đính kèm";
+"notice_invalid_attachment" = "đính kèm không hợp lệ";
+"notice_unsupported_attachment" = "Đính kèm chưa được hỗ trợ: %@";
+"notice_feedback" = "Sự kiện phản hồi (id: %@): %@";
+"notice_redaction" = "%@ đã biên soạn một sự kiện (id: %@)";
+"notice_error_unsupported_event" = "Sự kiện không được hỗ trợ";
+"notice_error_unexpected_event" = "Sự kiện bất ngờ";
+"notice_error_unknown_event_type" = "Loại sự kiện không xác định";
+"notice_room_history_visible_to_anyone" = "%@ làm cho mọi người có thể thấy lịch sử phòng.";
+"notice_room_history_visible_to_members" = "%@ làm cho thành viên trong phòng có thể thấy được lịch sử phòng.";
+"notice_room_history_visible_to_members_from_invited_point" = "%@ làm cho mọi thành viên có thể thấy được lịch sử phòng từ thời điểm được mời.";
+"notice_room_history_visible_to_members_from_joined_point" = "%@ làm cho mọi người có thể thấy lịch sử phòng từ thời điểm họ tham gia.";
+"notice_crypto_unable_to_decrypt" = "** Không thể giải mã: %@ **";
+"notice_crypto_error_unknown_inbound_session_id" = "Thiết bị gửi đi đã không gửi cho chúng ta các khoá giải mã tin nhắn.";
+// room display name
+"room_displayname_empty_room" = "Phòng trống";
+"room_displayname_two_members" = "%@ và %@";
+"room_displayname_more_than_two_members" = "%@ và %u người khác";
+// Settings
+"settings" = "Cài đặt";
+"settings_enable_inapp_notifications" = "Bật thông báo bên trong ứng dụng";
+"settings_enable_push_notifications" = "Bật thông báo";
+"settings_enter_validation_token_for" = "Nhập mã thông báo xác nhận cho %@:";
+"notification_settings_room_rule_title" = "Phòng chat: '%@'";
+// Devices
+"device_details_title" = "Thông tin thiết bị\n";
+"device_details_name" = "Tên\n";
+"device_details_identifier" = "ID Thiết bị\n";
+"device_details_last_seen" = "Lần cuối nhìn thấy\n";
+"device_details_last_seen_format" = "%@ @ %@\n";
+"device_details_rename_prompt_message" = "Tên thiết bị:";
+"device_details_delete_prompt_title" = "Xác thực";
+"device_details_delete_prompt_message" = "Thao tác này yêu cầu xác thực bổ sung.\nĐể tiếp tục, vui lòng nhập mật khẩu của bạn.";
+// Encryption information
+"room_event_encryption_info_title" = "Thông tin mã hoá end-to-end\n\n";
+"room_event_encryption_info_event" = "Thông tin sự kiện\n";
+"room_event_encryption_info_event_user_id" = "ID Người dùng\n";
+"room_event_encryption_info_event_identity_key" = "Khoá xác thực Curve25519\n";
+"room_event_encryption_info_event_fingerprint_key" = "Chìa khoá vân tay Claimed Ed25519\n";
+"room_event_encryption_info_event_algorithm" = "Thuật toán\n";
+"room_event_encryption_info_event_session_id" = "ID Phiên làm việc\n";
+"room_event_encryption_info_event_decryption_error" = "Lỗi giải mã\n";
+"room_event_encryption_info_event_unencrypted" = "đã giải mã";
+"room_event_encryption_info_event_none" = "không";
+"room_event_encryption_info_device" = "\nThông tin thiết bị gửi đi\n";
+"room_event_encryption_info_device_unknown" = "thiết bị không xác định\n";
+"room_event_encryption_info_device_name" = "Tên\n";
+"room_event_encryption_info_device_id" = "ID Thiết bị\n";
+"room_event_encryption_info_device_verification" = "Xác minh\n";
+"room_event_encryption_info_device_fingerprint" = "Dấu vân tay Claimed Ed25519\n";
+"room_event_encryption_info_device_verified" = "Đã xác thực";
+"room_event_encryption_info_device_not_verified" = "CHƯA xác thực";
+"room_event_encryption_info_device_blocked" = "Danh sách đen";
+"room_event_encryption_info_verify" = "Xác thực...";
+"room_event_encryption_info_unverify" = "Huỷ xác thực";
+"room_event_encryption_info_block" = "Danh sách đen";
+"room_event_encryption_info_unblock" = "Huỷ danh sách đen";
+"room_event_encryption_verify_title" = "Xác minh thiết bị\n\n";
+"room_event_encryption_verify_message" = "Để xác minh rằng thiết bị này có thể tin cậy, vui lòng liên hệ chủ sở hữu bằng cách sử dụng một số phương tiện khác (ví dụ: trực tiếp hoặc qua điện thoại) và yêu cầu họ xem khoá mà họ nhìn thấy trong \"Cài đặt người dùng\" cho thiết bị này có trùng khớp với khoá dưới đây:\n\n\tTên thiết bị: %@\n\tID thiết bị: %@\n\tKhóa thiết bị: %@\n\nNếu nó trùng khớp, nhấn nút xác minh dưới đây. Nếu không, thì ai đó đang chặn thiết bị này và bạn có thể muốn nhấn nút danh sách đen thay vì nhấn nút xác minh.\n\nTrong tương lai quá trình xác minh này sẽ phức tạp hơn.";
+"room_event_encryption_verify_ok" = "Xác thực";
+// Account
+"account_save_changes" = "Lưu thay đổi";
+"account_link_email" = "Liên kết email";
+"account_linked_emails" = "Email đã được liên kết";
+"account_email_validation_title" = "Xác minh đang chờ xử lí";
+"account_email_validation_message" = "Vui lòng check email của bạn và nhấn vào đường dẫn đính kèm trong đó. Sau khi việc đó hoàn thành, nhấn tiếp tục.";
+"account_email_validation_error" = "Không thể xác minh địa chỉ email. Vui lòng kiểm tra email của bạn và nhấn vào liên kết mà nó được đính kèm. Sau khi việc đó hoàn thành, nhất tiếp tục";
+"account_msisdn_validation_title" = "Xác minh đang chờ xử lí";
+"account_msisdn_validation_message" = "Chúng tôi đã gửi mã kích hoạt qua SMS. Vui lòng nhập mã kích hoạt bên dưới.";
+"account_msisdn_validation_error" = "Không thể xác thực số điện thoại.";
+"account_error_display_name_change_failed" = "Thay đổi tên hiển thị thất bại";
+"account_error_picture_change_failed" = "Thay đổi hình ảnh thất bại";
+"account_error_matrix_session_is_not_opened" = "Phiên làm việc matrix chưa được mở";
+"account_error_email_wrong_title" = "Địa chỉ email không hợp lệ";
+"account_error_email_wrong_description" = "Nó có vẻ không phải là một địa chỉ email hợp lệ";
+"account_error_msisdn_wrong_title" = "Số điện thoại không hợp lệ";
+"account_error_msisdn_wrong_description" = "Nó có vẻ không phải là một số điện thoại hợp lệ";
+// Room creation
+"room_creation_name_title" = "Tên phòng:";
+"room_creation_name_placeholder" = "(ví dụ: lunchGroup)";
+"room_creation_alias_title" = "Bí danh phòng:";
+"room_creation_alias_placeholder" = "(ví dụ: #foo:example.org)";
+"room_creation_alias_placeholder_with_homeserver" = "(ví dụ: #foo%@)";
+"room_creation_participants_title" = "Người tham gia:";
+"room_creation_participants_placeholder" = "(ví dụ: @bob:homeserver1; @john:homeserver2...)";
+// Room
+"room_please_select" = "Vui lòng chọn một phòng";
+"room_error_join_failed_title" = "Tham gia phòng thất bại";
+"room_error_join_failed_empty_room" = "Hiện tại không thể tham gia lại phòng trống.";
+"room_error_name_edition_not_authorized" = "Bạn chưa được uỷ quyền để sửa tên phòng";
+"room_error_topic_edition_not_authorized" = "Bạn chưa được uỷ quyền để sửa chủ đề phòng";
+"room_error_cannot_load_timeline" = "Không tải được dòng thời gian";
+"room_error_timeline_event_not_found_title" = "Không tải được vị trí dòng thời gian";
+"room_error_timeline_event_not_found" = "Ứng dụng đã cố gắng tải một điểm cụ thể trong thời gian của phòng này nhưng không thể tìm thấy nó";
+"room_left" = "Bạn đã rời phòng";
+"room_no_power_to_create_conference_call" = "Bạn cần cấp quyền để mời bắt đầu cuộc gọi hội họp trong phòng này";
+"room_no_conference_call_in_encrypted_rooms" = "Gọi hội họp chưa được hỗ trợ trong các phòng đã mã hoá";
+// Room members
+"room_member_ignore_prompt" = "Bạn có chắc chắn muốn ẩn tất cả tin nhắn từ người dùng này?";
+"room_member_power_level_prompt" = "Bạn sẽ không thể hoàn tác thay đổi này khi bạn đang quảng cáo cho người dùng có mức năng lượng như chính bạn.\nBạn có chắc không?";
+// Attachment
+"attachment_size_prompt" = "Bạn có muốn gửi dưới dạng:";
+"attachment_original" = "Kích thước thực sự: %@";
+"attachment_small" = "Nhỏ: %@";
+"attachment_medium" = "Trung bình: %@";
+"attachment_large" = "Lớn: %@";
+"attachment_cancel_download" = "Huỷ bỏ tải xuống?";
+"attachment_cancel_upload" = "Huỷ bỏ tải lên?";
+"attachment_multiselection_size_prompt" = "Bạn có muốn gửi hình ảnh dưới dạng:";
+"attachment_multiselection_original" = "Kích thước thực sự";
+"attachment_e2e_keys_file_prompt" = "Tập tin này chứa các khoá mã hoá được xuất từ một máy khách Matrix.\nBạn có muốn xem nội dung tập tin hoặc nhập khẩu các khóa nó chứa?";
+"attachment_e2e_keys_import" = "Nhập...";
+// Contacts
+"contact_mx_users" = "Người dùng của Matrix";
+"contact_local_contacts" = "Danh bạ";
+// Search
+"search_no_results" = "Không có kết quả";
+"search_searching" = "Đang tìm kiếm...";
+// Time
+"format_time_s" = "giây";
+"format_time_m" = "phút";
+"format_time_h" = "giờ";
+"format_time_d" = "ngày";
+// E2E import
+"e2e_import_room_keys" = "Nhập khóa phòng";
+"e2e_import_prompt" = "Quá trình này cho phép bạn nhập các khoá mã hoá mà trước đây bạn đã xuất từ một máy khách Matrix khác. Sau đó, bạn sẽ có thể giải mã bất kỳ thư nào mà khách hàng khác có thể giải mã.\nTệp xuất được bảo vệ bằng cụm mật khẩu. Bạn nên nhập cụm từ mật khẩu ở đây, để giải mã tệp.";
+"e2e_import" = "Nhập";
+"e2e_passphrase_enter" = "Nhập cụm mật khẩu";
+// E2E export
+"e2e_export_room_keys" = "Xuất các khoá của phòng";
+"e2e_export_prompt" = "Quá trình này cho phép bạn xuất khẩu các khoá cho tin nhắn bạn đã nhận được trong các phòng được mã hoá tới một tập tin cục bộ. Sau đó, bạn sẽ có thể nhập tập tin vào máy khách Matrix khác trong tương lai, để khách hàng cũng có thể giải mã các thư này.\nTập tin được xuất sẽ cho phép bất kỳ ai có thể đọc nó để giải mã bất kỳ tin nhắn nào được mã hóa mà bạn có thể thấy, do đó bạn nên cẩn thận để giữ an toàn.";
+"e2e_export" = "Xuất";
+"e2e_passphrase_confirm" = "Xác nhận cụm mật khẩu";
+"e2e_passphrase_empty" = "Cụm mật khẩu không được để trống";
+"e2e_passphrase_not_match" = "Cụm mật khẩu phải trùng khớp";
+// Others
+"user_id_title" = "ID Người dùng:";
+"offline" = "ngoại tuyến";
+"unsent" = "Chưa được gửi";
+"error" = "Lỗi";
+"not_supported_yet" = "Chưa hỗ trợ";
+"default" = "mặc định";
+"private" = "Riêng tư";
+"public" = "Công khai";
+"power_level" = "Độ nhiệt huyết";
+"network_error_not_reachable" = "Vui lòng kiểm tra kết nối mạng của bạn";
+"user_id_placeholder" = "ví dụ: @bob:homeserver";
+"ssl_homeserver_url" = "Home Server URL: %@";
+// Permissions
+"camera_access_not_granted_for_call" = "Cuộc gọi video yêu cầu quyền truy cập tới máy ảnh nhưng %@ chưa có quyền để sử dụng nó";
+"microphone_access_not_granted_for_call" = "Cuộc gọi thoại yêu cầu truy cập tới Microphone nhưng %@ chưa có quyền để sử dụng nó";
+"local_contacts_access_not_granted" = "Người dùng tìm thấy từ địa chỉ liên hệ cục bộ yêu cầu quyền truy cập vào danh sách liên hệ của bạn nhưng %@ không có quyền sử dụng nó";
+"local_contacts_access_discovery_warning_title" = "Quét người dùng";
+"local_contacts_access_discovery_warning" = "%@ muốn tải lên danh sách email và số điện thoại từ danh bạ của bạn để quét người dùng";
+// Country picker
+"country_picker_title" = "Chọn một quốc gia";
+// Language picker
+"language_picker_title" = "Chọn một ngôn ngữ";
+"language_picker_default_language" = "Mặc định (%@)";
+"notice_room_invite" = "%@ đã mời %@";
+"notice_room_third_party_invite" = "%@ gửi một lời mời tới %@ để tham gia phòng";
+"notice_room_third_party_registered_invite" = "%@ chấp nhận lời mời cho %@";
+"notice_room_join" = "%@ đã tham gia";
+"notice_room_leave" = "%@ đã rời";
+"notice_room_reject" = "%@ từ chối lời yêu cầu";
+"notice_room_kick" = "%@ đã đá %@ ra khỏi phòng";
+"notice_room_unban" = "%@ đã huỷ cấm %@";
+"notice_room_ban" = "%@ đã cấm %@";
+"notice_room_withdraw" = "%@ đã thu hồi lời mời của %@";
+"notice_room_reason" = ". Lý do: %@";
+"notice_avatar_url_changed" = "%@ đã thay đổi ảnh đại diện của họ";
+"notice_display_name_set" = "%@ đặt tên hiển thị của họ thành %@";
+"notice_display_name_changed_from" = "%@ thay đổi tên hiển thị của họ từ %@ thành %@";
+"notice_display_name_removed" = "%@ đã gỡ bỏ tên hiển thị của họ";
+"notice_topic_changed" = "%@ thay đổi chủ đề thành: %@";
+"notice_room_name_changed" = "%@ đã thay đổi tên phòng thành: %@";
+"notice_placed_voice_call" = "%@ tạo một cuộc gọi thoại";
+"notice_placed_video_call" = "%@ tạo một cuộc gọi video";
+"notice_answered_video_call" = "%@ đã trả lời cuộc gọi";
+"notice_ended_video_call" = "%@ kết thúc cuộc gọi";
+"notice_conference_call_request" = "%@ yêu cầu một hội nghị VoIP";
+"notice_conference_call_started" = "Cuộc gọi hội nghị VoIP đã bắt đầu";
+"notice_conference_call_finished" = "Cuộc gọi hội nghị VoIP đã kết thúc";
+// button names
+"ok" = "OK";
+"cancel" = "Huỷ";
+"save" = "Lưu";
+"leave" = "Rời khỏi";
+"send" = "Gửi";
+"copy_button_name" = "Sao chép";
+"resend" = "Gửi lại";
+"redact" = "Biên tập lại";
+"share" = "Chia sẻ";
+"set_power_level" = "Độ nhiệt huyết";
+"delete" = "Xoá";
+"view" = "Xem";
+// actions
+"action_logout" = "Đăng xuất";
+"create_room" = "Tạo phòng";
+"login" = "Đăng nhập";
+"create_account" = "Tạo tài khoản";
+"membership_invite" = "Đã mời";
+"membership_leave" = "Đã rời";
+"membership_ban" = "Bị cấm";
+"num_members_one" = "%@ người dùng";
+"num_members_other" = "%@ người dùng";
+"invite" = "Mời";
+"kick" = "Đá";
+"ban" = "Cấm";
+"unban" = "Huỷ cấm";
+"message_unsaved_changes" = "Có vài thay đổi chưa được lưu. Rời khỏi sẽ mất những thay đổi này.";
+// Login Screen
+"login_error_already_logged_in" = "Đã đăng nhập";
+"login_error_must_start_http" = "URL phải bắt đầu với http[s]://";
+// room details dialog screen
+"room_details_title" = "Chi tiết phòng chat";
+// contacts list screen
+"invitation_message" = "Tôi muốn trò chuyện với bạn với matrix. Vui lòng truy cập trang web http://matrix.org để biết thêm thông tin.";
+// Settings screen
+"settings_title_config" = "Cấu hình";
+"settings_title_notifications" = "Thông báo";
+// Notification settings screen
+"notification_settings_disable_all" = "Tắt tất cả thông báo";
+"notification_settings_enable_notifications" = "Bật thông báo";
+"notification_settings_enable_notifications_warning" = "Tất cả thông báo hiện tại đang bị vô hiệu hoá cho tất cả thiết bị.";
+"notification_settings_global_info" = "Cài đặt thông báo được lưu vào tài khoản người dùng của bạn và được chia sẻ giữa tất cả các máy khách có hỗ trợ chúng (bao gồm thông báo trên máy bàn).\n\nCác quy tắc được áp dụng theo thứ tự; quy tắc đầu tiên khớp với định nghĩa của thông báo đến từ bên ngoài.\nVì vậy: Thông báo mỗi lần quan trọng hơn thông báo cho mỗi phòng điều mà quan trọng hơn thông báo mỗi người gửi.\nĐối với nhiều quy tắc cùng loại, lựa chọn đầu tiên trong danh sách phù hợp sẽ được ưu tiên.";
+"notification_settings_per_word_notifications" = "Thông báo mỗi từ";
+"notification_settings_per_word_info" = "Các từ khớp với trường hợp vô nghĩa và có thể bao gồm ký tự đại diện *. Vì thế:\nfoo phù hợp với chuỗi foo bao quanh bởi từ phân cách chữ (ví dụ: chấm câu và khoảng trắng hoặc bắt đầu / kết thúc dòng).\nfoo * khớp với bất kỳ từ nào bắt đầu foo.\n* foo * khớp bất kỳ từ nào trong đó bao gồm 3 chữ foo.";
+"notification_settings_always_notify" = "Luôn thông báo";
+"notification_settings_never_notify" = "Không bao giờ thông báo";
+"notification_settings_word_to_match" = "từ để so khớp";
+"notification_settings_highlight" = "Điểm nhấn";
+"notification_settings_custom_sound" = "Âm thanh tuỳ chỉnh";
+"notification_settings_per_room_notifications" = "Thông báo mỗi phòng";
+"notification_settings_per_sender_notifications" = "Thông báo mỗi người gửi";
+"notification_settings_sender_hint" = "@nguoidung:tenmien.com";
+"notification_settings_select_room" = "Chọn một phòng";
+"notification_settings_other_alerts" = "Các thông báo khác";
+"notification_settings_contain_my_user_name" = "Thông báo cho tôi với âm thanh về các tin nhắn chứa tên của tôi";
+"notification_settings_contain_my_display_name" = "Thông báo cho tôi với âm thanh về các tin nhắn chưa tên hiển thị của tôi";
+"notification_settings_just_sent_to_me" = "Thông báo cho tôi với âm thành về các tin nhắn chỉ gửi cho tôi";
+"notification_settings_invite_to_a_new_room" = "Thông báo cho tôi khi tôi được mời vào một phòng mới";
+"notification_settings_people_join_leave_rooms" = "Thông báo cho tôi khi mọi người tham gia hoặc rời các phòng";
+"notification_settings_receive_a_call" = "Thông báo cho tôi khi tôi nhận cuộc gọi";
+"notification_settings_suppress_from_bots" = "Chặn thông báo từ các bot";
+"notification_settings_by_default" = "Theo mặc định...";
+"notification_settings_notify_all_other" = "Thông báo cho tất cả tin nhắn/phòng";
+// gcm section
+"settings_config_home_server" = "Home server: %@";
+"settings_config_identity_server" = "Máy chủ xác thực: %@";
+"settings_config_user_id" = "ID Người dùng: %@";
+// call string
+"call_waiting" = "Đang đợi...";
+"call_connecting" = "Đang kết nối cuộc gọi...";
+"call_ended" = "Cuộc gọi kết thúc";
+"call_ring" = "Đang gọi...";
+"incoming_video_call" = "Cuộc gọi video tới";
+"incoming_voice_call" = "Cuộc gọi thoại tới";
+"call_invite_expired" = "Lời mời cuộc gọi đã quá hạn";
+// unrecognized SSL certificate
+"ssl_trust" = "Tin tưởng";
+"ssl_logout_account" = "Đăng xuất";
+"ssl_remain_offline" = "Bỏ qua";
+"ssl_fingerprint_hash" = "Vân tay (%@):";
+"ssl_could_not_verify" = "Không thể xác minh danh tính của máy chủ từ xa.";
+"ssl_cert_not_trust" = "Điều này có thể có nghĩa là ai đó đang ngăn chặn lưu lượng truy cập của bạn, hoặc điện thoại của bạn không tin tưởng vào chứng chỉ được cung cấp bởi máy chủ từ xa.";
+"ssl_cert_new_account_expl" = "Nếu quản trị viên máy chủ đã nói rằng điều này được mong đợi, đảm bảo rằng các dấu vân tay dưới đây phù hợp với dấu vân tay được cung cấp bởi chúng.";
+"ssl_unexpected_existing_expl" = "Chứng chỉ đã thay đổi từ chứng chỉ mà điện thoại của bạn tin cậy. Điều này là KHÔNG THƯỜNG XUYÊN. Chúng tôi khuyên bạn KHÔNG CHẤP NHẬN chứng chỉ mới này.";
+"ssl_expected_existing_expl" = "Chứng chỉ đã thay đổi từ một tài khoản đáng tin cậy trước đó sang chứng chỉ không đáng tin cậy. Máy chủ có thể đã gia hạn chứng chỉ của nó. Liên hệ với quản trị viên máy chủ để lấy dấu vân tay được mong đợi.";
+"ssl_only_accept" = "CHỈ chấp nhận chứng chỉ nếu quản trị viên máy chủ đã xuất bản một dấu vân tay phù hợp với bảng trên.";
diff --git a/Riot/Modules/MatrixKit/Assets/MatrixKitAssets.bundle/vls.lproj/MatrixKit.strings b/Riot/Modules/MatrixKit/Assets/MatrixKitAssets.bundle/vls.lproj/MatrixKit.strings
new file mode 100644
index 000000000..0e58e8e60
--- /dev/null
+++ b/Riot/Modules/MatrixKit/Assets/MatrixKitAssets.bundle/vls.lproj/MatrixKit.strings
@@ -0,0 +1,123 @@
+"matrix" = "Matrix";
+// Login Screen
+"login_create_account" = "Account anmoakn:";
+"login_server_url_placeholder" = "URL (bv. https://matrix.org)";
+"login_home_server_title" = "Thuusserver:";
+"login_home_server_info" = "Je thuusserver sloat al je gespreks- en accountgegeevns ip";
+"login_identity_server_title" = "Identiteitsserver:";
+"login_user_id_placeholder" = "Matrix-ID (bv. @jean:matrix.org of jean)";
+"login_password_placeholder" = "Paswoord";
+"login_optional_field" = "optioneel";
+"login_display_name_placeholder" = "Weergoavenoame (bv. Jean Kieckens)";
+"login_email_info" = "Door een e-mailadresse in te geevn kunn andere gebruukers je gemakkeliker ip Matrix wereviendn, en 't gift jen ook e maniere voor in den toekomst je paswoord te verandern.";
+"login_email_placeholder" = "E-mailadresse";
+"login_prompt_email_token" = "Gif jen e-mailadressevalidoatiebewys in:";
+"login_error_title" = "Anmeldn mislukt";
+"login_error_no_login_flow" = "Iphoaln van de authenticoatie-informoatie van dezen thuusserver is mislukt";
+"view" = "Toogn";
+"login_identity_server_info" = "Matrix verstrekt identiteitsservers vo t’achterhoaln wukke e-mailadressn e.d.m. dat der by wukke Matrix-ID’s hoorn. Tot nu toe bestoat alleene mo https://matrix.org.";
+"login_error_do_not_support_login_flows" = "Vo de moment biedn we geen oundersteunienge vo sommigste of alle anmeldiengsmethoodn van dezen thuusserver";
+"login_error_registration_is_not_supported" = "Registroasje wor vo de moment nie oundersteund";
+"login_error_forbidden" = "Oungeldig(e) gebrukersnoame/paswoord";
+"login_error_unknown_token" = "’t Ingegeevn toegangsbewys is nie herkend";
+"login_error_bad_json" = "Oungeldigen JSON";
+"login_error_not_json" = "Bevat gene geldigen JSON";
+"login_error_limit_exceeded" = "’t Zyn te vele verzoekn verzonden gewist";
+"login_error_user_in_use" = "Deze gebrukersnoame is al in gebruuk";
+"login_error_login_email_not_yet" = "De koppelienge in den e-mail is nog nie geopend gewist";
+"login_use_fallback" = "Weerevalblad gebruukn";
+"login_leave_fallback" = "Annuleern";
+"login_invalid_param" = "Oungeldige parameter";
+"register_error_title" = "Registroasje mislukt";
+"login_error_forgot_password_is_not_supported" = "Paswoord vergeten wor vo de moment nog nie oundersteund";
+"login_mobile_device" = "Gsm";
+"login_tablet_device" = "Tablet";
+"login_desktop_device" = "Computer";
+"login_error_resource_limit_exceeded_title" = "Bronlimiet overschreedn";
+"login_error_resource_limit_exceeded_message_default" = "Dezen thuusserver èt één of meer van z’n bronlimietn overschreedn.";
+"login_error_resource_limit_exceeded_message_monthly_active_user" = "Dezen thuusserver è z’n limiet vo moandeliks actieve gebrukers bereikt.";
+"login_error_resource_limit_exceeded_message_contact" = "\n\nContacteert je dienstbeheerder vo deze dienst te bluuvn gebruukn.";
+"login_error_resource_limit_exceeded_contact_button" = "Beheerder contacteern";
+// Action
+"no" = "Nee";
+"yes" = "Ja";
+"abort" = "Afbreekn";
+"back" = "Weere";
+"close" = "Sluutn";
+"continue" = "Verdergoan";
+"discard" = "Verwerpn";
+"dismiss" = "Sluutn";
+"retry" = "Herprobeern";
+"sign_up" = "Anmeldn";
+"submit" = "Indienn";
+"submit_code" = "Code indienn";
+"set_power_level" = "Machtsniveau instelln";
+"set_default_power_level" = "Machtsniveau herinstelln";
+"set_moderator" = "Benoemn tou moderator";
+"set_admin" = "Benoemn tou beheerder";
+"start_chat" = "Gesprek beginn";
+"start_voice_call" = "Sproakiproep beginn";
+"start_video_call" = "Video-iproep beginn";
+"mention" = "Vermeldn";
+"select_account" = "Selecteert een account";
+"attach_media" = "Media van de bibliotheek byvoegn";
+"capture_media" = "Fotootje/filmtje moakn";
+"invite_user" = "Matrix-gebruker uutnodign";
+"reset_to_default" = "Standoardwoardn herstelln";
+"resend_message" = "Bericht hersteurn";
+"select_all" = "Alles selecteern";
+"cancel_upload" = "Upload annuleern";
+"cancel_download" = "Download annuleern";
+"show_details" = "Details weeregeevn";
+"answer_call" = "Iproep beantwoordn";
+"reject_call" = "Iproep afwyzn";
+"end_call" = "Iphangn";
+"ignore" = "Negeern";
+"unignore" = "Stoppen me negeern";
+// Events formatter
+"notice_avatar_changed_too" = "(profielfoto is ook veranderd gewist)";
+"notice_room_name_removed" = "%@ èt de gespreksnoame verwyderd";
+"notice_room_topic_removed" = "%@ è ’t ounderwerp verwyderd";
+"notice_event_redacted" = "";
+"notice_event_redacted_by" = " deur %@";
+"notice_event_redacted_reason" = " [reden: %@]";
+"notice_profile_change_redacted" = "%@ èt zyn/heur profiel bygewerkt %@";
+"notice_room_created" = "%@ è ’t gesprek angemakt";
+"notice_room_join_rule" = "De toetrediengsregel is: %@";
+"notice_room_power_level_intro" = "’t Machtsniveau van gespreksleden is:";
+"notice_room_power_level_acting_requirement" = "De minimoale machtsniveau waarover dat e gebruker moe beschikkn vooraleer da t’n kut handeln zyn:";
+"notice_room_power_level_event_requirement" = "De minimoale machtsniveaus gerelateerd an gebeurtenissn zyn:";
+"notice_room_aliases" = "De gespreksbynoamn zyn: %@";
+"notice_room_related_groups" = "De groepn da geassocieerd zyn me da gesprek hier zyn: %@";
+"notice_encrypted_message" = "Versleuterd bericht";
+"notice_encryption_enabled" = "%@ èt end-tout-end-versleuterienge angezet (%@-algoritme)";
+"notice_image_attachment" = "fotobylage";
+"notice_audio_attachment" = "geluudsbylage";
+"notice_video_attachment" = "videobylage";
+"notice_location_attachment" = "locoasjebylage";
+"notice_file_attachment" = "bestandsbylage";
+"notice_invalid_attachment" = "oungeldige bylage";
+"notice_unsupported_attachment" = "Nie-oundersteunde bylage: %@";
+"notice_feedback" = "Feedbackgebeurtenisse (id: %@): %@";
+"notice_redaction" = "%@ èt e gebeurtenisse verwyderd (id: %@)";
+"notice_error_unsupported_event" = "Nie-oundersteunde gebeurtenisse";
+"notice_error_unexpected_event" = "Ounverwachte gebeurtenisse";
+"notice_error_unknown_event_type" = "Ounbekend gebeurtenistype";
+"notice_room_history_visible_to_anyone" = "%@ èt de toekomstige gesprekgeschiedenisse voor iedereen zichtboar gemakt.";
+"cancel" = "Annuleern";
+"save" = "Ipsloan";
+"leave" = "Verloatn";
+"invite" = "Uutnodign";
+"notice_room_history_visible_to_members" = "%@ èt de toekomstige gespreksgeschiedenisse voor alle gespreksleedn zichtboar gemakt ghed.";
+"notice_crypto_unable_to_decrypt" = "** Kostege nie ountsleutern: %@ **";
+"notice_crypto_error_unknown_inbound_session_id" = "’t Toestel van den afzender èt uus geen sleuters vo da bericht hier gestuurd ghed.";
+"notice_sticker" = "sticker";
+"notice_in_reply_to" = "In antwoord op";
+// room display name
+"room_displayname_empty_room" = "Leeg gesprek";
+"room_displayname_two_members" = "%@ en %@";
+"room_displayname_more_than_two_members" = "%@ en %@ anderen";
+// Settings
+"settings" = "Instelliengn";
+"settings_enable_inapp_notifications" = "In-app-meldiengn inschoakeln";
+"settings_enable_push_notifications" = "Pushmeldiengn inschoakeln";
diff --git a/Riot/Modules/MatrixKit/Assets/MatrixKitAssets.bundle/zh_Hans.lproj/MatrixKit.strings b/Riot/Modules/MatrixKit/Assets/MatrixKitAssets.bundle/zh_Hans.lproj/MatrixKit.strings
new file mode 100644
index 000000000..b1a270dd2
--- /dev/null
+++ b/Riot/Modules/MatrixKit/Assets/MatrixKitAssets.bundle/zh_Hans.lproj/MatrixKit.strings
@@ -0,0 +1,480 @@
+"view" = "视图";
+"back" = "返回";
+"continue" = "继续";
+"leave" = "离开";
+"invite" = "邀请";
+"retry" = "重试";
+"cancel" = "取消";
+"save" = "保存";
+// room details dialog screen
+"room_details_title" = "聊天室详情";
+"matrix" = "Matrix";
+// Login Screen
+"login_create_account" = "创建账户:";
+"login_server_url_placeholder" = "网址(例如 https://matrix.org)";
+"login_home_server_title" = "主服务器网址:";
+"login_home_server_info" = "您的主服务器存储了您所有的对话和账户数据";
+"login_identity_server_title" = "身份认证服务器网址:";
+"login_identity_server_info" = "Matrix 提供了身份认证服务器以验证邮箱地址等是否属于某个 Matrix ID。目前只有 https://matrix.org 支持。";
+"login_user_id_placeholder" = "Matrix ID(例如 @bob:matrix.org 或 bob)";
+"login_password_placeholder" = "密码";
+"login_optional_field" = "可选";
+"login_display_name_placeholder" = "显示名(例如 Bob Obson)";
+"login_email_placeholder" = "电子邮件地址";
+"login_prompt_email_token" = "请输入您的电子邮件认证令牌:";
+"login_error_title" = "登录失败";
+"login_error_registration_is_not_supported" = "目前不支持注册";
+"login_error_forbidden" = "无效的用户名/密码";
+"login_error_unknown_token" = "不能识别特定访问令牌";
+"login_error_bad_json" = "JSON 格式错误";
+"login_error_not_json" = "不包含有效的 JSON";
+"login_error_limit_exceeded" = "发送了太多的请求";
+"login_error_user_in_use" = "此用户名已占用";
+"login_error_login_email_not_yet" = "此电子邮件链接还没有被点击";
+"login_use_fallback" = "使用备用页面";
+"login_leave_fallback" = "取消";
+"login_invalid_param" = "参数无效";
+"register_error_title" = "注册失败";
+"login_error_forgot_password_is_not_supported" = "当前不支持忘记密码";
+// Action
+"no" = "否";
+"yes" = "是";
+"abort" = "中止";
+"close" = "关闭";
+"discard" = "放弃";
+"dismiss" = "无视";
+"sign_up" = "注册";
+"submit" = "提交";
+"submit_code" = "提交码";
+"set_default_power_level" = "重置权限级别";
+"set_moderator" = "设置主持人";
+"set_admin" = "设置管理员";
+"start_chat" = "启动聊天";
+"start_voice_call" = "启动语音通话";
+"start_video_call" = "启动视频通话";
+"mention" = "提到";
+"select_account" = "选择一个账户";
+"attach_media" = "从库附加媒体";
+"capture_media" = "拍照片/视频";
+"invite_user" = "邀请 Matrix 用户";
+"reset_to_default" = "重置到默认";
+"resend_message" = "重新发送此消息";
+"select_all" = "选择全部";
+"cancel_upload" = "取消上传";
+"cancel_download" = "取消下载";
+"show_details" = "显示详情";
+"answer_call" = "接听通话";
+"reject_call" = "拒绝通话";
+"end_call" = "结束通话";
+"ignore" = "忽略";
+"unignore" = "取消忽略";
+// Events formatter
+"notice_avatar_changed_too" = "(头像也已经改变)";
+"notice_room_name_removed" = "%@ 移除了此聊天室的名称";
+"notice_room_topic_removed" = "%@ 移除了话题";
+"notice_event_redacted" = "";
+"notice_event_redacted_by" = " 由 %@";
+"notice_event_redacted_reason" = " [理由:%@]";
+"notice_profile_change_redacted" = "%@ 已经更新了他的个人档案 %@";
+"notice_room_created" = "%@ 创建并配置了此聊天室。";
+"notice_room_join_rule" = "加入规则是:%@";
+"notice_room_power_level_intro" = "此聊天室成员的权限级别是:";
+"notice_room_power_level_acting_requirement" = "要进行此操作,用户必须具备的最低权限级别是 :";
+"notice_room_power_level_event_requirement" = "事件所需的最小权限级别:";
+"notice_room_aliases" = "此聊天室的别名是:%@";
+"notice_encrypted_message" = "已加密消息";
+"notice_encryption_enabled" = "%@ 打开了端对端加密(算法 %@)";
+"notice_image_attachment" = "图片附件";
+"notice_audio_attachment" = "音频附件";
+"notice_video_attachment" = "视频附件";
+"notice_location_attachment" = "位置附件";
+"notice_file_attachment" = "文件附件";
+"notice_invalid_attachment" = "无效附件";
+"notice_unsupported_attachment" = "不支持的附件:%@";
+"login_email_info" = "指定邮箱地址可以让其他 Matrix 用户更容易找到您,并允许您可以在未来重置密码。";
+"login_error_no_login_flow" = "我们未能从此主服务器获取认证信息";
+"login_error_do_not_support_login_flows" = "当前我们不支持此主服务器定义的任何或者所有登录流";
+"notice_feedback" = "反馈事件 (id: %@):%@";
+"notice_redaction" = "%@ 取消了一个事件 (id: %@)";
+"notice_error_unsupported_event" = "不支持的事件";
+"notice_error_unexpected_event" = "意外的事件";
+"notice_error_unknown_event_type" = "未知的事件类型";
+"notice_room_history_visible_to_anyone" = "%@ 将未来的聊天室消息历史设为对所有人可见。";
+"notice_room_history_visible_to_members" = "%@ 将未来的聊天室消息历史设为对所有聊天室成员可见。";
+"notice_room_history_visible_to_members_from_invited_point" = "你将未来的聊天室消息历史设为对所有聊天室成员可见,从他们被邀请时开始。";
+"notice_room_history_visible_to_members_from_joined_point" = "%@ 将未来的聊天室消息历史设为对所有聊天室成员可见,从他们加入时开始。";
+"notice_crypto_unable_to_decrypt" = "** 无法解密:%@ **";
+"notice_crypto_error_unknown_inbound_session_id" = "发送者的会话没有向我们发送此消息的密钥。";
+// room display name
+"room_displayname_empty_room" = "空聊天室";
+"room_displayname_two_members" = "%@ 和 %@";
+"room_displayname_more_than_two_members" = "%@ 和 %@ 个其他人";
+// Settings
+"settings" = "设置";
+"settings_enable_inapp_notifications" = "启用应用内通知";
+"settings_enable_push_notifications" = "启用推送通知";
+"settings_enter_validation_token_for" = "请输入 %@ 的验证令牌:";
+"notification_settings_room_rule_title" = "聊天室:“%@”";
+// Devices
+"device_details_title" = "会话信息\n";
+"device_details_name" = "公开名称\n";
+"device_details_identifier" = "ID\n";
+"device_details_last_seen" = "最近一次上线\n";
+"device_details_last_seen_format" = "%@ @ %@\n";
+"device_details_rename_prompt_message" = "会话的公开名称对与你联络的人可见";
+"device_details_delete_prompt_title" = "认证";
+"device_details_delete_prompt_message" = "此操作需要额外的认证。\n要继续,请输入您的密码。";
+// Encryption information
+"room_event_encryption_info_title" = "端对端加密信息\n\n";
+"room_event_encryption_info_event" = "事件信息\n";
+"room_event_encryption_info_event_user_id" = "用户 ID\n";
+"room_event_encryption_info_event_identity_key" = "Curve25519 认证密钥\n";
+"login_mobile_device" = "移动设备";
+"login_tablet_device" = "平板电脑";
+"login_desktop_device" = "桌面设备";
+"notice_room_related_groups" = "和此聊天室关联的社群是:%@";
+"room_event_encryption_info_event_session_id" = "会话 ID\n";
+"room_event_encryption_info_event_decryption_error" = "解密错误\n";
+"room_event_encryption_info_event_unencrypted" = "未加密";
+"room_event_encryption_info_event_none" = "无";
+"room_event_encryption_info_device" = "\n发送者的会话信息\n";
+"room_event_encryption_info_device_unknown" = "未知会话\n";
+"room_event_encryption_info_device_name" = "公开名称\n";
+"room_event_encryption_info_device_id" = "ID\n";
+"room_event_encryption_info_device_verification" = "验证\n";
+"room_event_encryption_info_device_verified" = "已验证";
+"room_event_encryption_info_device_not_verified" = "未验证";
+"room_event_encryption_info_device_blocked" = "已拉黑";
+"room_event_encryption_info_verify" = "验证…";
+"room_event_encryption_info_unverify" = "取消验证";
+"room_event_encryption_verify_title" = "验证会话\n\n";
+"account_error_display_name_change_failed" = "昵称修改失败";
+"account_error_email_wrong_title" = "邮箱地址无效";
+"account_error_msisdn_wrong_title" = "手机号码无效";
+// Room creation
+"room_creation_name_title" = "聊天室名称:";
+"room_creation_name_placeholder" = "(例如:今天中午吃啥)";
+// Room
+"room_please_select" = "请选择一个聊天室";
+"room_error_join_failed_title" = "加入聊天室失败";
+"room_error_cannot_load_timeline" = "时间线加载失败";
+"room_error_timeline_event_not_found_title" = "时间线位置加载失败";
+"room_left" = "你离开了聊天室";
+"attachment_small" = "小 (~%@)";
+"attachment_medium" = "中 (~%@)";
+"attachment_large" = "大 (~%@)";
+"attachment_cancel_download" = "是否取消下载?";
+"attachment_cancel_upload" = "是否取消上传?";
+"attachment_e2e_keys_import" = "导入…";
+// Search
+"search_no_results" = "没有结果";
+"search_searching" = "正在搜索…";
+// Time
+"format_time_s" = "秒";
+"format_time_m" = "分钟";
+"format_time_h" = "小时";
+"format_time_d" = "天";
+// E2E import
+"e2e_import_room_keys" = "导入聊天室密钥";
+"e2e_import" = "导入";
+"e2e_passphrase_enter" = "输入密码";
+// E2E export
+"e2e_export_room_keys" = "导出聊天室密钥";
+"e2e_export" = "导出";
+"e2e_passphrase_empty" = "密码不能为空";
+// Others
+"user_id_title" = "用户 ID:";
+"offline" = "离线";
+"unsent" = "取消发送";
+"error" = "错误";
+"default" = "默认";
+"private" = "私人";
+"public" = "公开";
+"power_level" = "权限级别";
+"network_error_not_reachable" = "请检查你的网络连接";
+"user_id_placeholder" = "例如:@bob:homeserver";
+"ssl_homeserver_url" = "主服务器网址:%@";
+// Country picker
+"country_picker_title" = "请选择国家";
+// Language picker
+"language_picker_title" = "请选择语言";
+"language_picker_default_language" = "默认(%@)";
+"notice_room_invite" = "%@ 邀请了 %@";
+"notice_room_third_party_invite" = "%@ 邀请 %@ 加入聊天室";
+"notice_room_third_party_registered_invite" = "%@ 同意了 %@ 的邀请";
+"notice_room_join" = "%@ 已加入";
+"notice_room_leave" = "%@ 已退出";
+"notice_room_reject" = "%@ 拒绝了邀请";
+"notice_room_kick" = "%@ 移除了 %@";
+"notice_room_unban" = "%@ 解封了 %@";
+"notice_room_ban" = "%@ 封禁了 %@";
+"notice_room_withdraw" = "%@ 撤回了对 %@ 的邀请";
+"notice_room_reason" = "。理由:%@";
+"notice_avatar_url_changed" = "%@ 更换了头像";
+"notice_display_name_set" = "%@ 将自己的昵称设置为 %@";
+"notice_display_name_changed_from" = "%@ 将自己的昵称从 %@ 改成 %@";
+"notice_display_name_removed" = "%@ 删除了自己的昵称";
+"notice_room_name_changed" = "%@ 将聊天室名称修改为 %@。";
+// button names
+"ok" = "确定";
+"send" = "发送";
+"copy_button_name" = "复制";
+"resend" = "重新发送";
+"share" = "分享";
+"redact" = "移除";
+"set_power_level" = "设置权限级别";
+"delete" = "删除";
+"create_room" = "创建聊天室";
+"login" = "登录";
+"create_account" = "创建账号";
+"membership_invite" = "邀请";
+"membership_leave" = "退出";
+"membership_ban" = "已被封禁";
+"num_members_one" = "%@ 位用户";
+"num_members_other" = "%@ 位用户";
+"kick" = "移除";
+"ban" = "封禁";
+"unban" = "解封";
+// Login Screen
+"login_error_already_logged_in" = "已登录";
+"login_error_must_start_http" = "URL 必须以 http[s]:// 开头";
+// Settings screen
+"settings_title_config" = "选项";
+"settings_title_notifications" = "通知";
+// contacts list screen
+"invitation_message" = "我想使用 Matrix 和你聊天。请访问 https://martix.org 以了解更多信息。";
+"account_error_matrix_session_is_not_opened" = "没有打开 Matrix 会话";
+"account_error_email_wrong_description" = "此邮箱地址似乎是无效的";
+"account_error_msisdn_wrong_description" = "此手机号码似乎是无效的";
+"room_creation_participants_placeholder" = "(例如:@bob:homeserver1; @john:homeserver2…)";
+"room_creation_participants_title" = "成员:";
+"room_creation_alias_placeholder_with_homeserver" = "(例如:#foo%@)";
+"room_creation_alias_placeholder" = "(例如:#foo:example.org)";
+"room_error_join_failed_empty_room" = "目前无法加入空聊天室。";
+"room_error_name_edition_not_authorized" = "你没有修改聊天室名称所需的权限";
+"room_error_topic_edition_not_authorized" = "你没有修改聊天室话题所需的权限";
+// Room members
+"room_member_ignore_prompt" = "你确定要隐藏所有此用户发送的消息吗?";
+// Contacts
+"contact_mx_users" = "Matrix 用户";
+"contact_local_contacts" = "本地联系人";
+// Groups
+"group_invite_section" = "邀请";
+"group_section" = "群组";
+"e2e_passphrase_confirm" = "确认密码";
+"notification_settings_enable_notifications" = "启用通知";
+// Notification settings screen
+"notification_settings_disable_all" = "禁用通知";
+"notification_settings_highlight" = "高亮";
+"notification_settings_custom_sound" = "自定义铃声";
+"notification_settings_select_room" = "选择一个聊天室";
+"notification_settings_other_alerts" = "其他警报";
+// gcm section
+"settings_config_home_server" = "主服务器:%@";
+"settings_config_identity_server" = "身份认证服务器:%@";
+"settings_config_user_id" = "用户 ID:%@";
+"call_ended" = "通话结束";
+// unrecognized SSL certificate
+"ssl_trust" = "信任";
+"ssl_logout_account" = "登出";
+// actions
+"action_logout" = "登出";
+"ssl_remain_offline" = "忽略";
+"message_unsaved_changes" = "尚有未经保存的修改。现在退出将会取消这些修改。";
+"notice_sticker" = "贴纸";
+"ssl_could_not_verify" = "无法验证远程服务器的身份。";
+// Account
+"account_save_changes" = "保存更改";
+"account_link_email" = "邮箱地址";
+"account_linked_emails" = "邮箱地址";
+"account_email_validation_title" = "等待验证中";
+"account_msisdn_validation_title" = "等待验证中";
+"account_msisdn_validation_error" = "无法验证此手机号。";
+"account_error_picture_change_failed" = "头像修改失败";
+"e2e_passphrase_not_match" = "密码必须匹配";
+"not_supported_yet" = "尚未支持";
+"local_contacts_access_discovery_warning_title" = "发现用户";
+"notice_topic_changed" = "%@ 将话题修改为 \"%@\"。";
+"notice_placed_voice_call" = "%@ 发起了语音通话";
+"notice_placed_video_call" = "%@ 发起了视频通话";
+"notice_answered_video_call" = "%@ 接听了通话";
+"notice_ended_video_call" = "%@ 结束了通话";
+"notification_settings_always_notify" = "总是通知";
+"notification_settings_never_notify" = "从不通知";
+"notification_settings_sender_hint" = "@user:domain.com";
+// call string
+"call_waiting" = "请等待…";
+"call_connecting" = "连接中…";
+"call_ring" = "正在通话…";
+"room_event_encryption_info_event_algorithm" = "算法\n";
+"room_event_encryption_verify_message" = "为验证此会话是否可信,请通过其他方式(例如当面交换或拨打电话)与其拥有者联系,并询问他们该会话的用户设置中的密钥是否与以下密钥匹配:\n\n\t会话名称:%@\n\t会话 ID:%@\n\t会话密钥:%@\n\n如果匹配,请点击下面的按钮。如果不匹配,那么说明有其他人截取了此会话,您可能想点击黑名单按钮。\n\n未来,这个验证过程将会变得更加精致、巧妙一些。";
+"room_event_encryption_info_event_fingerprint_key" = "声称的 Ed25519 指纹密钥\n";
+"room_event_encryption_info_device_fingerprint" = "Ed25519 指纹\n";
+"room_event_encryption_verify_ok" = "验证";
+"room_event_encryption_info_block" = "拉黑";
+"room_event_encryption_info_unblock" = "取消拉黑";
+"room_creation_alias_title" = "聊天室别称:";
+"notice_conference_call_started" = "VoIP 会议已开始";
+"notice_conference_call_request" = "%@ 发起了 VoIP 会议";
+"notice_conference_call_finished" = "VoIP 会议已结束";
+"notification_settings_enable_notifications_warning" = "所有设备上的通知都已被禁用。";
+"notice_in_reply_to" = "回复";
+"account_email_validation_message" = "请检查您的电子邮箱并点击邮件中的链接。完成此操作后,点击继续。";
+"account_email_validation_error" = "无法验证邮箱地址。请检查你的电子邮箱并点击邮件中的链接。完成后,请点击继续";
+"login_error_resource_limit_exceeded_title" = "超出资源使用限制";
+"login_error_resource_limit_exceeded_contact_button" = "联系管理员";
+"login_error_resource_limit_exceeded_message_default" = "此主服务器已超出某资源的使用限制。";
+"login_error_resource_limit_exceeded_message_monthly_active_user" = "此主服务器已达到月活跃用户限制。";
+"login_error_resource_limit_exceeded_message_contact" = "\n\n请联系管理员以继续使用本服务。";
+"account_msisdn_validation_message" = "我们通过短信向您发送了一条验证码,请在下方输入它。";
+"room_error_timeline_event_not_found" = "应用程序试图加载此聊天室时间线中的特定点,但无法找到该时间点";
+"room_no_power_to_create_conference_call" = "您需要获得邀请权限才能在此聊天室开始会议";
+"room_no_conference_call_in_encrypted_rooms" = "加密聊天室暂不支持通话会议";
+// Reply to message
+"message_reply_to_sender_sent_an_image" = "发送了一张图片。";
+"message_reply_to_sender_sent_an_audio_file" = "发送了一个音频文件。";
+"message_reply_to_sender_sent_a_file" = "发送了一个文件。";
+"message_reply_to_message_to_reply_to_prefix" = "回复";
+"room_member_power_level_prompt" = "该用户将被提升至与您一样的权限级别,所以此操作将无法撤销。\n您确定吗?";
+// Attachment
+"attachment_size_prompt" = "发送为:";
+"attachment_original" = "实际大小 (%@)";
+"attachment_multiselection_size_prompt" = "发送图片为:";
+"attachment_multiselection_original" = "实际大小";
+"attachment_e2e_keys_file_prompt" = "此文件含有 Matrix 客户端导出的密钥。\n您想要查看文件内容还是导入密钥?";
+"message_reply_to_sender_sent_a_video" = "发送了一段视频。";
+"call_invite_expired" = "通话邀请已过期";
+"ssl_fingerprint_hash" = "指纹(%@):";
+"e2e_import_prompt" = "此操作允许您导入此前从其他 Matrix 客户端上导出的加密密钥。您将能够解密任何该客户端能解密的消息。\n该导出文件受密码保护。您应在此处输入密码以解密该文件。";
+"e2e_export_prompt" = "此操作允许您将加密聊天室中接收到的消息导出为一个本地文件。您将来可以将此文件导入到其他 Matrix 客户端中去解密这些消息。\n导出的文件将允许任何能够读取它的人解密您可以看到的任何加密消息,因此您应该小心保证其安全。";
+"e2e_passphrase_create" = "创建密码";
+"error_common_message" = "出现错误。请稍后再试。";
+// Permissions
+"camera_access_not_granted_for_call" = "视频通话需要摄像头使用权限,但 %@ 无此权限";
+"microphone_access_not_granted_for_call" = "通话需要麦克风使用权限,但 %@ 无此权限";
+"local_contacts_access_not_granted" = "本地通讯录用户查找功能需要通讯录权限,但 %@ 无此权限";
+"local_contacts_access_discovery_warning" = "为了发现已经使用 Matrix 的联系人,%@ 可以把你地址簿里的邮箱地址和电话号码发送给你选定的 Matrix 身份认证服务器。如果支持的话,个人数据会在发送前被哈希——请检查你的身份认证服务器的隐私条款获知更多细节。";
+"notification_settings_global_info" = "通知设置已保存在您的账号中并在所有支持的客户端中共享(包括桌面通知)。\n\n规则会按顺序应用;第一条匹配的规则定义了消息的输出结果。\n因此:按字符规则的通知比按聊天室规则的通知级别更高,而这两者都比按发送者规则的通知级别更高。\n对于同一类型的多条规则,匹配列表中的第一条优先级最高。";
+"notification_settings_per_word_notifications" = "按字符通知";
+"notification_settings_per_word_info" = "单词不区分大小写,并且可能包含 * 通配符。 所以:\nfoo 匹配由单词分隔符包围的字符串 foo(例如标点符号和空格,或一行的开头/结尾)。\nfoo* 匹配任何以 foo 开头的单词。\n*foo* 匹配任何包含3个字母 foo 的单词。";
+"notification_settings_word_to_match" = "匹配的单词";
+"notification_settings_per_room_notifications" = "按聊天室通知";
+"notification_settings_per_sender_notifications" = "按发送者通知";
+"notification_settings_contain_my_user_name" = "有包含我的用户名的消息时用铃声通知";
+"notification_settings_contain_my_display_name" = "有包含我的昵称的消息时用铃声通知";
+"notification_settings_just_sent_to_me" = "有发送给我的消息时用铃声通知";
+"notification_settings_invite_to_a_new_room" = "我被邀请去一个新聊天室时用铃声通知";
+"notification_settings_people_join_leave_rooms" = "有人加入或离开聊天室时发送通知";
+"notification_settings_receive_a_call" = "当我收到通话请求时发送通知";
+"notification_settings_suppress_from_bots" = "取消来自机器人的通知";
+"notification_settings_by_default" = "默认…";
+"notification_settings_notify_all_other" = "为所有其他消息/聊天室发送通知";
+"incoming_video_call" = "视频通话来电";
+"incoming_voice_call" = "语音通话来电";
+"ssl_cert_not_trust" = "这可能意味着有人正在恶意劫持您的流量,或者您的手机不信任远程服务器提供的数字证书。";
+"ssl_cert_new_account_expl" = "如果服务器管理员说这是预期的情况,请确保下面的指纹与管理员提供的指纹相匹配。";
+"ssl_unexpected_existing_expl" = "证书已从一个先前受您的设备信任的证书更改为另一个。这非常反常!建议您 不要 接受此新证书。";
+"ssl_expected_existing_expl" = "证书已从曾受信任的证书更改为不受信任的证书。服务器可能已更新其证书,请联系管理员并核对服务器的指纹。";
+"ssl_only_accept" = "请 仅 在服务器管理员发布了与上述指纹匹配的指纹的情况下接受该证书。";
+"notice_encryption_enabled_ok" = "%@ 启用了端到端加密。";
+"notice_encryption_enabled_unknown_algorithm" = "%1$@ 启用了端到端加密(无法识别的算法 %2$@)。";
+"device_details_rename_prompt_title" = "会话名称";
+"account_error_push_not_allowed" = "未允许通知";
+"notice_room_third_party_revoked_invite" = "%@ 撤回了对 %@ 加入聊天室的邀请";
+"notice_encryption_enabled_ok_by_you" = "你启用了端对端加密。";
+"notice_room_created_by_you_for_dm" = "你加入了。";
+"notice_room_topic_removed_by_you" = "你移除了话题";
+"notice_room_name_removed_by_you_for_dm" = "你移除了名称";
+"notice_room_name_removed_by_you" = "你移除了聊天室名称";
+"notice_conference_call_request_by_you" = "你请求了 VoIP 会议";
+"notice_room_ban_by_you" = "你封禁了 %@";
+"notice_room_unban_by_you" = "你解封了 %@";
+"notice_room_kick_by_you" = "你移除了 %@";
+"notice_room_reject_by_you" = "你拒绝了邀请";
+"notice_room_leave_by_you" = "你退出了";
+"notice_room_join_by_you" = "你加入了";
+"notice_room_third_party_registered_invite_by_you" = "你接受了 %@ 的邀请";
+"notice_room_third_party_invite_by_you_for_dm" = "你邀请了 %@";
+"notice_room_invite_you" = "%@ 邀请了你";
+
+// Notice Events with "You"
+"notice_room_invite_by_you" = "你邀请了 %@";
+"notice_declined_video_call" = "%@ 拒接了通话";
+"notice_room_name_changed_for_dm" = "%@ 将名称修改为 %@。";
+"notice_room_third_party_revoked_invite_for_dm" = "%@ 撤回了对 %@ 的邀请";
+"notice_room_third_party_invite_for_dm" = "%@ 邀请了 %@";
+"room_left_for_dm" = "你离开了";
+"notice_room_power_level_intro_for_dm" = "成员的权限级别是:";
+"notice_room_aliases_for_dm" = "别名是:%@";
+"notice_room_created_for_dm" = "%@ 已加入。";
+"notice_room_name_removed_for_dm" = "%@ 移除了名称";
+"resume_call" = "恢复";
+"notice_encryption_enabled_unknown_algorithm_by_you" = "你启用了端对端加密(无法识别的算法 %@)。";
+"notice_event_redacted_by_you" = " 被你";
+"notice_room_third_party_revoked_invite_by_you" = "你撤回了对 %@ 加入此聊天室的邀请";
+"notice_room_join_rule_public_by_you_for_dm" = "你将此聊天设为公开。";
+"notice_room_join_rule_public_by_you" = "你将此聊天室设为公开。";
+"notice_room_join_rule_public_for_dm" = "%@ 将此聊天设为公开。";
+"notice_room_join_rule_public" = "%@ 将此聊天室设为公开。";
+"notice_room_join_rule_invite_by_you_for_dm" = "你将此聊天设为仅邀请。";
+"notice_room_join_rule_invite_by_you" = "你将此聊天室设为仅邀请。";
+"notice_room_join_rule_invite_for_dm" = "%@ 将此聊天设为仅邀请。";
+// New
+"notice_room_join_rule_invite" = "%@ 将此聊天室设为仅邀请。";
+"notice_room_history_visible_to_members_from_invited_point_for_dm" = "%@ 将未来的消息设为对所有人可见,从他们被邀请时开始。";
+"notice_room_history_visible_to_members_from_invited_point_by_you_for_dm" = "你将未来的消息设为对所有人可见,从他们被邀请时开始。";
+"notice_room_history_visible_to_members_from_joined_point_by_you_for_dm" = "你将未来的消息设为对所有人可见,从他们加入时开始。";
+"notice_room_history_visible_to_members_from_joined_point_for_dm" = "%@ 将未来的消息设为对所有人可见,从他们加入时开始。";
+"notice_room_history_visible_to_members_from_joined_point_by_you" = "你将未来的聊天室消息历史设为对所有聊天室成员可见,从他们加入时开始。";
+"notice_room_history_visible_to_members_from_invited_point_by_you" = "你将未来的聊天室消息历史设为对所有聊天室成员可见,从他们被邀请时开始。";
+"notice_room_history_visible_to_members_for_dm" = "%@ 将未来的消息设为对所有聊天室成员可见。";
+"notice_room_third_party_invite_by_you" = "你邀请 %@ 加入此聊天室";
+"notice_room_history_visible_to_members_by_you_for_dm" = "你将未来的消息设为对所有聊天室成员可见。";
+"notice_room_history_visible_to_members_by_you" = "你将未来的聊天室消息历史设为对所有聊天室成员可见。";
+"notice_room_history_visible_to_anyone_by_you" = "你将未来的聊天室消息历史设为对所有人可见。";
+"notice_redaction_by_you" = "你撤回了一个事件(id:%@)";
+"notice_room_withdraw_by_you" = "你撤回了对 %@ 的邀请";
+"notice_room_third_party_revoked_invite_by_you_for_dm" = "你撤回了对 %@ 的邀请";
+"call_more_actions_dialpad" = "拨号键盘";
+"call_more_actions_transfer" = "转移";
+"call_more_actions_audio_use_device" = "设备扬声器";
+"call_more_actions_audio_use_headset" = "使用耳机音频";
+"call_more_actions_change_audio_device" = "更改音频设备";
+"call_more_actions_unhold" = "继续";
+"call_more_actions_hold" = "挂起";
+"call_holded" = "你挂起了通话";
+"call_remote_holded" = "%@ 挂起了通话";
+"notice_room_created_by_you" = "你创建并配置了此聊天室。";
+"notice_profile_change_redacted_by_you" = "你更新了你的资料 %@";
+"notice_declined_video_call_by_you" = "你拒绝了通话";
+"notice_ended_video_call_by_you" = "你挂断了通话";
+"notice_answered_video_call_by_you" = "你接听了通话";
+"notice_placed_video_call_by_you" = "你发起了视频通话";
+"notice_placed_voice_call_by_you" = "你发起了语音通话";
+"notice_room_name_changed_by_you_for_dm" = "你将名称修改为 %@。";
+"notice_room_name_changed_by_you" = "你将聊天室名称修改为 %@。";
+"notice_topic_changed_by_you" = "你将话题修改为 \"%@\"。";
+"notice_display_name_set_by_you" = "你将你的昵称设置为 %@";
+"notice_display_name_changed_from_by_you" = "你将你的昵称从 %@ 更改为 %@";
+"notice_display_name_removed_by_you" = "你移除了你的昵称";
+"notice_avatar_url_changed_by_you" = "你更换了头像";
+"call_transfer_to_user" = "转接到 %@";
+"call_ringing" = "响铃中…";
+"call_consulting_with_user" = "与 %@ 商量";
+"call_video_with_user" = "与 %@ 进行视频通话";
+"call_voice_with_user" = "与 %@ 进行语音通话";
+"e2e_passphrase_too_short" = "密码口令太短 (长度至少为 %d 个字符)";
+"microphone_access_not_granted_for_voice_message" = "语音消息需要访问麦克风,但 %@ 无权使用它";
+"message_reply_to_sender_sent_a_voice_message" = "发送了一条语音消息。";
+"attachment_large_with_resolution" = "大 %@ (~%@)";
+"attachment_medium_with_resolution" = "中等 %@ (~%@)";
+"attachment_small_with_resolution" = "小 %@ (~%@)";
+"attachment_size_prompt_message" = "你可以在设置中关闭这个。";
+"attachment_size_prompt_title" = "确认要发送的大小";
+"room_displayname_all_other_participants_left" = "%@ (离开)";
+"auth_reset_password_error_not_found" = "未找到";
+"auth_reset_password_error_unauthorized" = "未经授权";
+"auth_invalid_user_name" = "用户名无效";
+"room_displayname_all_other_members_left" = "%@ (离开)";
+"auth_username_in_use" = "用户名被占用";
+"rename" = "重命名";
diff --git a/Riot/Modules/MatrixKit/Assets/MatrixKitAssets.bundle/zh_Hant.lproj/MatrixKit.strings b/Riot/Modules/MatrixKit/Assets/MatrixKitAssets.bundle/zh_Hant.lproj/MatrixKit.strings
new file mode 100644
index 000000000..a30a6d084
--- /dev/null
+++ b/Riot/Modules/MatrixKit/Assets/MatrixKitAssets.bundle/zh_Hant.lproj/MatrixKit.strings
@@ -0,0 +1,247 @@
+"view" = "檢視";
+"back" = "上一步";
+"continue" = "繼續";
+"leave" = "離開";
+"invite" = "邀請";
+"retry" = "重試";
+"cancel" = "取消";
+"save" = "儲存";
+"matrix" = "Matrix";
+// Login Screen
+"login_create_account" = "建立帳號:";
+"login_server_url_placeholder" = "URL(如 https://matrix.org)";
+"login_home_server_title" = "主伺服器:";
+"login_home_server_info" = "您的主伺服器會儲存所有的對話紀錄跟帳號資料";
+"login_identity_server_title" = "身份認證伺服器:";
+"login_identity_server_info" = "Matrix 提供身份認證伺服器來追蹤電子郵件信箱與 Matrix ID 的關係。目前只有 https://matrix.org 提供這個服務。";
+"login_user_id_placeholder" = "Matrix ID(如 @bob:matrix.org 或 bob)";
+"login_password_placeholder" = "密碼";
+"login_optional_field" = "選擇性";
+"login_display_name_placeholder" = "顯示名稱(如 Bob Obson)";
+"login_email_placeholder" = "電子郵件地址";
+"login_leave_fallback" = "取消";
+// Encryption information
+"room_event_encryption_info_title" = "點對點加密資訊\n\n";
+"room_event_encryption_info_event" = "事件資訊\n";
+"room_event_encryption_info_event_user_id" = "使用者 ID\n";
+"room_event_encryption_info_event_identity_key" = "Curve25519 身份認證金鑰\n";
+"room_event_encryption_info_event_fingerprint_key" = "已聲請之 Ed25519 指紋金鑰\n";
+"room_event_encryption_info_event_algorithm" = "演算法\n";
+"room_event_encryption_info_event_session_id" = "會話 ID\n";
+"room_event_encryption_info_event_decryption_error" = "解密錯誤\n";
+"room_event_encryption_info_event_unencrypted" = "未加密";
+"room_event_encryption_info_event_none" = "無";
+"room_event_encryption_info_device" = "\n發送者的裝置訊息\n";
+"room_event_encryption_info_device_unknown" = "未知的裝置\n";
+"room_event_encryption_info_device_name" = "名稱\n";
+"room_event_encryption_info_device_id" = "裝置 ID\n";
+"room_event_encryption_info_device_verification" = "驗證\n";
+"room_event_encryption_info_device_fingerprint" = "Ed25519 指紋\n";
+"room_event_encryption_info_device_verified" = "已驗證";
+"room_event_encryption_info_device_not_verified" = "未驗證";
+"room_event_encryption_info_device_blocked" = "已列入黑名單";
+"room_event_encryption_info_verify" = "驗證...";
+"room_event_encryption_info_unverify" = "取消驗證";
+"room_event_encryption_info_block" = "黑名單";
+"room_event_encryption_info_unblock" = "解除黑名單";
+"room_event_encryption_verify_title" = "驗證裝置\n\n";
+"room_event_encryption_verify_message" = "若要檢查這個裝置是可被信任的,請透過其他方法聯絡所有者(例如面對面或是在電話中),並詢問在其使用者設定中以下金鑰是否是一致的:\n\n\n\t裝置名稱:%@\n\t裝置 ID:%@\n\t裝置金鑰:%@\n\n若相同,請點選下面的「驗證確認」按鈕。如果不相同,表示有人從中攔截這個裝置,您可能要點選「黑名單」按鈕。\n\n未來驗證手續會更加簡單,若有不便敬請見諒。";
+"room_event_encryption_verify_ok" = "驗證確認";
+// Account
+"account_save_changes" = "儲存修改";
+// Groups
+"group_invite_section" = "邀請";
+// E2E import
+"e2e_import_room_keys" = "匯入聊天室金鑰";
+"e2e_import" = "匯入";
+"e2e_passphrase_enter" = "輸入通關密語";
+// E2E export
+"e2e_export_room_keys" = "匯出房間金鑰";
+"e2e_export" = "匯出";
+"e2e_passphrase_empty" = "通關密語不能為空";
+"e2e_passphrase_not_match" = "通關密語必須符合";
+// Others
+"user_id_title" = "使用者 ID:";
+"offline" = "離線";
+"unsent" = "取消傳送";
+"error" = "錯誤";
+"not_supported_yet" = "尚未支援";
+"default" = "預設";
+"private" = "私密";
+"public" = "公開";
+"power_level" = "權限等級";
+"network_error_not_reachable" = "請檢查您的網路連線";
+"user_id_placeholder" = "例:@bob:homeserver";
+"ssl_homeserver_url" = "家伺服器 URL:%@";
+// Permissions
+"camera_access_not_granted_for_call" = "視訊電話需要使用相機權限,但是 %@ 沒有存取權限";
+"microphone_access_not_granted_for_call" = "電話需要使用麥克風權限,但是 %@ 沒有存取權限";
+"local_contacts_access_not_granted" = "從本機的聯絡資訊探索使用者,需要存取聯絡資訊的權限,但是 %@ 沒有存取權限";
+"local_contacts_access_discovery_warning_title" = "使用者探索";
+"local_contacts_access_discovery_warning" = "%@ 要從您的聯絡資訊上傳電子郵件位址跟電話號碼來探索使用者";
+// Country picker
+"country_picker_title" = "選擇國家";
+// Language picker
+"language_picker_title" = "選擇語言";
+"language_picker_default_language" = "預設 (%@)";
+"notice_room_invite" = "%@ 邀請了 %@";
+"notice_room_third_party_invite" = "%@ 已邀請 %@ 加入聊天室";
+"notice_room_third_party_registered_invite" = "%@ 同意了 %@ 的邀請";
+"notice_room_join" = "%@ 已進入";
+"notice_room_leave" = "%@ 已離開";
+"notice_room_reject" = "%@ 拒絕了邀請";
+"notice_room_kick" = "%@ 踢了 %@";
+"notice_room_unban" = "%@ 解除了 %@ 的封鎖";
+"notice_room_ban" = "%@ 封鎖了 %@";
+"notice_room_withdraw" = "%@ 撤回了 %@ 的邀請";
+"notice_room_reason" = ",原因:%@";
+"notice_avatar_url_changed" = "%@ 變更了頭像";
+"notice_display_name_set" = "%@ 設定了自己的顯示名稱為 %@";
+"notice_display_name_changed_from" = "%@ 將自己的顯示名稱從 %@ 改為 %@";
+"notice_display_name_removed" = "%@ 移除了自己的顯示名稱";
+"notice_topic_changed" = "%@ 已經變更主題為:%@";
+"notice_room_name_changed" = "%@ 將房間名稱變更為 %@";
+"notice_placed_voice_call" = "%@ 開始了語音通話";
+"notice_placed_video_call" = "%@ 開始了視訊通話";
+"notice_answered_video_call" = "%@ 接聽了通話";
+"notice_ended_video_call" = "%@ 結束了通話";
+"notice_conference_call_request" = "%@ 請求了 VoIP 會議";
+"notice_conference_call_started" = "VoIP 會議已開始";
+"notice_conference_call_finished" = "VoIP 會議已結束";
+// button names
+"ok" = "好";
+"send" = "傳送";
+"copy_button_name" = "複製";
+"resend" = "重新傳送";
+"redact" = "撤除";
+"share" = "分享";
+"set_power_level" = "權限等級";
+"delete" = "刪除";
+// actions
+"action_logout" = "登出";
+"create_room" = "建立聊天室";
+"login" = "登入";
+"create_account" = "建立帳號";
+"membership_invite" = "邀請";
+"membership_leave" = "離開";
+"membership_ban" = "已被封鎖";
+"num_members_one" = "%@ 位使用者";
+"num_members_other" = "%@ 位使用者";
+"kick" = "踢人";
+"ban" = "封鎖";
+"unban" = "解除封鎖";
+// unrecognized SSL certificate
+"ssl_trust" = "信任";
+"ssl_logout_account" = "登出";
+"ssl_remain_offline" = "忽略";
+"ssl_fingerprint_hash" = "指紋 (%@):";
+"ssl_could_not_verify" = "無法驗證遠端伺服器的身份。";
+"ssl_cert_not_trust" = "這可能代表有人惡意攔截您的流量,或是裝置無法信任遠端伺服器所提供的憑證。";
+"ssl_cert_new_account_expl" = "如果伺服器管理者表示這是可預期的狀況,請確定以下指紋與管理者提供的一致。";
+"ssl_unexpected_existing_expl" = "這個憑證有別於原本在您裝置所信任的憑證,這個狀況相當不常見。建議您不要信任新的憑證。";
+"ssl_expected_existing_expl" = "這個憑證從原本信任的憑證換成不信任的憑證,可能因為伺服器更新了它的憑證。請聯絡伺服器管理者確認新的指紋一致。";
+"ssl_only_accept" = "只有在伺服器管理者提供的指紋與以上指紋一致時,您才能信任這個憑證。";
+// Devices
+"device_details_title" = "裝置資訊\n";
+"login_error_title" = "登入失敗";
+"login_error_no_login_flow" = "無法從該主伺服器取得驗證訊息";
+"login_error_do_not_support_login_flows" = "目前我們不支援任何該主伺服器定義的登入流程";
+"login_error_registration_is_not_supported" = "目前不支援註冊";
+"login_error_forbidden" = "無效的使用者名稱/密碼";
+"login_error_unknown_token" = "不能識別指定的訪問權杖";
+"login_error_bad_json" = "JSON 格式錯誤";
+"login_error_not_json" = "未包含有效的 JSON";
+"login_error_limit_exceeded" = "已傳送過多的請求";
+"login_error_user_in_use" = "該使用者名稱已被使用";
+"login_error_login_email_not_yet" = "該電子郵件連結向未被點擊";
+"login_use_fallback" = "使用備用頁";
+"login_invalid_param" = "無效的參數";
+"register_error_title" = "註冊失敗";
+"login_tablet_device" = "平板電腦";
+// Action
+"no" = "否";
+"yes" = "是";
+"abort" = "終止";
+"login_email_info" = "指定一個電子郵件地址可以讓其他 Matirx 用戶更容易找到您,並讓您可以在未來重置密碼。";
+"login_prompt_email_token" = "請輸入您的電子郵件認證權杖:";
+"login_error_forgot_password_is_not_supported" = "目前不支援忘記密碼";
+"login_mobile_device" = "行動裝置";
+"login_desktop_device" = "桌上型電腦";
+"close" = "關閉";
+"discard" = "放棄";
+"dismiss" = "無視";
+"sign_up" = "註冊";
+"submit" = "送出";
+"submit_code" = "送出碼";
+"set_default_power_level" = "重設權限等級";
+"set_admin" = "設定管理員";
+"set_moderator" = "設定主持人";
+"start_chat" = "開始聊天";
+"start_voice_call" = "開始語音通話";
+"start_video_call" = "開始視訊通話";
+"mention" = "提到";
+"select_account" = "選擇一個帳號";
+"capture_media" = "拍攝照片/影片";
+"invite_user" = "邀請 Matrix 用戶";
+"reset_to_default" = "重置為預設值";
+"attach_media" = "從庫中附加媒體";
+"resend_message" = "重新傳送該訊息";
+"select_all" = "全選";
+"cancel_upload" = "取消上傳";
+"cancel_download" = "取消下載";
+"show_details" = "顯示詳細資料";
+"answer_call" = "接聽來電";
+"reject_call" = "拒絕來電";
+"end_call" = "結束通話";
+"ignore" = "忽略";
+"unignore" = "取消忽略";
+// Events formatter
+"notice_avatar_changed_too" = "(頭像也已經改變)";
+"notice_room_name_removed" = "%@ 移除了該聊天室的名字";
+"notice_room_topic_removed" = "%@ 移除了該主題";
+"notice_event_redacted_by" = " 由 %@";
+"notice_event_redacted_reason" = " [理由:%@]";
+"notice_profile_change_redacted" = "%@ 已更新他的個人檔案 %@";
+"notice_room_created" = "%@ 創建了該聊天室";
+"notice_room_join_rule" = "加入規則: %@";
+"notice_room_power_level_intro" = "聊天室成員們的權限级别是:";
+"notice_event_redacted" = "<撤回%@>";
+// room details dialog screen
+"room_details_title" = "聊天室詳細資料";
+"notice_encrypted_message" = "已加密的訊息";
+"notice_image_attachment" = "附加圖片";
+"notice_audio_attachment" = "附加音訊";
+"notice_video_attachment" = "附加視訊";
+"notice_location_attachment" = "附加位置資訊";
+"notice_file_attachment" = "附加檔案";
+"notice_invalid_attachment" = "無效的附加資訊";
+"notice_unsupported_attachment" = "未支援的附加資訊:%@";
+"notice_sticker" = "貼圖";
+// room display name
+"room_displayname_empty_room" = "空的聊天室";
+"room_displayname_two_members" = "%@ 和 %@";
+"room_displayname_more_than_two_members" = "%@ 和 %u 個其他人";
+// Settings
+"settings" = "設定";
+"settings_enable_push_notifications" = "啟用推播通知";
+"device_details_name" = "名稱\n";
+"device_details_identifier" = "裝置代碼\n";
+"device_details_last_seen" = "上次使用\n";
+"device_details_rename_prompt_message" = "裝置名稱:";
+"login_error_resource_limit_exceeded_title" = "超過資源限制";
+"login_error_resource_limit_exceeded_message_default" = "此家伺服器已經超過其中一項資源限制。";
+"login_error_resource_limit_exceeded_message_monthly_active_user" = "此家伺服器已經達到其每月活躍使用者限制。";
+"login_error_resource_limit_exceeded_message_contact" = "\n\n請聯絡您的伺服器管理員以繼續使用其服務。";
+"login_error_resource_limit_exceeded_contact_button" = "聯絡管理員";
+"notice_room_power_level_acting_requirement" = "完成此操作之前使用者必須具有的最小權限級別是:";
+"notice_room_power_level_event_requirement" = "事件相關的最小權限級別是:";
+"notice_room_aliases" = "此聊天室別名是:%@";
+"notice_room_related_groups" = "此聊天室關聯的群組是:%@";
+"notice_encryption_enabled" = "%@ 開啓了端對端加密 (演算法 %@)";
+"notice_feedback" = "回報事件 (id:%@):%@";
+"notice_redaction" = "%@ 取消了一个事件 (id: %@)";
+"notice_error_unsupported_event" = "不支援的事件";
+"notice_error_unexpected_event" = "意外事件";
+"notice_error_unknown_event_type" = "未知的事件類型";
+"notice_room_history_visible_to_anyone" = "%@ 讓任何人都能看到未來的聊天室歷史記錄。";
+"notice_room_history_visible_to_members" = "%@ 讓所有聊天室成員都能看到未來的房間歷史記錄。";
diff --git a/Riot/Modules/MatrixKit/Categories/DTHTMLElement+MatrixKit.swift b/Riot/Modules/MatrixKit/Categories/DTHTMLElement+MatrixKit.swift
new file mode 100644
index 000000000..9e4315d09
--- /dev/null
+++ b/Riot/Modules/MatrixKit/Categories/DTHTMLElement+MatrixKit.swift
@@ -0,0 +1,97 @@
+//
+// Copyright 2020 The Matrix.org Foundation C.I.C
+//
+// 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 DTCoreText
+
+public extension DTHTMLElement {
+ typealias ImageHandler = (_ sourceURL: String, _ width: CGFloat, _ height: CGFloat) -> URL?
+
+ /// Sanitize the element using the given parameters.
+ /// - Parameters:
+ /// - allowedHTMLTags: An array of tags that are allowed. All other tags will be removed.
+ /// - font: The default font to use when resetting the content of any unsupported tags.
+ /// - imageHandler: An optional image handler to be run on `img` tags (if allowed) to update the `src` attribute.
+ @objc func sanitize(with allowedHTMLTags: [String], bodyFont font: UIFont, imageHandler: ImageHandler?) {
+ if let name = name, !allowedHTMLTags.contains(name) {
+
+ // This is an unsupported tag.
+ // Remove any attachments to fix rendering.
+ textAttachment = nil
+
+ // If the element has plain text content show that,
+ // otherwise prevent the tag from displaying.
+ if let stringContent = attributedString()?.string,
+ !stringContent.isEmpty,
+ let element = DTTextHTMLElement(name: nil, attributes: nil) {
+ element.setText(stringContent)
+ removeAllChildNodes()
+ addChildNode(element)
+
+ if let parent = parent() {
+ element.inheritAttributes(from: parent)
+ } else {
+ fontDescriptor = DTCoreTextFontDescriptor()
+ fontDescriptor.fontFamily = font.familyName
+ fontDescriptor.fontName = font.fontName
+ fontDescriptor.pointSize = font.pointSize
+ paragraphStyle = DTCoreTextParagraphStyle.default()
+
+ element.inheritAttributes(from: self)
+ }
+ element.interpretAttributes()
+
+ } else if let parent = parent() {
+ parent.removeChildNode(self)
+ } else {
+ didOutput = true
+ }
+
+ } else {
+ // Process images with the handler when self is an image tag.
+ if name == "img", let imageHandler = imageHandler {
+ process(with: imageHandler)
+ }
+
+ // This element is a supported tag, but it may contain children that aren't,
+ // so santize all child nodes to ensure correct tags.
+ if let childNodes = childNodes as? [DTHTMLElement] {
+ childNodes.forEach { $0.sanitize(with: allowedHTMLTags, bodyFont: font, imageHandler: imageHandler) }
+ }
+ }
+ }
+
+ /// Process the element with the supplied image handler.
+ private func process(with imageHandler: ImageHandler) {
+ // Get the values required to pass to the image handler
+ guard let sourceURL = attributes["src"] as? String else { return }
+
+ var width: CGFloat = -1
+ if let widthString = attributes["width"] as? String,
+ let widthDouble = Double(widthString) {
+ width = CGFloat(widthDouble)
+ }
+
+ var height: CGFloat = -1
+ if let heightString = attributes["height"] as? String,
+ let heightDouble = Double(heightString) {
+ height = CGFloat(heightDouble)
+ }
+
+ // If the handler returns an updated URL, update the text attachment.
+ guard let localSourceURL = imageHandler(sourceURL, width, height) else { return }
+ textAttachment.contentURL = localSourceURL
+ }
+}
diff --git a/Riot/Modules/MatrixKit/Categories/MXAggregatedReactions+MatrixKit.h b/Riot/Modules/MatrixKit/Categories/MXAggregatedReactions+MatrixKit.h
new file mode 100644
index 000000000..f4d023328
--- /dev/null
+++ b/Riot/Modules/MatrixKit/Categories/MXAggregatedReactions+MatrixKit.h
@@ -0,0 +1,30 @@
+/*
+ Copyright 2019 The Matrix.org Foundation C.I.C
+
+ 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 "MXKEventFormatter.h"
+
+NS_ASSUME_NONNULL_BEGIN
+
+/**
+ Define a `MXEvent` category at matrixKit level to store data related to UI handling.
+ */
+@interface MXAggregatedReactions (MatrixKit)
+
+- (nullable MXAggregatedReactions *)aggregatedReactionsWithSingleEmoji;
+
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/Riot/Modules/MatrixKit/Categories/MXAggregatedReactions+MatrixKit.m b/Riot/Modules/MatrixKit/Categories/MXAggregatedReactions+MatrixKit.m
new file mode 100644
index 000000000..4d4baee51
--- /dev/null
+++ b/Riot/Modules/MatrixKit/Categories/MXAggregatedReactions+MatrixKit.m
@@ -0,0 +1,44 @@
+/*
+ Copyright 2019 The Matrix.org Foundation C.I.C
+
+ 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 "MXAggregatedReactions+MatrixKit.h"
+
+#import "MXKTools.h"
+
+@implementation MXAggregatedReactions (MatrixKit)
+
+- (nullable MXAggregatedReactions *)aggregatedReactionsWithSingleEmoji
+{
+ NSMutableArray *reactions = [NSMutableArray arrayWithCapacity:self.reactions.count];
+ for (MXReactionCount *reactionCount in self.reactions)
+ {
+ if ([MXKTools isSingleEmojiString:reactionCount.reaction])
+ {
+ [reactions addObject:reactionCount];
+ }
+ }
+
+ MXAggregatedReactions *aggregatedReactionsWithSingleEmoji;
+ if (reactions.count)
+ {
+ aggregatedReactionsWithSingleEmoji = [MXAggregatedReactions new];
+ aggregatedReactionsWithSingleEmoji.reactions = reactions;
+ }
+
+ return aggregatedReactionsWithSingleEmoji;
+}
+
+@end
diff --git a/Riot/Modules/MatrixKit/Categories/MXEvent+MatrixKit.h b/Riot/Modules/MatrixKit/Categories/MXEvent+MatrixKit.h
new file mode 100644
index 000000000..9ef0eb1b1
--- /dev/null
+++ b/Riot/Modules/MatrixKit/Categories/MXEvent+MatrixKit.h
@@ -0,0 +1,34 @@
+/*
+ Copyright 2015 OpenMarket 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 "MXKEventFormatter.h"
+
+/**
+ Define a `MXEvent` category at matrixKit level to store data related to UI handling.
+ */
+@interface MXEvent (MatrixKit)
+
+/**
+ The potential error observed when the event formatter tried to stringify the event (MXKEventFormatterErrorNone by default).
+ */
+@property (nonatomic) MXKEventFormatterError mxkEventFormatterError;
+
+/**
+ Tell whether the event is highlighted or not (NO by default).
+ */
+@property (nonatomic) BOOL mxkIsHighlighted;
+
+@end
diff --git a/Riot/Modules/MatrixKit/Categories/MXEvent+MatrixKit.m b/Riot/Modules/MatrixKit/Categories/MXEvent+MatrixKit.m
new file mode 100644
index 000000000..53c79e6c9
--- /dev/null
+++ b/Riot/Modules/MatrixKit/Categories/MXEvent+MatrixKit.m
@@ -0,0 +1,52 @@
+/*
+ Copyright 2015 OpenMarket 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 "MXEvent+MatrixKit.h"
+#import
+
+@implementation MXEvent (MatrixKit)
+
+- (MXKEventFormatterError)mxkEventFormatterError
+{
+ NSNumber *associatedError = objc_getAssociatedObject(self, @selector(mxkEventFormatterError));
+ if (associatedError)
+ {
+ return [associatedError unsignedIntegerValue];
+ }
+ return MXKEventFormatterErrorNone;
+}
+
+- (void)setMxkEventFormatterError:(MXKEventFormatterError)mxkEventFormatterError
+{
+ objc_setAssociatedObject(self, @selector(mxkEventFormatterError), [NSNumber numberWithUnsignedInteger:mxkEventFormatterError], OBJC_ASSOCIATION_RETAIN_NONATOMIC);
+}
+
+- (BOOL)mxkIsHighlighted
+{
+ NSNumber *associatedIsHighlighted = objc_getAssociatedObject(self, @selector(mxkIsHighlighted));
+ if (associatedIsHighlighted)
+ {
+ return [associatedIsHighlighted boolValue];
+ }
+ return NO;
+}
+
+- (void)setMxkIsHighlighted:(BOOL)mxkIsHighlighted
+{
+ objc_setAssociatedObject(self, @selector(mxkIsHighlighted), [NSNumber numberWithBool:mxkIsHighlighted], OBJC_ASSOCIATION_RETAIN_NONATOMIC);
+}
+
+@end
diff --git a/Riot/Modules/MatrixKit/Categories/MXRoom+Sync.h b/Riot/Modules/MatrixKit/Categories/MXRoom+Sync.h
new file mode 100644
index 000000000..b95389d39
--- /dev/null
+++ b/Riot/Modules/MatrixKit/Categories/MXRoom+Sync.h
@@ -0,0 +1,34 @@
+/*
+ Copyright 2018 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
+
+/**
+ Temporary category to help in the transition from synchronous access to room.state
+ to asynchronous access.
+ */
+@interface MXRoom (Sync)
+
+/**
+ Get the room state if it has been already loaded else return nil.
+
+ Use this method only where you are sure the room state is already mounted.
+ */
+@property (nonatomic, readonly) MXRoomState *dangerousSyncState;
+
+@end
diff --git a/Riot/Modules/MatrixKit/Categories/MXRoom+Sync.m b/Riot/Modules/MatrixKit/Categories/MXRoom+Sync.m
new file mode 100644
index 000000000..8866c8a49
--- /dev/null
+++ b/Riot/Modules/MatrixKit/Categories/MXRoom+Sync.m
@@ -0,0 +1,36 @@
+/*
+ Copyright 2018 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 "MXRoom+Sync.h"
+
+@implementation MXRoom (Sync)
+
+- (MXRoomState *)dangerousSyncState
+{
+ __block MXRoomState *syncState;
+
+ // If syncState is called from the right place, the following call will be
+ // synchronous and every thing will be fine
+ [self state:^(MXRoomState *roomState) {
+ syncState = roomState;
+ }];
+
+ NSAssert(syncState, @"[MXRoom+Sync] syncState failed. Are you sure the state of the room has been already loaded?");
+
+ return syncState;
+}
+
+@end
diff --git a/Riot/Modules/MatrixKit/Categories/MXSession+MatrixKit.h b/Riot/Modules/MatrixKit/Categories/MXSession+MatrixKit.h
new file mode 100644
index 000000000..188b6b40b
--- /dev/null
+++ b/Riot/Modules/MatrixKit/Categories/MXSession+MatrixKit.h
@@ -0,0 +1,28 @@
+//
+// Copyright 2020 The Matrix.org Foundation C.I.C
+//
+// 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
+
+NS_ASSUME_NONNULL_BEGIN
+
+@interface MXSession (MatrixKit)
+
+/// Flag to indicate whether the session is in a suitable state to show some activity indicators on UI.
+@property (nonatomic, readonly) BOOL shouldShowActivityIndicator;
+
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/Riot/Modules/MatrixKit/Categories/MXSession+MatrixKit.m b/Riot/Modules/MatrixKit/Categories/MXSession+MatrixKit.m
new file mode 100644
index 000000000..6d136c306
--- /dev/null
+++ b/Riot/Modules/MatrixKit/Categories/MXSession+MatrixKit.m
@@ -0,0 +1,34 @@
+//
+// Copyright 2020 The Matrix.org Foundation C.I.C
+//
+// 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 "MXSession+MatrixKit.h"
+
+@implementation MXSession (MatrixKit)
+
+- (BOOL)shouldShowActivityIndicator
+{
+ switch (self.state)
+ {
+ case MXSessionStateInitialised:
+ case MXSessionStateProcessingBackgroundSyncCache:
+ case MXSessionStateSyncInProgress:
+ return YES;
+ default:
+ return NO;
+ }
+}
+
+@end
diff --git a/Riot/Modules/MatrixKit/Categories/NSAttributedString+MatrixKit.swift b/Riot/Modules/MatrixKit/Categories/NSAttributedString+MatrixKit.swift
new file mode 100644
index 000000000..c72e5c76c
--- /dev/null
+++ b/Riot/Modules/MatrixKit/Categories/NSAttributedString+MatrixKit.swift
@@ -0,0 +1,32 @@
+//
+// Copyright 2020 The Matrix.org Foundation C.I.C
+//
+// 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 Foundation
+
+public extension NSAttributedString {
+ /// Returns a string created by joining all ranges of the attributed string that don't have
+ /// the `kMXKToolsBlockquoteMarkAttribute` attribute.
+ @objc func mxk_unquotedString() -> NSString? {
+ var unquotedSubstrings = [String]()
+
+ enumerateAttributes(in: NSRange(location: 0, length: self.length), options: []) { attributes, range, stop in
+ guard !attributes.keys.contains(where: { $0.rawValue == kMXKToolsBlockquoteMarkAttribute }) else { return }
+ unquotedSubstrings.append(self.attributedSubstring(from: range).string)
+ }
+
+ return unquotedSubstrings.joined(separator: " ") as NSString
+ }
+}
diff --git a/Riot/Modules/MatrixKit/Categories/NSBundle+MXKLanguage.h b/Riot/Modules/MatrixKit/Categories/NSBundle+MXKLanguage.h
new file mode 100644
index 000000000..f8e5dff5d
--- /dev/null
+++ b/Riot/Modules/MatrixKit/Categories/NSBundle+MXKLanguage.h
@@ -0,0 +1,54 @@
+/*
+ Copyright 2017 Vector Creations 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
+
+@interface NSBundle (MXKLanguage)
+
+/**
+ Set the application language independently from the device language.
+
+ The language can be changed at runtime but the app display must be reloaded.
+
+ @param language the ISO language code. nil lets the OS choose it according to the device language
+ and languages available in the app bundle.
+ */
++ (void)mxk_setLanguage:(NSString *)language;
+
+/**
+ The language set by mxk_setLanguage.
+
+ @return the ISO language code of the current language.
+ */
++ (NSString *)mxk_language;
+
+/**
+ Some strings may lack a translation in a language.
+ Use mxk_setFallbackLanguage to define a fallback language where all the
+ translation is complete.
+
+ @param language the ISO language code.
+ */
++ (void)mxk_setFallbackLanguage:(NSString*)language;
+
+/**
+ The fallback language set by mxk_setFallbackLanguage.
+
+ @return the ISO language code of the current fallback language.
+ */
++ (NSString *)mxk_fallbackLanguage;
+
+@end
diff --git a/Riot/Modules/MatrixKit/Categories/NSBundle+MXKLanguage.m b/Riot/Modules/MatrixKit/Categories/NSBundle+MXKLanguage.m
new file mode 100644
index 000000000..a0112bf99
--- /dev/null
+++ b/Riot/Modules/MatrixKit/Categories/NSBundle+MXKLanguage.m
@@ -0,0 +1,103 @@
+/*
+ Copyright 2017 Vector Creations 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 "NSBundle+MXKLanguage.h"
+
+#import
+
+static const char _bundle = 0;
+static const char _fallbackBundle = 0;
+static const char _language = 0;
+static const char _fallbackLanguage = 0;
+
+@interface MXKLanguageBundle : NSBundle
+@end
+
+@implementation MXKLanguageBundle
+
+- (NSString*)localizedStringForKey:(NSString *)key value:(NSString *)value table:(NSString *)tableName
+{
+ NSBundle* bundle = objc_getAssociatedObject(self, &_bundle);
+
+ // Check if the translation is available in the selected or default language.
+ // Use "_", a string that does not worth to be translated, as default value to mark
+ // a key that does not have a translation.
+ NSString *localizedString = bundle ? [bundle localizedStringForKey:key value:@"_" table:tableName] : [super localizedStringForKey:key value:@"_" table:tableName];
+
+ if (!localizedString || (localizedString.length == 1 && [localizedString isEqualToString:@"_"]))
+ {
+ // Use the string in the fallback language
+ NSBundle *fallbackBundle = objc_getAssociatedObject(self, &_fallbackBundle);
+ localizedString = [fallbackBundle localizedStringForKey:key value:value table:tableName];
+ }
+
+ return localizedString;
+}
+@end
+
+@implementation NSBundle (MXKLanguage)
+
++ (void)mxk_setLanguage:(NSString *)language
+{
+ [self setupMXKLanguageBundle];
+
+ // [NSBundle localizedStringForKey] calls will be redirected to the bundle corresponding
+ // to "language"
+ objc_setAssociatedObject([NSBundle mainBundle],
+ &_bundle, language ? [NSBundle bundleWithPath:[[NSBundle mainBundle] pathForResource:language ofType:@"lproj"]] : nil,
+ OBJC_ASSOCIATION_RETAIN_NONATOMIC);
+
+ objc_setAssociatedObject([NSBundle mainBundle],
+ &_language, language,
+ OBJC_ASSOCIATION_RETAIN_NONATOMIC);
+}
+
++ (NSString *)mxk_language
+{
+ return objc_getAssociatedObject([NSBundle mainBundle], &_language);
+}
+
++ (void)mxk_setFallbackLanguage:(NSString *)language
+{
+ [self setupMXKLanguageBundle];
+
+ objc_setAssociatedObject([NSBundle mainBundle],
+ &_fallbackBundle, language ? [NSBundle bundleWithPath:[[NSBundle mainBundle] pathForResource:language ofType:@"lproj"]] : nil,
+ OBJC_ASSOCIATION_RETAIN_NONATOMIC);
+
+ objc_setAssociatedObject([NSBundle mainBundle],
+ &_fallbackLanguage, language,
+ OBJC_ASSOCIATION_RETAIN_NONATOMIC);
+}
+
++ (NSString *)mxk_fallbackLanguage
+{
+ return objc_getAssociatedObject([NSBundle mainBundle], &_fallbackLanguage);
+}
+
+#pragma mark - Private methods
+
++ (void)setupMXKLanguageBundle
+{
+ static dispatch_once_t onceToken;
+ dispatch_once(&onceToken, ^{
+
+ // Use MXKLanguageBundle as the [NSBundle mainBundle] class
+ object_setClass([NSBundle mainBundle], [MXKLanguageBundle class]);
+ });
+}
+
+@end
diff --git a/Riot/Modules/MatrixKit/Categories/NSBundle+MatrixKit.h b/Riot/Modules/MatrixKit/Categories/NSBundle+MatrixKit.h
new file mode 100644
index 000000000..9bcc5572a
--- /dev/null
+++ b/Riot/Modules/MatrixKit/Categories/NSBundle+MatrixKit.h
@@ -0,0 +1,61 @@
+/*
+ Copyright 2015 OpenMarket 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
+
+/**
+ Define a `NSBundle` category at MatrixKit level to retrieve images and sounds from MatrixKit Assets bundle.
+ */
+@interface NSBundle (MatrixKit)
+
+/**
+ Retrieve an image from MatrixKit Assets bundle.
+
+ @param name image file name without extension.
+ @return a UIImage instance (nil if the file does not exist).
+ */
++ (UIImage *)mxk_imageFromMXKAssetsBundleWithName:(NSString *)name;
+
+/**
+ Retrieve an audio file url from MatrixKit Assets bundle.
+
+ @param name audio file name without extension.
+ @return a NSURL instance.
+ */
++ (NSURL *)mxk_audioURLFromMXKAssetsBundleWithName:(NSString *)name;
+
+/**
+ Customize the table used to retrieve the localized version of a string during [mxk_localizedStringForKey:] call.
+ If the key is not defined in this table, the localized string is retrieved from the default table "MatrixKit.strings".
+
+ @param tableName the name of the table containing the key-value pairs. Also, the suffix for the strings file (a file with the .strings extension) to store the localized string.
+ */
++ (void)mxk_customizeLocalizedStringTableName:(NSString*)tableName;
+
+/**
+ Retrieve localized string from the customized table. If none, MatrixKit Assets bundle is used.
+
+ @param key The string key.
+ @return The localized string.
+ */
++ (NSString *)mxk_localizedStringForKey:(NSString *)key;
+
+/**
+ An AppExtension-compatible wrapper for bundleForClass.
+ */
++ (NSBundle *)mxk_bundleForClass:(Class)aClass;
+
+@end
diff --git a/Riot/Modules/MatrixKit/Categories/NSBundle+MatrixKit.m b/Riot/Modules/MatrixKit/Categories/NSBundle+MatrixKit.m
new file mode 100644
index 000000000..70a75a59b
--- /dev/null
+++ b/Riot/Modules/MatrixKit/Categories/NSBundle+MatrixKit.m
@@ -0,0 +1,150 @@
+/*
+ Copyright 2015 OpenMarket 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 "NSBundle+MatrixKit.h"
+#import "NSBundle+MXKLanguage.h"
+#import "MXKViewController.h"
+
+@implementation NSBundle (MatrixKit)
+
+static NSString *customLocalizedStringTableName = nil;
+
++ (NSBundle*)mxk_assetsBundle
+{
+ // Get the bundle within MatrixKit
+ NSBundle *bundle = [NSBundle mxk_bundleForClass:[MXKViewController class]];
+ NSURL *assetsBundleURL = [bundle URLForResource:@"MatrixKitAssets" withExtension:@"bundle"];
+
+ return [NSBundle bundleWithURL:assetsBundleURL];
+}
+
++ (NSBundle*)mxk_languageBundle
+{
+ NSString *language = [NSBundle mxk_language];
+ NSBundle *bundle = [NSBundle mxk_assetsBundle];
+
+ // If there is a runtime language (different from the legacy language chose by the OS),
+ // return the sub bundle for this language
+ if (language)
+ {
+ bundle = [NSBundle bundleWithPath:[bundle pathForResource:[NSBundle mxk_language] ofType:@"lproj"]];
+ }
+
+ return bundle;
+}
+
++ (NSBundle*)mxk_fallbackLanguageBundle
+{
+ NSString *fallbackLanguage = [NSBundle mxk_fallbackLanguage];
+ NSBundle *bundle = [NSBundle mxk_assetsBundle];
+
+ // Return the sub bundle of the fallback language if any
+ if (fallbackLanguage)
+ {
+ bundle = [NSBundle bundleWithPath:[bundle pathForResource:fallbackLanguage ofType:@"lproj"]];
+ }
+
+ return bundle;
+}
+
+// use a cache to avoid loading images from file system.
+// It often triggers an UI lag.
+static MXLRUCache *imagesResourceCache = nil;
+
++ (UIImage *)mxk_imageFromMXKAssetsBundleWithName:(NSString *)name
+{
+ // use a cache to avoid loading the image at each call
+ if (!imagesResourceCache)
+ {
+ imagesResourceCache = [[MXLRUCache alloc] initWithCapacity:20];
+ }
+
+ NSString *imagePath = [[NSBundle mxk_assetsBundle] pathForResource:name ofType:@"png" inDirectory:@"Images"];
+ UIImage* image = (UIImage*)[imagesResourceCache get:imagePath];
+
+ // the image does not exist
+ if (!image)
+ {
+ // retrieve it
+ image = [UIImage imageWithContentsOfFile:imagePath];
+ // and store it in the cache.
+ [imagesResourceCache put:imagePath object:image];
+ }
+
+ return image;
+}
+
++ (NSURL*)mxk_audioURLFromMXKAssetsBundleWithName:(NSString *)name
+{
+ return [NSURL fileURLWithPath:[[NSBundle mxk_assetsBundle] pathForResource:name ofType:@"mp3" inDirectory:@"Sounds"]];
+}
+
++ (void)mxk_customizeLocalizedStringTableName:(NSString*)tableName
+{
+ customLocalizedStringTableName = tableName;
+}
+
++ (NSString *)mxk_localizedStringForKey:(NSString *)key
+{
+ NSString *localizedString;
+
+ // Check first customized table
+ // Use "_", a string that does not worth to be translated, as default value to mark
+ // a key that does not have a value in the customized table.
+ if (customLocalizedStringTableName)
+ {
+ localizedString = NSLocalizedStringWithDefaultValue(key, customLocalizedStringTableName, [NSBundle mainBundle], @"_", nil);
+ }
+
+ if (!localizedString || (localizedString.length == 1 && [localizedString isEqualToString:@"_"]))
+ {
+ // Check if we need to manage a fallback language
+ // as we do in NSBundle+MXKLanguage
+ NSString *language = [NSBundle mxk_language];
+ NSString *fallbackLanguage = [NSBundle mxk_fallbackLanguage];
+
+ BOOL manageFallbackLanguage = fallbackLanguage && ![fallbackLanguage isEqualToString:language];
+
+ localizedString = NSLocalizedStringWithDefaultValue(key, @"MatrixKit",
+ [NSBundle mxk_languageBundle],
+ manageFallbackLanguage ? @"_" : nil,
+ nil);
+
+ if (manageFallbackLanguage
+ && (!localizedString || (localizedString.length == 1 && [localizedString isEqualToString:@"_"])))
+ {
+ // The translation is not available, use the fallback language
+ localizedString = NSLocalizedStringFromTableInBundle(key, @"MatrixKit",
+ [NSBundle mxk_fallbackLanguageBundle],
+ nil);
+ }
+ }
+
+ return localizedString;
+}
+
++ (NSBundle *)mxk_bundleForClass:(Class)aClass
+{
+ NSBundle *bundle = [NSBundle bundleForClass:aClass];
+ if ([[bundle.bundleURL pathExtension] isEqualToString:@"appex"])
+ {
+ // For App extensions, peel off two levels
+ bundle = [NSBundle bundleWithURL:[[bundle.bundleURL URLByDeletingLastPathComponent] URLByDeletingLastPathComponent]];
+ }
+ return bundle;
+}
+
+@end
diff --git a/Riot/Modules/MatrixKit/Categories/NSString+MatrixKit.swift b/Riot/Modules/MatrixKit/Categories/NSString+MatrixKit.swift
new file mode 100644
index 000000000..0a7826571
--- /dev/null
+++ b/Riot/Modules/MatrixKit/Categories/NSString+MatrixKit.swift
@@ -0,0 +1,66 @@
+//
+// Copyright 2020 The Matrix.org Foundation C.I.C
+//
+// 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 Foundation
+import MatrixSDK.MXLog
+
+public extension NSString {
+ /// Gets the first URL contained in the string ignoring any links to hosts defined in
+ /// the `firstURLDetectionIgnoredHosts` property of `MXKAppSettings`.
+ /// - Returns: A URL if detected, otherwise nil.
+ @objc func mxk_firstURLDetected() -> NSURL? {
+ let hosts = MXKAppSettings.standard().firstURLDetectionIgnoredHosts ?? []
+ return mxk_firstURLDetected(ignoring: hosts)
+ }
+
+ /// Gets the first URL contained in the string ignoring any links to the specified hosts.
+ /// - Returns: A URL if detected, otherwise nil.
+ @objc func mxk_firstURLDetected(ignoring ignoredHosts: [String]) -> NSURL? {
+ guard let linkDetector = try? NSDataDetector(types: NSTextCheckingResult.CheckingType.link.rawValue) else {
+ MXLog.debug("[NSString+URLDetector]: Unable to create link detector.")
+ return nil
+ }
+
+ var detectedURL: NSURL?
+
+ // enumerate all urls that were found in the string to ensure
+ // detection of a valid link if there are invalid links preceding it
+ linkDetector.enumerateMatches(in: self as String,
+ options: [],
+ range: NSRange(location: 0, length: self.length)) { match, flags, stop in
+ guard let match = match else { return }
+
+ // check if the match is a valid url
+ let urlString = self.substring(with: match.range)
+ guard let url = NSURL(string: urlString) else { return }
+
+ // ensure the match is a web link
+ guard let scheme = url.scheme?.lowercased(),
+ scheme == "https" || scheme == "http"
+ else { return }
+
+ // discard any links to ignored hosts
+ guard let host = url.host?.lowercased(),
+ !ignoredHosts.contains(host)
+ else { return }
+
+ detectedURL = url
+ stop.pointee = true
+ }
+
+ return detectedURL
+ }
+}
diff --git a/Riot/Modules/MatrixKit/Categories/UIAlertController+MatrixKit.h b/Riot/Modules/MatrixKit/Categories/UIAlertController+MatrixKit.h
new file mode 100644
index 000000000..d30b01787
--- /dev/null
+++ b/Riot/Modules/MatrixKit/Categories/UIAlertController+MatrixKit.h
@@ -0,0 +1,31 @@
+/*
+ Copyright 2017 Vector Creations 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
+
+/**
+ Define a `UIAlertController` category at MatrixKit level to handle accessibility identifiers.
+ */
+@interface UIAlertController (MatrixKit)
+
+/**
+ Apply an accessibility on the alert view and its items (actions and text fields).
+
+ @param accessibilityIdentifier the identifier.
+ */
+- (void)mxk_setAccessibilityIdentifier:(NSString *)accessibilityIdentifier;
+
+@end
diff --git a/Riot/Modules/MatrixKit/Categories/UIAlertController+MatrixKit.m b/Riot/Modules/MatrixKit/Categories/UIAlertController+MatrixKit.m
new file mode 100644
index 000000000..716da03ce
--- /dev/null
+++ b/Riot/Modules/MatrixKit/Categories/UIAlertController+MatrixKit.m
@@ -0,0 +1,38 @@
+/*
+ Copyright 2017 Vector Creations 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 "UIAlertController+MatrixKit.h"
+
+@implementation UIAlertController (MatrixKit)
+
+- (void)mxk_setAccessibilityIdentifier:(NSString *)accessibilityIdentifier
+{
+ self.view.accessibilityIdentifier = accessibilityIdentifier;
+
+ for (UIAlertAction *action in self.actions)
+ {
+ action.accessibilityLabel = [NSString stringWithFormat:@"%@Action%@", accessibilityIdentifier, action.title];
+ }
+
+ NSArray *textFieldArray = self.textFields;
+ for (NSUInteger index = 0; index < textFieldArray.count; index++)
+ {
+ UITextField *textField = textFieldArray[index];
+ textField.accessibilityIdentifier = [NSString stringWithFormat:@"%@TextField%tu", accessibilityIdentifier, index];
+ }
+}
+
+@end
diff --git a/Riot/Modules/MatrixKit/Categories/UITextView+MatrixKit.h b/Riot/Modules/MatrixKit/Categories/UITextView+MatrixKit.h
new file mode 100644
index 000000000..775aa9f8b
--- /dev/null
+++ b/Riot/Modules/MatrixKit/Categories/UITextView+MatrixKit.h
@@ -0,0 +1,33 @@
+/*
+ Copyright 2019 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;
+
+NS_ASSUME_NONNULL_BEGIN
+
+@interface UITextView(MatrixKit)
+
+/**
+ Determine if there is a link near a location point in UITextView bounds.
+
+ @param point The point inside the UITextView bounds
+ @return YES to indicate that a link has been detected near the location point.
+ */
+- (BOOL)isThereALinkNearPoint:(CGPoint)point;
+
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/Riot/Modules/MatrixKit/Categories/UITextView+MatrixKit.m b/Riot/Modules/MatrixKit/Categories/UITextView+MatrixKit.m
new file mode 100644
index 000000000..b22817f60
--- /dev/null
+++ b/Riot/Modules/MatrixKit/Categories/UITextView+MatrixKit.m
@@ -0,0 +1,54 @@
+/*
+ Copyright 2019 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 "UITextView+MatrixKit.h"
+
+@implementation UITextView(MatrixKit)
+
+- (BOOL)isThereALinkNearPoint:(CGPoint)point
+{
+ if (!CGRectContainsPoint(self.bounds, point))
+ {
+ return NO;
+ }
+
+ UITextPosition *textPosition = [self closestPositionToPoint:point];
+
+ if (!textPosition)
+ {
+ return NO;
+ }
+
+ UITextRange *textRange = [self.tokenizer rangeEnclosingPosition:textPosition
+ withGranularity:UITextGranularityCharacter
+ inDirection:UITextLayoutDirectionLeft];
+
+ if (!textRange)
+ {
+ return NO;
+ }
+
+ NSInteger startIndex = [self offsetFromPosition:self.beginningOfDocument toPosition:textRange.start];
+
+ if (startIndex < 0)
+ {
+ return NO;
+ }
+
+ return [self.attributedText attribute:NSLinkAttributeName atIndex:startIndex effectiveRange:NULL] != nil;
+}
+
+@end
diff --git a/Riot/Modules/MatrixKit/Categories/UIViewController+MatrixKit.h b/Riot/Modules/MatrixKit/Categories/UIViewController+MatrixKit.h
new file mode 100644
index 000000000..1b1b8fde2
--- /dev/null
+++ b/Riot/Modules/MatrixKit/Categories/UIViewController+MatrixKit.h
@@ -0,0 +1,30 @@
+/*
+ Copyright 2018 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
+
+NS_ASSUME_NONNULL_BEGIN
+
+@interface UIViewController (MatrixKit)
+
+/**
+ The main navigation controller if the view controller is embedded inside a split view controller.
+ */
+@property (nullable, nonatomic, readonly) UINavigationController *mxk_mainNavigationController;
+
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/Riot/Modules/MatrixKit/Categories/UIViewController+MatrixKit.m b/Riot/Modules/MatrixKit/Categories/UIViewController+MatrixKit.m
new file mode 100644
index 000000000..4d9f981fb
--- /dev/null
+++ b/Riot/Modules/MatrixKit/Categories/UIViewController+MatrixKit.m
@@ -0,0 +1,45 @@
+/*
+ Copyright 2018 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 "UIViewController+MatrixKit.h"
+
+@implementation UIViewController (MatrixKit)
+
+- (UINavigationController *)mxk_mainNavigationController
+{
+ UINavigationController *mainNavigationController;
+
+ if (self.splitViewController)
+ {
+ mainNavigationController = self.navigationController;
+ UIViewController *parentViewController = self.parentViewController;
+ while (parentViewController)
+ {
+ if (parentViewController.navigationController)
+ {
+ mainNavigationController = parentViewController.navigationController;
+ parentViewController = parentViewController.parentViewController;
+ }
+ else
+ {
+ break;
+ }
+ }
+ }
+
+ return mainNavigationController;
+}
+@end
diff --git a/Riot/Modules/MatrixKit/Controllers/MXKAccountDetailsViewController.h b/Riot/Modules/MatrixKit/Controllers/MXKAccountDetailsViewController.h
new file mode 100644
index 000000000..60b9c4044
--- /dev/null
+++ b/Riot/Modules/MatrixKit/Controllers/MXKAccountDetailsViewController.h
@@ -0,0 +1,124 @@
+/*
+Copyright 2015 OpenMarket 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 "MXKTableViewController.h"
+
+#import "MXKAccountManager.h"
+
+#import "MXK3PID.h"
+
+/**
+ */
+typedef void (^blockMXKAccountDetailsViewController_onReadyToLeave)(void);
+
+/**
+ MXKAccountDetailsViewController instance may be used to display/edit the details of a matrix account.
+ Only one matrix session is handled by this view controller.
+ */
+@interface MXKAccountDetailsViewController : MXKTableViewController
+{
+@protected
+
+ /**
+ Section index
+ */
+ NSInteger linkedEmailsSection;
+ NSInteger notificationsSection;
+ NSInteger configurationSection;
+
+ /**
+ The logout button
+ */
+ UIButton *logoutButton;
+
+ /**
+ Linked email
+ */
+ MXK3PID *submittedEmail;
+ UIButton *emailSubmitButton;
+ UITextField *emailTextField;
+
+ // Notifications
+ UISwitch *apnsNotificationsSwitch;
+ UISwitch *inAppNotificationsSwitch;
+
+ // The table cell with "Global Notification Settings" button
+ UIButton *notificationSettingsButton;
+}
+
+/**
+ The account displayed into the view controller.
+ */
+@property (nonatomic) MXKAccount *mxAccount;
+
+/**
+ The default account picture displayed when no picture is defined.
+ */
+@property (nonatomic) UIImage *picturePlaceholder;
+
+@property (nonatomic, readonly) IBOutlet UIButton *userPictureButton;
+@property (nonatomic, readonly) IBOutlet UITextField *userDisplayName;
+@property (nonatomic, readonly) IBOutlet UIButton *saveUserInfoButton;
+
+@property (nonatomic, readonly) IBOutlet UIView *profileActivityIndicatorBgView;
+@property (nonatomic, readonly) IBOutlet UIActivityIndicatorView *profileActivityIndicator;
+
+#pragma mark - Class methods
+
+/**
+ Returns the `UINib` object initialized for a `MXKAccountDetailsViewController`.
+
+ @return The initialized `UINib` object or `nil` if there were errors during initialization
+ or the nib file could not be located.
+
+ @discussion You may override this method to provide a customized nib. If you do,
+ you should also override `accountDetailsViewController` to return your
+ view controller loaded from your custom nib.
+ */
++ (UINib *)nib;
+
+/**
+ Creates and returns a new `MXKAccountDetailsViewController` object.
+
+ @discussion This is the designated initializer for programmatic instantiation.
+ @return An initialized `MXKAccountDetailsViewController` object if successful, `nil` otherwise.
+ */
++ (instancetype)accountDetailsViewController;
+
+/**
+ Action registered on the following events:
+ - 'UIControlEventTouchUpInside' for each UIButton instance.
+ - 'UIControlEventValueChanged' for each UISwitch instance.
+ */
+- (IBAction)onButtonPressed:(id)sender;
+
+/**
+ Action registered to handle text field edition
+ */
+- (IBAction)textFieldEditingChanged:(id)sender;
+
+/**
+ Prompt user to save potential changes before leaving the view controller.
+
+ @param handler A block object called when the changes have been saved or discarded.
+
+ @return YES if no change is observed. NO when the user is prompted.
+ */
+- (BOOL)shouldLeave:(blockMXKAccountDetailsViewController_onReadyToLeave)handler;
+
+@end
diff --git a/Riot/Modules/MatrixKit/Controllers/MXKAccountDetailsViewController.m b/Riot/Modules/MatrixKit/Controllers/MXKAccountDetailsViewController.m
new file mode 100644
index 000000000..95732b072
--- /dev/null
+++ b/Riot/Modules/MatrixKit/Controllers/MXKAccountDetailsViewController.m
@@ -0,0 +1,1172 @@
+/*
+ Copyright 2015 OpenMarket Ltd
+ Copyright 2017 Vector Creations Ltd
+ Copyright 2018 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 "MXKAccountDetailsViewController.h"
+
+@import MatrixSDK;
+#import "MXK3PID.h"
+
+#import "MXKTools.h"
+
+#import "MXKTableViewCellWithButton.h"
+#import "MXKTableViewCellWithTextFieldAndButton.h"
+#import "MXKTableViewCellWithLabelTextFieldAndButton.h"
+#import "MXKTableViewCellWithTextView.h"
+#import "MXKTableViewCellWithLabelAndSwitch.h"
+
+#import "NSBundle+MatrixKit.h"
+
+#import "MXKConstants.h"
+
+#import "MXKSwiftHeader.h"
+
+NSString* const kMXKAccountDetailsLinkedEmailCellId = @"kMXKAccountDetailsLinkedEmailCellId";
+
+@interface MXKAccountDetailsViewController ()
+{
+ NSMutableArray *alertsArray;
+
+ // User's profile
+ MXMediaLoader *imageLoader;
+ NSString *currentDisplayName;
+ NSString *currentPictureURL;
+ NSString *currentDownloadId;
+ NSString *uploadedPictureURL;
+ // Local changes
+ BOOL isAvatarUpdated;
+ BOOL isSavingInProgress;
+ blockMXKAccountDetailsViewController_onReadyToLeave onReadyToLeaveHandler;
+
+ // account user's profile observer
+ id accountUserInfoObserver;
+
+ // Dynamic rows in the Linked emails section
+ NSInteger submittedEmailRowIndex;
+
+ // Notifications
+ // Dynamic rows in the Notifications section
+ NSInteger enablePushNotifRowIndex;
+ NSInteger enableInAppNotifRowIndex;
+
+ UIImagePickerController *mediaPicker;
+}
+
+@end
+
+@implementation MXKAccountDetailsViewController
+@synthesize userPictureButton, userDisplayName, saveUserInfoButton;
+@synthesize profileActivityIndicator, profileActivityIndicatorBgView;
+
+#pragma mark - Class methods
+
++ (UINib *)nib
+{
+ return [UINib nibWithNibName:NSStringFromClass([MXKAccountDetailsViewController class])
+ bundle:[NSBundle bundleForClass:[MXKAccountDetailsViewController class]]];
+}
+
++ (instancetype)accountDetailsViewController
+{
+ return [[[self class] alloc] initWithNibName:NSStringFromClass([MXKAccountDetailsViewController class])
+ bundle:[NSBundle bundleForClass:[MXKAccountDetailsViewController class]]];
+}
+
+#pragma mark -
+
+- (void)finalizeInit
+{
+ [super finalizeInit];
+
+ alertsArray = [NSMutableArray array];
+
+ isAvatarUpdated = NO;
+ isSavingInProgress = NO;
+}
+
+- (void)viewDidLoad
+{
+ [super viewDidLoad];
+
+ // Check whether the view controller has been pushed via storyboard
+ if (!userPictureButton)
+ {
+ // Instantiate view controller objects
+ [[[self class] nib] instantiateWithOwner:self options:nil];
+ }
+
+ self.userPictureButton.backgroundColor = [UIColor clearColor];
+ [self updateUserPictureButton:self.picturePlaceholder];
+
+ [userPictureButton.layer setCornerRadius:userPictureButton.frame.size.width / 2];
+ userPictureButton.clipsToBounds = YES;
+
+ [saveUserInfoButton setTitle:[MatrixKitL10n accountSaveChanges] forState:UIControlStateNormal];
+ [saveUserInfoButton setTitle:[MatrixKitL10n accountSaveChanges] forState:UIControlStateHighlighted];
+
+ // Force refresh
+ self.mxAccount = _mxAccount;
+}
+
+- (void)didReceiveMemoryWarning
+{
+ [super didReceiveMemoryWarning];
+ // Dispose of any resources that can be recreated.
+
+ if (imageLoader)
+ {
+ [imageLoader cancel];
+ imageLoader = nil;
+ }
+}
+
+- (void)dealloc
+{
+ alertsArray = nil;
+}
+
+- (void)viewWillAppear:(BOOL)animated
+{
+ [super viewWillAppear:animated];
+
+ [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(onAPNSStatusUpdate) name:kMXKAccountAPNSActivityDidChangeNotification object:nil];
+}
+
+- (void)viewWillDisappear:(BOOL)animated
+{
+ [super viewWillDisappear:animated];
+
+ [self stopProfileActivityIndicator];
+
+ [[NSNotificationCenter defaultCenter] removeObserver:self name:kMXKAccountAPNSActivityDidChangeNotification object:nil];
+}
+
+#pragma mark - override
+
+- (void)onMatrixSessionChange
+{
+ [super onMatrixSessionChange];
+
+ if (self.mainSession.state != MXSessionStateRunning)
+ {
+ userPictureButton.enabled = NO;
+ userDisplayName.enabled = NO;
+ }
+ else if (!isSavingInProgress)
+ {
+ userPictureButton.enabled = YES;
+ userDisplayName.enabled = YES;
+ }
+}
+
+#pragma mark -
+
+- (void)setMxAccount:(MXKAccount *)account
+{
+ // Remove observer and existing data
+ [self reset];
+
+ _mxAccount = account;
+
+ if (account)
+ {
+ // Report matrix account session
+ [self addMatrixSession:account.mxSession];
+
+ // Set current user's information and add observers
+ [self updateUserPicture:_mxAccount.userAvatarUrl force:YES];
+ currentDisplayName = _mxAccount.userDisplayName;
+ self.userDisplayName.text = currentDisplayName;
+ [self updateSaveUserInfoButtonStatus];
+
+ // Load linked emails
+ [self loadLinkedEmails];
+
+ // Add observer on user's information
+ accountUserInfoObserver = [[NSNotificationCenter defaultCenter] addObserverForName:kMXKAccountUserInfoDidChangeNotification object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *notif) {
+ // Ignore any refresh when saving is in progress
+ if (self->isSavingInProgress)
+ {
+ return;
+ }
+
+ NSString *accountUserId = notif.object;
+
+ if ([accountUserId isEqualToString:self->_mxAccount.mxCredentials.userId])
+ {
+ // Update displayName
+ if (![self->currentDisplayName isEqualToString:self->_mxAccount.userDisplayName])
+ {
+ self->currentDisplayName = self->_mxAccount.userDisplayName;
+ self.userDisplayName.text = self->_mxAccount.userDisplayName;
+ }
+ // Update user's avatar
+ [self updateUserPicture:self->_mxAccount.userAvatarUrl force:NO];
+
+ // Update button management
+ [self updateSaveUserInfoButtonStatus];
+
+ // Display user's presence
+ UIColor *presenceColor = [MXKAccount presenceColor:self->_mxAccount.userPresence];
+ if (presenceColor)
+ {
+ self->userPictureButton.layer.borderWidth = 2;
+ self->userPictureButton.layer.borderColor = presenceColor.CGColor;
+ }
+ else
+ {
+ self->userPictureButton.layer.borderWidth = 0;
+ }
+ }
+ }];
+ }
+
+ [self.tableView reloadData];
+}
+
+- (UIImage*)picturePlaceholder
+{
+ return [NSBundle mxk_imageFromMXKAssetsBundleWithName:@"default-profile"];
+}
+
+- (BOOL)shouldLeave:(blockMXKAccountDetailsViewController_onReadyToLeave)handler
+{
+ // Check whether some local changes have not been saved
+ if (saveUserInfoButton.enabled)
+ {
+ dispatch_async(dispatch_get_main_queue(), ^{
+
+ UIAlertController *alert = [UIAlertController alertControllerWithTitle:nil message:[MatrixKitL10n messageUnsavedChanges] preferredStyle:UIAlertControllerStyleAlert];
+
+ [self->alertsArray addObject:alert];
+ [alert addAction:[UIAlertAction actionWithTitle:[MatrixKitL10n discard]
+ style:UIAlertActionStyleDefault
+ handler:^(UIAlertAction * action) {
+
+ [self->alertsArray removeObject:alert];
+
+ // Discard changes
+ self.userDisplayName.text = self->currentDisplayName;
+ [self updateUserPicture:self->_mxAccount.userAvatarUrl force:YES];
+
+ // Ready to leave
+ if (handler)
+ {
+ handler();
+ }
+
+ }]];
+
+ [alert addAction:[UIAlertAction actionWithTitle:[MatrixKitL10n save]
+ style:UIAlertActionStyleDefault
+ handler:^(UIAlertAction * action) {
+
+ [self->alertsArray removeObject:alert];
+
+ // Start saving (Report handler to leave at the end).
+ self->onReadyToLeaveHandler = handler;
+ [self saveUserInfo];
+
+ }]];
+
+ [self presentViewController:alert animated:YES completion:nil];
+ });
+
+ return NO;
+ }
+ else if (isSavingInProgress)
+ {
+ // Report handler to leave at the end of saving
+ onReadyToLeaveHandler = handler;
+ return NO;
+ }
+ return YES;
+}
+
+#pragma mark - Internal methods
+
+- (void)startProfileActivityIndicator
+{
+ if (profileActivityIndicatorBgView.hidden)
+ {
+ profileActivityIndicatorBgView.hidden = NO;
+ [profileActivityIndicator startAnimating];
+ }
+ userPictureButton.enabled = NO;
+ userDisplayName.enabled = NO;
+ saveUserInfoButton.enabled = NO;
+}
+
+- (void)stopProfileActivityIndicator
+{
+ if (!isSavingInProgress)
+ {
+ if (!profileActivityIndicatorBgView.hidden)
+ {
+ profileActivityIndicatorBgView.hidden = YES;
+ [profileActivityIndicator stopAnimating];
+ }
+ userPictureButton.enabled = YES;
+ userDisplayName.enabled = YES;
+ [self updateSaveUserInfoButtonStatus];
+ }
+}
+
+- (void)reset
+{
+ [self dismissMediaPicker];
+
+ // Remove observers
+ [[NSNotificationCenter defaultCenter] removeObserver:self];
+
+ // Cancel picture loader (if any)
+ if (imageLoader)
+ {
+ [imageLoader cancel];
+ imageLoader = nil;
+ }
+
+ // Cancel potential alerts
+ for (UIAlertController *alert in alertsArray)
+ {
+ [alert dismissViewControllerAnimated:NO completion:nil];
+ }
+
+ // Remove listener
+ if (accountUserInfoObserver)
+ {
+ [[NSNotificationCenter defaultCenter] removeObserver:accountUserInfoObserver];
+ accountUserInfoObserver = nil;
+ }
+
+ currentPictureURL = nil;
+ currentDownloadId = nil;
+ uploadedPictureURL = nil;
+ isAvatarUpdated = NO;
+ [self updateUserPictureButton:self.picturePlaceholder];
+
+ currentDisplayName = nil;
+ self.userDisplayName.text = nil;
+
+ saveUserInfoButton.enabled = NO;
+
+ submittedEmail = nil;
+ emailSubmitButton = nil;
+ emailTextField = nil;
+
+ [self removeMatrixSession:self.mainSession];
+
+ logoutButton = nil;
+
+ onReadyToLeaveHandler = nil;
+}
+
+- (void)destroy
+{
+ if (isSavingInProgress)
+ {
+ __weak typeof(self) weakSelf = self;
+ onReadyToLeaveHandler = ^()
+ {
+ __strong __typeof(weakSelf)strongSelf = weakSelf;
+ [strongSelf destroy];
+ };
+ }
+ else
+ {
+ // Reset account to dispose all resources (Discard here potentials changes)
+ self.mxAccount = nil;
+
+ if (imageLoader)
+ {
+ [imageLoader cancel];
+ imageLoader = nil;
+ }
+
+ // Remove listener
+ if (accountUserInfoObserver)
+ {
+ [[NSNotificationCenter defaultCenter] removeObserver:accountUserInfoObserver];
+ accountUserInfoObserver = nil;
+ }
+
+ [super destroy];
+ }
+}
+
+- (void)saveUserInfo
+{
+ [self startProfileActivityIndicator];
+ isSavingInProgress = YES;
+
+ // Check whether the display name has been changed
+ NSString *displayname = self.userDisplayName.text;
+ if ((displayname.length || currentDisplayName.length) && [displayname isEqualToString:currentDisplayName] == NO)
+ {
+ // Save display name
+ __weak typeof(self) weakSelf = self;
+
+ [_mxAccount setUserDisplayName:displayname success:^{
+
+ if (weakSelf)
+ {
+ // Update the current displayname
+ typeof(self) self = weakSelf;
+ self->currentDisplayName = displayname;
+
+ // Go to the next change saving step
+ [self saveUserInfo];
+ }
+
+ } failure:^(NSError *error) {
+
+ MXLogDebug(@"[MXKAccountDetailsVC] Failed to set displayName");
+ if (weakSelf)
+ {
+ typeof(self) self = weakSelf;
+
+ // Alert user
+ NSString *title = [error.userInfo valueForKey:NSLocalizedFailureReasonErrorKey];
+ if (!title)
+ {
+ title = [MatrixKitL10n accountErrorDisplayNameChangeFailed];
+ }
+ NSString *msg = [error.userInfo valueForKey:NSLocalizedDescriptionKey];
+
+ UIAlertController *alert = [UIAlertController alertControllerWithTitle:title message:msg preferredStyle:UIAlertControllerStyleAlert];
+
+ [self->alertsArray addObject:alert];
+
+ [alert addAction:[UIAlertAction actionWithTitle:[MatrixKitL10n abort]
+ style:UIAlertActionStyleDefault
+ handler:^(UIAlertAction * action) {
+
+ [self->alertsArray removeObject:alert];
+ // Discard changes
+ self.userDisplayName.text = self->currentDisplayName;
+ [self updateUserPicture:self.mxAccount.userAvatarUrl force:YES];
+ // Loop to end saving
+ [self saveUserInfo];
+
+ }]];
+
+ [alert addAction:[UIAlertAction actionWithTitle:[MatrixKitL10n retry]
+ style:UIAlertActionStyleDefault
+ handler:^(UIAlertAction * action) {
+
+ [self->alertsArray removeObject:alert];
+ // Loop to retry saving
+ [self saveUserInfo];
+
+ }]];
+
+
+ [self presentViewController:alert animated:YES completion:nil];
+ }
+
+ }];
+
+ return;
+ }
+
+ // Check whether avatar has been updated
+ if (isAvatarUpdated)
+ {
+ if (uploadedPictureURL == nil)
+ {
+ // Retrieve the current picture and make sure its orientation is up
+ UIImage *updatedPicture = [MXKTools forceImageOrientationUp:[self.userPictureButton imageForState:UIControlStateNormal]];
+
+ MXWeakify(self);
+
+ // Upload picture
+ MXMediaLoader *uploader = [MXMediaManager prepareUploaderWithMatrixSession:self.mainSession initialRange:0 andRange:1.0];
+ [uploader uploadData:UIImageJPEGRepresentation(updatedPicture, 0.5) filename:nil mimeType:@"image/jpeg" success:^(NSString *url)
+ {
+ MXStrongifyAndReturnIfNil(self);
+
+ // Store uploaded picture url and trigger picture saving
+ self->uploadedPictureURL = url;
+ [self saveUserInfo];
+ } failure:^(NSError *error)
+ {
+ MXLogDebug(@"[MXKAccountDetailsVC] Failed to upload image");
+ MXStrongifyAndReturnIfNil(self);
+ [self handleErrorDuringPictureSaving:error];
+ }];
+
+ }
+ else
+ {
+ MXWeakify(self);
+
+ [_mxAccount setUserAvatarUrl:uploadedPictureURL
+ success:^{
+
+ // uploadedPictureURL becomes the user's picture
+ MXStrongifyAndReturnIfNil(self);
+
+ [self updateUserPicture:self->uploadedPictureURL force:YES];
+ // Loop to end saving
+ [self saveUserInfo];
+
+ }
+ failure:^(NSError *error) {
+ MXLogDebug(@"[MXKAccountDetailsVC] Failed to set avatar url");
+ MXStrongifyAndReturnIfNil(self);
+ [self handleErrorDuringPictureSaving:error];
+ }];
+ }
+
+ return;
+ }
+
+ // Backup is complete
+ isSavingInProgress = NO;
+ [self stopProfileActivityIndicator];
+
+ // Ready to leave
+ if (onReadyToLeaveHandler)
+ {
+ onReadyToLeaveHandler();
+ onReadyToLeaveHandler = nil;
+ }
+}
+
+- (void)handleErrorDuringPictureSaving:(NSError*)error
+{
+ NSString *title = [error.userInfo valueForKey:NSLocalizedFailureReasonErrorKey];
+ if (!title)
+ {
+ title = [MatrixKitL10n accountErrorPictureChangeFailed];
+ }
+ NSString *msg = [error.userInfo valueForKey:NSLocalizedDescriptionKey];
+
+ UIAlertController *alert = [UIAlertController alertControllerWithTitle:title message:msg preferredStyle:UIAlertControllerStyleAlert];
+
+ [alertsArray addObject:alert];
+ [alert addAction:[UIAlertAction actionWithTitle:[MatrixKitL10n abort]
+ style:UIAlertActionStyleDefault
+ handler:^(UIAlertAction * action) {
+
+ [self->alertsArray removeObject:alert];
+
+ // Remove change
+ self.userDisplayName.text = self->currentDisplayName;
+ [self updateUserPicture:self->_mxAccount.userAvatarUrl force:YES];
+ // Loop to end saving
+ [self saveUserInfo];
+
+ }]];
+
+ [alert addAction:[UIAlertAction actionWithTitle:[MatrixKitL10n retry]
+ style:UIAlertActionStyleDefault
+ handler:^(UIAlertAction * action) {
+
+ [self->alertsArray removeObject:alert];
+
+ // Loop to retry saving
+ [self saveUserInfo];
+
+ }]];
+
+ [self presentViewController:alert animated:YES completion:nil];
+}
+
+- (void)updateUserPicture:(NSString *)avatar_url force:(BOOL)force
+{
+ if (force || currentPictureURL == nil || [currentPictureURL isEqualToString:avatar_url] == NO)
+ {
+ // Remove any pending observers
+ [[NSNotificationCenter defaultCenter] removeObserver:self];
+ // Cancel previous loader (if any)
+ if (imageLoader)
+ {
+ [imageLoader cancel];
+ imageLoader = nil;
+ }
+ // Cancel any local change
+ isAvatarUpdated = NO;
+ uploadedPictureURL = nil;
+
+ currentPictureURL = [avatar_url isEqual:[NSNull null]] ? nil : avatar_url;
+
+ // Check whether this url is valid
+ currentDownloadId = [MXMediaManager thumbnailDownloadIdForMatrixContentURI:currentPictureURL
+ inFolder:kMXMediaManagerAvatarThumbnailFolder
+ toFitViewSize:self.userPictureButton.frame.size
+ withMethod:MXThumbnailingMethodCrop];
+ if (!currentDownloadId)
+ {
+ // Set the placeholder in case of invalid Matrix Content URI.
+ [self updateUserPictureButton:self.picturePlaceholder];
+ }
+ else
+ {
+ // Check whether the image download is in progress
+ id loader = [MXMediaManager existingDownloaderWithIdentifier:currentDownloadId];
+ if (loader)
+ {
+ // Observe this loader
+ [[NSNotificationCenter defaultCenter] addObserver:self
+ selector:@selector(onMediaLoaderStateChange:)
+ name:kMXMediaLoaderStateDidChangeNotification
+ object:loader];
+ }
+ else
+ {
+ NSString *cacheFilePath = [MXMediaManager thumbnailCachePathForMatrixContentURI:currentPictureURL
+ andType:nil
+ inFolder:kMXMediaManagerAvatarThumbnailFolder
+ toFitViewSize:self.userPictureButton.frame.size
+ withMethod:MXThumbnailingMethodCrop];
+ // Retrieve the image from cache
+ UIImage* image = [MXMediaManager loadPictureFromFilePath:cacheFilePath];
+ if (image)
+ {
+ [self updateUserPictureButton:image];
+ }
+ else
+ {
+ // Download the image, by adding download observer
+ [[NSNotificationCenter defaultCenter] addObserver:self
+ selector:@selector(onMediaLoaderStateChange:)
+ name:kMXMediaLoaderStateDidChangeNotification
+ object:nil];
+ imageLoader = [self.mainSession.mediaManager downloadThumbnailFromMatrixContentURI:currentPictureURL
+ withType:nil
+ inFolder:kMXMediaManagerAvatarThumbnailFolder
+ toFitViewSize:self.userPictureButton.frame.size
+ withMethod:MXThumbnailingMethodCrop
+ success:nil
+ failure:nil];
+ }
+ }
+ }
+ }
+}
+
+- (void)updateUserPictureButton:(UIImage*)image
+{
+ [self.userPictureButton setImage:image forState:UIControlStateNormal];
+ [self.userPictureButton setImage:image forState:UIControlStateHighlighted];
+ [self.userPictureButton setImage:image forState:UIControlStateDisabled];
+}
+
+- (void)updateSaveUserInfoButtonStatus
+{
+ // Check whether display name has been changed
+ NSString *displayname = self.userDisplayName.text;
+ BOOL isDisplayNameUpdated = ((displayname.length || currentDisplayName.length) && [displayname isEqualToString:currentDisplayName] == NO);
+
+ saveUserInfoButton.enabled = (isDisplayNameUpdated || isAvatarUpdated) && !isSavingInProgress;
+}
+
+- (void)onMediaLoaderStateChange:(NSNotification *)notif
+{
+ MXMediaLoader *loader = (MXMediaLoader*)notif.object;
+ if ([loader.downloadId isEqualToString:currentDownloadId])
+ {
+ // update the image
+ switch (loader.state) {
+ case MXMediaLoaderStateDownloadCompleted:
+ {
+ UIImage *image = [MXMediaManager loadPictureFromFilePath:loader.downloadOutputFilePath];
+ if (image == nil)
+ {
+ image = self.picturePlaceholder;
+ }
+ [self updateUserPictureButton:image];
+ // remove the observers
+ [[NSNotificationCenter defaultCenter] removeObserver:self];
+ imageLoader = nil;
+ break;
+ }
+ case MXMediaLoaderStateDownloadFailed:
+ case MXMediaLoaderStateCancelled:
+ [self updateUserPictureButton:self.picturePlaceholder];
+ // remove the observers
+ [[NSNotificationCenter defaultCenter] removeObserver:self];
+ imageLoader = nil;
+ // Reset picture URL in order to try next time
+ currentPictureURL = nil;
+ break;
+ default:
+ break;
+ }
+ }
+}
+
+- (void)onAPNSStatusUpdate
+{
+ // Force table reload to update notifications section
+ apnsNotificationsSwitch = nil;
+
+ [self.tableView reloadData];
+}
+
+- (void)dismissMediaPicker
+{
+ if (mediaPicker)
+ {
+ [self dismissViewControllerAnimated:NO completion:nil];
+ mediaPicker.delegate = nil;
+ mediaPicker = nil;
+ }
+}
+
+- (void)showValidationEmailDialogWithMessage:(NSString*)message
+{
+ UIAlertController *alert = [UIAlertController alertControllerWithTitle:[MatrixKitL10n accountEmailValidationTitle] message:message preferredStyle:UIAlertControllerStyleAlert];
+
+ [alertsArray addObject:alert];
+ [alert addAction:[UIAlertAction actionWithTitle:[MatrixKitL10n abort]
+ style:UIAlertActionStyleDefault
+ handler:^(UIAlertAction * action) {
+
+ [self->alertsArray removeObject:alert];
+
+ self->emailSubmitButton.enabled = YES;
+
+ }]];
+
+ [alert addAction:[UIAlertAction actionWithTitle:[MatrixKitL10n continue]
+ style:UIAlertActionStyleDefault
+ handler:^(UIAlertAction * action) {
+
+ [self->alertsArray removeObject:alert];
+
+ __weak typeof(self) weakSelf = self;
+
+ // We do not bind anymore emails when registering, so let's do the same here
+ [self->submittedEmail add3PIDToUser:NO success:^{
+
+ if (weakSelf)
+ {
+ typeof(self) self = weakSelf;
+
+ // Release pending email and refresh table to remove related cell
+ self->emailTextField.text = nil;
+ self->submittedEmail = nil;
+
+ // Update linked emails
+ [self loadLinkedEmails];
+ }
+
+ } failure:^(NSError *error) {
+
+ if (weakSelf)
+ {
+ typeof(self) self = weakSelf;
+
+ MXLogDebug(@"[MXKAccountDetailsVC] Failed to bind email");
+
+ // Display the same popup again if the error is M_THREEPID_AUTH_FAILED
+ MXError *mxError = [[MXError alloc] initWithNSError:error];
+ if (mxError && [mxError.errcode isEqualToString:kMXErrCodeStringThreePIDAuthFailed])
+ {
+ [self showValidationEmailDialogWithMessage:[MatrixKitL10n accountEmailValidationError]];
+ }
+ else
+ {
+ // Notify MatrixKit user
+ NSString *myUserId = self.mxAccount.mxCredentials.userId;
+ [[NSNotificationCenter defaultCenter] postNotificationName:kMXKErrorNotification object:error userInfo:myUserId ? @{kMXKErrorUserIdKey: myUserId} : nil];
+ }
+
+ // Release the pending email (even if it is Authenticated)
+ [self.tableView reloadData];
+ }
+
+ }];
+
+ }]];
+
+ [self presentViewController:alert animated:YES completion:nil];
+}
+
+- (void)loadLinkedEmails
+{
+ // Refresh the account 3PIDs list
+ [_mxAccount load3PIDs:^{
+
+ [self.tableView reloadData];
+
+ } failure:^(NSError *error) {
+ // Display the data that has been loaded last time
+ [self.tableView reloadData];
+ }];
+}
+
+#pragma mark - Actions
+
+- (IBAction)onButtonPressed:(id)sender
+{
+ [self dismissKeyboard];
+
+ if (sender == saveUserInfoButton)
+ {
+ [self saveUserInfo];
+ }
+ else if (sender == userPictureButton)
+ {
+ // Open picture gallery
+ mediaPicker = [[UIImagePickerController alloc] init];
+ mediaPicker.delegate = self;
+ mediaPicker.sourceType = UIImagePickerControllerSourceTypePhotoLibrary;
+ mediaPicker.allowsEditing = NO;
+ [self presentViewController:mediaPicker animated:YES completion:nil];
+ }
+ else if (sender == logoutButton)
+ {
+ [[MXKAccountManager sharedManager] removeAccount:_mxAccount completion:nil];
+ self.mxAccount = nil;
+ }
+ else if (sender == emailSubmitButton)
+ {
+ // Email check
+ if (![MXTools isEmailAddress:emailTextField.text])
+ {
+ UIAlertController *alert = [UIAlertController alertControllerWithTitle:[MatrixKitL10n accountErrorEmailWrongTitle] message:[MatrixKitL10n accountErrorEmailWrongDescription] preferredStyle:UIAlertControllerStyleAlert];
+
+ [alertsArray addObject:alert];
+ [alert addAction:[UIAlertAction actionWithTitle:[MatrixKitL10n ok]
+ style:UIAlertActionStyleDefault
+ handler:^(UIAlertAction * action) {
+
+ [self->alertsArray removeObject:alert];
+
+ }]];
+
+ [self presentViewController:alert animated:YES completion:nil];
+
+ return;
+ }
+
+ if (!submittedEmail || ![submittedEmail.address isEqualToString:emailTextField.text])
+ {
+ submittedEmail = [[MXK3PID alloc] initWithMedium:kMX3PIDMediumEmail andAddress:emailTextField.text];
+ }
+
+ emailSubmitButton.enabled = NO;
+ __weak typeof(self) weakSelf = self;
+
+ [submittedEmail requestValidationTokenWithMatrixRestClient:self.mainSession.matrixRestClient isDuringRegistration:NO nextLink:nil success:^{
+
+ if (weakSelf)
+ {
+ typeof(self) self = weakSelf;
+ [self showValidationEmailDialogWithMessage:[MatrixKitL10n accountEmailValidationMessage]];
+ }
+
+ } failure:^(NSError *error) {
+
+ MXLogDebug(@"[MXKAccountDetailsVC] Failed to request email token");
+ if (weakSelf)
+ {
+ typeof(self) self = weakSelf;
+ // Notify MatrixKit user
+ NSString *myUserId = self.mxAccount.mxCredentials.userId;
+ [[NSNotificationCenter defaultCenter] postNotificationName:kMXKErrorNotification object:error userInfo:myUserId ? @{kMXKErrorUserIdKey: myUserId} : nil];
+
+ self->emailSubmitButton.enabled = YES;
+ }
+
+ }];
+ }
+ else if (sender == apnsNotificationsSwitch)
+ {
+ [_mxAccount enablePushNotifications:apnsNotificationsSwitch.on success:nil failure:nil];
+ apnsNotificationsSwitch.enabled = NO;
+ }
+ else if (sender == inAppNotificationsSwitch)
+ {
+ _mxAccount.enableInAppNotifications = inAppNotificationsSwitch.on;
+ [self.tableView reloadData];
+ }
+}
+
+#pragma mark - keyboard
+
+- (void)dismissKeyboard
+{
+ if ([userDisplayName isFirstResponder])
+ {
+ // Hide the keyboard
+ [userDisplayName resignFirstResponder];
+ [self updateSaveUserInfoButtonStatus];
+ }
+ else if ([emailTextField isFirstResponder])
+ {
+ [emailTextField resignFirstResponder];
+ }
+}
+
+#pragma mark - UITextField delegate
+
+- (BOOL)textFieldShouldReturn:(UITextField*)textField
+{
+ // "Done" key has been pressed
+ [self dismissKeyboard];
+ return YES;
+}
+
+- (IBAction)textFieldEditingChanged:(id)sender
+{
+ if (sender == userDisplayName)
+ {
+ [self updateSaveUserInfoButtonStatus];
+ }
+}
+
+#pragma mark - UITableView data source
+
+- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
+{
+ NSInteger count = 0;
+
+ linkedEmailsSection = notificationsSection = configurationSection = -1;
+
+ if (!_mxAccount.disabled)
+ {
+ linkedEmailsSection = count ++;
+ notificationsSection = count ++;
+ }
+
+ configurationSection = count ++;
+
+ return count;
+}
+
+- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
+{
+ NSInteger count = 0;
+ if (section == linkedEmailsSection)
+ {
+ count = _mxAccount.linkedEmails.count;
+ submittedEmailRowIndex = count++;
+ }
+ else if (section == notificationsSection)
+ {
+ enableInAppNotifRowIndex = enablePushNotifRowIndex = -1;
+
+ if ([MXKAccountManager sharedManager].isAPNSAvailable) {
+ enablePushNotifRowIndex = count++;
+ }
+ enableInAppNotifRowIndex = count++;
+ }
+ else if (section == configurationSection)
+ {
+ count = 2;
+ }
+
+ return count;
+}
+
+- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
+{
+ if (indexPath.section == configurationSection)
+ {
+ if (indexPath.row == 0)
+ {
+ UITextView *textView = [[UITextView alloc] initWithFrame:CGRectMake(0, 0, tableView.frame.size.width, MAXFLOAT)];
+ textView.font = [UIFont systemFontOfSize:14];
+ textView.text = [NSString stringWithFormat:@"%@\n%@\n%@", [MatrixKitL10n settingsConfigHomeServer:_mxAccount.mxCredentials.homeServer], [MatrixKitL10n settingsConfigIdentityServer:_mxAccount.identityServerURL], [MatrixKitL10n settingsConfigUserId:_mxAccount.mxCredentials.userId]];
+
+ CGSize contentSize = [textView sizeThatFits:textView.frame.size];
+ return contentSize.height + 1;
+ }
+ }
+
+ return 44;
+}
+
+- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
+{
+ UITableViewCell *cell = nil;
+
+ if (indexPath.section == linkedEmailsSection)
+ {
+ if (indexPath.row < _mxAccount.linkedEmails.count)
+ {
+ cell = [tableView dequeueReusableCellWithIdentifier:kMXKAccountDetailsLinkedEmailCellId];
+ if (!cell)
+ {
+ cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:kMXKAccountDetailsLinkedEmailCellId];
+ }
+
+ cell.selectionStyle = UITableViewCellSelectionStyleNone;
+ cell.textLabel.text = [_mxAccount.linkedEmails objectAtIndex:indexPath.row];
+ }
+ else if (indexPath.row == submittedEmailRowIndex)
+ {
+ // Report the current email value (if any)
+ NSString *currentEmail = nil;
+ if (emailTextField)
+ {
+ currentEmail = emailTextField.text;
+ }
+
+ MXKTableViewCellWithTextFieldAndButton *submittedEmailCell = [tableView dequeueReusableCellWithIdentifier:[MXKTableViewCellWithTextFieldAndButton defaultReuseIdentifier]];
+ if (!submittedEmailCell)
+ {
+ submittedEmailCell = [[MXKTableViewCellWithTextFieldAndButton alloc] init];
+ }
+
+ submittedEmailCell.mxkTextField.text = currentEmail;
+ submittedEmailCell.mxkTextField.keyboardType = UIKeyboardTypeEmailAddress;
+ submittedEmailCell.mxkButton.enabled = (currentEmail.length != 0);
+ [submittedEmailCell.mxkButton setTitle:[MatrixKitL10n accountLinkEmail] forState:UIControlStateNormal];
+ [submittedEmailCell.mxkButton setTitle:[MatrixKitL10n accountLinkEmail] forState:UIControlStateHighlighted];
+ [submittedEmailCell.mxkButton addTarget:self action:@selector(onButtonPressed:) forControlEvents:UIControlEventTouchUpInside];
+
+ emailSubmitButton = submittedEmailCell.mxkButton;
+ emailTextField = submittedEmailCell.mxkTextField;
+
+ cell = submittedEmailCell;
+ }
+ }
+ else if (indexPath.section == notificationsSection)
+ {
+ MXKTableViewCellWithLabelAndSwitch *notificationsCell = [tableView dequeueReusableCellWithIdentifier:[MXKTableViewCellWithLabelAndSwitch defaultReuseIdentifier]];
+ if (!notificationsCell)
+ {
+ notificationsCell = [[MXKTableViewCellWithLabelAndSwitch alloc] init];
+ }
+ else
+ {
+ // Force layout before reusing a cell (fix switch displayed outside the screen)
+ [notificationsCell layoutIfNeeded];
+ }
+
+ [notificationsCell.mxkSwitch addTarget:self action:@selector(onButtonPressed:) forControlEvents:UIControlEventValueChanged];
+
+ if (indexPath.row == enableInAppNotifRowIndex)
+ {
+ notificationsCell.mxkLabel.text = [MatrixKitL10n settingsEnableInappNotifications];
+ notificationsCell.mxkSwitch.on = _mxAccount.enableInAppNotifications;
+ inAppNotificationsSwitch = notificationsCell.mxkSwitch;
+ }
+ else /* enablePushNotifRowIndex */
+ {
+ notificationsCell.mxkLabel.text = [MatrixKitL10n settingsEnablePushNotifications];
+ notificationsCell.mxkSwitch.on = _mxAccount.pushNotificationServiceIsActive;
+ notificationsCell.mxkSwitch.enabled = YES;
+ apnsNotificationsSwitch = notificationsCell.mxkSwitch;
+ }
+
+ cell = notificationsCell;
+ }
+ else if (indexPath.section == configurationSection)
+ {
+ if (indexPath.row == 0)
+ {
+ MXKTableViewCellWithTextView *configCell = [tableView dequeueReusableCellWithIdentifier:[MXKTableViewCellWithTextView defaultReuseIdentifier]];
+ if (!configCell)
+ {
+ configCell = [[MXKTableViewCellWithTextView alloc] init];
+ }
+
+ configCell.mxkTextView.text = [NSString stringWithFormat:@"%@\n%@\n%@", [MatrixKitL10n settingsConfigHomeServer:_mxAccount.mxCredentials.homeServer], [MatrixKitL10n settingsConfigIdentityServer:_mxAccount.identityServerURL], [MatrixKitL10n settingsConfigUserId:_mxAccount.mxCredentials.userId]];
+
+ cell = configCell;
+ }
+ else
+ {
+ MXKTableViewCellWithButton *logoutBtnCell = [tableView dequeueReusableCellWithIdentifier:[MXKTableViewCellWithButton defaultReuseIdentifier]];
+ if (!logoutBtnCell)
+ {
+ logoutBtnCell = [[MXKTableViewCellWithButton alloc] init];
+ }
+ [logoutBtnCell.mxkButton setTitle:[MatrixKitL10n actionLogout] forState:UIControlStateNormal];
+ [logoutBtnCell.mxkButton setTitle:[MatrixKitL10n actionLogout] forState:UIControlStateHighlighted];
+ [logoutBtnCell.mxkButton addTarget:self action:@selector(onButtonPressed:) forControlEvents:UIControlEventTouchUpInside];
+
+ logoutButton = logoutBtnCell.mxkButton;
+
+ cell = logoutBtnCell;
+ }
+
+ }
+ else
+ {
+ // Return a fake cell to prevent app from crashing.
+ cell = [[UITableViewCell alloc] init];
+ }
+
+ return cell;
+}
+
+#pragma mark - UITableView delegate
+
+- (CGFloat) tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section
+{
+ return 30;
+}
+- (CGFloat) tableView:(UITableView *)tableView heightForFooterInSection:(NSInteger)section
+{
+ return 1;
+}
+
+- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section
+{
+ UIView *sectionHeader = [[UIView alloc] initWithFrame:[tableView rectForHeaderInSection:section]];
+ sectionHeader.backgroundColor = [UIColor colorWithRed:0.9 green:0.9 blue:0.9 alpha:1.0];
+ UILabel *sectionLabel = [[UILabel alloc] initWithFrame:CGRectMake(5, 5, sectionHeader.frame.size.width - 10, sectionHeader.frame.size.height - 10)];
+ sectionLabel.font = [UIFont boldSystemFontOfSize:16];
+ sectionLabel.backgroundColor = [UIColor clearColor];
+ [sectionHeader addSubview:sectionLabel];
+
+ if (section == linkedEmailsSection)
+ {
+ sectionLabel.text = [MatrixKitL10n accountLinkedEmails];
+ }
+ else if (section == notificationsSection)
+ {
+ sectionLabel.text = [MatrixKitL10n settingsTitleNotifications];
+ }
+ else if (section == configurationSection)
+ {
+ sectionLabel.text = [MatrixKitL10n settingsTitleConfig];
+ }
+
+ return sectionHeader;
+}
+
+- (void)tableView:(UITableView *)aTableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
+{
+ if (self.tableView == aTableView)
+ {
+ [aTableView deselectRowAtIndexPath:indexPath animated:YES];
+ }
+}
+
+# pragma mark - UIImagePickerControllerDelegate
+
+- (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)info
+{
+ UIImage *selectedImage = [info objectForKey:UIImagePickerControllerOriginalImage];
+ if (selectedImage)
+ {
+ [self updateUserPictureButton:selectedImage];
+ isAvatarUpdated = YES;
+ saveUserInfoButton.enabled = YES;
+ }
+ [self dismissMediaPicker];
+}
+
+@end
diff --git a/Riot/Modules/MatrixKit/Controllers/MXKAccountDetailsViewController.xib b/Riot/Modules/MatrixKit/Controllers/MXKAccountDetailsViewController.xib
new file mode 100644
index 000000000..2d7cfd528
--- /dev/null
+++ b/Riot/Modules/MatrixKit/Controllers/MXKAccountDetailsViewController.xib
@@ -0,0 +1,95 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Riot/Modules/MatrixKit/Controllers/MXKActivityHandlingViewController.h b/Riot/Modules/MatrixKit/Controllers/MXKActivityHandlingViewController.h
new file mode 100644
index 000000000..400b9cc3b
--- /dev/null
+++ b/Riot/Modules/MatrixKit/Controllers/MXKActivityHandlingViewController.h
@@ -0,0 +1,26 @@
+//
+// Copyright 2020 The Matrix.org Foundation C.I.C
+//
+// 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 "MXKViewControllerActivityHandling.h"
+
+NS_ASSUME_NONNULL_BEGIN
+
+@interface MXKActivityHandlingViewController : UIViewController
+
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/Riot/Modules/MatrixKit/Controllers/MXKActivityHandlingViewController.m b/Riot/Modules/MatrixKit/Controllers/MXKActivityHandlingViewController.m
new file mode 100644
index 000000000..5628c7396
--- /dev/null
+++ b/Riot/Modules/MatrixKit/Controllers/MXKActivityHandlingViewController.m
@@ -0,0 +1,83 @@
+//
+// Copyright 2020 The Matrix.org Foundation C.I.C
+//
+// 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 "MXKActivityHandlingViewController.h"
+
+@interface MXKActivityHandlingViewController ()
+
+@end
+
+@implementation MXKActivityHandlingViewController
+@synthesize activityIndicator;
+
+#pragma mark -
+
+- (void)viewDidLoad
+{
+ [super viewDidLoad];
+
+ // Add default activity indicator
+ activityIndicator = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhite];
+ activityIndicator.backgroundColor = [UIColor colorWithRed:0.8 green:0.8 blue:0.8 alpha:1.0];
+ activityIndicator.autoresizingMask = UIViewAutoresizingFlexibleTopMargin | UIViewAutoresizingFlexibleBottomMargin | UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleRightMargin;
+ activityIndicator.hidesWhenStopped = YES;
+
+ CGRect frame = activityIndicator.frame;
+ frame.size.width += 30;
+ frame.size.height += 30;
+ activityIndicator.bounds = frame;
+ [activityIndicator.layer setCornerRadius:5];
+
+ activityIndicator.center = self.view.center;
+ [self.view addSubview:activityIndicator];
+}
+
+- (void)dealloc
+{
+ if (activityIndicator)
+ {
+ [activityIndicator removeFromSuperview];
+ activityIndicator = nil;
+ }
+}
+
+#pragma mark - Activity indicator
+
+- (void)startActivityIndicator
+{
+ if (activityIndicator)
+ {
+ [self.view bringSubviewToFront:activityIndicator];
+ [activityIndicator startAnimating];
+
+ // Show the loading wheel after a delay so that if the caller calls stopActivityIndicator
+ // in a short future, the loading wheel will not be displayed to the end user.
+ activityIndicator.alpha = 0;
+ [UIView animateWithDuration:0.3 delay:0.3 options:UIViewAnimationOptionBeginFromCurrentState animations:^{
+ self->activityIndicator.alpha = 1;
+ } completion:^(BOOL finished)
+ {
+ }];
+ }
+}
+
+- (void)stopActivityIndicator
+{
+ [activityIndicator stopAnimating];
+}
+
+
+@end
diff --git a/Riot/Modules/MatrixKit/Controllers/MXKAttachmentsViewController.h b/Riot/Modules/MatrixKit/Controllers/MXKAttachmentsViewController.h
new file mode 100644
index 000000000..60bfdf732
--- /dev/null
+++ b/Riot/Modules/MatrixKit/Controllers/MXKAttachmentsViewController.h
@@ -0,0 +1,123 @@
+/*
+Copyright 2015 OpenMarket 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 "MXKViewController.h"
+#import "MXKAttachment.h"
+#import "MXKAttachmentAnimator.h"
+
+@protocol MXKAttachmentsViewControllerDelegate;
+
+/**
+ This view controller is used to display attachments of a room.
+ Only one attachment is displayed at once, the user is able to swipe one by one the attachment.
+ */
+@interface MXKAttachmentsViewController : MXKViewController
+
+@property (nonatomic) IBOutlet UICollectionView *attachmentsCollection;
+@property (nonatomic) IBOutlet UINavigationBar *navigationBar;
+@property (unsafe_unretained, nonatomic) IBOutlet UIBarButtonItem *backButton;
+
+/**
+ The attachments array.
+ */
+@property (nonatomic, readonly) NSArray *attachments;
+
+/**
+ Tell whether all attachments have been retrieved from the room history (In that case no attachment can be added at the beginning of attachments array).
+ */
+@property (nonatomic) BOOL complete;
+
+/**
+ The delegate notified when inputs are ready.
+ */
+@property (nonatomic, weak) id delegate;
+
+#pragma mark - Class methods
+
+/**
+ Returns the `UINib` object initialized for a `MXKAttachmentsViewController`.
+
+ @return The initialized `UINib` object or `nil` if there were errors during initialization
+ or the nib file could not be located.
+
+ @discussion You may override this method to provide a customized nib. If you do,
+ you should also override `roomViewController` to return your
+ view controller loaded from your custom nib.
+ */
++ (UINib *)nib;
+
+/**
+ Creates and returns a new `MXKAttachmentsViewController` object.
+
+ @discussion This is the designated initializer for programmatic instantiation.
+ @return An initialized `MXKAttachmentsViewController` object if successful, `nil` otherwise.
+ */
++ (instancetype)attachmentsViewController;
+
+/**
+ Creates and returns a new `MXKAttachmentsViewController` object, also sets sets up environment for animated interactive transitions.
+ */
++ (instancetype)animatedAttachmentsViewControllerWithSourceViewController:(UIViewController *)sourceViewController;
+
+/**
+ Display attachments of a room.
+
+ The provided event id is used to select the attachment to display first. Use nil to unchange the current displayed attachment.
+ By default the first attachment is displayed.
+ If the back pagination spinner is currently displayed and provided event id is nil,
+ the viewer will display the first added attachment during back pagination.
+
+ @param attachmentArray the array of attachments (MXKAttachment instances).
+ @param eventId the identifier of the attachment to display first.
+
+ */
+- (void)displayAttachments:(NSArray*)attachmentArray focusOn:(NSString*)eventId;
+
+/**
+ Action used to handle the `backButton` in the navigation bar.
+ */
+- (IBAction)onButtonPressed:(id)sender;
+
+@end
+
+@protocol MXKAttachmentsViewControllerDelegate
+
+/**
+ Ask the delegate for more attachments.
+ This method is called only if 'complete' is NO.
+
+ When some attachments are available, the delegate update the attachmnet list by using
+ [MXKAttachmentsViewController displayAttachments: focusOn:].
+ When no new attachment is available, the delegate must update the property 'complete'.
+
+ @param attachmentsViewController the attachments view controller.
+ @param eventId the event identifier of the current first attachment.
+ @return a boolean which tells whether some new attachments may be added or not.
+ */
+- (BOOL)attachmentsViewController:(MXKAttachmentsViewController*)attachmentsViewController paginateAttachmentBefore:(NSString*)eventId;
+
+@optional
+
+/**
+ Informs the delegate that a new attachment has been shown
+ the parameter eventId is used by the delegate to identify the attachment
+ */
+- (void)displayedNewAttachmentWithEventId:(NSString *)eventId;
+
+
+@end
diff --git a/Riot/Modules/MatrixKit/Controllers/MXKAttachmentsViewController.m b/Riot/Modules/MatrixKit/Controllers/MXKAttachmentsViewController.m
new file mode 100644
index 000000000..a21fbc396
--- /dev/null
+++ b/Riot/Modules/MatrixKit/Controllers/MXKAttachmentsViewController.m
@@ -0,0 +1,1439 @@
+/*
+ Copyright 2015 OpenMarket Ltd
+ Copyright 2017 Vector Creations Ltd
+ Copyright 2018 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 "MXKAttachmentsViewController.h"
+
+#import
+
+@import MatrixSDK.MXMediaManager;
+
+#import "MXKMediaCollectionViewCell.h"
+
+#import "MXKPieChartView.h"
+
+#import "MXKConstants.h"
+
+#import "MXKTools.h"
+
+#import "NSBundle+MatrixKit.h"
+
+#import "MXKEventFormatter.h"
+
+#import "MXKAttachmentInteractionController.h"
+
+#import "MXKSwiftHeader.h"
+
+@interface MXKAttachmentsViewController ()
+{
+ /**
+ Current alert (if any).
+ */
+ UIAlertController *currentAlert;
+
+ /**
+ Navigation bar handling
+ */
+ NSTimer *navigationBarDisplayTimer;
+
+ /**
+ SplitViewController handling
+ */
+ BOOL shouldRestoreBottomBar;
+ UISplitViewControllerDisplayMode savedSplitViewControllerDisplayMode;
+
+ /**
+ Audio session handling
+ */
+ NSString *savedAVAudioSessionCategory;
+
+ /**
+ The attachments array (MXAttachment instances).
+ */
+ NSMutableArray *attachments;
+
+ /**
+ The index of the current visible collection item
+ */
+ NSInteger currentVisibleItemIndex;
+
+ /**
+ The document interaction Controller used to share attachment
+ */
+ UIDocumentInteractionController *documentInteractionController;
+ MXKAttachment *currentSharedAttachment;
+
+ /**
+ Tells whether back pagination is in progress.
+ */
+ BOOL isBackPaginationInProgress;
+
+ /**
+ A temporary file used to store decrypted attachments
+ */
+ NSString *tempFile;
+
+ /**
+ Path to a file containing video data for the currently selected
+ attachment, if it's a video attachment and the data is
+ available.
+ */
+ NSString *videoFile;
+}
+
+//animations
+@property (nonatomic) MXKAttachmentInteractionController *interactionController;
+
+@property (nonatomic, weak) UIViewController *sourceViewController;
+
+@property (nonatomic) UIImageView *originalImageView;
+@property (nonatomic) CGRect convertedFrame;
+
+@property (nonatomic) BOOL customAnimationsEnabled;
+
+@end
+
+@implementation MXKAttachmentsViewController
+@synthesize attachments;
+
+#pragma mark - Class methods
+
++ (UINib *)nib
+{
+ return [UINib nibWithNibName:NSStringFromClass([MXKAttachmentsViewController class])
+ bundle:[NSBundle bundleForClass:[MXKAttachmentsViewController class]]];
+}
+
++ (instancetype)attachmentsViewController
+{
+ return [[[self class] alloc] initWithNibName:NSStringFromClass([MXKAttachmentsViewController class])
+ bundle:[NSBundle bundleForClass:[MXKAttachmentsViewController class]]];
+}
+
++ (instancetype)animatedAttachmentsViewControllerWithSourceViewController:(UIViewController *)sourceViewController
+{
+ MXKAttachmentsViewController *attachmentsController = [[[self class] alloc] initWithNibName:NSStringFromClass([MXKAttachmentsViewController class])
+ bundle:[NSBundle bundleForClass:[MXKAttachmentsViewController class]]];
+
+ //create an interactionController for it to handle the gestue recognizer and control the interactions
+ attachmentsController.interactionController = [[MXKAttachmentInteractionController alloc] initWithDestinationViewController:attachmentsController sourceViewController:sourceViewController];
+
+ //we use the animationsEnabled property to enable/disable animations. Instances created not using this method should use the default animations
+ attachmentsController.customAnimationsEnabled = YES;
+
+ //this properties will be needed by animationControllers in order to perform the animations
+ attachmentsController.sourceViewController = sourceViewController;
+
+ //setting transitioningDelegate and navigationController.delegate so that the animations will work for present/dismiss as well as push/pop
+ attachmentsController.transitioningDelegate = attachmentsController;
+ sourceViewController.navigationController.delegate = attachmentsController;
+
+
+ return attachmentsController;
+}
+
+#pragma mark -
+
+- (void)finalizeInit
+{
+ [super finalizeInit];
+
+ tempFile = nil;
+}
+
+- (void)viewDidLoad
+{
+ [super viewDidLoad];
+
+ // Check whether the view controller has been pushed via storyboard
+ if (!_attachmentsCollection)
+ {
+ // Instantiate view controller objects
+ [[[self class] nib] instantiateWithOwner:self options:nil];
+ }
+
+ self.backButton.image = [NSBundle mxk_imageFromMXKAssetsBundleWithName:@"back_icon"];
+
+ // Register collection view cell class
+ [self.attachmentsCollection registerClass:MXKMediaCollectionViewCell.class forCellWithReuseIdentifier:[MXKMediaCollectionViewCell defaultReuseIdentifier]];
+
+ // Hide collection to hide first scrolling into the attachments.
+ _attachmentsCollection.hidden = YES;
+
+ // Display collection cell in full screen
+ self.automaticallyAdjustsScrollViewInsets = NO;
+}
+
+- (BOOL)prefersStatusBarHidden
+{
+ // Hide status bar.
+ // Caution: Enable [UIViewController prefersStatusBarHidden] use at application level
+ // by turning on UIViewControllerBasedStatusBarAppearance in Info.plist.
+ return YES;
+}
+
+- (void)viewWillAppear:(BOOL)animated
+{
+ [super viewWillAppear:animated];
+
+ videoFile = nil;
+
+ savedAVAudioSessionCategory = [[AVAudioSession sharedInstance] category];
+
+ // Hide navigation bar by default.
+ [self hideNavigationBar];
+
+ // Hide status bar
+ // TODO: remove this [UIApplication statusBarHidden] use (deprecated since iOS 9).
+ // Note: setting statusBarHidden does nothing if your application is using the default UIViewController-based status bar system.
+ UIApplication *sharedApplication = [UIApplication performSelector:@selector(sharedApplication)];
+ if (sharedApplication)
+ {
+ sharedApplication.statusBarHidden = YES;
+ }
+
+ // Handle here the case of splitviewcontroller use on iOS 8 and later.
+ if (self.splitViewController && [self.splitViewController respondsToSelector:@selector(displayMode)])
+ {
+ if (self.hidesBottomBarWhenPushed)
+ {
+ // This screen should be displayed without tabbar, but hidesBottomBarWhenPushed flag has no effect in case of splitviewcontroller use.
+ // Trick: on iOS 8 and later the tabbar is hidden manually
+ shouldRestoreBottomBar = YES;
+ self.tabBarController.tabBar.hidden = YES;
+ }
+
+ // Hide the primary view controller to allow full screen display
+ savedSplitViewControllerDisplayMode = [self.splitViewController displayMode];
+ self.splitViewController.preferredDisplayMode = UISplitViewControllerDisplayModePrimaryHidden;
+ [self.splitViewController.view layoutIfNeeded];
+ }
+
+ [_attachmentsCollection reloadData];
+}
+
+- (void)viewDidAppear:(BOOL)animated
+{
+ [super viewDidAppear:animated];
+
+ // Adjust content offset and make visible the attachmnet collections
+ [self refreshAttachmentCollectionContentOffset];
+ _attachmentsCollection.hidden = NO;
+}
+
+- (void)viewWillDisappear:(BOOL)animated
+{
+ if (tempFile)
+ {
+ NSError *err;
+ [[NSFileManager defaultManager] removeItemAtPath:tempFile error:&err];
+ tempFile = nil;
+ }
+
+ if (currentAlert)
+ {
+ [currentAlert dismissViewControllerAnimated:NO completion:nil];
+ currentAlert = nil;
+ }
+
+ // Stop playing any video
+ for (MXKMediaCollectionViewCell *cell in self.attachmentsCollection.visibleCells)
+ {
+ [cell.moviePlayer.player pause];
+ cell.moviePlayer.player = nil;
+ }
+
+ // Restore audio category
+ if (savedAVAudioSessionCategory)
+ {
+ [[AVAudioSession sharedInstance] setCategory:savedAVAudioSessionCategory error:nil];
+ savedAVAudioSessionCategory = nil;
+ }
+
+ [navigationBarDisplayTimer invalidate];
+ navigationBarDisplayTimer = nil;
+
+ // Restore status bar
+ // TODO: remove this [UIApplication statusBarHidden] use (deprecated since iOS 9).
+ // Note: setting statusBarHidden does nothing if your application is using the default UIViewController-based status bar system.
+ UIApplication *sharedApplication = [UIApplication performSelector:@selector(sharedApplication)];
+ if (sharedApplication)
+ {
+ sharedApplication.statusBarHidden = NO;
+ }
+
+ if (shouldRestoreBottomBar)
+ {
+ self.tabBarController.tabBar.hidden = NO;
+ }
+
+ if (self.splitViewController && [self.splitViewController respondsToSelector:@selector(displayMode)])
+ {
+ self.splitViewController.preferredDisplayMode = savedSplitViewControllerDisplayMode;
+ [self.splitViewController.view layoutIfNeeded];
+ }
+
+ [super viewWillDisappear:animated];
+}
+
+- (void)dealloc
+{
+ [self destroy];
+}
+
+- (void)viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(id )coordinator
+{
+ [super viewWillTransitionToSize:size withTransitionCoordinator:coordinator];
+
+ dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(coordinator.transitionDuration * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
+
+ // Cell width will be updated, force collection layout refresh to take into account the changes
+ [self->_attachmentsCollection.collectionViewLayout invalidateLayout];
+
+ // Refresh the current attachment display
+ [self refreshAttachmentCollectionContentOffset];
+
+ });
+}
+
+#pragma mark - Override MXKViewController
+
+- (void)destroy
+{
+ if (documentInteractionController)
+ {
+ [documentInteractionController dismissPreviewAnimated:NO];
+ [documentInteractionController dismissMenuAnimated:NO];
+ documentInteractionController = nil;
+ }
+
+ if (currentSharedAttachment)
+ {
+ [currentSharedAttachment onShareEnded];
+ currentSharedAttachment = nil;
+ }
+
+ if (self.sourceViewController)
+ {
+ self.sourceViewController.navigationController.delegate = nil;
+ self.sourceViewController = nil;
+ }
+
+ [super destroy];
+}
+
+#pragma mark - Public API
+
+- (void)displayAttachments:(NSArray*)attachmentArray focusOn:(NSString*)eventId
+{
+ NSString *currentAttachmentEventId = eventId;
+ NSString *currentAttachmentOriginalFileName = nil;
+
+ if (currentAttachmentEventId.length == 0 && attachments)
+ {
+ if (isBackPaginationInProgress && currentVisibleItemIndex == 0)
+ {
+ // Here the spinner were displayed, we update the viewer by displaying the first added attachment
+ // (the one just added before the first item of the current attachments array).
+ if (attachments.count)
+ {
+ // Retrieve the event id of the first item in the current attachments array
+ MXKAttachment *attachment = attachments[0];
+ NSString *firstAttachmentEventId = attachment.eventId;
+ NSString *firstAttachmentOriginalFileName = nil;
+
+ // The original file name is used when the attachment is a local echo.
+ // Indeed its event id may be replaced by the actual one in the new attachments array.
+ if ([firstAttachmentEventId hasPrefix:kMXEventLocalEventIdPrefix])
+ {
+ firstAttachmentOriginalFileName = attachment.originalFileName;
+ }
+
+ // Look for the attachment added before this attachment in new array.
+ for (attachment in attachmentArray)
+ {
+ if (firstAttachmentOriginalFileName && [attachment.originalFileName isEqualToString:firstAttachmentOriginalFileName])
+ {
+ break;
+ }
+ else if ([attachment.eventId isEqualToString:firstAttachmentEventId])
+ {
+ break;
+ }
+ currentAttachmentEventId = attachment.eventId;
+ }
+ }
+ }
+ else if (currentVisibleItemIndex != NSNotFound)
+ {
+ // Compute the attachment index
+ NSUInteger currentAttachmentIndex = (isBackPaginationInProgress ? currentVisibleItemIndex - 1 : currentVisibleItemIndex);
+
+ if (currentAttachmentIndex < attachments.count)
+ {
+ MXKAttachment *attachment = attachments[currentAttachmentIndex];
+ currentAttachmentEventId = attachment.eventId;
+
+ // The original file name is used when the attachment is a local echo.
+ // Indeed its event id may be replaced by the actual one in the new attachments array.
+ if ([currentAttachmentEventId hasPrefix:kMXEventLocalEventIdPrefix])
+ {
+ currentAttachmentOriginalFileName = attachment.originalFileName;
+ }
+ }
+ }
+ }
+
+ // Stop back pagination (Do not call here 'stopBackPaginationActivity' because a full collection reload is planned at the end).
+ isBackPaginationInProgress = NO;
+
+ // Set/reset the attachments array
+ attachments = [NSMutableArray arrayWithArray:attachmentArray];
+
+ // Update the index of the current displayed attachment by looking for the
+ // current event id (or the current original file name, if any) in the new attachments array.
+ currentVisibleItemIndex = 0;
+ if (currentAttachmentEventId)
+ {
+ for (NSUInteger index = 0; index < attachments.count; index++)
+ {
+ MXKAttachment *attachment = attachments[index];
+
+ // Check first the original filename if any.
+ if (currentAttachmentOriginalFileName && [attachment.originalFileName isEqualToString:currentAttachmentOriginalFileName])
+ {
+ currentVisibleItemIndex = index;
+ break;
+ }
+ // Check the event id then
+ else if ([attachment.eventId isEqualToString:currentAttachmentEventId])
+ {
+ currentVisibleItemIndex = index;
+ break;
+ }
+ }
+ }
+
+ // Refresh
+ [_attachmentsCollection reloadData];
+
+ // Adjust content offset
+ [self refreshAttachmentCollectionContentOffset];
+}
+
+- (void)setComplete:(BOOL)complete
+{
+ _complete = complete;
+
+ if (complete)
+ {
+ [self stopBackPaginationActivity];
+ }
+}
+
+- (IBAction)onButtonPressed:(id)sender
+{
+ if (sender == self.backButton)
+ {
+ [self withdrawViewControllerAnimated:YES completion:nil];
+ }
+}
+
+#pragma mark - Privates
+
+- (IBAction)hideNavigationBar
+{
+ self.navigationBar.hidden = YES;
+
+ [navigationBarDisplayTimer invalidate];
+ navigationBarDisplayTimer = nil;
+}
+
+- (void)refreshCurrentVisibleItemIndex
+{
+ // Check whether the collection is actually rendered
+ if (_attachmentsCollection.contentSize.width)
+ {
+ currentVisibleItemIndex = _attachmentsCollection.contentOffset.x / [[UIScreen mainScreen] bounds].size.width;
+ }
+ else
+ {
+ currentVisibleItemIndex = NSNotFound;
+ }
+}
+
+- (void)refreshAttachmentCollectionContentOffset
+{
+ if (currentVisibleItemIndex != NSNotFound && _attachmentsCollection)
+ {
+ // Set the content offset to display the current attachment
+ CGPoint contentOffset = _attachmentsCollection.contentOffset;
+ contentOffset.x = currentVisibleItemIndex * [[UIScreen mainScreen] bounds].size.width;
+ _attachmentsCollection.contentOffset = contentOffset;
+ }
+}
+
+- (void)refreshCurrentVisibleCell
+{
+ // In case of attached image, load here the high res image.
+
+ [self refreshCurrentVisibleItemIndex];
+
+ if (currentVisibleItemIndex == NSNotFound) {
+ // Tell the delegate that no attachment is displayed for the moment
+ if ([self.delegate respondsToSelector:@selector(displayedNewAttachmentWithEventId:)])
+ {
+ [self.delegate displayedNewAttachmentWithEventId:nil];
+ }
+ }
+ else
+ {
+ NSInteger item = currentVisibleItemIndex;
+ if (isBackPaginationInProgress)
+ {
+ if (item == 0)
+ {
+ // Tell the delegate that no attachment is displayed for the moment
+ if ([self.delegate respondsToSelector:@selector(displayedNewAttachmentWithEventId:)])
+ {
+ [self.delegate displayedNewAttachmentWithEventId:nil];
+ }
+
+ return;
+ }
+
+ item --;
+ }
+
+ if (item < attachments.count)
+ {
+ MXKAttachment *attachment = attachments[item];
+ NSString *mimeType = attachment.contentInfo[@"mimetype"];
+
+ // Tell the delegate which attachment has been shown using its eventId
+ if ([self.delegate respondsToSelector:@selector(displayedNewAttachmentWithEventId:)])
+ {
+ [self.delegate displayedNewAttachmentWithEventId:attachment.eventId];
+ }
+
+ // Check attachment type
+ if (attachment.type == MXKAttachmentTypeImage && attachment.contentURL && ![mimeType isEqualToString:@"image/gif"])
+ {
+ // Retrieve the related cell
+ UICollectionViewCell *cell = [_attachmentsCollection cellForItemAtIndexPath:[NSIndexPath indexPathForItem:currentVisibleItemIndex inSection:0]];
+ if ([cell isKindOfClass:[MXKMediaCollectionViewCell class]])
+ {
+ MXKMediaCollectionViewCell *mediaCollectionViewCell = (MXKMediaCollectionViewCell*)cell;
+
+ // Load high res image
+ mediaCollectionViewCell.mxkImageView.stretchable = YES;
+ mediaCollectionViewCell.mxkImageView.enableInMemoryCache = NO;
+
+ [mediaCollectionViewCell.mxkImageView setAttachment:attachment];
+ }
+ }
+ }
+ }
+}
+
+- (void)stopBackPaginationActivity
+{
+ if (isBackPaginationInProgress)
+ {
+ isBackPaginationInProgress = NO;
+
+ [self.attachmentsCollection deleteItemsAtIndexPaths:@[[NSIndexPath indexPathForItem:0 inSection:0]]];
+ }
+}
+
+- (void)prepareVideoForItem:(NSInteger)item success:(void(^)(void))success failure:(void(^)(NSError *))failure
+{
+ MXKAttachment *attachment = attachments[item];
+ if (attachment.isEncrypted)
+ {
+ [attachment decryptToTempFile:^(NSString *file) {
+ if (self->tempFile)
+ {
+ [[NSFileManager defaultManager] removeItemAtPath:self->tempFile error:nil];
+ }
+ self->tempFile = file;
+ self->videoFile = file;
+ success();
+ } failure:^(NSError *error) {
+ if (failure) failure(error);
+ }];
+ }
+ else
+ {
+ if ([[NSFileManager defaultManager] fileExistsAtPath:attachment.cacheFilePath])
+ {
+ videoFile = attachment.cacheFilePath;
+ success();
+ }
+ else
+ {
+ [attachment prepare:^{
+ self->videoFile = attachment.cacheFilePath;
+ success();
+ } failure:^(NSError *error) {
+ if (failure) failure(error);
+ }];
+ }
+ }
+}
+
+#pragma mark - UICollectionViewDataSource
+
+- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section
+{
+ if (isBackPaginationInProgress)
+ {
+ return (attachments.count + 1);
+ }
+
+ return attachments.count;
+}
+
+- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
+{
+ MXKMediaCollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:[MXKMediaCollectionViewCell defaultReuseIdentifier]
+ forIndexPath:indexPath];
+
+ NSInteger item = indexPath.item;
+
+ if (isBackPaginationInProgress)
+ {
+ if (item == 0)
+ {
+ cell.mxkImageView.hidden = YES;
+ cell.customView.hidden = NO;
+
+ // Add back pagination spinner
+ UIActivityIndicatorView* spinner = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhiteLarge];
+ spinner.autoresizingMask = (UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleRightMargin | UIViewAutoresizingFlexibleTopMargin | UIViewAutoresizingFlexibleBottomMargin);
+ spinner.hidesWhenStopped = NO;
+ spinner.backgroundColor = [UIColor clearColor];
+ [spinner startAnimating];
+
+ spinner.center = cell.customView.center;
+ [cell.customView addSubview:spinner];
+
+ return cell;
+ }
+
+ item --;
+ }
+
+ if (item < attachments.count)
+ {
+ MXKAttachment *attachment = attachments[item];
+ NSString *mimeType = attachment.contentInfo[@"mimetype"];
+
+ // Use the cached thumbnail (if any) as preview
+ UIImage* preview = [attachment getCachedThumbnail];
+
+ // Check attachment type
+ if ((attachment.type == MXKAttachmentTypeImage || attachment.type == MXKAttachmentTypeSticker) && attachment.contentURL)
+ {
+ if ([mimeType isEqualToString:@"image/gif"])
+ {
+ cell.mxkImageView.hidden = YES;
+ // Set the preview as the default image even if the image view is hidden. It will be used during zoom out animation.
+ cell.mxkImageView.image = preview;
+
+ cell.customView.hidden = NO;
+
+ // Animated gif is displayed in webview
+ CGFloat minSize = (cell.frame.size.width < cell.frame.size.height) ? cell.frame.size.width : cell.frame.size.height;
+ CGFloat width, height;
+ if (attachment.contentInfo[@"w"] && attachment.contentInfo[@"h"])
+ {
+ width = [attachment.contentInfo[@"w"] integerValue];
+ height = [attachment.contentInfo[@"h"] integerValue];
+ if (width > minSize || height > minSize)
+ {
+ if (width > height)
+ {
+ height = (height * minSize) / width;
+ height = floorf(height / 2) * 2;
+ width = minSize;
+ }
+ else
+ {
+ width = (width * minSize) / height;
+ width = floorf(width / 2) * 2;
+ height = minSize;
+ }
+ }
+ else
+ {
+ width = minSize;
+ height = minSize;
+ }
+ }
+ else
+ {
+ width = minSize;
+ height = minSize;
+ }
+
+ WKWebView *animatedGifViewer = [[WKWebView alloc] initWithFrame:CGRectMake(0, 0, width, height)];
+ animatedGifViewer.center = cell.customView.center;
+ animatedGifViewer.opaque = NO;
+ animatedGifViewer.backgroundColor = cell.customView.backgroundColor;
+ animatedGifViewer.contentMode = UIViewContentModeScaleAspectFit;
+ animatedGifViewer.autoresizingMask = (UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleRightMargin | UIViewAutoresizingFlexibleTopMargin | UIViewAutoresizingFlexibleBottomMargin);
+ animatedGifViewer.userInteractionEnabled = NO;
+ [cell.customView addSubview:animatedGifViewer];
+
+ UIImageView *previewImage = [[UIImageView alloc] initWithFrame:animatedGifViewer.frame];
+ previewImage.contentMode = animatedGifViewer.contentMode;
+ previewImage.autoresizingMask = animatedGifViewer.autoresizingMask;
+ previewImage.image = preview;
+ previewImage.center = cell.customView.center;
+ [cell.customView addSubview:previewImage];
+
+ MXKPieChartView *pieChartView = [[MXKPieChartView alloc] initWithFrame:CGRectMake(0, 0, 40, 40)];
+ pieChartView.progress = 0;
+ pieChartView.progressColor = [UIColor colorWithRed:1 green:1 blue:1 alpha:0.25];
+ pieChartView.unprogressColor = [UIColor clearColor];
+ pieChartView.autoresizingMask = animatedGifViewer.autoresizingMask;
+ pieChartView.center = cell.customView.center;
+ [cell.customView addSubview:pieChartView];
+
+ // Add download progress observer
+ NSString *downloadId = attachment.downloadId;
+ cell.notificationObserver = [[NSNotificationCenter defaultCenter] addObserverForName:kMXMediaLoaderStateDidChangeNotification object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *notif) {
+
+ MXMediaLoader *loader = (MXMediaLoader*)notif.object;
+ if ([loader.downloadId isEqualToString:downloadId])
+ {
+ // update the image
+ switch (loader.state) {
+ case MXMediaLoaderStateDownloadInProgress:
+ {
+ NSNumber* progressNumber = [loader.statisticsDict valueForKey:kMXMediaLoaderProgressValueKey];
+ if (progressNumber)
+ {
+ pieChartView.progress = progressNumber.floatValue;
+ }
+ break;
+ }
+ default:
+ break;
+ }
+ }
+
+ }];
+
+ void (^onDownloaded)(NSData *) = ^(NSData *data){
+ if (cell.notificationObserver)
+ {
+ [[NSNotificationCenter defaultCenter] removeObserver:cell.notificationObserver];
+ cell.notificationObserver = nil;
+ }
+
+ if (animatedGifViewer.superview)
+ {
+ [animatedGifViewer loadData:data MIMEType:@"image/gif" characterEncodingName:@"UTF-8" baseURL:[NSURL URLWithString:@"http://"]];
+
+ [pieChartView removeFromSuperview];
+ [previewImage removeFromSuperview];
+ }
+ };
+
+ void (^onFailure)(NSError *) = ^(NSError *error){
+ if (cell.notificationObserver)
+ {
+ [[NSNotificationCenter defaultCenter] removeObserver:cell.notificationObserver];
+ cell.notificationObserver = nil;
+ }
+
+ MXLogDebug(@"[MXKAttachmentsVC] gif download failed");
+ // Notify MatrixKit user
+ [[NSNotificationCenter defaultCenter] postNotificationName:kMXKErrorNotification object:error];
+ };
+
+
+ [attachment getAttachmentData:^(NSData *data) {
+ onDownloaded(data);
+ } failure:^(NSError *error) {
+ onFailure(error);
+ }];
+ }
+ else if (indexPath.item == currentVisibleItemIndex)
+ {
+ // Load high res image
+ cell.mxkImageView.stretchable = YES;
+ [cell.mxkImageView setAttachment:attachment];
+ }
+ else
+ {
+ // Use the thumbnail here - Full res images should only be downloaded explicitly when requested (see [self refreshCurrentVisibleItemIndex])
+ cell.mxkImageView.stretchable = YES;
+ [cell.mxkImageView setAttachmentThumb:attachment];
+ }
+ }
+ else if (attachment.type == MXKAttachmentTypeVideo && attachment.contentURL)
+ {
+ cell.mxkImageView.mediaFolder = attachment.eventRoomId;
+ cell.mxkImageView.stretchable = NO;
+ cell.mxkImageView.enableInMemoryCache = YES;
+ // Display video thumbnail, the video is played only when user selects this cell
+ [cell.mxkImageView setAttachmentThumb:attachment];
+
+ cell.centerIcon.image = [NSBundle mxk_imageFromMXKAssetsBundleWithName:@"play"];
+ cell.centerIcon.hidden = NO;
+ }
+
+ // Add gesture recognizers on collection cell to handle tap and long press on collection cell.
+ // Note: tap gesture recognizer is required here because mxkImageView enables user interaction to allow image stretching.
+ // [collectionView:didSelectItemAtIndexPath] is not triggered when mxkImageView is displayed.
+ UITapGestureRecognizer *cellTapGesture = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(onCollectionViewCellTap:)];
+ [cellTapGesture setNumberOfTouchesRequired:1];
+ [cellTapGesture setNumberOfTapsRequired:1];
+ cell.tag = item;
+ [cell addGestureRecognizer:cellTapGesture];
+
+ UILongPressGestureRecognizer *cellLongPressGesture = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(onCollectionViewCellLongPress:)];
+ [cell addGestureRecognizer:cellLongPressGesture];
+ }
+
+ return cell;
+}
+
+#pragma mark - UICollectionViewDelegate
+
+- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath
+{
+ NSInteger item = indexPath.item;
+
+ BOOL navigationBarDisplayHandled = NO;
+
+ if (isBackPaginationInProgress)
+ {
+ if (item == 0)
+ {
+ return;
+ }
+
+ item --;
+ }
+
+ // Check whether the selected attachment is a video
+ if (item < attachments.count)
+ {
+ MXKAttachment *attachment = attachments[item];
+
+ if (attachment.type == MXKAttachmentTypeVideo && attachment.contentURL)
+ {
+ MXKMediaCollectionViewCell *selectedCell = (MXKMediaCollectionViewCell*)[collectionView cellForItemAtIndexPath:indexPath];
+
+ // Add movie player if none
+ if (selectedCell.moviePlayer == nil)
+ {
+ [[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayback error:nil];
+
+ selectedCell.moviePlayer = [[AVPlayerViewController alloc] init];
+ if (selectedCell.moviePlayer != nil)
+ {
+ // Switch in custom view
+ selectedCell.mxkImageView.hidden = YES;
+ selectedCell.customView.hidden = NO;
+
+ // Report the video preview
+ UIImageView *previewImage = [[UIImageView alloc] initWithFrame:selectedCell.customView.frame];
+ previewImage.contentMode = UIViewContentModeScaleAspectFit;
+ previewImage.autoresizingMask = (UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleRightMargin | UIViewAutoresizingFlexibleTopMargin | UIViewAutoresizingFlexibleBottomMargin);
+ previewImage.image = selectedCell.mxkImageView.image;
+ previewImage.center = selectedCell.customView.center;
+ [selectedCell.customView addSubview:previewImage];
+
+ selectedCell.moviePlayer.videoGravity = AVLayerVideoGravityResizeAspect;
+ selectedCell.moviePlayer.view.frame = selectedCell.customView.frame;
+ selectedCell.moviePlayer.view.center = selectedCell.customView.center;
+ selectedCell.moviePlayer.view.hidden = YES;
+ [selectedCell.customView addSubview:selectedCell.moviePlayer.view];
+
+ // Force the video to stay in fullscreen
+ NSLayoutConstraint* topConstraint = [NSLayoutConstraint constraintWithItem:selectedCell.moviePlayer.view
+ attribute:NSLayoutAttributeTop
+ relatedBy:NSLayoutRelationEqual
+ toItem:selectedCell.customView
+ attribute:NSLayoutAttributeTop
+ multiplier:1.0f
+ constant:0.0f];
+
+ NSLayoutConstraint *leadingConstraint = [NSLayoutConstraint constraintWithItem:selectedCell.moviePlayer.view
+ attribute:NSLayoutAttributeLeading
+ relatedBy:0
+ toItem:selectedCell.customView
+ attribute:NSLayoutAttributeLeading
+ multiplier:1.0
+ constant:0];
+
+ NSLayoutConstraint *bottomConstraint = [NSLayoutConstraint constraintWithItem:selectedCell.moviePlayer.view
+ attribute:NSLayoutAttributeBottom
+ relatedBy:0
+ toItem:selectedCell.customView
+ attribute:NSLayoutAttributeBottom
+ multiplier:1
+ constant:0];
+
+ NSLayoutConstraint *trailingConstraint = [NSLayoutConstraint constraintWithItem:selectedCell.moviePlayer.view
+ attribute:NSLayoutAttributeTrailing
+ relatedBy:0
+ toItem:selectedCell.customView
+ attribute:NSLayoutAttributeTrailing
+ multiplier:1.0
+ constant:0];
+
+ selectedCell.moviePlayer.view.translatesAutoresizingMaskIntoConstraints = NO;
+
+ [NSLayoutConstraint activateConstraints:@[topConstraint, leadingConstraint, bottomConstraint, trailingConstraint]];
+ [[NSNotificationCenter defaultCenter] addObserver:self
+ selector:@selector(moviePlayerPlaybackDidFinishWithErrorNotification:)
+ name:AVPlayerItemFailedToPlayToEndTimeNotification
+ object:nil];
+ }
+ }
+
+ if (selectedCell.moviePlayer)
+ {
+ if (selectedCell.moviePlayer.player.status == AVPlayerStatusReadyToPlay)
+ {
+ // Show or hide the navigation bar
+
+ // The video controls bar display is automatically managed by MPMoviePlayerController.
+ // We have no control on it and no notifications about its displays changes.
+ // The following code synchronizes the display of the navigation bar with the
+ // MPMoviePlayerController controls bar.
+
+ // Check the MPMoviePlayerController controls bar display status by an hacky way
+ BOOL controlsVisible = NO;
+ for(id views in [[selectedCell.moviePlayer view] subviews])
+ {
+ for(id subViews in [views subviews])
+ {
+ for (id controlView in [subViews subviews])
+ {
+ if ([controlView isKindOfClass:[UIView class]] && ((UIView*)controlView).tag == 1004)
+ {
+ UIView *subView = (UIView*)controlView;
+
+ controlsVisible = (subView.alpha <= 0.0) ? NO : YES;
+ }
+ }
+ }
+ }
+
+ // Apply the same display to the navigation bar
+ self.navigationBar.hidden = !controlsVisible;
+
+ navigationBarDisplayHandled = YES;
+ if (!self.navigationBar.hidden)
+ {
+ // Automaticaly hide the nav bar after 5s. This is the same timer value that
+ // MPMoviePlayerController uses for its controls bar
+ [navigationBarDisplayTimer invalidate];
+ navigationBarDisplayTimer = [NSTimer scheduledTimerWithTimeInterval:5 target:self selector:@selector(hideNavigationBar) userInfo:self repeats:NO];
+ }
+ }
+ else
+ {
+ MXKPieChartView *pieChartView = [[MXKPieChartView alloc] initWithFrame:CGRectMake(0, 0, 40, 40)];
+ pieChartView.progress = 0;
+ pieChartView.progressColor = [UIColor colorWithRed:1 green:1 blue:1 alpha:0.25];
+ pieChartView.unprogressColor = [UIColor clearColor];
+ pieChartView.autoresizingMask = (UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleRightMargin | UIViewAutoresizingFlexibleTopMargin | UIViewAutoresizingFlexibleBottomMargin);
+ pieChartView.center = selectedCell.customView.center;
+ [selectedCell.customView addSubview:pieChartView];
+
+ // Add download progress observer
+ NSString *downloadId = attachment.downloadId;
+ selectedCell.notificationObserver = [[NSNotificationCenter defaultCenter] addObserverForName:kMXMediaLoaderStateDidChangeNotification object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *notif) {
+
+ MXMediaLoader *loader = (MXMediaLoader*)notif.object;
+ if ([loader.downloadId isEqualToString:downloadId])
+ {
+ // update progress
+ switch (loader.state) {
+ case MXMediaLoaderStateDownloadInProgress:
+ {
+ NSNumber* progressNumber = [loader.statisticsDict valueForKey:kMXMediaLoaderProgressValueKey];
+ if (progressNumber)
+ {
+ pieChartView.progress = progressNumber.floatValue;
+ }
+ break;
+ }
+ default:
+ break;
+ }
+ }
+
+ }];
+
+ [self prepareVideoForItem:item success:^{
+
+ if (selectedCell.notificationObserver)
+ {
+ [[NSNotificationCenter defaultCenter] removeObserver:selectedCell.notificationObserver];
+ selectedCell.notificationObserver = nil;
+ }
+
+ if (selectedCell.moviePlayer.view.superview)
+ {
+ selectedCell.moviePlayer.view.hidden = NO;
+ selectedCell.centerIcon.hidden = YES;
+ selectedCell.moviePlayer.player = [AVPlayer playerWithURL:[NSURL fileURLWithPath:self->videoFile]];
+ [selectedCell.moviePlayer.player play];
+
+ [pieChartView removeFromSuperview];
+
+ [self hideNavigationBar];
+ }
+
+ } failure:^(NSError *error) {
+
+ if (selectedCell.notificationObserver)
+ {
+ [[NSNotificationCenter defaultCenter] removeObserver:selectedCell.notificationObserver];
+ selectedCell.notificationObserver = nil;
+ }
+
+ MXLogDebug(@"[MXKAttachmentsVC] video download failed");
+
+ [pieChartView removeFromSuperview];
+
+ // Display the navigation bar so that the user can leave this screen
+ self.navigationBar.hidden = NO;
+
+ // Notify MatrixKit user
+ [[NSNotificationCenter defaultCenter] postNotificationName:kMXKErrorNotification object:error];
+
+ }];
+
+ // Do not animate the navigation bar on video playback preparing
+ return;
+ }
+ }
+ }
+ }
+
+ // Animate navigation bar if it is has not been handled
+ if (!navigationBarDisplayHandled)
+ {
+ if (self.navigationBar.hidden)
+ {
+ self.navigationBar.hidden = NO;
+ [navigationBarDisplayTimer invalidate];
+ navigationBarDisplayTimer = [NSTimer scheduledTimerWithTimeInterval:3 target:self selector:@selector(hideNavigationBar) userInfo:self repeats:NO];
+ }
+ else
+ {
+ [self hideNavigationBar];
+ }
+ }
+}
+
+- (void)collectionView:(UICollectionView *)collectionView didEndDisplayingCell:(UICollectionViewCell *)cell forItemAtIndexPath:(NSIndexPath *)indexPath
+{
+ // Here the cell is not displayed anymore, but it may be displayed again if the user swipes on it.
+ if ([cell isKindOfClass:[MXKMediaCollectionViewCell class]])
+ {
+ MXKMediaCollectionViewCell *mediaCollectionViewCell = (MXKMediaCollectionViewCell*)cell;
+
+ // Check whether a video was playing in this cell.
+ if (mediaCollectionViewCell.moviePlayer)
+ {
+ // This cell concerns an attached video.
+ // We stop the player, and restore the default display based on the video thumbnail
+ [mediaCollectionViewCell.moviePlayer.player pause];
+ mediaCollectionViewCell.moviePlayer.player = nil;
+ mediaCollectionViewCell.moviePlayer = nil;
+
+ mediaCollectionViewCell.mxkImageView.hidden = NO;
+ mediaCollectionViewCell.centerIcon.hidden = NO;
+ mediaCollectionViewCell.customView.hidden = YES;
+
+ // Remove potential media download observer
+ if (mediaCollectionViewCell.notificationObserver)
+ {
+ [[NSNotificationCenter defaultCenter] removeObserver:mediaCollectionViewCell.notificationObserver];
+ mediaCollectionViewCell.notificationObserver = nil;
+ }
+ }
+ }
+}
+
+- (void)scrollViewWillEndDragging:(UIScrollView *)scrollView withVelocity:(CGPoint)velocity targetContentOffset:(inout CGPoint *)targetContentOffset
+{
+ // Detect horizontal bounce at the beginning of the collection to trigger pagination
+ if (scrollView == self.attachmentsCollection && !isBackPaginationInProgress && !self.complete && self.delegate)
+ {
+ if (scrollView.contentOffset.x < -30)
+ {
+ isBackPaginationInProgress = YES;
+ [self.attachmentsCollection insertItemsAtIndexPaths:@[[NSIndexPath indexPathForItem:0 inSection:0]]];
+ }
+ }
+}
+
+- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView
+{
+ if (scrollView == self.attachmentsCollection)
+ {
+ if (isBackPaginationInProgress)
+ {
+ MXKAttachment *attachment = self.attachments.firstObject;
+ self.complete = ![self.delegate attachmentsViewController:self paginateAttachmentBefore:attachment.eventId];
+ }
+ else
+ {
+ [self refreshCurrentVisibleCell];
+ }
+ }
+}
+
+#pragma mark - UICollectionViewDelegateFlowLayout
+
+- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath
+{
+ return [[UIScreen mainScreen] bounds].size;
+}
+
+#pragma mark - Movie Player
+
+- (void)moviePlayerPlaybackDidFinishWithErrorNotification:(NSNotification *)notification
+{
+ NSDictionary *notificationUserInfo = [notification userInfo];
+
+ NSError *mediaPlayerError = [notificationUserInfo objectForKey:AVPlayerItemFailedToPlayToEndTimeErrorKey];
+ if (mediaPlayerError)
+ {
+ MXLogDebug(@"[MXKAttachmentsVC] Playback failed with error description: %@", [mediaPlayerError localizedDescription]);
+
+ // Display the navigation bar so that the user can leave this screen
+ self.navigationBar.hidden = NO;
+
+ // Notify MatrixKit user
+ [[NSNotificationCenter defaultCenter] postNotificationName:kMXKErrorNotification object:mediaPlayerError];
+ }
+}
+
+#pragma mark - Gesture recognizer
+
+- (void)onCollectionViewCellTap:(UIGestureRecognizer*)gestureRecognizer
+{
+ MXKMediaCollectionViewCell *selectedCell;
+
+ UIView *view = gestureRecognizer.view;
+ if ([view isKindOfClass:[MXKMediaCollectionViewCell class]])
+ {
+ selectedCell = (MXKMediaCollectionViewCell*)view;
+ }
+
+ // Notify the collection view delegate a cell has been selected.
+ if (selectedCell && selectedCell.tag < attachments.count)
+ {
+ [self collectionView:self.attachmentsCollection didSelectItemAtIndexPath:[NSIndexPath indexPathForItem:(isBackPaginationInProgress ? selectedCell.tag + 1: selectedCell.tag) inSection:0]];
+ }
+}
+
+- (void)onCollectionViewCellLongPress:(UIGestureRecognizer*)gestureRecognizer
+{
+ MXKMediaCollectionViewCell *selectedCell;
+
+ if (gestureRecognizer.state == UIGestureRecognizerStateBegan)
+ {
+ UIView *view = gestureRecognizer.view;
+ if ([view isKindOfClass:[MXKMediaCollectionViewCell class]])
+ {
+ selectedCell = (MXKMediaCollectionViewCell*)view;
+ }
+ }
+
+ // Notify the collection view delegate a cell has been selected.
+ if (selectedCell && selectedCell.tag < attachments.count)
+ {
+ MXKAttachment *attachment = attachments[selectedCell.tag];
+
+ if (currentAlert)
+ {
+ [currentAlert dismissViewControllerAnimated:NO completion:nil];
+ }
+
+ __weak __typeof(self) weakSelf = self;
+
+ currentAlert = [UIAlertController alertControllerWithTitle:nil message:nil preferredStyle:UIAlertControllerStyleActionSheet];
+
+ if ([MXKAppSettings standardAppSettings].messageDetailsAllowSaving)
+ {
+ [currentAlert addAction:[UIAlertAction actionWithTitle:[MatrixKitL10n save]
+ style:UIAlertActionStyleDefault
+ handler:^(UIAlertAction * action) {
+
+ typeof(self) self = weakSelf;
+ self->currentAlert = nil;
+
+ [self startActivityIndicator];
+
+ [attachment save:^{
+
+ typeof(self) self = weakSelf;
+ [self stopActivityIndicator];
+
+ } failure:^(NSError *error) {
+
+ typeof(self) self = weakSelf;
+ [self stopActivityIndicator];
+
+ // Notify MatrixKit user
+ [[NSNotificationCenter defaultCenter] postNotificationName:kMXKErrorNotification object:error];
+
+ }];
+
+ }]];
+ }
+
+ if ([MXKAppSettings standardAppSettings].messageDetailsAllowCopyingMedia)
+ {
+ [currentAlert addAction:[UIAlertAction actionWithTitle:[MatrixKitL10n copyButtonName]
+ style:UIAlertActionStyleDefault
+ handler:^(UIAlertAction * action) {
+
+ typeof(self) self = weakSelf;
+ self->currentAlert = nil;
+
+ [self startActivityIndicator];
+
+ [attachment copy:^{
+
+ typeof(self) self = weakSelf;
+ [self stopActivityIndicator];
+
+ } failure:^(NSError *error) {
+
+ typeof(self) self = weakSelf;
+ [self stopActivityIndicator];
+
+ // Notify MatrixKit user
+ [[NSNotificationCenter defaultCenter] postNotificationName:kMXKErrorNotification object:error];
+
+ }];
+
+ }]];
+ }
+
+ if ([MXKAppSettings standardAppSettings].messageDetailsAllowSharing)
+ {
+ [currentAlert addAction:[UIAlertAction actionWithTitle:[MatrixKitL10n share]
+ style:UIAlertActionStyleDefault
+ handler:^(UIAlertAction * action) {
+
+ MXWeakify(self);
+
+ self->currentAlert = nil;
+
+ [self startActivityIndicator];
+
+ [attachment prepareShare:^(NSURL *fileURL) {
+
+ MXStrongifyAndReturnIfNil(self);
+
+ [self stopActivityIndicator];
+
+ self->documentInteractionController = [UIDocumentInteractionController interactionControllerWithURL:fileURL];
+ [self->documentInteractionController setDelegate:self];
+ self->currentSharedAttachment = attachment;
+
+ if (![self->documentInteractionController presentOptionsMenuFromRect:self.view.frame inView:self.view animated:YES])
+ {
+ self->documentInteractionController = nil;
+ [attachment onShareEnded];
+ self->currentSharedAttachment = nil;
+ }
+
+ } failure:^(NSError *error) {
+
+ MXStrongifyAndReturnIfNil(self);
+
+ [self stopActivityIndicator];
+
+ // Notify MatrixKit user
+ [[NSNotificationCenter defaultCenter] postNotificationName:kMXKErrorNotification object:error];
+
+ }];
+
+ }]];
+ }
+
+ if ([MXMediaManager existingDownloaderWithIdentifier:attachment.downloadId])
+ {
+ [currentAlert addAction:[UIAlertAction actionWithTitle:[MatrixKitL10n cancelDownload]
+ style:UIAlertActionStyleDefault
+ handler:^(UIAlertAction * action) {
+
+ typeof(self) self = weakSelf;
+ self->currentAlert = nil;
+
+ // Get again the loader
+ MXMediaLoader *loader = [MXMediaManager existingDownloaderWithIdentifier:attachment.downloadId];
+ if (loader)
+ {
+ [loader cancel];
+ }
+
+ }]];
+ }
+
+ if (currentAlert.actions.count)
+ {
+ [currentAlert addAction:[UIAlertAction actionWithTitle:[MatrixKitL10n cancel]
+ style:UIAlertActionStyleCancel
+ handler:^(UIAlertAction * action) {
+
+ typeof(self) self = weakSelf;
+ self->currentAlert = nil;
+
+ }]];
+
+ [currentAlert popoverPresentationController].sourceView = _attachmentsCollection;
+ [currentAlert popoverPresentationController].sourceRect = _attachmentsCollection.bounds;
+ [self presentViewController:currentAlert animated:YES completion:nil];
+ }
+ else
+ {
+ currentAlert = nil;
+ }
+ }
+}
+
+#pragma mark - UIDocumentInteractionControllerDelegate
+
+- (UIViewController *)documentInteractionControllerViewControllerForPreview: (UIDocumentInteractionController *) controller
+{
+ return self;
+}
+
+// Preview presented/dismissed on document. Use to set up any HI underneath.
+- (void)documentInteractionControllerWillBeginPreview:(UIDocumentInteractionController *)controller
+{
+ documentInteractionController = controller;
+}
+
+- (void)documentInteractionControllerDidEndPreview:(UIDocumentInteractionController *)controller
+{
+ documentInteractionController = nil;
+ if (currentSharedAttachment)
+ {
+ [currentSharedAttachment onShareEnded];
+ currentSharedAttachment = nil;
+ }
+}
+
+- (void)documentInteractionControllerDidDismissOptionsMenu:(UIDocumentInteractionController *)controller
+{
+ documentInteractionController = nil;
+ if (currentSharedAttachment)
+ {
+ [currentSharedAttachment onShareEnded];
+ currentSharedAttachment = nil;
+ }
+}
+
+- (void)documentInteractionControllerDidDismissOpenInMenu:(UIDocumentInteractionController *)controller
+{
+ documentInteractionController = nil;
+ if (currentSharedAttachment)
+ {
+ [currentSharedAttachment onShareEnded];
+ currentSharedAttachment = nil;
+ }
+}
+
+- (UIImageView *)finalImageView
+{
+ MXKMediaCollectionViewCell *cell = (MXKMediaCollectionViewCell *)[self.attachmentsCollection.visibleCells firstObject];
+ return cell.mxkImageView.imageView;
+}
+
+#pragma mark - UIViewControllerTransitioningDelegate
+
+- (id )animationControllerForPresentedController:(UIViewController *)presented presentingController:(UIViewController *)presenting sourceController:(UIViewController *)source
+{
+ if (self.customAnimationsEnabled)
+ {
+ return [[MXKAttachmentAnimator alloc] initWithAnimationType:PhotoBrowserZoomInAnimation sourceViewController:self.sourceViewController];
+ }
+ return nil;
+}
+
+- (id )animationControllerForDismissedController:(UIViewController *)dismissed
+{
+ [self hideNavigationBar];
+
+ if (self.customAnimationsEnabled)
+ {
+ return [[MXKAttachmentAnimator alloc] initWithAnimationType:PhotoBrowserZoomOutAnimation sourceViewController:self.sourceViewController];
+ }
+ return nil;
+}
+
+- (id)interactionControllerForDismissal:(id)animator
+{
+ //if there is an interaction, use the custom interaction controller to handle it
+ if (self.interactionController.interactionInProgress)
+ {
+ return self.interactionController;
+ }
+ return nil;
+}
+
+#pragma mark - UINavigationControllerDelegate
+
+- (id )navigationController:(UINavigationController *)navigationController interactionControllerForAnimationController:(id ) animationController {
+ if (self.customAnimationsEnabled && self.interactionController.interactionInProgress)
+ {
+ return self.interactionController;
+ }
+ return nil;
+}
+
+- (id )navigationController:(UINavigationController *)navigationController animationControllerForOperation:(UINavigationControllerOperation)operation
+ fromViewController:(UIViewController *)fromVC
+ toViewController:(UIViewController *)toVC
+{
+
+ if (self.customAnimationsEnabled)
+ {
+ if (operation == UINavigationControllerOperationPush)
+ {
+ return [[MXKAttachmentAnimator alloc] initWithAnimationType:PhotoBrowserZoomInAnimation sourceViewController:self.sourceViewController];
+ }
+ if (operation == UINavigationControllerOperationPop)
+ {
+ return [[MXKAttachmentAnimator alloc] initWithAnimationType:PhotoBrowserZoomOutAnimation sourceViewController:self.sourceViewController];
+ }
+ return nil;
+ }
+
+ return nil;
+}
+
+@end
diff --git a/Riot/Modules/MatrixKit/Controllers/MXKAttachmentsViewController.xib b/Riot/Modules/MatrixKit/Controllers/MXKAttachmentsViewController.xib
new file mode 100644
index 000000000..28409d2de
--- /dev/null
+++ b/Riot/Modules/MatrixKit/Controllers/MXKAttachmentsViewController.xib
@@ -0,0 +1,69 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Riot/Modules/MatrixKit/Controllers/MXKAuthenticationViewController.h b/Riot/Modules/MatrixKit/Controllers/MXKAuthenticationViewController.h
new file mode 100644
index 000000000..ffb64afbe
--- /dev/null
+++ b/Riot/Modules/MatrixKit/Controllers/MXKAuthenticationViewController.h
@@ -0,0 +1,311 @@
+/*
+ Copyright 2015 OpenMarket Ltd
+ Copyright 2017 Vector Creations Ltd
+ Copyright 2019 The Matrix.org Foundation C.I.C
+
+ 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 "MXKViewController.h"
+
+#import "MXKAuthInputsView.h"
+#import "MXKAuthenticationFallbackWebView.h"
+
+@class MXKAuthenticationViewController;
+
+/**
+ `MXKAuthenticationViewController` delegate.
+ */
+@protocol MXKAuthenticationViewControllerDelegate
+
+/**
+ Tells the delegate the authentication process succeeded to add a new account.
+
+ @param authenticationViewController the `MXKAuthenticationViewController` instance.
+ @param userId the user id of the new added account.
+ */
+- (void)authenticationViewController:(MXKAuthenticationViewController *)authenticationViewController didLogWithUserId:(NSString*)userId;
+
+@end
+
+/**
+ This view controller should be used to manage registration or login flows with matrix homeserver.
+
+ Only the flow based on password is presently supported. Other flows should be added later.
+
+ You may add a delegate to be notified when a new account has been added successfully.
+ */
+@interface MXKAuthenticationViewController : MXKViewController
+{
+@protected
+
+ /**
+ Reference to any opened alert view.
+ */
+ UIAlertController *alert;
+
+ /**
+ Tell whether the password has been reseted with success.
+ Used to return on login screen on submit button pressed.
+ */
+ BOOL isPasswordReseted;
+}
+
+@property (weak, nonatomic) IBOutlet UIImageView *welcomeImageView;
+
+@property (strong, nonatomic) IBOutlet UIScrollView *authenticationScrollView;
+@property (weak, nonatomic) IBOutlet NSLayoutConstraint *authScrollViewBottomConstraint;
+
+@property (weak, nonatomic) IBOutlet UIView *contentView;
+@property (weak, nonatomic) IBOutlet NSLayoutConstraint *contentViewHeightConstraint;
+
+@property (weak, nonatomic) IBOutlet UILabel *subTitleLabel;
+
+@property (weak, nonatomic) IBOutlet UIView *authInputsContainerView;
+@property (weak, nonatomic) IBOutlet NSLayoutConstraint *authInputContainerViewHeightConstraint;
+@property (weak, nonatomic) IBOutlet NSLayoutConstraint *authInputContainerViewMinHeightConstraint;
+
+@property (weak, nonatomic) IBOutlet UILabel *homeServerLabel;
+@property (weak, nonatomic) IBOutlet UITextField *homeServerTextField;
+@property (weak, nonatomic) IBOutlet UILabel *homeServerInfoLabel;
+@property (weak, nonatomic) IBOutlet UIView *identityServerContainer;
+@property (weak, nonatomic) IBOutlet UILabel *identityServerLabel;
+@property (weak, nonatomic) IBOutlet UITextField *identityServerTextField;
+@property (weak, nonatomic) IBOutlet UILabel *identityServerInfoLabel;
+
+@property (weak, nonatomic) IBOutlet UIButton *submitButton;
+@property (weak, nonatomic) IBOutlet UIButton *authSwitchButton;
+
+@property (strong, nonatomic) IBOutlet UIActivityIndicatorView *authenticationActivityIndicator;
+@property (weak, nonatomic) IBOutlet UIView *authenticationActivityIndicatorContainerView;
+@property (weak, nonatomic) IBOutlet UILabel *noFlowLabel;
+@property (weak, nonatomic) IBOutlet UIButton *retryButton;
+
+@property (weak, nonatomic) IBOutlet UIView *authFallbackContentView;
+// WKWebView is not available to be created from xib because of NSCoding support below iOS 11. So we're using a container view.
+// See this: https://stackoverflow.com/questions/46221577/xcode-9-gm-wkwebview-nscoding-support-was-broken-in-previous-versions
+@property (weak, nonatomic) IBOutlet UIView *authFallbackWebViewContainer;
+@property (strong, nonatomic) MXKAuthenticationFallbackWebView *authFallbackWebView;
+@property (weak, nonatomic) IBOutlet UIButton *cancelAuthFallbackButton;
+
+/**
+ The current authentication type (MXKAuthenticationTypeLogin by default).
+ */
+@property (nonatomic) MXKAuthenticationType authType;
+
+/**
+ The view in which authentication inputs are displayed (`MXKAuthInputsView-inherited` instance).
+ */
+@property (nonatomic) MXKAuthInputsView *authInputsView;
+
+/**
+ The default homeserver url (nil by default).
+ */
+@property (nonatomic) NSString *defaultHomeServerUrl;
+
+/**
+ The default identity server url (nil by default).
+ */
+@property (nonatomic) NSString *defaultIdentityServerUrl;
+
+/**
+ Force a registration process based on a predefined set of parameters.
+ Use this property to pursue a registration from the next_link sent in an email validation email.
+ */
+@property (nonatomic) NSDictionary* externalRegistrationParameters;
+
+/**
+ Use a login process based on the soft logout credentials.
+ */
+@property (nonatomic) MXCredentials *softLogoutCredentials;
+
+/**
+ Enable/disable overall the user interaction option.
+ It is used during authentication process to prevent multiple requests.
+ */
+@property(nonatomic,getter=isUserInteractionEnabled) BOOL userInteractionEnabled;
+
+/**
+ The device name used to display it in the user's devices list (nil by default).
+ If nil, the device display name field is filled with a default string: "Mobile", "Tablet"...
+ */
+@property (nonatomic) NSString *deviceDisplayName;
+
+/**
+ The delegate for the view controller.
+ */
+@property (nonatomic, weak) id delegate;
+
+/**
+ current ongoing MXHTTPOperation. Nil if none.
+ */
+@property (nonatomic, nullable, readonly) MXHTTPOperation *currentHttpOperation;
+
+/**
+ Returns the `UINib` object initialized for a `MXKAuthenticationViewController`.
+
+ @return The initialized `UINib` object or `nil` if there were errors during initialization
+ or the nib file could not be located.
+
+ @discussion You may override this method to provide a customized nib. If you do,
+ you should also override `authenticationViewController` to return your
+ view controller loaded from your custom nib.
+ */
++ (UINib *)nib;
+
+/**
+ Creates and returns a new `MXKAuthenticationViewController` object.
+
+ @discussion This is the designated initializer for programmatic instantiation.
+
+ @return An initialized `MXKAuthenticationViewController` object if successful, `nil` otherwise.
+ */
++ (instancetype)authenticationViewController;
+
+/**
+ Register the MXKAuthInputsView class that will be used to display inputs for an authentication type.
+
+ By default the 'MXKAuthInputsPasswordBasedView' class is registered for 'MXKAuthenticationTypeLogin' authentication.
+ No class is registered for 'MXKAuthenticationTypeRegister' type.
+ No class is registered for 'MXKAuthenticationTypeForgotPassword' type.
+
+ @param authInputsViewClass a MXKAuthInputsView-inherited class.
+ @param authType the concerned authentication type
+ */
+- (void)registerAuthInputsViewClass:(Class)authInputsViewClass forAuthType:(MXKAuthenticationType)authType;
+
+/**
+ Refresh login/register mechanism supported by the server and the application.
+ */
+- (void)refreshAuthenticationSession;
+
+/**
+ Handle supported flows and associated information returned by the homeserver.
+ */
+- (void)handleAuthenticationSession:(MXAuthenticationSession *)authSession;
+
+/**
+ Customize the MXHTTPClientOnUnrecognizedCertificate block that will be used to handle unrecognized certificate observed during authentication challenge from a server.
+ By default we prompt the user by displaying a fingerprint (SHA256) of the certificate. The user is then able to trust or not the certificate.
+
+ @param onUnrecognizedCertificateBlock the block that will be used to handle unrecognized certificate
+ */
+- (void)setOnUnrecognizedCertificateBlock:(MXHTTPClientOnUnrecognizedCertificate)onUnrecognizedCertificateBlock;
+
+/**
+ Check whether the current username is already in use.
+
+ @param callback A block object called when the operation is completed.
+ */
+- (void)isUserNameInUse:(void (^)(BOOL isUserNameInUse))callback;
+
+/**
+ Make a ping to the registration endpoint to detect a possible registration problem earlier.
+
+ @param callback A block object called when the operation is completed.
+ It provides a MXError to check to verify if the user can be registered.
+ */
+- (void)testUserRegistration:(void (^)(MXError *mxError))callback;
+
+/**
+ Action registered on the following events:
+ - 'UIControlEventTouchUpInside' for each UIButton instance.
+ - 'UIControlEventValueChanged' for each UISwitch instance.
+ */
+- (IBAction)onButtonPressed:(id)sender;
+
+/**
+ Set the homeserver url and force a new authentication session.
+ The default homeserver url is used when the provided url is nil.
+
+ @param homeServerUrl the homeserver url to use
+ */
+- (void)setHomeServerTextFieldText:(NSString *)homeServerUrl;
+
+/**
+ Set the identity server url.
+ The default identity server url is used when the provided url is nil.
+
+ @param identityServerUrl the identity server url to use
+ */
+- (void)setIdentityServerTextFieldText:(NSString *)identityServerUrl;
+
+/**
+ Fetch the identity server from the wellknown API of the selected homeserver.
+ and check if the HS requires an identity server.
+ */
+- (void)checkIdentityServer;
+
+/**
+ Force dismiss keyboard
+ */
+- (void)dismissKeyboard;
+
+/**
+ Cancel the current operation, and return to the initial step
+ */
+- (void)cancel;
+
+/**
+ Handle the error received during an authentication request.
+
+ @param error the received error.
+ */
+- (void)onFailureDuringAuthRequest:(NSError *)error;
+
+
+/**
+ Display a kMXErrCodeStringResourceLimitExceeded error received during an authentication
+ request.
+
+ @param errorDict the error data.
+ @param onAdminContactTapped a callback indicating if the user wants to contact their admin.
+ */
+- (void)showResourceLimitExceededError:(NSDictionary *)errorDict onAdminContactTapped:(void (^)(NSURL *adminContact))onAdminContactTapped;
+
+/**
+ Handle the successful authentication request.
+
+ @param credentials the user's credentials.
+ */
+- (void)onSuccessfulLogin:(MXCredentials*)credentials;
+
+/// Login with custom parameters
+/// @param parameters Login parameters
+- (void)loginWithParameters:(NSDictionary*)parameters;
+
+/// Create an account with the given credentials
+/// @param credentials Account credentials
+- (void)createAccountWithCredentials:(MXCredentials *)credentials;
+
+#pragma mark - Authentication Fallback
+
+/**
+ Display the fallback URL within a webview.
+ */
+- (void)showAuthenticationFallBackView;
+
+#pragma mark - Device rehydration
+
+/**
+ Call this method at an appropriate time to attempt rehydrating from an existing dehydrated device
+ @param keyData Secret key data
+ @param credentials Account credentials
+ */
+
+- (void)attemptDeviceRehydrationWithKeyData:(NSData *)keyData credentials:(MXCredentials *)credentials;
+
+@end
+
diff --git a/Riot/Modules/MatrixKit/Controllers/MXKAuthenticationViewController.m b/Riot/Modules/MatrixKit/Controllers/MXKAuthenticationViewController.m
new file mode 100644
index 000000000..91a1eda04
--- /dev/null
+++ b/Riot/Modules/MatrixKit/Controllers/MXKAuthenticationViewController.m
@@ -0,0 +1,2150 @@
+/*
+ Copyright 2015 OpenMarket Ltd
+ Copyright 2017 Vector Creations Ltd
+ Copyright 2019 The Matrix.org Foundation C.I.C
+
+ 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 "MXKAuthenticationViewController.h"
+
+#import "MXKAuthInputsEmailCodeBasedView.h"
+#import "MXKAuthInputsPasswordBasedView.h"
+
+#import "MXKAccountManager.h"
+
+#import "NSBundle+MatrixKit.h"
+
+#import
+#import "MXKAppSettings.h"
+
+#import "MXKSwiftHeader.h"
+
+@interface MXKAuthenticationViewController ()
+{
+ /**
+ The matrix REST client used to make matrix API requests.
+ */
+ MXRestClient *mxRestClient;
+
+ /**
+ Current request in progress.
+ */
+ MXHTTPOperation *mxCurrentOperation;
+
+ /**
+ The MXKAuthInputsView class or a sub-class used when logging in.
+ */
+ Class loginAuthInputsViewClass;
+
+ /**
+ The MXKAuthInputsView class or a sub-class used when registering.
+ */
+ Class registerAuthInputsViewClass;
+
+ /**
+ The MXKAuthInputsView class or a sub-class used to handle forgot password case.
+ */
+ Class forgotPasswordAuthInputsViewClass;
+
+ /**
+ Customized block used to handle unrecognized certificate (nil by default).
+ */
+ MXHTTPClientOnUnrecognizedCertificate onUnrecognizedCertificateCustomBlock;
+
+ /**
+ The current authentication fallback URL (if any).
+ */
+ NSString *authenticationFallback;
+
+ /**
+ The cancel button added in navigation bar when fallback page is opened.
+ */
+ UIBarButtonItem *cancelFallbackBarButton;
+
+ /**
+ The timer used to postpone the registration when the authentication is pending (for example waiting for email validation)
+ */
+ NSTimer* registrationTimer;
+
+ /**
+ Identity server discovery.
+ */
+ MXAutoDiscovery *autoDiscovery;
+
+ MXHTTPOperation *checkIdentityServerOperation;
+}
+
+/**
+ The identity service used to make identity server API requests.
+ */
+@property (nonatomic) MXIdentityService *identityService;
+
+@end
+
+@implementation MXKAuthenticationViewController
+
+#pragma mark - Class methods
+
++ (UINib *)nib
+{
+ return [UINib nibWithNibName:NSStringFromClass([MXKAuthenticationViewController class])
+ bundle:[NSBundle bundleForClass:[MXKAuthenticationViewController class]]];
+}
+
++ (instancetype)authenticationViewController
+{
+ return [[[self class] alloc] initWithNibName:NSStringFromClass([MXKAuthenticationViewController class])
+ bundle:[NSBundle bundleForClass:[MXKAuthenticationViewController class]]];
+}
+
+#pragma mark -
+
+- (void)finalizeInit
+{
+ [super finalizeInit];
+
+ // Set initial auth type
+ _authType = MXKAuthenticationTypeLogin;
+
+ _deviceDisplayName = nil;
+
+ // Initialize authInputs view classes
+ loginAuthInputsViewClass = MXKAuthInputsPasswordBasedView.class;
+ registerAuthInputsViewClass = nil; // No registration flow is supported yet
+ forgotPasswordAuthInputsViewClass = nil;
+}
+
+#pragma mark -
+
+- (void)viewDidLoad
+{
+ [super viewDidLoad];
+ // Do any additional setup after loading the view, typically from a nib.
+
+ // Check whether the view controller has been pushed via storyboard
+ if (!_authenticationScrollView)
+ {
+ // Instantiate view controller objects
+ [[[self class] nib] instantiateWithOwner:self options:nil];
+ }
+
+ self.authFallbackWebView = [[MXKAuthenticationFallbackWebView alloc] initWithFrame:self.authFallbackWebViewContainer.bounds];
+ [self.authFallbackWebViewContainer addSubview:self.authFallbackWebView];
+ [self.authFallbackWebView.leadingAnchor constraintEqualToAnchor:self.authFallbackWebViewContainer.leadingAnchor constant:0].active = YES;
+ [self.authFallbackWebView.trailingAnchor constraintEqualToAnchor:self.authFallbackWebViewContainer.trailingAnchor constant:0].active = YES;
+ [self.authFallbackWebView.topAnchor constraintEqualToAnchor:self.authFallbackWebViewContainer.topAnchor constant:0].active = YES;
+ [self.authFallbackWebView.bottomAnchor constraintEqualToAnchor:self.authFallbackWebViewContainer.bottomAnchor constant:0].active = YES;
+
+ // Load welcome image from MatrixKit asset bundle
+ self.welcomeImageView.image = [NSBundle mxk_imageFromMXKAssetsBundleWithName:@"logoHighRes"];
+
+ _authenticationScrollView.autoresizingMask = UIViewAutoresizingFlexibleWidth;
+
+ _subTitleLabel.numberOfLines = 0;
+
+ _submitButton.enabled = NO;
+ _authSwitchButton.enabled = YES;
+
+ _homeServerTextField.text = _defaultHomeServerUrl;
+ _identityServerTextField.text = _defaultIdentityServerUrl;
+
+ // Hide the identity server by default
+ [self setIdentityServerHidden:YES];
+
+ // Create here REST client (if homeserver is defined)
+ [self updateRESTClient];
+
+ // Localize labels
+ _homeServerLabel.text = [MatrixKitL10n loginHomeServerTitle];
+ _homeServerTextField.placeholder = [MatrixKitL10n loginServerUrlPlaceholder];
+ _homeServerInfoLabel.text = [MatrixKitL10n loginHomeServerInfo];
+ _identityServerLabel.text = [MatrixKitL10n loginIdentityServerTitle];
+ _identityServerTextField.placeholder = [MatrixKitL10n loginServerUrlPlaceholder];
+ _identityServerInfoLabel.text = [MatrixKitL10n loginIdentityServerInfo];
+ [_cancelAuthFallbackButton setTitle:[MatrixKitL10n cancel] forState:UIControlStateNormal];
+ [_cancelAuthFallbackButton setTitle:[MatrixKitL10n cancel] forState:UIControlStateHighlighted];
+}
+
+- (void)didReceiveMemoryWarning
+{
+ [super didReceiveMemoryWarning];
+ // Dispose of any resources that can be recreated.
+}
+
+- (void)viewWillAppear:(BOOL)animated
+{
+ [super viewWillAppear:animated];
+
+ [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(onTextFieldChange:) name:UITextFieldTextDidChangeNotification object:nil];
+}
+
+- (void)viewWillDisappear:(BOOL)animated
+{
+ [super viewWillDisappear:animated];
+
+ [self dismissKeyboard];
+
+ // close any opened alert
+ if (alert)
+ {
+ [alert dismissViewControllerAnimated:NO completion:nil];
+ alert = nil;
+ }
+ [[NSNotificationCenter defaultCenter] removeObserver:self name:AFNetworkingReachabilityDidChangeNotification object:nil];
+
+ [[NSNotificationCenter defaultCenter] removeObserver:self name:UITextFieldTextDidChangeNotification object:nil];
+}
+
+#pragma mark - Override MXKViewController
+
+- (void)onKeyboardShowAnimationComplete
+{
+ // Report the keyboard view in order to track keyboard frame changes
+ // TODO define inputAccessoryView for each text input
+ // and report the inputAccessoryView.superview of the firstResponder in self.keyboardView.
+}
+
+- (void)setKeyboardHeight:(CGFloat)keyboardHeight
+{
+ // Deduce the bottom inset for the scroll view (Don't forget the potential tabBar)
+ CGFloat scrollViewInsetBottom = keyboardHeight - self.bottomLayoutGuide.length;
+ // Check whether the keyboard is over the tabBar
+ if (scrollViewInsetBottom < 0)
+ {
+ scrollViewInsetBottom = 0;
+ }
+
+ UIEdgeInsets insets = self.authenticationScrollView.contentInset;
+ insets.bottom = scrollViewInsetBottom;
+ self.authenticationScrollView.contentInset = insets;
+}
+
+- (void)destroy
+{
+ self.authInputsView = nil;
+
+ if (registrationTimer)
+ {
+ [registrationTimer invalidate];
+ registrationTimer = nil;
+ }
+
+ if (mxCurrentOperation)
+ {
+ [mxCurrentOperation cancel];
+ mxCurrentOperation = nil;
+ }
+
+ [self cancelIdentityServerCheck];
+
+ [mxRestClient close];
+ mxRestClient = nil;
+
+ authenticationFallback = nil;
+ cancelFallbackBarButton = nil;
+
+ [super destroy];
+}
+
+#pragma mark - Class methods
+
+- (void)registerAuthInputsViewClass:(Class)authInputsViewClass forAuthType:(MXKAuthenticationType)authType
+{
+ // Sanity check: accept only MXKAuthInputsView classes or sub-classes
+ NSParameterAssert([authInputsViewClass isSubclassOfClass:MXKAuthInputsView.class]);
+
+ if (authType == MXKAuthenticationTypeLogin)
+ {
+ loginAuthInputsViewClass = authInputsViewClass;
+ }
+ else if (authType == MXKAuthenticationTypeRegister)
+ {
+ registerAuthInputsViewClass = authInputsViewClass;
+ }
+ else if (authType == MXKAuthenticationTypeForgotPassword)
+ {
+ forgotPasswordAuthInputsViewClass = authInputsViewClass;
+ }
+}
+
+- (void)setAuthType:(MXKAuthenticationType)authType
+{
+ if (_authType != authType)
+ {
+ _authType = authType;
+
+ // Cancel external registration parameters if any
+ _externalRegistrationParameters = nil;
+
+ // Remove the current inputs view
+ self.authInputsView = nil;
+
+ isPasswordReseted = NO;
+
+ [self.authInputsContainerView bringSubviewToFront: _authenticationActivityIndicator];
+ [_authenticationActivityIndicator startAnimating];
+ }
+
+ // Restore user interaction
+ self.userInteractionEnabled = YES;
+
+ if (authType == MXKAuthenticationTypeLogin)
+ {
+ _subTitleLabel.hidden = YES;
+ [_submitButton setTitle:[MatrixKitL10n login] forState:UIControlStateNormal];
+ [_submitButton setTitle:[MatrixKitL10n login] forState:UIControlStateHighlighted];
+ [_authSwitchButton setTitle:[MatrixKitL10n createAccount] forState:UIControlStateNormal];
+ [_authSwitchButton setTitle:[MatrixKitL10n createAccount] forState:UIControlStateHighlighted];
+
+ // Update supported authentication flow and associated information (defined in authentication session)
+ [self refreshAuthenticationSession];
+ }
+ else if (authType == MXKAuthenticationTypeRegister)
+ {
+ _subTitleLabel.hidden = NO;
+ _subTitleLabel.text = [MatrixKitL10n loginCreateAccount];
+ [_submitButton setTitle:[MatrixKitL10n signUp] forState:UIControlStateNormal];
+ [_submitButton setTitle:[MatrixKitL10n signUp] forState:UIControlStateHighlighted];
+ [_authSwitchButton setTitle:[MatrixKitL10n back] forState:UIControlStateNormal];
+ [_authSwitchButton setTitle:[MatrixKitL10n back] forState:UIControlStateHighlighted];
+
+ // Update supported authentication flow and associated information (defined in authentication session)
+ [self refreshAuthenticationSession];
+ }
+ else if (authType == MXKAuthenticationTypeForgotPassword)
+ {
+ _subTitleLabel.hidden = YES;
+
+ if (isPasswordReseted)
+ {
+ [_submitButton setTitle:[MatrixKitL10n back] forState:UIControlStateNormal];
+ [_submitButton setTitle:[MatrixKitL10n back] forState:UIControlStateHighlighted];
+ }
+ else
+ {
+ [_submitButton setTitle:[MatrixKitL10n submit] forState:UIControlStateNormal];
+ [_submitButton setTitle:[MatrixKitL10n submit] forState:UIControlStateHighlighted];
+
+ [self refreshForgotPasswordSession];
+ }
+
+ [_authSwitchButton setTitle:[MatrixKitL10n back] forState:UIControlStateNormal];
+ [_authSwitchButton setTitle:[MatrixKitL10n back] forState:UIControlStateHighlighted];
+ }
+
+ [self checkIdentityServer];
+}
+
+- (void)setAuthInputsView:(MXKAuthInputsView *)authInputsView
+{
+ // Here a new view will be loaded, hide first subviews which depend on auth flow
+ _submitButton.hidden = YES;
+ _noFlowLabel.hidden = YES;
+ _retryButton.hidden = YES;
+
+ if (_authInputsView)
+ {
+ [_authInputsView removeObserver:self forKeyPath:@"viewHeightConstraint.constant"];
+
+ [NSLayoutConstraint deactivateConstraints:_authInputsView.constraints];
+ [_authInputsView removeFromSuperview];
+ _authInputsView.delegate = nil;
+ [_authInputsView destroy];
+ _authInputsView = nil;
+ }
+
+ _authInputsView = authInputsView;
+
+ CGFloat previousInputsContainerViewHeight = _authInputContainerViewHeightConstraint.constant;
+
+ if (_authInputsView)
+ {
+ _authInputsView.translatesAutoresizingMaskIntoConstraints = NO;
+ [_authInputsContainerView addSubview:_authInputsView];
+
+ _authInputsView.delegate = self;
+
+ _submitButton.hidden = NO;
+ _authInputsView.hidden = NO;
+
+ _authInputContainerViewHeightConstraint.constant = _authInputsView.viewHeightConstraint.constant;
+
+ NSLayoutConstraint* topConstraint = [NSLayoutConstraint constraintWithItem:_authInputsContainerView
+ attribute:NSLayoutAttributeTop
+ relatedBy:NSLayoutRelationEqual
+ toItem:_authInputsView
+ attribute:NSLayoutAttributeTop
+ multiplier:1.0f
+ constant:0.0f];
+
+
+ NSLayoutConstraint* leadingConstraint = [NSLayoutConstraint constraintWithItem:_authInputsContainerView
+ attribute:NSLayoutAttributeLeading
+ relatedBy:NSLayoutRelationEqual
+ toItem:_authInputsView
+ attribute:NSLayoutAttributeLeading
+ multiplier:1.0f
+ constant:0.0f];
+
+ NSLayoutConstraint* trailingConstraint = [NSLayoutConstraint constraintWithItem:_authInputsContainerView
+ attribute:NSLayoutAttributeTrailing
+ relatedBy:NSLayoutRelationEqual
+ toItem:_authInputsView
+ attribute:NSLayoutAttributeTrailing
+ multiplier:1.0f
+ constant:0.0f];
+
+
+ [NSLayoutConstraint activateConstraints:@[topConstraint, leadingConstraint, trailingConstraint]];
+
+ [_authInputsView addObserver:self forKeyPath:@"viewHeightConstraint.constant" options:0 context:nil];
+ }
+ else
+ {
+ // No input fields are displayed
+ _authInputContainerViewHeightConstraint.constant = _authInputContainerViewMinHeightConstraint.constant;
+ }
+
+ [self.view layoutIfNeeded];
+
+ // Refresh content view height by considering the updated height of inputs container
+ _contentViewHeightConstraint.constant += (_authInputContainerViewHeightConstraint.constant - previousInputsContainerViewHeight);
+}
+
+- (void)setDefaultHomeServerUrl:(NSString *)defaultHomeServerUrl
+{
+ _defaultHomeServerUrl = defaultHomeServerUrl;
+
+ if (!_homeServerTextField.text.length)
+ {
+ [self setHomeServerTextFieldText:defaultHomeServerUrl];
+ }
+}
+
+- (void)setDefaultIdentityServerUrl:(NSString *)defaultIdentityServerUrl
+{
+ _defaultIdentityServerUrl = defaultIdentityServerUrl;
+
+ if (!_identityServerTextField.text.length)
+ {
+ [self setIdentityServerTextFieldText:defaultIdentityServerUrl];
+ }
+}
+
+- (void)setHomeServerTextFieldText:(NSString *)homeServerUrl
+{
+ if (!homeServerUrl.length)
+ {
+ // Force refresh with default value
+ homeServerUrl = _defaultHomeServerUrl;
+ }
+
+ _homeServerTextField.text = homeServerUrl;
+
+ if (!mxRestClient || ![mxRestClient.homeserver isEqualToString:homeServerUrl])
+ {
+ [self updateRESTClient];
+
+ if (_authType == MXKAuthenticationTypeLogin || _authType == MXKAuthenticationTypeRegister)
+ {
+ // Restore default UI
+ self.authType = _authType;
+ }
+ else
+ {
+ // Refresh the IS anyway
+ [self checkIdentityServer];
+ }
+ }
+}
+
+- (void)setIdentityServerTextFieldText:(NSString *)identityServerUrl
+{
+ _identityServerTextField.text = identityServerUrl;
+
+ [self updateIdentityServerURL:identityServerUrl];
+}
+
+- (void)updateIdentityServerURL:(NSString*)url
+{
+ if (![self.identityService.identityServer isEqualToString:url])
+ {
+ if (url.length)
+ {
+ self.identityService = [[MXIdentityService alloc] initWithIdentityServer:url accessToken:nil andHomeserverRestClient:mxRestClient];
+ }
+ else
+ {
+ self.identityService = nil;
+ }
+ }
+
+ [mxRestClient setIdentityServer:url.length ? url : nil];
+}
+
+- (void)setIdentityServerHidden:(BOOL)hidden
+{
+ _identityServerContainer.hidden = hidden;
+}
+
+- (void)checkIdentityServer
+{
+ [self cancelIdentityServerCheck];
+
+ // Hide the field while checking data
+ [self setIdentityServerHidden:YES];
+
+ NSString *homeserver = mxRestClient.homeserver;
+
+ // First, fetch the IS advertised by the HS
+ if (homeserver)
+ {
+ MXLogDebug(@"[MXKAuthenticationVC] checkIdentityServer for homeserver %@", homeserver);
+
+ autoDiscovery = [[MXAutoDiscovery alloc] initWithUrl:homeserver];
+
+ MXWeakify(self);
+ checkIdentityServerOperation = [autoDiscovery findClientConfig:^(MXDiscoveredClientConfig * _Nonnull discoveredClientConfig) {
+ MXStrongifyAndReturnIfNil(self);
+
+ NSString *identityServer = discoveredClientConfig.wellKnown.identityServer.baseUrl;
+ MXLogDebug(@"[MXKAuthenticationVC] checkIdentityServer: Identity server: %@", identityServer);
+
+ if (identityServer)
+ {
+ // Apply the provided IS
+ [self setIdentityServerTextFieldText:identityServer];
+ }
+
+ // Then, check if the HS needs an IS for running
+ MXWeakify(self);
+ MXHTTPOperation *operation = [self checkIdentityServerRequirementWithCompletion:^(BOOL identityServerRequired) {
+
+ MXStrongifyAndReturnIfNil(self);
+
+ self->checkIdentityServerOperation = nil;
+
+ // Show the field only if an IS is required so that the user can customise it
+ [self setIdentityServerHidden:!identityServerRequired];
+ }];
+
+ if (operation)
+ {
+ [self->checkIdentityServerOperation mutateTo:operation];
+ }
+ else
+ {
+ self->checkIdentityServerOperation = nil;
+ }
+
+ self->autoDiscovery = nil;
+
+ } failure:^(NSError *error) {
+ MXStrongifyAndReturnIfNil(self);
+
+ // No need to report this error to the end user
+ // There will be already an error about failing to get the auth flow from the HS
+ MXLogDebug(@"[MXKAuthenticationVC] checkIdentityServer. Error: %@", error);
+
+ self->autoDiscovery = nil;
+ }];
+ }
+}
+
+- (void)cancelIdentityServerCheck
+{
+ if (checkIdentityServerOperation)
+ {
+ [checkIdentityServerOperation cancel];
+ checkIdentityServerOperation = nil;
+ }
+}
+
+- (MXHTTPOperation*)checkIdentityServerRequirementWithCompletion:(void (^)(BOOL identityServerRequired))completion
+{
+ MXHTTPOperation *operation;
+
+ if (_authType == MXKAuthenticationTypeLogin)
+ {
+ // The identity server is only required for registration and password reset
+ // It is then stored in the user account data
+ completion(NO);
+ }
+ else
+ {
+ operation = [mxRestClient supportedMatrixVersions:^(MXMatrixVersions *matrixVersions) {
+
+ MXLogDebug(@"[MXKAuthenticationVC] checkIdentityServerRequirement: %@", matrixVersions.doesServerRequireIdentityServerParam ? @"YES": @"NO");
+ completion(matrixVersions.doesServerRequireIdentityServerParam);
+
+ } failure:^(NSError *error) {
+ // No need to report this error to the end user
+ // There will be already an error about failing to get the auth flow from the HS
+ MXLogDebug(@"[MXKAuthenticationVC] checkIdentityServerRequirement. Error: %@", error);
+ }];
+ }
+
+ return operation;
+}
+
+- (void)setUserInteractionEnabled:(BOOL)userInteractionEnabled
+{
+ _submitButton.enabled = (userInteractionEnabled && _authInputsView.areAllRequiredFieldsSet);
+ _authSwitchButton.enabled = userInteractionEnabled;
+
+ _homeServerTextField.enabled = userInteractionEnabled;
+ _identityServerTextField.enabled = userInteractionEnabled;
+
+ _userInteractionEnabled = userInteractionEnabled;
+}
+
+- (void)refreshAuthenticationSession
+{
+ // Remove reachability observer
+ [[NSNotificationCenter defaultCenter] removeObserver:self name:AFNetworkingReachabilityDidChangeNotification object:nil];
+
+ // Cancel potential request in progress
+ [mxCurrentOperation cancel];
+ mxCurrentOperation = nil;
+
+ // Reset potential authentication fallback url
+ authenticationFallback = nil;
+
+ if (mxRestClient)
+ {
+ if (_authType == MXKAuthenticationTypeLogin)
+ {
+ mxCurrentOperation = [mxRestClient getLoginSession:^(MXAuthenticationSession* authSession) {
+
+ [self handleAuthenticationSession:authSession];
+
+ } failure:^(NSError *error) {
+
+ [self onFailureDuringMXOperation:error];
+
+ }];
+ }
+ else if (_authType == MXKAuthenticationTypeRegister)
+ {
+ mxCurrentOperation = [mxRestClient getRegisterSession:^(MXAuthenticationSession* authSession){
+
+ [self handleAuthenticationSession:authSession];
+
+ } failure:^(NSError *error){
+
+ [self onFailureDuringMXOperation:error];
+
+ }];
+ }
+ else
+ {
+ // Not supported for other types
+ MXLogDebug(@"[MXKAuthenticationVC] refreshAuthenticationSession is ignored");
+ }
+ }
+}
+
+- (void)handleAuthenticationSession:(MXAuthenticationSession *)authSession
+{
+ mxCurrentOperation = nil;
+
+ [_authenticationActivityIndicator stopAnimating];
+
+ // Check whether fallback is defined, and retrieve the right input view class.
+ Class authInputsViewClass;
+ if (_authType == MXKAuthenticationTypeLogin)
+ {
+ authenticationFallback = [mxRestClient loginFallback];
+ authInputsViewClass = loginAuthInputsViewClass;
+
+ }
+ else if (_authType == MXKAuthenticationTypeRegister)
+ {
+ authenticationFallback = [mxRestClient registerFallback];
+ authInputsViewClass = registerAuthInputsViewClass;
+ }
+ else
+ {
+ // Not supported for other types
+ MXLogDebug(@"[MXKAuthenticationVC] handleAuthenticationSession is ignored");
+ return;
+ }
+
+ MXKAuthInputsView *authInputsView = nil;
+ if (authInputsViewClass)
+ {
+ // Instantiate a new auth inputs view, except if the current one is already an instance of this class.
+ if (self.authInputsView && self.authInputsView.class == authInputsViewClass)
+ {
+ // Use the current view
+ authInputsView = self.authInputsView;
+ }
+ else
+ {
+ authInputsView = [authInputsViewClass authInputsView];
+ }
+ }
+
+ if (authInputsView)
+ {
+ // Apply authentication session on inputs view
+ if ([authInputsView setAuthSession:authSession withAuthType:_authType] == NO)
+ {
+ MXLogDebug(@"[MXKAuthenticationVC] Received authentication settings are not supported");
+ authInputsView = nil;
+ }
+ else if (!_softLogoutCredentials)
+ {
+ // If all listed flows in this authentication session are not supported we suggest using the fallback page.
+ if (authenticationFallback.length && authInputsView.authSession.flows.count == 0)
+ {
+ MXLogDebug(@"[MXKAuthenticationVC] No supported flow, suggest using fallback page");
+ authInputsView = nil;
+ }
+ else if (authInputsView.authSession.flows.count != authSession.flows.count)
+ {
+ MXLogDebug(@"[MXKAuthenticationVC] The authentication session contains at least one unsupported flow");
+ }
+ }
+ }
+
+ if (authInputsView)
+ {
+ // Check whether the current view must be replaced
+ if (self.authInputsView != authInputsView)
+ {
+ // Refresh layout
+ self.authInputsView = authInputsView;
+ }
+
+ // Refresh user interaction
+ self.userInteractionEnabled = _userInteractionEnabled;
+
+ // Check whether an external set of parameters have been defined to pursue a registration
+ if (self.externalRegistrationParameters)
+ {
+ if ([authInputsView setExternalRegistrationParameters:self.externalRegistrationParameters])
+ {
+ // Launch authentication now
+ [self onButtonPressed:_submitButton];
+ }
+ else
+ {
+ [self onFailureDuringAuthRequest:[NSError errorWithDomain:MXKAuthErrorDomain code:0 userInfo:@{NSLocalizedDescriptionKey:[MatrixKitL10n notSupportedYet]}]];
+
+ _externalRegistrationParameters = nil;
+
+ // Restore login screen on failure
+ self.authType = MXKAuthenticationTypeLogin;
+ }
+ }
+
+ if (_softLogoutCredentials)
+ {
+ [authInputsView setSoftLogoutCredentials:_softLogoutCredentials];
+ }
+ }
+ else
+ {
+ // Remove the potential auth inputs view
+ self.authInputsView = nil;
+
+ // Cancel external registration parameters if any
+ _externalRegistrationParameters = nil;
+
+ // Notify user that no flow is supported
+ if (_authType == MXKAuthenticationTypeLogin)
+ {
+ _noFlowLabel.text = [MatrixKitL10n loginErrorDoNotSupportLoginFlows];
+ }
+ else
+ {
+ _noFlowLabel.text = [MatrixKitL10n loginErrorRegistrationIsNotSupported];
+ }
+ MXLogDebug(@"[MXKAuthenticationVC] Warning: %@", _noFlowLabel.text);
+
+ if (authenticationFallback.length)
+ {
+ [_retryButton setTitle:[MatrixKitL10n loginUseFallback] forState:UIControlStateNormal];
+ [_retryButton setTitle:[MatrixKitL10n loginUseFallback] forState:UIControlStateNormal];
+ }
+ else
+ {
+ [_retryButton setTitle:[MatrixKitL10n retry] forState:UIControlStateNormal];
+ [_retryButton setTitle:[MatrixKitL10n retry] forState:UIControlStateNormal];
+ }
+
+ _noFlowLabel.hidden = NO;
+ _retryButton.hidden = NO;
+ }
+}
+
+- (void)setExternalRegistrationParameters:(NSDictionary*)parameters
+{
+ if (parameters.count)
+ {
+ MXLogDebug(@"[MXKAuthenticationVC] setExternalRegistrationParameters");
+
+ // Cancel the current operation if any.
+ [self cancel];
+
+ // Load the view controller’s view if it has not yet been loaded.
+ // This is required before updating view's textfields (homeserver url...)
+ [self loadViewIfNeeded];
+
+ // Force register mode
+ self.authType = MXKAuthenticationTypeRegister;
+
+ // Apply provided homeserver if any
+ id hs_url = parameters[@"hs_url"];
+ NSString *homeserverURL = nil;
+ if (hs_url && [hs_url isKindOfClass:NSString.class])
+ {
+ homeserverURL = hs_url;
+ }
+ [self setHomeServerTextFieldText:homeserverURL];
+
+ // Apply provided identity server if any
+ id is_url = parameters[@"is_url"];
+ NSString *identityURL = nil;
+ if (is_url && [is_url isKindOfClass:NSString.class])
+ {
+ identityURL = is_url;
+ }
+ [self setIdentityServerTextFieldText:identityURL];
+
+ // Disable user interaction
+ self.userInteractionEnabled = NO;
+
+ // Cancel potential request in progress
+ [mxCurrentOperation cancel];
+ mxCurrentOperation = nil;
+
+ // Remove the current auth inputs view
+ self.authInputsView = nil;
+
+ // Set external parameters and trigger a refresh (the parameters will be taken into account during [handleAuthenticationSession:])
+ _externalRegistrationParameters = parameters;
+ [self refreshAuthenticationSession];
+ }
+ else
+ {
+ MXLogDebug(@"[MXKAuthenticationVC] reset externalRegistrationParameters");
+ _externalRegistrationParameters = nil;
+
+ // Restore default UI
+ self.authType = _authType;
+ }
+}
+
+- (void)setSoftLogoutCredentials:(MXCredentials *)softLogoutCredentials
+{
+ MXLogDebug(@"[MXKAuthenticationVC] setSoftLogoutCredentials");
+
+ // Cancel the current operation if any.
+ [self cancel];
+
+ // Load the view controller’s view if it has not yet been loaded.
+ // This is required before updating view's textfields (homeserver url...)
+ [self loadViewIfNeeded];
+
+ // Force register mode
+ self.authType = MXKAuthenticationTypeLogin;
+
+ [self setHomeServerTextFieldText:softLogoutCredentials.homeServer];
+ [self setIdentityServerTextFieldText:softLogoutCredentials.identityServer];
+
+ // Cancel potential request in progress
+ [mxCurrentOperation cancel];
+ mxCurrentOperation = nil;
+
+ // Remove the current auth inputs view
+ self.authInputsView = nil;
+
+ // Set parameters and trigger a refresh (the parameters will be taken into account during [handleAuthenticationSession:])
+ _softLogoutCredentials = softLogoutCredentials;
+ [self refreshAuthenticationSession];
+}
+
+- (void)setOnUnrecognizedCertificateBlock:(MXHTTPClientOnUnrecognizedCertificate)onUnrecognizedCertificateBlock
+{
+ onUnrecognizedCertificateCustomBlock = onUnrecognizedCertificateBlock;
+}
+
+- (void)isUserNameInUse:(void (^)(BOOL isUserNameInUse))callback
+{
+ mxCurrentOperation = [mxRestClient isUserNameInUse:self.authInputsView.userId callback:^(BOOL isUserNameInUse) {
+
+ self->mxCurrentOperation = nil;
+
+ if (callback)
+ {
+ callback (isUserNameInUse);
+ }
+
+ }];
+}
+
+- (void)testUserRegistration:(void (^)(MXError *mxError))callback
+{
+ mxCurrentOperation = [mxRestClient testUserRegistration:self.authInputsView.userId callback:callback];
+}
+
+- (IBAction)onButtonPressed:(id)sender
+{
+ [self dismissKeyboard];
+
+ if (sender == _submitButton)
+ {
+ // Disable user interaction to prevent multiple requests
+ self.userInteractionEnabled = NO;
+
+ // Check parameters validity
+ NSString *errorMsg = [self.authInputsView validateParameters];
+ if (errorMsg)
+ {
+ [self onFailureDuringAuthRequest:[NSError errorWithDomain:MXKAuthErrorDomain code:0 userInfo:@{NSLocalizedDescriptionKey:errorMsg}]];
+ }
+ else
+ {
+ [self.authInputsContainerView bringSubviewToFront: _authenticationActivityIndicator];
+
+ // Launch the authentication according to its type
+ if (_authType == MXKAuthenticationTypeLogin)
+ {
+ // Prepare the parameters dict
+ [self.authInputsView prepareParameters:^(NSDictionary *parameters, NSError *error) {
+
+ if (parameters && self->mxRestClient)
+ {
+ [self->_authenticationActivityIndicator startAnimating];
+ [self loginWithParameters:parameters];
+ }
+ else
+ {
+ MXLogDebug(@"[MXKAuthenticationVC] Failed to prepare parameters");
+ [self onFailureDuringAuthRequest:error];
+ }
+
+ }];
+ }
+ else if (_authType == MXKAuthenticationTypeRegister)
+ {
+ // Check here the availability of the userId
+ if (self.authInputsView.userId.length)
+ {
+ [_authenticationActivityIndicator startAnimating];
+
+ if (self.authInputsView.password.length)
+ {
+ // Trigger here a register request in order to associate the filled userId and password to the current session id
+ // This will check the availability of the userId at the same time
+ NSDictionary *parameters = @{@"auth": @{},
+ @"username": self.authInputsView.userId,
+ @"password": self.authInputsView.password,
+ @"bind_email": @(NO),
+ @"initial_device_display_name":self.deviceDisplayName
+ };
+
+ mxCurrentOperation = [mxRestClient registerWithParameters:parameters success:^(NSDictionary *JSONResponse) {
+
+ // Unexpected case where the registration succeeds without any other stages
+ MXLoginResponse *loginResponse;
+ MXJSONModelSetMXJSONModel(loginResponse, MXLoginResponse, JSONResponse);
+
+ MXCredentials *credentials = [[MXCredentials alloc] initWithLoginResponse:loginResponse
+ andDefaultCredentials:self->mxRestClient.credentials];
+
+ // Sanity check
+ if (!credentials.userId || !credentials.accessToken)
+ {
+ [self onFailureDuringAuthRequest:[NSError errorWithDomain:MXKAuthErrorDomain code:0 userInfo:@{NSLocalizedDescriptionKey:[MatrixKitL10n notSupportedYet]}]];
+ }
+ else
+ {
+ MXLogDebug(@"[MXKAuthenticationVC] Registration succeeded");
+
+ // Report the certificate trusted by user (if any)
+ credentials.allowedCertificate = self->mxRestClient.allowedCertificate;
+
+ [self onSuccessfulLogin:credentials];
+ }
+
+ } failure:^(NSError *error) {
+
+ self->mxCurrentOperation = nil;
+
+ // An updated authentication session should be available in response data in case of unauthorized request.
+ NSDictionary *JSONResponse = nil;
+ if (error.userInfo[MXHTTPClientErrorResponseDataKey])
+ {
+ JSONResponse = error.userInfo[MXHTTPClientErrorResponseDataKey];
+ }
+
+ if (JSONResponse)
+ {
+ MXAuthenticationSession *authSession = [MXAuthenticationSession modelFromJSON:JSONResponse];
+
+ [self->_authenticationActivityIndicator stopAnimating];
+
+ // Update session identifier
+ self.authInputsView.authSession.session = authSession.session;
+
+ // Launch registration by preparing parameters dict
+ [self.authInputsView prepareParameters:^(NSDictionary *parameters, NSError *error) {
+
+ if (parameters && self->mxRestClient)
+ {
+ [self->_authenticationActivityIndicator startAnimating];
+ [self registerWithParameters:parameters];
+ }
+ else
+ {
+ MXLogDebug(@"[MXKAuthenticationVC] Failed to prepare parameters");
+ [self onFailureDuringAuthRequest:error];
+ }
+
+ }];
+ }
+ else
+ {
+ [self onFailureDuringAuthRequest:error];
+ }
+ }];
+ }
+ else
+ {
+ [self isUserNameInUse:^(BOOL isUserNameInUse) {
+
+ if (isUserNameInUse)
+ {
+ MXLogDebug(@"[MXKAuthenticationVC] User name is already use");
+ [self onFailureDuringAuthRequest:[NSError errorWithDomain:MXKAuthErrorDomain code:0 userInfo:@{NSLocalizedDescriptionKey:[MatrixKitL10n authUsernameInUse]}]];
+ }
+ else
+ {
+ [self->_authenticationActivityIndicator stopAnimating];
+
+ // Launch registration by preparing parameters dict
+ [self.authInputsView prepareParameters:^(NSDictionary *parameters, NSError *error) {
+
+ if (parameters && self->mxRestClient)
+ {
+ [self->_authenticationActivityIndicator startAnimating];
+ [self registerWithParameters:parameters];
+ }
+ else
+ {
+ MXLogDebug(@"[MXKAuthenticationVC] Failed to prepare parameters");
+ [self onFailureDuringAuthRequest:error];
+ }
+
+ }];
+ }
+
+ }];
+ }
+ }
+ else if (self.externalRegistrationParameters)
+ {
+ // Launch registration by preparing parameters dict
+ [self.authInputsView prepareParameters:^(NSDictionary *parameters, NSError *error) {
+
+ if (parameters && self->mxRestClient)
+ {
+ [self->_authenticationActivityIndicator startAnimating];
+ [self registerWithParameters:parameters];
+ }
+ else
+ {
+ MXLogDebug(@"[MXKAuthenticationVC] Failed to prepare parameters");
+ [self onFailureDuringAuthRequest:error];
+ }
+
+ }];
+ }
+ else
+ {
+ MXLogDebug(@"[MXKAuthenticationVC] User name is missing");
+ [self onFailureDuringAuthRequest:[NSError errorWithDomain:MXKAuthErrorDomain code:0 userInfo:@{NSLocalizedDescriptionKey:[MatrixKitL10n authInvalidUserName]}]];
+ }
+ }
+ else if (_authType == MXKAuthenticationTypeForgotPassword)
+ {
+ // Check whether the password has been reseted
+ if (isPasswordReseted)
+ {
+ // Return to login screen
+ self.authType = MXKAuthenticationTypeLogin;
+ }
+ else
+ {
+ // Prepare the parameters dict
+ [self.authInputsView prepareParameters:^(NSDictionary *parameters, NSError *error) {
+
+ if (parameters && self->mxRestClient)
+ {
+ [self->_authenticationActivityIndicator startAnimating];
+ [self resetPasswordWithParameters:parameters];
+ }
+ else
+ {
+ MXLogDebug(@"[MXKAuthenticationVC] Failed to prepare parameters");
+ [self onFailureDuringAuthRequest:error];
+ }
+
+ }];
+ }
+ }
+ }
+ }
+ else if (sender == _authSwitchButton)
+ {
+ if (_authType == MXKAuthenticationTypeLogin)
+ {
+ self.authType = MXKAuthenticationTypeRegister;
+ }
+ else
+ {
+ self.authType = MXKAuthenticationTypeLogin;
+ }
+ }
+ else if (sender == _retryButton)
+ {
+ if (authenticationFallback)
+ {
+ [self showAuthenticationFallBackView:authenticationFallback];
+ }
+ else
+ {
+ [self refreshAuthenticationSession];
+ }
+ }
+ else if (sender == _cancelAuthFallbackButton)
+ {
+ // Hide fallback webview
+ [self hideRegistrationFallbackView];
+ }
+}
+
+- (void)cancel
+{
+ MXLogDebug(@"[MXKAuthenticationVC] cancel");
+
+ // Cancel external registration parameters if any
+ _externalRegistrationParameters = nil;
+
+ if (registrationTimer)
+ {
+ [registrationTimer invalidate];
+ registrationTimer = nil;
+ }
+
+ // Cancel request in progress
+ if (mxCurrentOperation)
+ {
+ [mxCurrentOperation cancel];
+ mxCurrentOperation = nil;
+ }
+
+ [_authenticationActivityIndicator stopAnimating];
+ self.userInteractionEnabled = YES;
+
+ // Reset potential completed stages
+ self.authInputsView.authSession.completed = nil;
+
+ // Update authentication inputs view to return in initial step
+ [self.authInputsView setAuthSession:self.authInputsView.authSession withAuthType:_authType];
+}
+
+- (void)onFailureDuringAuthRequest:(NSError *)error
+{
+ mxCurrentOperation = nil;
+ [_authenticationActivityIndicator stopAnimating];
+ self.userInteractionEnabled = YES;
+
+ // Ignore connection cancellation error
+ if (([error.domain isEqualToString:NSURLErrorDomain] && error.code == NSURLErrorCancelled))
+ {
+ MXLogDebug(@"[MXKAuthenticationVC] Auth request cancelled");
+ return;
+ }
+
+ MXLogDebug(@"[MXKAuthenticationVC] Auth request failed: %@", error);
+
+ // Cancel external registration parameters if any
+ _externalRegistrationParameters = nil;
+
+ // Translate the error code to a human message
+ NSString *title = error.localizedFailureReason;
+ if (!title)
+ {
+ if (self.authType == MXKAuthenticationTypeLogin)
+ {
+ title = [MatrixKitL10n loginErrorTitle];
+ }
+ else if (self.authType == MXKAuthenticationTypeRegister)
+ {
+ title = [MatrixKitL10n registerErrorTitle];
+ }
+ else
+ {
+ title = [MatrixKitL10n error];
+ }
+ }
+ NSString* message = error.localizedDescription;
+ NSDictionary* dict = error.userInfo;
+
+ // detect if it is a Matrix SDK issue
+ if (dict)
+ {
+ NSString* localizedError = [dict valueForKey:@"error"];
+ NSString* errCode = [dict valueForKey:@"errcode"];
+
+ if (localizedError.length > 0)
+ {
+ message = localizedError;
+ }
+
+ if (errCode)
+ {
+ if ([errCode isEqualToString:kMXErrCodeStringForbidden])
+ {
+ message = [MatrixKitL10n loginErrorForbidden];
+ }
+ else if ([errCode isEqualToString:kMXErrCodeStringUnknownToken])
+ {
+ message = [MatrixKitL10n loginErrorUnknownToken];
+ }
+ else if ([errCode isEqualToString:kMXErrCodeStringBadJSON])
+ {
+ message = [MatrixKitL10n loginErrorBadJson];
+ }
+ else if ([errCode isEqualToString:kMXErrCodeStringNotJSON])
+ {
+ message = [MatrixKitL10n loginErrorNotJson];
+ }
+ else if ([errCode isEqualToString:kMXErrCodeStringLimitExceeded])
+ {
+ message = [MatrixKitL10n loginErrorLimitExceeded];
+ }
+ else if ([errCode isEqualToString:kMXErrCodeStringUserInUse])
+ {
+ message = [MatrixKitL10n loginErrorUserInUse];
+ }
+ else if ([errCode isEqualToString:kMXErrCodeStringLoginEmailURLNotYet])
+ {
+ message = [MatrixKitL10n loginErrorLoginEmailNotYet];
+ }
+ else if ([errCode isEqualToString:kMXErrCodeStringResourceLimitExceeded])
+ {
+ [self showResourceLimitExceededError:dict onAdminContactTapped:nil];
+ return;
+ }
+ else if (!message.length)
+ {
+ message = errCode;
+ }
+ }
+ }
+
+ // Alert user
+ if (alert)
+ {
+ [alert dismissViewControllerAnimated:NO completion:nil];
+ }
+
+ alert = [UIAlertController alertControllerWithTitle:title message:message preferredStyle:UIAlertControllerStyleAlert];
+
+ [alert addAction:[UIAlertAction actionWithTitle:[MatrixKitL10n ok]
+ style:UIAlertActionStyleDefault
+ handler:^(UIAlertAction * action) {
+
+ self->alert = nil;
+
+ }]];
+
+
+ [self presentViewController:alert animated:YES completion:nil];
+
+ // Update authentication inputs view to return in initial step
+ [self.authInputsView setAuthSession:self.authInputsView.authSession withAuthType:_authType];
+ if (self.softLogoutCredentials)
+ {
+ self.authInputsView.softLogoutCredentials = self.softLogoutCredentials;
+ }
+}
+
+- (void)showResourceLimitExceededError:(NSDictionary *)errorDict onAdminContactTapped:(void (^)(NSURL *adminContact))onAdminContactTapped
+{
+ mxCurrentOperation = nil;
+ [_authenticationActivityIndicator stopAnimating];
+ self.userInteractionEnabled = YES;
+
+ // Alert user
+ if (alert)
+ {
+ [alert dismissViewControllerAnimated:NO completion:nil];
+ }
+
+ // Parse error data
+ NSString *limitType, *adminContactString;
+ NSURL *adminContact;
+
+ MXJSONModelSetString(limitType, errorDict[kMXErrorResourceLimitExceededLimitTypeKey]);
+ MXJSONModelSetString(adminContactString, errorDict[kMXErrorResourceLimitExceededAdminContactKey]);
+
+ if (adminContactString)
+ {
+ adminContact = [NSURL URLWithString:adminContactString];
+ }
+
+ NSString *title = [MatrixKitL10n loginErrorResourceLimitExceededTitle];
+
+ // Build the message content
+ NSMutableString *message = [NSMutableString new];
+ if ([limitType isEqualToString:kMXErrorResourceLimitExceededLimitTypeMonthlyActiveUserValue])
+ {
+ [message appendString:[MatrixKitL10n loginErrorResourceLimitExceededMessageMonthlyActiveUser]];
+ }
+ else
+ {
+ [message appendString:[MatrixKitL10n loginErrorResourceLimitExceededMessageDefault]];
+ }
+
+ [message appendString:[MatrixKitL10n loginErrorResourceLimitExceededMessageContact]];
+
+ // Build the alert
+ alert = [UIAlertController alertControllerWithTitle:title message:message preferredStyle:UIAlertControllerStyleAlert];
+
+ MXWeakify(self);
+ if (adminContact && onAdminContactTapped)
+ {
+ [alert addAction:[UIAlertAction actionWithTitle:[MatrixKitL10n loginErrorResourceLimitExceededContactButton]
+ style:UIAlertActionStyleDefault
+ handler:^(UIAlertAction * action)
+ {
+ MXStrongifyAndReturnIfNil(self);
+ self->alert = nil;
+
+ // Let the system handle the URI
+ // It could be something like "mailto: server.admin@example.com"
+ onAdminContactTapped(adminContact);
+ }]];
+ }
+
+ [alert addAction:[UIAlertAction actionWithTitle:[MatrixKitL10n cancel]
+ style:UIAlertActionStyleDefault
+ handler:^(UIAlertAction * action)
+ {
+ MXStrongifyAndReturnIfNil(self);
+ self->alert = nil;
+ }]];
+
+ [self presentViewController:alert animated:YES completion:nil];
+
+ // Update authentication inputs view to return in initial step
+ [self.authInputsView setAuthSession:self.authInputsView.authSession withAuthType:_authType];
+}
+
+- (void)onSuccessfulLogin:(MXCredentials*)credentials
+{
+ mxCurrentOperation = nil;
+ [_authenticationActivityIndicator stopAnimating];
+ self.userInteractionEnabled = YES;
+
+ if (self.softLogoutCredentials)
+ {
+ // Hydrate the account with the new access token
+ MXKAccount *account = [[MXKAccountManager sharedManager] accountForUserId:self.softLogoutCredentials.userId];
+ [[MXKAccountManager sharedManager] hydrateAccount:account withCredentials:credentials];
+
+ if (_delegate)
+ {
+ [_delegate authenticationViewController:self didLogWithUserId:credentials.userId];
+ }
+ }
+ // Sanity check: check whether the user is not already logged in with this id
+ else if ([[MXKAccountManager sharedManager] accountForUserId:credentials.userId])
+ {
+ //Alert user
+ __weak typeof(self) weakSelf = self;
+
+ if (alert)
+ {
+ [alert dismissViewControllerAnimated:NO completion:nil];
+ }
+
+ alert = [UIAlertController alertControllerWithTitle:[MatrixKitL10n loginErrorAlreadyLoggedIn] message:nil preferredStyle:UIAlertControllerStyleAlert];
+
+ [alert addAction:[UIAlertAction actionWithTitle:[MatrixKitL10n ok]
+ style:UIAlertActionStyleDefault
+ handler:^(UIAlertAction * action) {
+
+ // We remove the authentication view controller.
+ typeof(self) self = weakSelf;
+ self->alert = nil;
+ [self withdrawViewControllerAnimated:YES completion:nil];
+
+ }]];
+
+
+ [self presentViewController:alert animated:YES completion:nil];
+ }
+ else
+ {
+ // Report the new account in account manager
+ if (!credentials.identityServer)
+ {
+ credentials.identityServer = _identityServerTextField.text;
+ }
+
+ [self createAccountWithCredentials:credentials];
+ }
+}
+
+- (MXHTTPOperation *)currentHttpOperation
+{
+ return mxCurrentOperation;
+}
+
+#pragma mark - Privates
+
+// Hook point for triggering device rehydration in subclasses
+// Avoid cycles by using a separate private method do to the actual work
+- (void)createAccountWithCredentials:(MXCredentials *)credentials
+{
+ [self _createAccountWithCredentials:credentials];
+}
+
+- (void)attemptDeviceRehydrationWithKeyData:(NSData *)keyData
+ credentials:(MXCredentials *)credentials
+{
+ [self attemptDeviceRehydrationWithKeyData:keyData
+ credentials:credentials
+ retry:YES];
+}
+
+- (void)attemptDeviceRehydrationWithKeyData:(NSData *)keyData
+ credentials:(MXCredentials *)credentials
+ retry:(BOOL)retry
+{
+ MXLogDebug(@"[MXKAuthenticationViewController] attemptDeviceRehydration: starting device rehydration");
+
+ if (keyData == nil)
+ {
+ MXLogError(@"[MXKAuthenticationViewController] attemptDeviceRehydration: no key provided for device rehydration");
+ [self _createAccountWithCredentials:credentials];
+ return;
+ }
+
+ MXRestClient *mxRestClient = [[MXRestClient alloc] initWithCredentials:credentials andOnUnrecognizedCertificateBlock:^BOOL(NSData *certificate) {
+ return NO;
+ }];
+
+ MXWeakify(self);
+ [[MXKAccountManager sharedManager].dehydrationService rehydrateDeviceWithMatrixRestClient:mxRestClient dehydrationKey:keyData success:^(NSString * deviceId) {
+ MXStrongifyAndReturnIfNil(self);
+
+ if (deviceId)
+ {
+ MXLogDebug(@"[MXKAuthenticationViewController] attemptDeviceRehydration: device %@ rehydrated successfully.", deviceId);
+ credentials.deviceId = deviceId;
+ }
+ else
+ {
+ MXLogDebug(@"[MXKAuthenticationViewController] attemptDeviceRehydration: device rehydration has been canceled.");
+ }
+
+ [self _createAccountWithCredentials:credentials];
+ } failure:^(NSError *error) {
+ MXStrongifyAndReturnIfNil(self);
+
+ if (retry)
+ {
+ MXLogError(@"[MXKAuthenticationViewController] attemptDeviceRehydration: device rehydration failed due to error: %@. Retrying", error);
+ [self attemptDeviceRehydrationWithKeyData:keyData credentials:credentials retry:NO];
+ return;
+ }
+
+ MXLogError(@"[MXKAuthenticationViewController] attemptDeviceRehydration: device rehydration failed due to error: %@", error);
+
+ [self _createAccountWithCredentials:credentials];
+ }];
+}
+
+- (void)_createAccountWithCredentials:(MXCredentials *)credentials
+{
+ MXKAccount *account = [[MXKAccount alloc] initWithCredentials:credentials];
+ account.identityServerURL = credentials.identityServer;
+
+ [[MXKAccountManager sharedManager] addAccount:account andOpenSession:YES];
+
+ if (_delegate)
+ {
+ [_delegate authenticationViewController:self didLogWithUserId:credentials.userId];
+ }
+}
+
+- (NSString *)deviceDisplayName
+{
+ if (_deviceDisplayName)
+ {
+ return _deviceDisplayName;
+ }
+
+#if TARGET_OS_IPHONE
+ NSString *deviceName = [[UIDevice currentDevice].model isEqualToString:@"iPad"] ? [MatrixKitL10n loginTabletDevice] : [MatrixKitL10n loginMobileDevice];
+#elif TARGET_OS_OSX
+ NSString *deviceName = [MatrixKitL10n loginDesktopDevice];
+#endif
+
+ return deviceName;
+}
+
+- (void)refreshForgotPasswordSession
+{
+ [_authenticationActivityIndicator stopAnimating];
+
+ MXKAuthInputsView *authInputsView = nil;
+ if (forgotPasswordAuthInputsViewClass)
+ {
+ // Instantiate a new auth inputs view, except if the current one is already an instance of this class.
+ if (self.authInputsView && self.authInputsView.class == forgotPasswordAuthInputsViewClass)
+ {
+ // Use the current view
+ authInputsView = self.authInputsView;
+ }
+ else
+ {
+ authInputsView = [forgotPasswordAuthInputsViewClass authInputsView];
+ }
+ }
+
+ if (authInputsView)
+ {
+ // Update authentication inputs view to return in initial step
+ [authInputsView setAuthSession:nil withAuthType:MXKAuthenticationTypeForgotPassword];
+
+ // Check whether the current view must be replaced
+ if (self.authInputsView != authInputsView)
+ {
+ // Refresh layout
+ self.authInputsView = authInputsView;
+ }
+
+ // Refresh user interaction
+ self.userInteractionEnabled = _userInteractionEnabled;
+ }
+ else
+ {
+ // Remove the potential auth inputs view
+ self.authInputsView = nil;
+
+ _noFlowLabel.text = [MatrixKitL10n loginErrorForgotPasswordIsNotSupported];
+
+ MXLogDebug(@"[MXKAuthenticationVC] Warning: %@", _noFlowLabel.text);
+
+ _noFlowLabel.hidden = NO;
+ }
+}
+
+- (void)updateRESTClient
+{
+ NSString *homeserverURL = _homeServerTextField.text;
+
+ if (homeserverURL.length)
+ {
+ // Check change
+ if ([homeserverURL isEqualToString:mxRestClient.homeserver] == NO)
+ {
+ mxRestClient = [[MXRestClient alloc] initWithHomeServer:homeserverURL andOnUnrecognizedCertificateBlock:^BOOL(NSData *certificate) {
+
+ // Check first if the app developer provided its own certificate handler.
+ if (self->onUnrecognizedCertificateCustomBlock)
+ {
+ return self->onUnrecognizedCertificateCustomBlock (certificate);
+ }
+
+ // Else prompt the user by displaying a fingerprint (SHA256) of the certificate.
+ __block BOOL isTrusted;
+ dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
+
+ NSString *title = [MatrixKitL10n sslCouldNotVerify];
+ NSString *homeserverURLStr = [MatrixKitL10n sslHomeserverUrl:homeserverURL];
+ NSString *fingerprint = [MatrixKitL10n sslFingerprintHash:@"SHA256"];
+ NSString *certFingerprint = [certificate mx_SHA256AsHexString];
+
+ NSString *msg = [NSString stringWithFormat:@"%@\n\n%@\n\n%@\n\n%@\n\n%@\n\n%@", [MatrixKitL10n sslCertNotTrust], [MatrixKitL10n sslCertNewAccountExpl], homeserverURLStr, fingerprint, certFingerprint, [MatrixKitL10n sslOnlyAccept]];
+
+ if (self->alert)
+ {
+ [self->alert dismissViewControllerAnimated:NO completion:nil];
+ }
+
+ self->alert = [UIAlertController alertControllerWithTitle:title message:msg preferredStyle:UIAlertControllerStyleAlert];
+
+ [self->alert addAction:[UIAlertAction actionWithTitle:[MatrixKitL10n cancel]
+ style:UIAlertActionStyleDefault
+ handler:^(UIAlertAction * action) {
+
+ self->alert = nil;
+ isTrusted = NO;
+ dispatch_semaphore_signal(semaphore);
+
+ }]];
+
+ [self->alert addAction:[UIAlertAction actionWithTitle:[MatrixKitL10n sslTrust]
+ style:UIAlertActionStyleDefault
+ handler:^(UIAlertAction * action) {
+
+ self->alert = nil;
+ isTrusted = YES;
+ dispatch_semaphore_signal(semaphore);
+
+ }]];
+
+ dispatch_async(dispatch_get_main_queue(), ^{
+ [self presentViewController:self->alert animated:YES completion:nil];
+ });
+
+ dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
+
+ if (!isTrusted)
+ {
+ // Cancel request in progress
+ [self->mxCurrentOperation cancel];
+ self->mxCurrentOperation = nil;
+ [[NSNotificationCenter defaultCenter] removeObserver:self name:AFNetworkingReachabilityDidChangeNotification object:nil];
+
+ [self->_authenticationActivityIndicator stopAnimating];
+ }
+
+ return isTrusted;
+ }];
+
+ if (_identityServerTextField.text.length)
+ {
+ [self updateIdentityServerURL:self.identityServerTextField.text];
+ }
+ }
+ }
+ else
+ {
+ [mxRestClient close];
+ mxRestClient = nil;
+ }
+}
+
+- (void)loginWithParameters:(NSDictionary*)parameters
+{
+ // Add the device name
+ NSMutableDictionary *theParameters = [NSMutableDictionary dictionaryWithDictionary:parameters];
+ theParameters[@"initial_device_display_name"] = self.deviceDisplayName;
+
+ mxCurrentOperation = [mxRestClient login:theParameters success:^(NSDictionary *JSONResponse) {
+
+ MXLoginResponse *loginResponse;
+ MXJSONModelSetMXJSONModel(loginResponse, MXLoginResponse, JSONResponse);
+
+ MXCredentials *credentials = [[MXCredentials alloc] initWithLoginResponse:loginResponse
+ andDefaultCredentials:self->mxRestClient.credentials];
+
+ // Sanity check
+ if (!credentials.userId || !credentials.accessToken)
+ {
+ [self onFailureDuringAuthRequest:[NSError errorWithDomain:MXKAuthErrorDomain code:0 userInfo:@{NSLocalizedDescriptionKey:[MatrixKitL10n notSupportedYet]}]];
+ }
+ else
+ {
+ MXLogDebug(@"[MXKAuthenticationVC] Login process succeeded");
+
+ // Report the certificate trusted by user (if any)
+ credentials.allowedCertificate = self->mxRestClient.allowedCertificate;
+
+ [self onSuccessfulLogin:credentials];
+ }
+
+ } failure:^(NSError *error) {
+
+ [self onFailureDuringAuthRequest:error];
+
+ }];
+}
+
+- (void)registerWithParameters:(NSDictionary*)parameters
+{
+ if (registrationTimer)
+ {
+ [registrationTimer invalidate];
+ registrationTimer = nil;
+ }
+
+ // Add the device name
+ NSMutableDictionary *theParameters = [NSMutableDictionary dictionaryWithDictionary:parameters];
+ theParameters[@"initial_device_display_name"] = self.deviceDisplayName;
+
+ mxCurrentOperation = [mxRestClient registerWithParameters:theParameters success:^(NSDictionary *JSONResponse) {
+
+ MXLoginResponse *loginResponse;
+ MXJSONModelSetMXJSONModel(loginResponse, MXLoginResponse, JSONResponse);
+
+ MXCredentials *credentials = [[MXCredentials alloc] initWithLoginResponse:loginResponse
+ andDefaultCredentials:self->mxRestClient.credentials];
+
+ // Sanity check
+ if (!credentials.userId || !credentials.accessToken)
+ {
+ [self onFailureDuringAuthRequest:[NSError errorWithDomain:MXKAuthErrorDomain code:0 userInfo:@{NSLocalizedDescriptionKey:[MatrixKitL10n notSupportedYet]}]];
+ }
+ else
+ {
+ MXLogDebug(@"[MXKAuthenticationVC] Registration succeeded");
+
+ // Report the certificate trusted by user (if any)
+ credentials.allowedCertificate = self->mxRestClient.allowedCertificate;
+
+ [self onSuccessfulLogin:credentials];
+ }
+
+ } failure:^(NSError *error) {
+
+ self->mxCurrentOperation = nil;
+
+ // Check whether the authentication is pending (for example waiting for email validation)
+ MXError *mxError = [[MXError alloc] initWithNSError:error];
+ if (mxError && [mxError.errcode isEqualToString:kMXErrCodeStringUnauthorized])
+ {
+ MXLogDebug(@"[MXKAuthenticationVC] Wait for email validation");
+
+ // Postpone a new attempt in 10 sec
+ self->registrationTimer = [NSTimer scheduledTimerWithTimeInterval:10 target:self selector:@selector(registrationTimerFireMethod:) userInfo:parameters repeats:NO];
+ }
+ else
+ {
+ // The completed stages should be available in response data in case of unauthorized request.
+ NSDictionary *JSONResponse = nil;
+ if (error.userInfo[MXHTTPClientErrorResponseDataKey])
+ {
+ JSONResponse = error.userInfo[MXHTTPClientErrorResponseDataKey];
+ }
+
+ if (JSONResponse)
+ {
+ MXAuthenticationSession *authSession = [MXAuthenticationSession modelFromJSON:JSONResponse];
+
+ if (authSession.completed)
+ {
+ [self->_authenticationActivityIndicator stopAnimating];
+
+ // Update session identifier in case of change
+ self.authInputsView.authSession.session = authSession.session;
+
+ [self.authInputsView updateAuthSessionWithCompletedStages:authSession.completed didUpdateParameters:^(NSDictionary *parameters, NSError *error) {
+
+ if (parameters)
+ {
+ MXLogDebug(@"[MXKAuthenticationVC] Pursue registration");
+
+ [self->_authenticationActivityIndicator startAnimating];
+ [self registerWithParameters:parameters];
+ }
+ else
+ {
+ MXLogDebug(@"[MXKAuthenticationVC] Failed to update parameters");
+
+ [self onFailureDuringAuthRequest:error];
+ }
+
+ }];
+
+ return;
+ }
+
+ [self onFailureDuringAuthRequest:[NSError errorWithDomain:MXKAuthErrorDomain code:0 userInfo:@{NSLocalizedDescriptionKey:[MatrixKitL10n notSupportedYet]}]];
+ }
+ else
+ {
+ [self onFailureDuringAuthRequest:error];
+ }
+ }
+ }];
+}
+
+- (void)registrationTimerFireMethod:(NSTimer *)timer
+{
+ if (timer == registrationTimer && timer.isValid)
+ {
+ MXLogDebug(@"[MXKAuthenticationVC] Retry registration");
+ [self registerWithParameters:registrationTimer.userInfo];
+ }
+}
+
+- (void)resetPasswordWithParameters:(NSDictionary*)parameters
+{
+ mxCurrentOperation = [mxRestClient resetPasswordWithParameters:parameters success:^() {
+
+ MXLogDebug(@"[MXKAuthenticationVC] Reset password succeeded");
+
+ self->mxCurrentOperation = nil;
+ [self->_authenticationActivityIndicator stopAnimating];
+
+ self->isPasswordReseted = YES;
+
+ // Force UI update to refresh submit button title.
+ self.authType = self->_authType;
+
+ // Refresh the authentication inputs view on success.
+ [self.authInputsView nextStep];
+
+ } failure:^(NSError *error) {
+
+ MXError *mxError = [[MXError alloc] initWithNSError:error];
+ if (mxError && [mxError.errcode isEqualToString:kMXErrCodeStringUnauthorized])
+ {
+ MXLogDebug(@"[MXKAuthenticationVC] Forgot Password: wait for email validation");
+
+ self->mxCurrentOperation = nil;
+ [self->_authenticationActivityIndicator stopAnimating];
+
+ if (self->alert)
+ {
+ [self->alert dismissViewControllerAnimated:NO completion:nil];
+ }
+
+ self->alert = [UIAlertController alertControllerWithTitle:[MatrixKitL10n error] message:[MatrixKitL10n authResetPasswordErrorUnauthorized] preferredStyle:UIAlertControllerStyleAlert];
+
+ [self->alert addAction:[UIAlertAction actionWithTitle:[MatrixKitL10n ok]
+ style:UIAlertActionStyleDefault
+ handler:^(UIAlertAction * action) {
+
+ self->alert = nil;
+
+ }]];
+
+
+ [self presentViewController:self->alert animated:YES completion:nil];
+ }
+ else if (mxError && [mxError.errcode isEqualToString:kMXErrCodeStringNotFound])
+ {
+ MXLogDebug(@"[MXKAuthenticationVC] Forgot Password: not found");
+
+ NSMutableDictionary *userInfo;
+ if (error.userInfo)
+ {
+ userInfo = [NSMutableDictionary dictionaryWithDictionary:error.userInfo];
+ }
+ else
+ {
+ userInfo = [NSMutableDictionary dictionary];
+ }
+ userInfo[NSLocalizedDescriptionKey] = [MatrixKitL10n authResetPasswordErrorNotFound];
+
+ [self onFailureDuringAuthRequest:[NSError errorWithDomain:kMXNSErrorDomain code:0 userInfo:userInfo]];
+ }
+ else
+ {
+ [self onFailureDuringAuthRequest:error];
+ }
+
+ }];
+}
+
+- (void)onFailureDuringMXOperation:(NSError*)error
+{
+ mxCurrentOperation = nil;
+
+ [_authenticationActivityIndicator stopAnimating];
+
+ if ([error.domain isEqualToString:NSURLErrorDomain] && error.code == NSURLErrorCancelled)
+ {
+ // Ignore this error
+ MXLogDebug(@"[MXKAuthenticationVC] flows request cancelled");
+ return;
+ }
+
+ MXLogDebug(@"[MXKAuthenticationVC] Failed to get %@ flows: %@", (_authType == MXKAuthenticationTypeLogin ? @"Login" : @"Register"), error);
+
+ // Cancel external registration parameters if any
+ _externalRegistrationParameters = nil;
+
+ // Alert user
+ NSString *title = [error.userInfo valueForKey:NSLocalizedFailureReasonErrorKey];
+ if (!title)
+ {
+ title = [MatrixKitL10n error];
+ }
+ NSString *msg = [error.userInfo valueForKey:NSLocalizedDescriptionKey];
+
+ if (alert)
+ {
+ [alert dismissViewControllerAnimated:NO completion:nil];
+ }
+
+ alert = [UIAlertController alertControllerWithTitle:title message:msg preferredStyle:UIAlertControllerStyleAlert];
+
+ [alert addAction:[UIAlertAction actionWithTitle:[MatrixKitL10n dismiss]
+ style:UIAlertActionStyleDefault
+ handler:^(UIAlertAction * action) {
+
+ self->alert = nil;
+
+ }]];
+
+
+ [self presentViewController:alert animated:YES completion:nil];
+
+ // Handle specific error code here
+ if ([error.domain isEqualToString:NSURLErrorDomain])
+ {
+ // Check network reachability
+ if (error.code == NSURLErrorNotConnectedToInternet)
+ {
+ // Add reachability observer in order to launch a new request when network will be available
+ [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(onReachabilityStatusChange:) name:AFNetworkingReachabilityDidChangeNotification object:nil];
+ }
+ else if (error.code == kCFURLErrorTimedOut)
+ {
+ // Send a new request in 2 sec
+ dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
+ [self refreshAuthenticationSession];
+ });
+ }
+ else
+ {
+ // Remove the potential auth inputs view
+ self.authInputsView = nil;
+ }
+ }
+ else
+ {
+ // Remove the potential auth inputs view
+ self.authInputsView = nil;
+ }
+
+ if (!_authInputsView)
+ {
+ // Display failure reason
+ _noFlowLabel.hidden = NO;
+ _noFlowLabel.text = [error.userInfo valueForKey:NSLocalizedDescriptionKey];
+ if (!_noFlowLabel.text.length)
+ {
+ _noFlowLabel.text = [MatrixKitL10n loginErrorNoLoginFlow];
+ }
+ [_retryButton setTitle:[MatrixKitL10n retry] forState:UIControlStateNormal];
+ [_retryButton setTitle:[MatrixKitL10n retry] forState:UIControlStateNormal];
+ _retryButton.hidden = NO;
+ }
+}
+
+- (void)onReachabilityStatusChange:(NSNotification *)notif
+{
+ AFNetworkReachabilityManager *reachabilityManager = [AFNetworkReachabilityManager sharedManager];
+ AFNetworkReachabilityStatus status = reachabilityManager.networkReachabilityStatus;
+
+ if (status == AFNetworkReachabilityStatusReachableViaWiFi || status == AFNetworkReachabilityStatusReachableViaWWAN)
+ {
+ dispatch_async(dispatch_get_main_queue(), ^{
+ [self refreshAuthenticationSession];
+ });
+ }
+ else if (status == AFNetworkReachabilityStatusNotReachable)
+ {
+ _noFlowLabel.text = [MatrixKitL10n networkErrorNotReachable];
+ }
+}
+
+#pragma mark - Keyboard handling
+
+- (void)dismissKeyboard
+{
+ // Hide the keyboard
+ [_authInputsView dismissKeyboard];
+ [_homeServerTextField resignFirstResponder];
+ [_identityServerTextField resignFirstResponder];
+}
+
+#pragma mark - UITextField delegate
+
+- (void)onTextFieldChange:(NSNotification *)notif
+{
+ _submitButton.enabled = _authInputsView.areAllRequiredFieldsSet;
+
+ if (notif.object == _homeServerTextField)
+ {
+ // If any, the current request is obsolete
+ [self cancelIdentityServerCheck];
+
+ [self setIdentityServerHidden:YES];
+ }
+}
+
+- (BOOL)textFieldShouldBeginEditing:(UITextField *)textField
+{
+ if (textField == _homeServerTextField)
+ {
+ // Cancel supported AuthFlow refresh if a request is in progress
+ [[NSNotificationCenter defaultCenter] removeObserver:self name:AFNetworkingReachabilityDidChangeNotification object:nil];
+
+ if (mxCurrentOperation)
+ {
+ // Cancel potential request in progress
+ [mxCurrentOperation cancel];
+ mxCurrentOperation = nil;
+ }
+ }
+
+ return YES;
+}
+
+- (void)textFieldDidEndEditing:(UITextField *)textField
+{
+ if (textField == _homeServerTextField)
+ {
+ [self setHomeServerTextFieldText:textField.text];
+ }
+ else if (textField == _identityServerTextField)
+ {
+ [self setIdentityServerTextFieldText:textField.text];
+ }
+}
+
+- (BOOL)textFieldShouldReturn:(UITextField*)textField
+{
+ if (textField.returnKeyType == UIReturnKeyDone)
+ {
+ // "Done" key has been pressed
+ [textField resignFirstResponder];
+ }
+ return YES;
+}
+
+#pragma mark - AuthInputsViewDelegate delegate
+
+- (void)authInputsView:(MXKAuthInputsView*)authInputsView presentAlertController:(UIAlertController*)inputsAlert
+{
+ [self dismissKeyboard];
+ [self presentViewController:inputsAlert animated:YES completion:nil];
+}
+
+- (void)authInputsViewDidPressDoneKey:(MXKAuthInputsView *)authInputsView
+{
+ if (_submitButton.isEnabled)
+ {
+ // Launch authentication now
+ [self onButtonPressed:_submitButton];
+ }
+}
+
+- (MXRestClient *)authInputsViewThirdPartyIdValidationRestClient:(MXKAuthInputsView *)authInputsView
+{
+ return mxRestClient;
+}
+
+- (MXIdentityService *)authInputsViewThirdPartyIdValidationIdentityService:(MXIdentityService *)authInputsView
+{
+ return self.identityService;
+}
+
+#pragma mark - Authentication Fallback
+
+- (void)showAuthenticationFallBackView
+{
+ [self showAuthenticationFallBackView:authenticationFallback];
+}
+
+- (void)showAuthenticationFallBackView:(NSString*)fallbackPage
+{
+ _authenticationScrollView.hidden = YES;
+ _authFallbackContentView.hidden = NO;
+
+ // Add a cancel button in case of navigation controller use.
+ if (self.navigationController)
+ {
+ if (!cancelFallbackBarButton)
+ {
+ cancelFallbackBarButton = [[UIBarButtonItem alloc] initWithTitle:[MatrixKitL10n loginLeaveFallback] style:UIBarButtonItemStylePlain target:self action:@selector(hideRegistrationFallbackView)];
+ }
+
+ // Add cancel button in right bar items
+ NSArray *rightBarButtonItems = self.navigationItem.rightBarButtonItems;
+ self.navigationItem.rightBarButtonItems = rightBarButtonItems ? [rightBarButtonItems arrayByAddingObject:cancelFallbackBarButton] : @[cancelFallbackBarButton];
+ }
+
+ if (self.softLogoutCredentials)
+ {
+ // Add device_id as query param of the fallback
+ NSURLComponents *components = [[NSURLComponents alloc] initWithString:fallbackPage];
+
+ NSMutableArray *queryItems = [components.queryItems mutableCopy];
+ if (!queryItems)
+ {
+ queryItems = [NSMutableArray array];
+ }
+
+ [queryItems addObject:[NSURLQueryItem queryItemWithName:@"device_id"
+ value:self.softLogoutCredentials.deviceId]];
+
+ components.queryItems = queryItems;
+
+ fallbackPage = components.URL.absoluteString;
+ }
+
+ [_authFallbackWebView openFallbackPage:fallbackPage success:^(MXLoginResponse *loginResponse) {
+
+ MXCredentials *credentials = [[MXCredentials alloc] initWithLoginResponse:loginResponse andDefaultCredentials:self->mxRestClient.credentials];
+
+ // TODO handle unrecognized certificate (if any) during registration through fallback webview.
+
+ [self onSuccessfulLogin:credentials];
+ }];
+}
+
+- (void)hideRegistrationFallbackView
+{
+ if (cancelFallbackBarButton)
+ {
+ NSMutableArray *rightBarButtonItems = [NSMutableArray arrayWithArray: self.navigationItem.rightBarButtonItems];
+ [rightBarButtonItems removeObject:cancelFallbackBarButton];
+ self.navigationItem.rightBarButtonItems = rightBarButtonItems;
+ }
+
+ [_authFallbackWebView stopLoading];
+ _authenticationScrollView.hidden = NO;
+ _authFallbackContentView.hidden = YES;
+}
+
+#pragma mark - KVO
+
+- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
+{
+ if ([@"viewHeightConstraint.constant" isEqualToString:keyPath])
+ {
+ // Refresh the height of the auth inputs view container.
+ CGFloat previousInputsContainerViewHeight = _authInputContainerViewHeightConstraint.constant;
+ _authInputContainerViewHeightConstraint.constant = _authInputsView.viewHeightConstraint.constant;
+
+ // Force to render the view
+ [self.view layoutIfNeeded];
+
+ // Refresh content view height by considering the updated height of inputs container
+ _contentViewHeightConstraint.constant += (_authInputContainerViewHeightConstraint.constant - previousInputsContainerViewHeight);
+ }
+ else
+ {
+ [super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
+ }
+}
+
+@end
diff --git a/Riot/Modules/MatrixKit/Controllers/MXKAuthenticationViewController.xib b/Riot/Modules/MatrixKit/Controllers/MXKAuthenticationViewController.xib
new file mode 100644
index 000000000..807f6382d
--- /dev/null
+++ b/Riot/Modules/MatrixKit/Controllers/MXKAuthenticationViewController.xib
@@ -0,0 +1,298 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Riot/Modules/MatrixKit/Controllers/MXKCallViewController.h b/Riot/Modules/MatrixKit/Controllers/MXKCallViewController.h
new file mode 100644
index 000000000..7e8e8ad92
--- /dev/null
+++ b/Riot/Modules/MatrixKit/Controllers/MXKCallViewController.h
@@ -0,0 +1,243 @@
+/*
+ Copyright 2015 OpenMarket 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
+
+#import
+
+#import "MXKViewController.h"
+
+#import "MXKImageView.h"
+
+@class MXKCallViewController;
+
+/**
+ Delegate for `MXKCallViewController` object
+ */
+@protocol MXKCallViewControllerDelegate
+
+/**
+ Tells the delegate to dismiss the call view controller.
+ This callback is called when the user wants to go back into the app during a call or when the call is ended.
+ The delegate should check the state of the associated call to know the actual reason.
+
+ @param callViewController the call view controller.
+ @param completion the block to execute at the end of the operation.
+ */
+- (void)dismissCallViewController:(MXKCallViewController *)callViewController completion:(void (^)(void))completion;
+
+/**
+ Tells the delegate that user tapped on hold call.
+ @param callViewController the call view controller.
+ */
+- (void)callViewControllerDidTapOnHoldCall:(MXKCallViewController *)callViewController;
+
+@end
+
+extern NSString *const kMXKCallViewControllerWillAppearNotification;
+extern NSString *const kMXKCallViewControllerAppearedNotification;
+extern NSString *const kMXKCallViewControllerWillDisappearNotification;
+extern NSString *const kMXKCallViewControllerDisappearedNotification;
+extern NSString *const kMXKCallViewControllerBackToAppNotification;
+
+/**
+ 'MXKCallViewController' instance displays a call. Only one matrix session is supported by this view controller.
+ */
+@interface MXKCallViewController : MXKViewController
+
+@property (weak, nonatomic) IBOutlet MXKImageView *backgroundImageView;
+
+@property (weak, nonatomic, readonly) IBOutlet UIView *localPreviewContainerView;
+@property (weak, nonatomic, readonly) IBOutlet UIView *localPreviewVideoView;
+@property (weak, nonatomic) IBOutlet UIActivityIndicatorView *localPreviewActivityView;
+
+@property (weak, nonatomic, readonly) IBOutlet UIView *onHoldCallContainerView;
+@property (weak, nonatomic) IBOutlet MXKImageView *onHoldCallerImageView;
+
+@property (weak, nonatomic, readonly) IBOutlet UIView *remotePreviewContainerView;
+
+@property (weak, nonatomic) IBOutlet UIView *overlayContainerView;
+@property (weak, nonatomic) IBOutlet UIView *callContainerView;
+@property (weak, nonatomic) IBOutlet MXKImageView *callerImageView;
+@property (weak, nonatomic) IBOutlet UIImageView *pausedIcon;
+@property (weak, nonatomic) IBOutlet UILabel *callerNameLabel;
+@property (weak, nonatomic) IBOutlet UILabel *callStatusLabel;
+@property (weak, nonatomic) IBOutlet UIButton *resumeButton;
+
+@property (weak, nonatomic) IBOutlet UIView *callToolBar;
+@property (weak, nonatomic) IBOutlet UIButton *rejectCallButton;
+@property (weak, nonatomic) IBOutlet UIButton *answerCallButton;
+@property (weak, nonatomic) IBOutlet UIButton *endCallButton;
+
+@property (weak, nonatomic) IBOutlet UIView *callControlContainerView;
+@property (weak, nonatomic) IBOutlet UIButton *speakerButton;
+@property (weak, nonatomic) IBOutlet UIButton *audioMuteButton;
+@property (weak, nonatomic) IBOutlet UIButton *videoMuteButton;
+@property (weak, nonatomic) IBOutlet UIButton *moreButtonForVoice;
+@property (weak, nonatomic) IBOutlet UIButton *moreButtonForVideo;
+
+@property (weak, nonatomic) IBOutlet UIButton *backToAppButton;
+@property (weak, nonatomic) IBOutlet UIButton *cameraSwitchButton;
+
+@property (unsafe_unretained, nonatomic) IBOutlet NSLayoutConstraint *localPreviewContainerViewLeadingConstraint;
+@property (unsafe_unretained, nonatomic) IBOutlet NSLayoutConstraint *localPreviewContainerViewTopConstraint;
+@property (unsafe_unretained, nonatomic) IBOutlet NSLayoutConstraint *localPreviewContainerViewHeightConstraint;
+@property (unsafe_unretained, nonatomic) IBOutlet NSLayoutConstraint *localPreviewContainerViewWidthConstraint;
+
+@property (weak, nonatomic) IBOutlet UIButton *transferButton;
+
+/**
+ The default picture displayed when no picture is available.
+ */
+@property (nonatomic) UIImage *picturePlaceholder;
+
+/**
+ The call status bar displayed on the top of the app during a call.
+ */
+@property (nonatomic, readonly) UIWindow *backToAppStatusWindow;
+
+/**
+ Flag whether this call screen is displaying an alert right now.
+ */
+@property (nonatomic, readonly, getter=isDisplayingAlert) BOOL displayingAlert;
+
+/**
+ The current call
+ */
+@property (nonatomic) MXCall *mxCall;
+
+/**
+ The current call on hold
+ */
+@property (nonatomic) MXCall *mxCallOnHold;
+
+/**
+ The current peer
+ */
+@property (nonatomic, readonly) MXUser *peer;
+
+/**
+ The current peer of the call on hold
+ */
+@property (nonatomic, readonly) MXUser *peerOnHold;
+
+/**
+ The delegate.
+ */
+@property (nonatomic, weak) id delegate;
+
+/*
+ Specifies whether a ringtone must be played on incoming call.
+ It's important to set this value before you will set `mxCall` otherwise value of this property can has no effect.
+
+ Defaults to YES.
+ */
+@property (nonatomic) BOOL playRingtone;
+
+#pragma mark - Class methods
+
+/**
+ Returns the `UINib` object initialized for a `MXKCallViewController`.
+
+ @return The initialized `UINib` object or `nil` if there were errors during initialization
+ or the nib file could not be located.
+
+ @discussion You may override this method to provide a customized nib. If you do,
+ you should also override `roomViewController` to return your
+ view controller loaded from your custom nib.
+ */
++ (UINib *)nib;
+
+/**
+ Creates and returns a new `MXKCallViewController` object.
+
+ @discussion This is the designated initializer for programmatic instantiation.
+
+ @param call a MXCall instance.
+ @return An initialized `MXKRoomViewController` object if successful, `nil` otherwise.
+ */
++ (instancetype)callViewController:(MXCall *)call;
+
+/**
+ Return an audio file url based on the provided name.
+
+ @param soundName audio file name without extension.
+ @return a NSURL instance.
+ */
+- (NSURL*)audioURLWithName:(NSString *)soundName;
+
+/**
+ Refresh the peer information in the call viewcontroller's view.
+ */
+- (void)updatePeerInfoDisplay;
+
+/**
+ Adjust the layout of the preview container.
+ */
+- (void)updateLocalPreviewLayout;
+
+/**
+ Show/Hide the overlay view.
+
+ @param isShown tell whether the overlay is shown or not.
+ */
+- (void)showOverlayContainer:(BOOL)isShown;
+
+/**
+ Set up or teardown the promixity monitoring and enable/disable the idle timer according to call type, state & audio route.
+ */
+- (void)updateProximityAndSleep;
+
+/**
+ Prepare and return the optional view displayed during incoming call notification.
+ Return nil by default
+
+ Subclasses may override this method to provide appropriate for their app view.
+ When this method is called peer and mxCall are valid so you can use them.
+ */
+- (UIView *)createIncomingCallView;
+
+/**
+ Action registered on the event 'UIControlEventTouchUpInside' for each UIButton instance.
+ */
+- (IBAction)onButtonPressed:(id)sender;
+
+/**
+ Default implementation presents an action sheet with proper options. Override to change the user interface.
+ */
+- (void)showAudioDeviceOptions;
+
+/**
+ Default implementation makes the button selected for loud speakers and external device options, non-selected for built-in device.
+ */
+- (void)configureSpeakerButton;
+
+#pragma mark - DTMF
+
+/**
+ Default implementation does nothing. Override to show a dial pad and then use MXCall methods to send DTMF tones.
+ */
+- (void)openDialpad;
+
+#pragma mark - Call Transfer
+
+/**
+ Default implementation does nothing. Override to show a contact selection screen and then use MXCallManager methods to start the transfer.
+ */
+- (void)openCallTransfer;
+
+@end
diff --git a/Riot/Modules/MatrixKit/Controllers/MXKCallViewController.m b/Riot/Modules/MatrixKit/Controllers/MXKCallViewController.m
new file mode 100644
index 000000000..55781b106
--- /dev/null
+++ b/Riot/Modules/MatrixKit/Controllers/MXKCallViewController.m
@@ -0,0 +1,1547 @@
+/*
+ Copyright 2015 OpenMarket Ltd
+ Copyright 2018 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 "MXKCallViewController.h"
+
+@import MatrixSDK;
+
+#import "MXKAppSettings.h"
+#import "MXKSoundPlayer.h"
+#import "MXKTools.h"
+#import "NSBundle+MatrixKit.h"
+
+#import "MXKSwiftHeader.h"
+
+NSString *const kMXKCallViewControllerWillAppearNotification = @"kMXKCallViewControllerWillAppearNotification";
+NSString *const kMXKCallViewControllerAppearedNotification = @"kMXKCallViewControllerAppearedNotification";
+NSString *const kMXKCallViewControllerWillDisappearNotification = @"kMXKCallViewControllerWillDisappearNotification";
+NSString *const kMXKCallViewControllerDisappearedNotification = @"kMXKCallViewControllerDisappearedNotification";
+NSString *const kMXKCallViewControllerBackToAppNotification = @"kMXKCallViewControllerBackToAppNotification";
+
+static const CGFloat kLocalPreviewMargin = 20;
+
+@interface MXKCallViewController ()
+{
+ NSTimer *hideOverlayTimer;
+ NSTimer *updateStatusTimer;
+
+ Boolean isMovingLocalPreview;
+ Boolean isSelectingLocalPreview;
+
+ CGPoint startNewLocalMove;
+
+ /**
+ The popup showed in case of call stack error.
+ */
+ UIAlertController *errorAlert;
+
+ // the room events listener
+ id roomListener;
+
+ // Observe kMXRoomDidFlushDataNotification to take into account the updated room members when the room history is flushed.
+ id roomDidFlushDataNotificationObserver;
+
+ // Observe AVAudioSessionRouteChangeNotification
+ id audioSessionRouteChangeNotificationObserver;
+
+ // Current alert (if any).
+ UIAlertController *currentAlert;
+
+ // Current peer display name
+ NSString *peerDisplayName;
+}
+
+@property (nonatomic, assign) Boolean isRinging;
+
+@property (nonatomic, nullable) UIView *incomingCallView;
+
+@property (nonatomic, strong) UITapGestureRecognizer *onHoldCallContainerTapRecognizer;
+
+@end
+
+@implementation MXKCallViewController
+@synthesize backgroundImageView;
+@synthesize localPreviewContainerView, localPreviewVideoView, localPreviewActivityView, remotePreviewContainerView;
+@synthesize overlayContainerView, callContainerView, callerImageView, callerNameLabel, callStatusLabel;
+@synthesize callToolBar, rejectCallButton, answerCallButton, endCallButton;
+@synthesize callControlContainerView, speakerButton, audioMuteButton, videoMuteButton;
+@synthesize backToAppButton, cameraSwitchButton;
+@synthesize backToAppStatusWindow;
+@synthesize mxCall;
+@synthesize mxCallOnHold;
+@synthesize onHoldCallerImageView;
+@synthesize onHoldCallContainerView;
+
+#pragma mark - Class methods
+
++ (UINib *)nib
+{
+ return [UINib nibWithNibName:NSStringFromClass(self.class)
+ bundle:[NSBundle bundleForClass:self.class]];
+}
+
++ (instancetype)callViewController:(MXCall*)call
+{
+ MXKCallViewController *instance = [[[self class] alloc] initWithNibName:NSStringFromClass(self.class)
+ bundle:[NSBundle bundleForClass:self.class]];
+
+ // Load the view controller's view now (buttons and views will then be available).
+ if ([instance respondsToSelector:@selector(loadViewIfNeeded)])
+ {
+ // iOS 9 and later
+ [instance loadViewIfNeeded];
+ }
+ else if (instance.view)
+ {
+ // Patch: on iOS < 9.0, we load the view by calling its getter.
+ }
+
+ instance.mxCall = call;
+
+ return instance;
+}
+
+#pragma mark -
+
+- (void)finalizeInit
+{
+ [super finalizeInit];
+
+ _playRingtone = YES;
+}
+
+- (void)viewDidLoad
+{
+ [super viewDidLoad];
+
+ updateStatusTimer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(updateTimeStatusLabel) userInfo:nil repeats:YES];
+
+ self.callerImageView.defaultBackgroundColor = [UIColor clearColor];
+ self.backToAppButton.backgroundColor = [UIColor clearColor];
+ self.audioMuteButton.backgroundColor = [UIColor clearColor];
+ self.videoMuteButton.backgroundColor = [UIColor clearColor];
+ self.resumeButton.backgroundColor = [UIColor clearColor];
+ self.moreButton.backgroundColor = [UIColor clearColor];
+ self.speakerButton.backgroundColor = [UIColor clearColor];
+ self.transferButton.backgroundColor = [UIColor clearColor];
+
+ [self.backToAppButton setImage:[NSBundle mxk_imageFromMXKAssetsBundleWithName:@"icon_backtoapp"] forState:UIControlStateNormal];
+ [self.backToAppButton setImage:[NSBundle mxk_imageFromMXKAssetsBundleWithName:@"icon_backtoapp"] forState:UIControlStateHighlighted];
+ [self.audioMuteButton setImage:[NSBundle mxk_imageFromMXKAssetsBundleWithName:@"icon_audio_unmute"] forState:UIControlStateNormal];
+ [self.audioMuteButton setImage:[NSBundle mxk_imageFromMXKAssetsBundleWithName:@"icon_audio_mute"] forState:UIControlStateSelected];
+ [self.videoMuteButton setImage:[NSBundle mxk_imageFromMXKAssetsBundleWithName:@"icon_video_unmute"] forState:UIControlStateNormal];
+ [self.videoMuteButton setImage:[NSBundle mxk_imageFromMXKAssetsBundleWithName:@"icon_video_mute"] forState:UIControlStateSelected];
+ [self.moreButton setImage:[NSBundle mxk_imageFromMXKAssetsBundleWithName:@"icon_call_more"] forState:UIControlStateNormal];
+ [self.moreButton setImage:[NSBundle mxk_imageFromMXKAssetsBundleWithName:@"icon_call_more"] forState:UIControlStateSelected];
+ [self.speakerButton setImage:[NSBundle mxk_imageFromMXKAssetsBundleWithName:@"icon_speaker_off"] forState:UIControlStateNormal];
+ [self.speakerButton setImage:[NSBundle mxk_imageFromMXKAssetsBundleWithName:@"icon_speaker_on"] forState:UIControlStateSelected];
+
+ // Localize string
+ [answerCallButton setTitle:[MatrixKitL10n answerCall] forState:UIControlStateNormal];
+ [answerCallButton setTitle:[MatrixKitL10n answerCall] forState:UIControlStateHighlighted];
+ [rejectCallButton setTitle:[MatrixKitL10n rejectCall] forState:UIControlStateNormal];
+ [rejectCallButton setTitle:[MatrixKitL10n rejectCall] forState:UIControlStateHighlighted];
+ [endCallButton setTitle:[MatrixKitL10n endCall] forState:UIControlStateNormal];
+ [endCallButton setTitle:[MatrixKitL10n endCall] forState:UIControlStateHighlighted];
+ [_resumeButton setTitle:[MatrixKitL10n resumeCall] forState:UIControlStateNormal];
+ [_resumeButton setTitle:[MatrixKitL10n resumeCall] forState:UIControlStateHighlighted];
+
+ // Refresh call information
+ self.mxCall = mxCall;
+
+ // Listen to AVAudioSession activation notification if CallKit is available and enabled
+ BOOL isCallKitAvailable = [MXCallKitAdapter callKitAvailable] && [MXKAppSettings standardAppSettings].isCallKitEnabled;
+ if (isCallKitAvailable)
+ {
+ [[NSNotificationCenter defaultCenter] addObserver:self
+ selector:@selector(handleAudioSessionActivationNotification)
+ name:kMXCallKitAdapterAudioSessionDidActive
+ object:nil];
+ }
+}
+
+- (void)didReceiveMemoryWarning
+{
+ [super didReceiveMemoryWarning];
+}
+
+- (void)dealloc
+{
+ [[NSNotificationCenter defaultCenter] removeObserver:self name:kMXCallKitAdapterAudioSessionDidActive object:nil];
+
+ [self removeObservers];
+}
+
+- (void)viewWillAppear:(BOOL)animated
+{
+ [super viewWillAppear:animated];
+ [[NSNotificationCenter defaultCenter] postNotificationName:kMXKCallViewControllerWillAppearNotification object:nil];
+
+ [self updateLocalPreviewLayout];
+ [self showOverlayContainer:YES];
+
+ if (mxCall)
+ {
+ // Refresh call display according to the call room state.
+ [self callRoomStateDidChange:^{
+ // Refresh call status
+ [self call:self->mxCall stateDidChange:self->mxCall.state reason:nil];
+ }];
+
+ }
+
+ if (_delegate)
+ {
+ backToAppButton.hidden = NO;
+ }
+ else
+ {
+ backToAppButton.hidden = YES;
+ }
+}
+
+- (void)viewDidAppear:(BOOL)animated
+{
+ [super viewDidAppear:animated];
+ [[NSNotificationCenter defaultCenter] postNotificationName:kMXKCallViewControllerAppearedNotification object:nil];
+
+ // trick to hide the volume at launch
+ // as the mininum volume is forced by the application
+ // the volume popup can be displayed
+ // volumeView = [[MPVolumeView alloc] initWithFrame: CGRectMake(5000, 5000, 0, 0)];
+ // [self.view addSubview: volumeView];
+ //
+ // dispatch_after(dispatch_walltime(DISPATCH_TIME_NOW, 0.5 * NSEC_PER_SEC), dispatch_get_main_queue(), ^{
+ // [volumeView removeFromSuperview];
+ // });
+}
+
+- (void)viewWillDisappear:(BOOL)animated
+{
+ [super viewWillDisappear:animated];
+ [[NSNotificationCenter defaultCenter] postNotificationName:kMXKCallViewControllerWillDisappearNotification object:nil];
+}
+
+- (void)viewDidDisappear:(BOOL)animated
+{
+ [super viewDidDisappear:animated];
+ [[NSNotificationCenter defaultCenter] postNotificationName:kMXKCallViewControllerDisappearedNotification object:nil];
+}
+
+- (void)dismiss
+{
+ if (_delegate)
+ {
+ [_delegate dismissCallViewController:self completion:nil];
+ }
+ else
+ {
+ // Auto dismiss after few seconds
+ dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
+ [self dismissViewControllerAnimated:YES completion:nil];
+ });
+ }
+}
+
+#pragma mark - override MXKViewController
+
+- (void)destroy
+{
+ self.peer = nil;
+
+ self.mxCall = nil;
+
+ _delegate = nil;
+
+ self.isRinging = NO;
+
+ [hideOverlayTimer invalidate];
+ [updateStatusTimer invalidate];
+
+ _incomingCallView = nil;
+
+ _onHoldCallContainerTapRecognizer = nil;
+
+ [super destroy];
+}
+
+#pragma mark - Properties
+
+- (UIImage *)picturePlaceholder
+{
+ return [NSBundle mxk_imageFromMXKAssetsBundleWithName:@"default-profile"];
+}
+
+- (void)setMxCall:(MXCall *)call
+{
+ // Remove previous call (if any)
+ if (mxCall)
+ {
+ mxCall.delegate = nil;
+ mxCall.selfVideoView = nil;
+ mxCall.remoteVideoView = nil;
+ [self removeMatrixSession:self.mainSession];
+
+ [self removeObservers];
+
+ mxCall = nil;
+ }
+
+ if (call && call.room)
+ {
+ mxCall = call;
+
+ [self addMatrixSession:mxCall.room.mxSession];
+
+ MXWeakify(self);
+
+ // Register a listener to handle messages related to room name, members...
+ roomListener = [mxCall.room listenToEventsOfTypes:@[kMXEventTypeStringRoomName, kMXEventTypeStringRoomTopic, kMXEventTypeStringRoomAliases, kMXEventTypeStringRoomAvatar, kMXEventTypeStringRoomCanonicalAlias, kMXEventTypeStringRoomMember] onEvent:^(MXEvent *event, MXTimelineDirection direction, MXRoomState *roomState) {
+ MXStrongifyAndReturnIfNil(self);
+
+ // Consider only live events
+ if (self->mxCall && direction == MXTimelineDirectionForwards)
+ {
+ // The room state has been changed
+ [self callRoomStateDidChange:nil];
+ }
+ }];
+
+ // Observe room history flush (sync with limited timeline, or state event redaction)
+ roomDidFlushDataNotificationObserver = [[NSNotificationCenter defaultCenter] addObserverForName:kMXRoomDidFlushDataNotification object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *notif) {
+ MXStrongifyAndReturnIfNil(self);
+
+ MXRoom *room = notif.object;
+ if (self->mxCall && self.mainSession == room.mxSession && [self->mxCall.room.roomId isEqualToString:room.roomId])
+ {
+ // The existing room history has been flushed during server sync.
+ // Take into account the updated room state
+ [self callRoomStateDidChange:nil];
+ }
+
+ }];
+
+ audioSessionRouteChangeNotificationObserver = [[NSNotificationCenter defaultCenter] addObserverForName:AVAudioSessionRouteChangeNotification object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *notif) {
+
+ [self updateProximityAndSleep];
+
+ }];
+
+ // Hide video mute on voice call
+ self.videoMuteButton.hidden = !call.isVideoCall;
+
+ // Hide camera switch on voice call
+ self.cameraSwitchButton.hidden = !call.isVideoCall;
+
+ _moreButtonForVideo.hidden = !call.isVideoCall;
+ _moreButtonForVoice.hidden = call.isVideoCall;
+
+ // Observe call state change
+ call.delegate = self;
+
+ // Display room call information
+ [self callRoomStateDidChange:^{
+ [self call:call stateDidChange:call.state reason:nil];
+ }];
+
+ if (call.isVideoCall && localPreviewContainerView)
+ {
+ // Access to the camera is mandatory to display the self view
+ // Check the permission right now
+ NSString *appDisplayName = [[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleDisplayName"];
+ [MXKTools checkAccessForMediaType:AVMediaTypeVideo
+ manualChangeMessage:[MatrixKitL10n cameraAccessNotGrantedForCall:appDisplayName]
+
+ showPopUpInViewController:self completionHandler:^(BOOL granted) {
+
+ if (granted)
+ {
+ self->localPreviewContainerView.hidden = NO;
+ self->remotePreviewContainerView.hidden = NO;
+
+ call.selfVideoView = self->localPreviewVideoView;
+ call.remoteVideoView = self->remotePreviewContainerView;
+ [self applyDeviceOrientation:YES];
+
+ [[NSNotificationCenter defaultCenter] addObserver:self
+ selector:@selector(deviceOrientationDidChange)
+ name:UIDeviceOrientationDidChangeNotification
+ object:nil];
+ }
+ }];
+ }
+ else
+ {
+ localPreviewContainerView.hidden = YES;
+ remotePreviewContainerView.hidden = YES;
+ }
+ }
+}
+
+- (void)setMxCallOnHold:(MXCall *)callOnHold
+{
+ if (mxCallOnHold == callOnHold)
+ {
+ // setting same property, return
+ return;
+ }
+
+ mxCallOnHold = callOnHold;
+
+ if (mxCallOnHold)
+ {
+ self.onHoldCallContainerView.hidden = NO;
+ [self.onHoldCallContainerView addGestureRecognizer:self.onHoldCallContainerTapRecognizer];
+ [self.onHoldCallContainerView setUserInteractionEnabled:YES];
+
+ // Handle peer here
+ if (mxCallOnHold.isIncoming)
+ {
+ self.peerOnHold = [mxCallOnHold.room.mxSession getOrCreateUser:mxCallOnHold.callerId];
+ }
+ else
+ {
+ // For 1:1 call, find the other peer
+ // Else, the room information will be used to display information about the call
+ MXWeakify(self);
+ [mxCallOnHold.room state:^(MXRoomState *roomState) {
+ MXStrongifyAndReturnIfNil(self);
+
+ MXUser *theMember = nil;
+ NSArray *members = roomState.members.joinedMembers;
+ for (MXUser *member in members)
+ {
+ if (![member.userId isEqualToString:self->mxCallOnHold.callerId])
+ {
+ theMember = member;
+ break;
+ }
+ }
+
+ self.peerOnHold = theMember;
+ }];
+ }
+ }
+ else
+ {
+ [self.onHoldCallContainerView removeGestureRecognizer:self.onHoldCallContainerTapRecognizer];
+ [self.onHoldCallContainerView setUserInteractionEnabled:NO];
+ self.onHoldCallContainerView.hidden = YES;
+ self.peerOnHold = nil;
+ }
+}
+
+- (void)setPeer:(MXUser *)peer
+{
+ _peer = peer;
+
+ [self updatePeerInfoDisplay];
+}
+
+- (void)setPeerOnHold:(MXUser *)peerOnHold
+{
+ _peerOnHold = peerOnHold;
+
+ NSString *peerAvatarURL;
+
+ if (_peerOnHold)
+ {
+ peerAvatarURL = _peerOnHold.avatarUrl;
+ }
+ else if (mxCall.isConferenceCall)
+ {
+ peerAvatarURL = mxCallOnHold.room.summary.avatar;
+ }
+
+ onHoldCallerImageView.imageView.contentMode = UIViewContentModeScaleAspectFill;
+
+ if (peerAvatarURL)
+ {
+ // Suppose avatar url is a matrix content uri, we use SDK to get the well adapted thumbnail from server
+ onHoldCallerImageView.mediaFolder = kMXMediaManagerAvatarThumbnailFolder;
+ onHoldCallerImageView.enableInMemoryCache = YES;
+ [onHoldCallerImageView setImageURI:peerAvatarURL
+ withType:nil
+ andImageOrientation:UIImageOrientationUp
+ toFitViewSize:onHoldCallerImageView.frame.size
+ withMethod:MXThumbnailingMethodCrop
+ previewImage:self.picturePlaceholder
+ mediaManager:self.mainSession.mediaManager];
+ }
+ else
+ {
+ onHoldCallerImageView.image = self.picturePlaceholder;
+ }
+}
+
+- (void)updatePeerInfoDisplay
+{
+ NSString *peerAvatarURL;
+
+ if (_peer)
+ {
+ peerDisplayName = [_peer displayname];
+ if (!peerDisplayName.length)
+ {
+ peerDisplayName = _peer.userId;
+ }
+ peerAvatarURL = _peer.avatarUrl;
+ }
+ else if (mxCall.isConferenceCall)
+ {
+ peerDisplayName = mxCall.room.summary.displayname;
+ peerAvatarURL = mxCall.room.summary.avatar;
+ }
+
+ if (mxCall.isConsulting)
+ {
+ callerNameLabel.text = [MatrixKitL10n callConsultingWithUser:peerDisplayName];
+ }
+ else
+ {
+ if (mxCall.isVideoCall)
+ {
+ callerNameLabel.text = [MatrixKitL10n callVideoWithUser:peerDisplayName];
+ }
+ else
+ {
+ callerNameLabel.text = [MatrixKitL10n callVoiceWithUser:peerDisplayName];
+ }
+ }
+
+ if (peerAvatarURL)
+ {
+ // Suppose avatar url is a matrix content uri, we use SDK to get the well adapted thumbnail from server
+ callerImageView.mediaFolder = kMXMediaManagerAvatarThumbnailFolder;
+ callerImageView.enableInMemoryCache = YES;
+ [callerImageView setImageURI:peerAvatarURL
+ withType:nil
+ andImageOrientation:UIImageOrientationUp
+ toFitViewSize:callerImageView.frame.size
+ withMethod:MXThumbnailingMethodCrop
+ previewImage:self.picturePlaceholder
+ mediaManager:self.mainSession.mediaManager];
+ }
+ else
+ {
+ callerImageView.image = self.picturePlaceholder;
+ }
+
+ // Round caller image view
+ [callerImageView.layer setCornerRadius:callerImageView.frame.size.width / 2];
+ callerImageView.clipsToBounds = YES;
+}
+
+- (void)setIsRinging:(Boolean)isRinging
+{
+ if (_isRinging != isRinging)
+ {
+ if (isRinging)
+ {
+ NSURL *audioUrl;
+ if (mxCall.isIncoming)
+ {
+ if (self.playRingtone)
+ audioUrl = [self audioURLWithName:@"ring"];
+ }
+ else
+ {
+ audioUrl = [self audioURLWithName:@"ringback"];
+ }
+
+ if (audioUrl)
+ {
+ [[MXKSoundPlayer sharedInstance] playSoundAt:audioUrl repeat:YES vibrate:mxCall.isIncoming routeToBuiltInReceiver:!mxCall.isIncoming];
+ }
+ }
+ else
+ {
+ [[MXKSoundPlayer sharedInstance] stopPlayingWithAudioSessionDeactivation:NO];
+ }
+
+ _isRinging = isRinging;
+ }
+}
+
+- (void)setDelegate:(id)delegate
+{
+ _delegate = delegate;
+
+ if (_delegate)
+ {
+ backToAppButton.hidden = NO;
+ }
+ else
+ {
+ backToAppButton.hidden = YES;
+ }
+}
+
+- (UITapGestureRecognizer *)onHoldCallContainerTapRecognizer
+{
+ if (_onHoldCallContainerTapRecognizer == nil)
+ {
+ _onHoldCallContainerTapRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self
+ action:@selector(onHoldCallContainerTapped:)];
+ }
+ return _onHoldCallContainerTapRecognizer;
+}
+
+- (BOOL)isDisplayingAlert
+{
+ return errorAlert != nil;
+}
+
+- (UIButton *)moreButton
+{
+ if (mxCall.isVideoCall)
+ {
+ return _moreButtonForVideo;
+ }
+ return _moreButtonForVoice;
+}
+
+#pragma mark - Sounds
+
+- (NSURL *)audioURLWithName:(NSString *)soundName
+{
+ return [NSBundle mxk_audioURLFromMXKAssetsBundleWithName:soundName];
+}
+
+#pragma mark - Actions
+
+- (void)onHoldCallContainerTapped:(UITapGestureRecognizer *)recognizer
+{
+ if ([self.delegate respondsToSelector:@selector(callViewControllerDidTapOnHoldCall:)])
+ {
+ [self.delegate callViewControllerDidTapOnHoldCall:self];
+ }
+}
+
+- (IBAction)onButtonPressed:(id)sender
+{
+ if (sender == answerCallButton)
+ {
+ // If we are here, we have access to the camera
+ // The following check is mainly to check microphone access permission
+ NSString *appDisplayName = [[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleDisplayName"];
+
+ [MXKTools checkAccessForCall:mxCall.isVideoCall
+ manualChangeMessageForAudio:[MatrixKitL10n microphoneAccessNotGrantedForCall:appDisplayName]
+ manualChangeMessageForVideo:[MatrixKitL10n cameraAccessNotGrantedForCall:appDisplayName]
+ showPopUpInViewController:self completionHandler:^(BOOL granted) {
+
+ if (granted)
+ {
+ [self->mxCall answer];
+ }
+ }];
+ }
+ else if (sender == rejectCallButton || sender == endCallButton)
+ {
+ if (mxCall.state != MXCallStateEnded)
+ {
+ [mxCall hangup];
+ }
+ else
+ {
+ [self dismiss];
+ }
+ }
+ else if (sender == audioMuteButton)
+ {
+ mxCall.audioMuted = !mxCall.audioMuted;
+ audioMuteButton.selected = mxCall.audioMuted;
+ }
+ else if (sender == videoMuteButton)
+ {
+ mxCall.videoMuted = !mxCall.videoMuted;
+ videoMuteButton.selected = mxCall.videoMuted;
+ }
+ else if (sender == _resumeButton)
+ {
+ [mxCall hold:NO];
+ }
+ else if (sender == self.moreButton)
+ {
+ [currentAlert dismissViewControllerAnimated:NO completion:nil];
+
+ MXWeakify(self);
+
+ NSMutableArray *actions = [NSMutableArray arrayWithCapacity:4];
+
+ if (self.speakerButton == nil)
+ {
+ // audio device action
+ UIAlertAction *audioDeviceAction = [UIAlertAction actionWithTitle:[MatrixKitL10n callMoreActionsChangeAudioDevice]
+ style:UIAlertActionStyleDefault
+ handler:^(UIAlertAction * action) {
+
+ MXStrongifyAndReturnIfNil(self);
+ self->currentAlert = nil;
+ [self showAudioDeviceOptions];
+
+ }];
+
+ [actions addObject:audioDeviceAction];
+ }
+
+ // check the call can be up/downgraded
+
+ // check the call can send DTMF tones
+ if (self.mxCall.supportsDTMF)
+ {
+ UIAlertAction *dialpadAction = [UIAlertAction actionWithTitle:[MatrixKitL10n callMoreActionsDialpad]
+ style:UIAlertActionStyleDefault
+ handler:^(UIAlertAction * action) {
+
+ MXStrongifyAndReturnIfNil(self);
+ self->currentAlert = nil;
+ [self openDialpad];
+
+ }];
+
+ [actions addObject:dialpadAction];
+ }
+
+ // check the call be holded/unholded
+ if (mxCall.supportsHolding)
+ {
+ NSString *actionLocKey = (mxCall.state == MXCallStateOnHold) ? [MatrixKitL10n callMoreActionsUnhold] : [MatrixKitL10n callMoreActionsHold];
+
+ UIAlertAction *holdAction = [UIAlertAction actionWithTitle:actionLocKey
+ style:UIAlertActionStyleDefault
+ handler:^(UIAlertAction * action) {
+
+ MXStrongifyAndReturnIfNil(self);
+ self->currentAlert = nil;
+ [self->mxCall hold:(self.mxCall.state != MXCallStateOnHold)];
+
+ }];
+
+ [actions addObject:holdAction];
+ }
+
+ // check the call be transferred
+ if (mxCall.supportsTransferring && self.peer)
+ {
+ UIAlertAction *transferAction = [UIAlertAction actionWithTitle:[MatrixKitL10n callMoreActionsTransfer]
+ style:UIAlertActionStyleDefault
+ handler:^(UIAlertAction * action) {
+
+ MXStrongifyAndReturnIfNil(self);
+ self->currentAlert = nil;
+
+ [self openCallTransfer];
+ }];
+
+ [actions addObject:transferAction];
+ }
+
+ if (actions.count > 0)
+ {
+ // create the alert
+ currentAlert = [UIAlertController alertControllerWithTitle:nil
+ message:nil
+ preferredStyle:UIAlertControllerStyleActionSheet];
+
+ // add actions
+ [actions enumerateObjectsUsingBlock:^(UIAlertAction * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
+ [currentAlert addAction:obj];
+ }];
+
+ // add cancel action always
+ [currentAlert addAction:[UIAlertAction actionWithTitle:[MatrixKitL10n cancel]
+ style:UIAlertActionStyleCancel
+ handler:^(UIAlertAction * action) {
+
+ MXStrongifyAndReturnIfNil(self);
+ self->currentAlert = nil;
+
+ }]];
+
+ [currentAlert popoverPresentationController].sourceView = self.moreButton;
+ [currentAlert popoverPresentationController].sourceRect = self.moreButton.bounds;
+ [self presentViewController:currentAlert animated:YES completion:nil];
+ }
+ }
+ else if (sender == speakerButton)
+ {
+ [self showAudioDeviceOptions];
+ }
+ else if (sender == cameraSwitchButton)
+ {
+ switch (mxCall.cameraPosition)
+ {
+ case AVCaptureDevicePositionFront:
+ mxCall.cameraPosition = AVCaptureDevicePositionBack;
+ break;
+
+ default:
+ mxCall.cameraPosition = AVCaptureDevicePositionFront;
+ break;
+ }
+ }
+ else if (sender == backToAppButton)
+ {
+ if (_delegate)
+ {
+ // Dismiss the view controller whereas the call is still running
+ [_delegate dismissCallViewController:self completion:nil];
+ }
+ }
+ else if (sender == _transferButton)
+ {
+ // actually transfer the call without consulting
+ [self.mainSession.callManager transferCall:mxCall.callWithTransferee
+ to:mxCall.transferTarget
+ withTransferee:mxCall.transferee
+ consultFirst:NO
+ success:^(NSString * _Nullable newCallId) {
+
+ }
+ failure:^(NSError * _Nullable error) {
+
+ }];
+ }
+
+ [self updateProximityAndSleep];
+}
+
+- (void)showAudioDeviceOptions
+{
+ NSMutableArray *actions = [NSMutableArray new];
+ NSArray *availableRoutes = mxCall.audioOutputRouter.availableOutputRoutes;
+
+ for (MXiOSAudioOutputRoute *route in availableRoutes)
+ {
+ // route action
+ NSString *name = route.name;
+ if (route.routeType == MXiOSAudioOutputRouteTypeLoudSpeakers)
+ {
+ name = [MatrixKitL10n callMoreActionsAudioUseDevice];
+ }
+ MXWeakify(self);
+ UIAlertAction *routeAction = [UIAlertAction actionWithTitle:name
+ style:UIAlertActionStyleDefault
+ handler:^(UIAlertAction * action) {
+
+ MXStrongifyAndReturnIfNil(self);
+ self->currentAlert = nil;
+ [self->mxCall.audioOutputRouter changeCurrentRouteTo:route];
+
+ }];
+
+ [actions addObject:routeAction];
+ }
+
+ if (actions.count > 0)
+ {
+ // create the alert
+ currentAlert = [UIAlertController alertControllerWithTitle:nil
+ message:nil
+ preferredStyle:UIAlertControllerStyleActionSheet];
+
+ for (UIAlertAction *action in actions)
+ {
+ [currentAlert addAction:action];
+ }
+
+ // add cancel action
+ MXWeakify(self);
+ [currentAlert addAction:[UIAlertAction actionWithTitle:[MatrixKitL10n cancel]
+ style:UIAlertActionStyleCancel
+ handler:^(UIAlertAction * action) {
+
+ MXStrongifyAndReturnIfNil(self);
+ self->currentAlert = nil;
+
+ }]];
+
+ [currentAlert popoverPresentationController].sourceView = self.moreButton;
+ [currentAlert popoverPresentationController].sourceRect = self.moreButton.bounds;
+ [self presentViewController:currentAlert animated:YES completion:nil];
+ }
+}
+
+#pragma mark - DTMF
+
+- (void)openDialpad
+{
+ // no-op
+}
+
+#pragma mark - Call Transfer
+
+- (void)openCallTransfer
+{
+ // no-op
+}
+
+#pragma mark - MXCallDelegate
+
+- (void)call:(MXCall *)call stateDidChange:(MXCallState)state reason:(MXEvent *)event
+{
+ // Set default configuration of bottom bar
+ endCallButton.hidden = NO;
+ rejectCallButton.hidden = YES;
+ answerCallButton.hidden = YES;
+ self.moreButton.enabled = YES;
+ _resumeButton.hidden = state != MXCallStateOnHold;
+ _pausedIcon.hidden = state != MXCallStateOnHold && state != MXCallStateRemotelyOnHold;
+ _transferButton.hidden = YES;
+
+ [localPreviewActivityView stopAnimating];
+
+ switch (state)
+ {
+ case MXCallStateFledgling:
+ self.isRinging = NO;
+ callStatusLabel.text = [MatrixKitL10n callConnecting];
+ break;
+ case MXCallStateWaitLocalMedia:
+ self.isRinging = NO;
+ [self configureSpeakerButton];
+ [localPreviewActivityView startAnimating];
+
+ // Try to show a special view for incoming view
+ [self configureIncomingCallViewIfRequiredWith:call];
+
+ break;
+ case MXCallStateCreateOffer:
+ {
+ // When CallKit is enabled and we have an outgoing call, we need to start playing ringback sound
+ // only after AVAudioSession will be activated by the system otherwise the sound will be gone.
+ // We always receive signal about MXCallStateCreateOffer earlier than the system activates AVAudioSession
+ // so we start playing ringback sound only on AVAudioSession activation in handleAudioSessionActivationNotification
+ BOOL isCallKitAvailable = [MXCallKitAdapter callKitAvailable] && [MXKAppSettings standardAppSettings].isCallKitEnabled;
+ if (!isCallKitAvailable)
+ {
+ self.isRinging = YES;
+ }
+
+ callStatusLabel.text = [MatrixKitL10n callConnecting];
+ break;
+ }
+ case MXCallStateInviteSent:
+ {
+ callStatusLabel.text = [MatrixKitL10n callRinging];
+ break;
+ }
+ case MXCallStateRinging:
+ self.isRinging = YES;
+ [self configureSpeakerButton];
+ if (call.isVideoCall)
+ {
+ callStatusLabel.text = [MatrixKitL10n incomingVideoCall];
+ }
+ else
+ {
+ callStatusLabel.text = [MatrixKitL10n incomingVoiceCall];
+ }
+ // Update bottom bar
+ endCallButton.hidden = YES;
+ rejectCallButton.hidden = NO;
+ answerCallButton.hidden = NO;
+
+ // Try to show a special view for incoming view
+ [self configureIncomingCallViewIfRequiredWith:call];
+
+ break;
+ case MXCallStateConnecting:
+ self.isRinging = NO;
+
+ // User has accepted the call and we can remove incomingCallView
+ if (self.incomingCallView)
+ {
+ [UIView transitionWithView:self.view
+ duration:0.33
+ options:UIViewAnimationOptionTransitionCrossDissolve | UIViewAnimationOptionCurveEaseOut
+ animations:^{
+ [self.incomingCallView removeFromSuperview];
+ }
+ completion:^(BOOL finished) {
+ self.incomingCallView = nil;
+ }];
+ }
+
+ break;
+ case MXCallStateConnected:
+ self.isRinging = NO;
+ [self updateTimeStatusLabel];
+
+ if (call.isVideoCall)
+ {
+ self.callerImageView.hidden = YES;
+
+ if (call.isConferenceCall)
+ {
+ // Do not show self view anymore because it is returned by the conference bridge
+ self.localPreviewContainerView.hidden = YES;
+
+ // Well, hide does not work. So, shrink the view to nil
+ self.localPreviewContainerView.frame = CGRectZero;
+ }
+ }
+ audioMuteButton.enabled = YES;
+ videoMuteButton.enabled = YES;
+ speakerButton.enabled = YES;
+ cameraSwitchButton.enabled = YES;
+ if (call.isConsulting)
+ {
+ _transferButton.hidden = NO;
+ }
+
+ break;
+ case MXCallStateOnHold:
+ callStatusLabel.text = [MatrixKitL10n callHolded];
+
+ break;
+ case MXCallStateRemotelyOnHold:
+ audioMuteButton.enabled = NO;
+ videoMuteButton.enabled = NO;
+ speakerButton.enabled = NO;
+ cameraSwitchButton.enabled = NO;
+ self.moreButton.enabled = NO;
+ callStatusLabel.text = [MatrixKitL10n callRemoteHolded:peerDisplayName];
+
+ break;
+ case MXCallStateInviteExpired:
+ // MXCallStateInviteExpired state is sent as an notification
+ // MXCall will move quickly to the MXCallStateEnded state
+ self.isRinging = NO;
+ callStatusLabel.text = [MatrixKitL10n callInviteExpired];
+
+ break;
+ case MXCallStateEnded:
+ {
+ self.isRinging = NO;
+ callStatusLabel.text = [MatrixKitL10n callEnded];
+
+ NSString *soundName = [self soundNameForCallEnding];
+ if (soundName)
+ {
+ NSURL *audioUrl = [self audioURLWithName:soundName];
+ [[MXKSoundPlayer sharedInstance] playSoundAt:audioUrl repeat:NO vibrate:NO routeToBuiltInReceiver:YES];
+ }
+ else
+ {
+ [[MXKSoundPlayer sharedInstance] stopPlayingWithAudioSessionDeactivation:YES];
+ }
+
+ // Except in case of call error, quit the screen right now
+ if (!errorAlert)
+ {
+ [self dismiss];
+ }
+
+ break;
+ }
+ default:
+ break;
+ }
+
+ [self updateProximityAndSleep];
+}
+
+- (void)call:(MXCall *)call didEncounterError:(NSError *)error reason:(MXCallHangupReason)reason
+{
+ MXLogDebug(@"[MXKCallViewController] didEncounterError. mxCall.state: %tu. Stop call due to error: %@", mxCall.state, error);
+
+ if (mxCall.state != MXCallStateEnded)
+ {
+ // Popup the error to the user
+ NSString *title = [error.userInfo valueForKey:NSLocalizedFailureReasonErrorKey];
+ if (!title)
+ {
+ title = [MatrixKitL10n error];
+ }
+ NSString *msg = [error.userInfo valueForKey:NSLocalizedDescriptionKey];
+ if (!msg)
+ {
+ msg = [MatrixKitL10n errorCommonMessage];
+ }
+
+ MXWeakify(self);
+ errorAlert = [UIAlertController alertControllerWithTitle:title message:msg preferredStyle:UIAlertControllerStyleAlert];
+
+ [errorAlert addAction:[UIAlertAction actionWithTitle:[MatrixKitL10n ok]
+ style:UIAlertActionStyleDefault
+ handler:^(UIAlertAction * action) {
+
+ MXStrongifyAndReturnIfNil(self);
+ self->errorAlert = nil;
+ [self dismiss];
+
+ }]];
+
+ [self presentViewController:errorAlert animated:YES completion:nil];
+
+ // And interrupt the call
+ [mxCall hangupWithReason:reason];
+ }
+}
+
+- (void)callConsultingStatusDidChange:(MXCall *)call
+{
+ [self updatePeerInfoDisplay];
+
+ if (call.isConsulting)
+ {
+ NSString *title = [MatrixKitL10n callTransferToUser:call.transferee.displayname];
+ [_transferButton setTitle:title forState:UIControlStateNormal];
+ _transferButton.hidden = call.state != MXCallStateConnected;
+ }
+ else
+ {
+ _transferButton.hidden = YES;
+ }
+}
+
+- (void)callAssertedIdentityDidChange:(MXCall *)call
+{
+ MXAssertedIdentityModel *assertedIdentity = call.assertedIdentity;
+
+ if (assertedIdentity)
+ {
+ // update caller display name and avatar with the asserted identity
+ NSString *peerAvatarURL = assertedIdentity.avatarUrl;
+
+ if (assertedIdentity.displayname)
+ {
+ peerDisplayName = assertedIdentity.displayname;
+ }
+ else if (assertedIdentity.userId)
+ {
+ peerDisplayName = assertedIdentity.userId;
+ }
+
+ if (mxCall.isVideoCall)
+ {
+ callerNameLabel.text = [MatrixKitL10n callVideoWithUser:peerDisplayName];
+ }
+ else
+ {
+ callerNameLabel.text = [MatrixKitL10n callVoiceWithUser:peerDisplayName];
+ }
+
+ if (peerAvatarURL)
+ {
+ // Suppose avatar url is a matrix content uri, we use SDK to get the well adapted thumbnail from server
+ callerImageView.mediaFolder = kMXMediaManagerAvatarThumbnailFolder;
+ callerImageView.enableInMemoryCache = YES;
+ [callerImageView setImageURI:peerAvatarURL
+ withType:nil
+ andImageOrientation:UIImageOrientationUp
+ toFitViewSize:callerImageView.frame.size
+ withMethod:MXThumbnailingMethodCrop
+ previewImage:self.picturePlaceholder
+ mediaManager:self.mainSession.mediaManager];
+ }
+ else
+ {
+ callerImageView.image = self.picturePlaceholder;
+ }
+
+ [updateStatusTimer fire];
+ }
+ else
+ {
+ // go back to the original display name and avatar
+ [self updatePeerInfoDisplay];
+ }
+}
+
+- (void)callAudioOutputRouteTypeDidChange:(MXCall *)call
+{
+ [self configureSpeakerButton];
+}
+
+- (void)callAvailableAudioOutputsDidChange:(MXCall *)call
+{
+
+}
+
+#pragma mark - Internal
+
+- (void)removeObservers
+{
+ if (roomDidFlushDataNotificationObserver)
+ {
+ [[NSNotificationCenter defaultCenter] removeObserver:roomDidFlushDataNotificationObserver];
+ roomDidFlushDataNotificationObserver = nil;
+ }
+
+ if (audioSessionRouteChangeNotificationObserver)
+ {
+ [[NSNotificationCenter defaultCenter] removeObserver:audioSessionRouteChangeNotificationObserver];
+ audioSessionRouteChangeNotificationObserver = nil;
+ }
+
+ [[NSNotificationCenter defaultCenter] removeObserver:self];
+
+ if (roomListener && mxCall.room)
+ {
+ MXWeakify(self);
+ [mxCall.room liveTimeline:^(MXEventTimeline *liveTimeline) {
+ MXStrongifyAndReturnIfNil(self);
+
+ [liveTimeline removeListener:self->roomListener];
+ self->roomListener = nil;
+ }];
+ }
+}
+
+- (void)callRoomStateDidChange:(dispatch_block_t)onComplete
+{
+ // Handle peer here
+ if (mxCall.isIncoming)
+ {
+ self.peer = [mxCall.room.mxSession getOrCreateUser:mxCall.callerId];
+ if (onComplete)
+ {
+ onComplete();
+ }
+ }
+ else
+ {
+ // For 1:1 call, find the other peer
+ // Else, the room information will be used to display information about the call
+ if (!mxCall.isConferenceCall)
+ {
+ MXWeakify(self);
+ [mxCall.room state:^(MXRoomState *roomState) {
+ MXStrongifyAndReturnIfNil(self);
+
+ MXUser *theMember = nil;
+ NSArray *members = roomState.members.joinedMembers;
+ for (MXUser *member in members)
+ {
+ if (![member.userId isEqualToString:self->mxCall.callerId])
+ {
+ theMember = member;
+ break;
+ }
+ }
+
+ self.peer = theMember;
+ if (onComplete)
+ {
+ onComplete();
+ }
+ }];
+ }
+ else
+ {
+ self.peer = nil;
+ if (onComplete)
+ {
+ onComplete();
+ }
+ }
+ }
+}
+
+- (BOOL)isBuiltInReceiverAudioOuput
+{
+#if TARGET_IPHONE_SIMULATOR
+ return YES;
+#endif
+ BOOL isBuiltInReceiverUsed = NO;
+
+ // Check whether the audio output is the built-in receiver
+ AVAudioSessionRouteDescription *audioRoute = [[AVAudioSession sharedInstance] currentRoute];
+ if (audioRoute.outputs.count)
+ {
+ // TODO: handle the case where multiple outputs are returned
+ AVAudioSessionPortDescription *audioOutputs = audioRoute.outputs.firstObject;
+ isBuiltInReceiverUsed = ([audioOutputs.portType isEqualToString:AVAudioSessionPortBuiltInReceiver]);
+ }
+
+ return isBuiltInReceiverUsed;
+}
+
+- (NSString *)soundNameForCallEnding
+{
+ if (mxCall.endReason == MXCallEndReasonUnknown)
+ return nil;
+
+ if (mxCall.isEstablished)
+ return @"callend";
+
+ if (mxCall.endReason == MXCallEndReasonBusy || (!mxCall.isIncoming && mxCall.endReason == MXCallEndReasonMissed))
+ return @"busy";
+
+ return nil;
+}
+
+- (void)handleAudioSessionActivationNotification
+{
+ // It's only relevant for outgoing calls which aren't in connected state
+ if (self.mxCall.state >= MXCallStateCreateOffer && self.mxCall.state != MXCallStateConnected && self.mxCall.state != MXCallStateEnded)
+ {
+ self.isRinging = YES;
+ }
+}
+
+#pragma mark - UI methods
+
+- (void)configureSpeakerButton
+{
+ switch (mxCall.audioOutputRouter.currentRoute.routeType)
+ {
+ case MXiOSAudioOutputRouteTypeBuiltIn:
+ self.speakerButton.selected = NO;
+ break;
+ case MXiOSAudioOutputRouteTypeLoudSpeakers:
+ case MXiOSAudioOutputRouteTypeExternalWired:
+ case MXiOSAudioOutputRouteTypeExternalBluetooth:
+ case MXiOSAudioOutputRouteTypeExternalCar:
+ self.speakerButton.selected = YES;
+ break;
+ }
+}
+
+- (void)configureIncomingCallViewIfRequiredWith:(MXCall *)call
+{
+ if (call.isIncoming && !self.incomingCallView)
+ {
+ UIView *incomingCallView = [self createIncomingCallView];
+ if (incomingCallView)
+ {
+ self.incomingCallView = incomingCallView;
+ [self.view addSubview:incomingCallView];
+
+ incomingCallView.translatesAutoresizingMaskIntoConstraints = NO;
+ [incomingCallView.topAnchor constraintEqualToAnchor:self.view.topAnchor constant:0].active = YES;
+ [incomingCallView.leadingAnchor constraintEqualToAnchor:self.view.leadingAnchor constant:0].active = YES;
+ [incomingCallView.bottomAnchor constraintEqualToAnchor:self.view.bottomAnchor constant:0].active = YES;
+ [incomingCallView.trailingAnchor constraintEqualToAnchor:self.view.trailingAnchor constant:0].active = YES;
+ }
+ }
+}
+
+- (void)updateLocalPreviewLayout
+{
+ // On IOS 8 and later, the screen size is oriented.
+ CGRect bounds = [[UIScreen mainScreen] bounds];
+ BOOL isLandscapeOriented = (bounds.size.width > bounds.size.height);
+
+ CGFloat maxPreviewFrameSize, minPreviewFrameSize;
+
+ if (_localPreviewContainerViewWidthConstraint.constant < _localPreviewContainerViewHeightConstraint.constant)
+ {
+ maxPreviewFrameSize = _localPreviewContainerViewHeightConstraint.constant;
+ minPreviewFrameSize = _localPreviewContainerViewWidthConstraint.constant;
+ }
+ else
+ {
+ minPreviewFrameSize = _localPreviewContainerViewHeightConstraint.constant;
+ maxPreviewFrameSize = _localPreviewContainerViewWidthConstraint.constant;
+ }
+
+ if (isLandscapeOriented)
+ {
+ _localPreviewContainerViewHeightConstraint.constant = minPreviewFrameSize;
+ _localPreviewContainerViewWidthConstraint.constant = maxPreviewFrameSize;
+ }
+ else
+ {
+ _localPreviewContainerViewHeightConstraint.constant = maxPreviewFrameSize;
+ _localPreviewContainerViewWidthConstraint.constant = minPreviewFrameSize;
+ }
+
+ CGPoint previewOrigin = self.localPreviewContainerView.frame.origin;
+
+ if (previewOrigin.x != (bounds.size.width - _localPreviewContainerViewWidthConstraint.constant - kLocalPreviewMargin))
+ {
+ CGFloat posX = (bounds.size.width - _localPreviewContainerViewWidthConstraint.constant - kLocalPreviewMargin);
+ _localPreviewContainerViewLeadingConstraint.constant = posX;
+ }
+
+ if (previewOrigin.y != kLocalPreviewMargin)
+ {
+ CGFloat posY = (bounds.size.height - _localPreviewContainerViewHeightConstraint.constant - kLocalPreviewMargin);
+ _localPreviewContainerViewTopConstraint.constant = posY;
+ }
+}
+
+- (void)showOverlayContainer:(BOOL)isShown
+{
+ if (mxCall && !mxCall.isVideoCall) isShown = YES;
+ if (mxCall.state != MXCallStateConnected) isShown = YES;
+
+ if (isShown)
+ {
+ overlayContainerView.hidden = NO;
+ if (mxCall && mxCall.isVideoCall)
+ {
+ [hideOverlayTimer invalidate];
+ hideOverlayTimer = [NSTimer scheduledTimerWithTimeInterval:5.0 target:self selector:@selector(hideOverlay:) userInfo:nil repeats:NO];
+ }
+ }
+ else
+ {
+ overlayContainerView.hidden = YES;
+ }
+}
+
+- (void)toggleOverlay
+{
+ [self showOverlayContainer:overlayContainerView.isHidden];
+}
+
+- (void)hideOverlay:(NSTimer*)theTimer
+{
+ [self showOverlayContainer:NO];
+ hideOverlayTimer = nil;
+}
+
+- (void)updateTimeStatusLabel
+{
+ if (mxCall.state == MXCallStateConnected)
+ {
+ NSUInteger duration = mxCall.duration / 1000;
+ NSUInteger secs = duration % 60;
+ NSUInteger mins = (duration - secs) / 60;
+ callStatusLabel.text = [NSString stringWithFormat:@"%02tu:%02tu", mins, secs];
+ }
+}
+
+- (void)updateProximityAndSleep
+{
+ BOOL inCall = (mxCall.state == MXCallStateConnected || mxCall.state == MXCallStateRinging || mxCall.state == MXCallStateInviteSent || mxCall.state == MXCallStateConnecting || mxCall.state == MXCallStateCreateOffer || mxCall.state == MXCallStateCreateAnswer);
+
+ if (inCall)
+ {
+ BOOL isBuiltInReceiverUsed = self.isBuiltInReceiverAudioOuput;
+
+ // Enable the proximity monitoring when the built in receiver is used as the audio output.
+ BOOL enableProxMonitoring = isBuiltInReceiverUsed;
+ [[UIDevice currentDevice] setProximityMonitoringEnabled:enableProxMonitoring];
+
+ // Disable the idle timer during a video call, or during a voice call which is performed with the built-in receiver.
+ // Note: if the device is locked, VoIP calling get dropped if an incoming GSM call is received.
+ BOOL disableIdleTimer = mxCall.isVideoCall || isBuiltInReceiverUsed;
+
+ UIApplication *sharedApplication = [UIApplication performSelector:@selector(sharedApplication)];
+ if (sharedApplication)
+ {
+ sharedApplication.idleTimerDisabled = disableIdleTimer;
+ }
+ }
+}
+
+- (UIView *)createIncomingCallView
+{
+ return nil;
+}
+
+#pragma mark - UIResponder Touch Events
+
+- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
+{
+ UITouch *touch = [touches anyObject];
+ CGPoint point = [touch locationInView:self.view];
+ if ((!self.localPreviewContainerView.hidden) && CGRectContainsPoint(self.localPreviewContainerView.frame, point))
+ {
+ // Starting to move the local preview view
+ if (mxCallOnHold)
+ {
+ // if there is a call on hold, do not move local preview for now
+ // TODO: Instead of wholly avoiding mobility of local preview, just avoid the on hold call's corner here
+ return;
+ }
+ isSelectingLocalPreview = YES;
+ }
+}
+
+- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event
+{
+ isMovingLocalPreview = NO;
+ isSelectingLocalPreview = NO;
+}
+
+- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
+{
+ if (isMovingLocalPreview)
+ {
+ UITouch *touch = [touches anyObject];
+ CGPoint point = [touch locationInView:self.view];
+
+ CGRect bounds = self.view.bounds;
+ CGFloat midX = bounds.size.width / 2.0;
+ CGFloat midY = bounds.size.height / 2.0;
+
+ CGFloat posX = (point.x < midX) ? 20.0 : (bounds.size.width - _localPreviewContainerViewWidthConstraint.constant - 20.0);
+ CGFloat posY = (point.y < midY) ? 20.0 : (bounds.size.height - _localPreviewContainerViewHeightConstraint.constant - 20.0);
+
+ _localPreviewContainerViewLeadingConstraint.constant = posX;
+ _localPreviewContainerViewTopConstraint.constant = posY;
+
+ [self.view setNeedsUpdateConstraints];
+ }
+ else
+ {
+ [self toggleOverlay];
+ }
+ isMovingLocalPreview = NO;
+ isSelectingLocalPreview = NO;
+}
+
+- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
+{
+ UITouch *touch = [touches anyObject];
+ CGPoint point = [touch locationInView:self.view];
+
+ if (isSelectingLocalPreview)
+ {
+ isMovingLocalPreview = YES;
+ self.localPreviewContainerView.center = point;
+ }
+}
+
+#pragma mark - UIDeviceOrientationDidChangeNotification
+
+- (void)deviceOrientationDidChange
+{
+ [self applyDeviceOrientation:NO];
+
+ [self showOverlayContainer:YES];
+}
+
+- (void)applyDeviceOrientation:(BOOL)forcePortrait
+{
+ if (mxCall)
+ {
+ UIDeviceOrientation deviceOrientation = [[UIDevice currentDevice] orientation];
+
+ // Set the camera orientation according to the orientation supported by the app
+ if (UIDeviceOrientationPortrait == deviceOrientation || UIDeviceOrientationLandscapeLeft == deviceOrientation || UIDeviceOrientationLandscapeRight == deviceOrientation)
+ {
+ mxCall.selfOrientation = deviceOrientation;
+ [self updateLocalPreviewLayout];
+ }
+ else if (forcePortrait)
+ {
+ mxCall.selfOrientation = UIDeviceOrientationPortrait;
+ [self updateLocalPreviewLayout];
+ }
+ }
+}
+
+@end
diff --git a/Riot/Modules/MatrixKit/Controllers/MXKCallViewController.xib b/Riot/Modules/MatrixKit/Controllers/MXKCallViewController.xib
new file mode 100644
index 000000000..52d4fc0ed
--- /dev/null
+++ b/Riot/Modules/MatrixKit/Controllers/MXKCallViewController.xib
@@ -0,0 +1,423 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Riot/Modules/MatrixKit/Controllers/MXKContactDetailsViewController.h b/Riot/Modules/MatrixKit/Controllers/MXKContactDetailsViewController.h
new file mode 100644
index 000000000..417f586a7
--- /dev/null
+++ b/Riot/Modules/MatrixKit/Controllers/MXKContactDetailsViewController.h
@@ -0,0 +1,90 @@
+/*
+ Copyright 2015 OpenMarket 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 "MXKTableViewController.h"
+
+#import "MXKContact.h"
+
+@class MXKContactDetailsViewController;
+
+/**
+ `MXKContactDetailsViewController` delegate.
+ */
+@protocol MXKContactDetailsViewControllerDelegate
+
+/**
+ Tells the delegate that the user wants to start chat with the contact by using the selected matrix id.
+
+ @param contactDetailsViewController the `MXKContactDetailsViewController` instance.
+ @param matrixId the selected matrix id of the contact.
+ @param completion the block to execute at the end of the operation (independently if it succeeded or not).
+ */
+- (void)contactDetailsViewController:(MXKContactDetailsViewController *)contactDetailsViewController startChatWithMatrixId:(NSString*)matrixId completion:(void (^)(void))completion;
+
+@end
+
+@interface MXKContactDetailsViewController : MXKTableViewController
+
+@property (weak, nonatomic) IBOutlet UIButton *contactThumbnail;
+@property (weak, nonatomic) IBOutlet UITextView *contactDisplayName;
+
+/**
+ The default account picture displayed when no picture is defined.
+ */
+@property (nonatomic) UIImage *picturePlaceholder;
+
+/**
+ The displayed contact
+ */
+@property (strong, nonatomic) MXKContact* contact;
+
+/**
+ The delegate for the view controller.
+ */
+@property (nonatomic, weak) id delegate;
+
+#pragma mark - Class methods
+
+/**
+ Returns the `UINib` object initialized for a `MXKContactDetailsViewController`.
+
+ @return The initialized `UINib` object or `nil` if there were errors during initialization
+ or the nib file could not be located.
+
+ @discussion You may override this method to provide a customized nib. If you do,
+ you should also override `contactDetailsViewController` to return your
+ view controller loaded from your custom nib.
+ */
++ (UINib *)nib;
+
+/**
+ Creates and returns a new `MXKContactDetailsViewController` object.
+
+ @discussion This is the designated initializer for programmatic instantiation.
+ @return An initialized `MXKContactDetailsViewController` object if successful, `nil` otherwise.
+ */
++ (instancetype)contactDetailsViewController;
+
+/**
+ The contact's thumbnail is displayed inside a button. The following action is registered on
+ `UIControlEventTouchUpInside` event of this button.
+ */
+- (IBAction)onContactThumbnailPressed:(id)sender;
+
+@end
+
diff --git a/Riot/Modules/MatrixKit/Controllers/MXKContactDetailsViewController.m b/Riot/Modules/MatrixKit/Controllers/MXKContactDetailsViewController.m
new file mode 100644
index 000000000..6f181b059
--- /dev/null
+++ b/Riot/Modules/MatrixKit/Controllers/MXKContactDetailsViewController.m
@@ -0,0 +1,207 @@
+/*
+ Copyright 2015 OpenMarket 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 "MXKContactDetailsViewController.h"
+
+#import "MXKTableViewCellWithLabelAndButton.h"
+
+#import "NSBundle+MatrixKit.h"
+#import "MXKSwiftHeader.h"
+
+@interface MXKContactDetailsViewController ()
+{
+ NSArray* matrixIDs;
+}
+
+@end
+
+@implementation MXKContactDetailsViewController
+
++ (UINib *)nib
+{
+ return [UINib nibWithNibName:NSStringFromClass([MXKContactDetailsViewController class])
+ bundle:[NSBundle bundleForClass:[MXKContactDetailsViewController class]]];
+}
+
++ (instancetype)contactDetailsViewController
+{
+ return [[[self class] alloc] initWithNibName:NSStringFromClass([MXKContactDetailsViewController class])
+ bundle:[NSBundle bundleForClass:[MXKContactDetailsViewController class]]];
+}
+
+- (void)finalizeInit
+{
+ [super finalizeInit];
+}
+
+- (void)viewDidLoad
+{
+ [super viewDidLoad];
+
+ // Check whether the view controller has been pushed via storyboard
+ if (!_contactThumbnail)
+ {
+ // Instantiate view controller objects
+ [[[self class] nib] instantiateWithOwner:self options:nil];
+ }
+
+ [self updatePictureButton:self.picturePlaceholder];
+}
+
+- (void)viewWillAppear:(BOOL)animated
+{
+ [super viewWillAppear:animated];
+
+ [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(onThumbnailUpdate:) name:kMXKContactThumbnailUpdateNotification object:nil];
+
+ // Force refresh
+ self.contact = _contact;
+}
+
+- (void)viewWillDisappear:(BOOL)animated
+{
+ [super viewWillDisappear:animated];
+
+ [[NSNotificationCenter defaultCenter] removeObserver:self];
+}
+
+- (void)destroy
+{
+ matrixIDs = nil;
+
+ self.delegate = nil;
+
+ [super destroy];
+}
+
+#pragma mark -
+
+- (void)setContact:(MXKContact *)contact
+{
+ _contact = contact;
+
+ self.contactDisplayName.text = _contact.displayName;
+
+ // set the thumbnail info
+ [self.contactThumbnail.imageView setContentMode: UIViewContentModeScaleAspectFill];
+ [self.contactThumbnail.imageView setClipsToBounds:YES];
+
+ if (_contact.thumbnail)
+ {
+ [self updatePictureButton:_contact.thumbnail];
+ }
+ else
+ {
+ [self updatePictureButton:self.picturePlaceholder];
+ }
+}
+
+- (UIImage*)picturePlaceholder
+{
+ return [NSBundle mxk_imageFromMXKAssetsBundleWithName:@"default-profile"];
+}
+
+- (IBAction)onContactThumbnailPressed:(id)sender
+{
+ // Do nothing by default
+}
+
+#pragma mark - UITableView datasource
+
+- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
+{
+ return 1;
+}
+
+- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
+{
+ matrixIDs = _contact.matrixIdentifiers;
+ return matrixIDs.count;
+}
+
+- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
+{
+ NSInteger row = indexPath.row;
+
+ MXKTableViewCellWithLabelAndButton *cell = [tableView dequeueReusableCellWithIdentifier:[MXKTableViewCellWithLabelAndButton defaultReuseIdentifier]];
+ if (!cell)
+ {
+ cell = [[MXKTableViewCellWithLabelAndButton alloc] init];
+ }
+
+ if (row < matrixIDs.count)
+ {
+ cell.mxkLabel.text = [matrixIDs objectAtIndex:row];
+ }
+ else
+ {
+ // should never happen
+ cell.mxkLabel.text = @"";
+ }
+
+ [cell.mxkButton setTitle:[MatrixKitL10n startChat] forState:UIControlStateNormal];
+ [cell.mxkButton setTitle:[MatrixKitL10n startChat] forState:UIControlStateHighlighted];
+ cell.mxkButton.tag = row;
+ [cell.mxkButton addTarget:self action:@selector(startChat:) forControlEvents:UIControlEventTouchUpInside];
+
+ return cell;
+}
+
+#pragma mark - Internals
+
+- (void)updatePictureButton:(UIImage*)image
+{
+ [self.contactThumbnail setImage:image forState:UIControlStateNormal];
+ [self.contactThumbnail setImage:image forState:UIControlStateHighlighted];
+ [self.contactThumbnail setImage:image forState:UIControlStateDisabled];
+}
+
+- (void)startChat:(UIButton*)sender
+{
+ if (self.delegate && sender.tag < matrixIDs.count)
+ {
+ sender.enabled = NO;
+
+ [self.delegate contactDetailsViewController:self startChatWithMatrixId:[matrixIDs objectAtIndex:sender.tag] completion:^{
+
+ sender.enabled = YES;
+
+ }];
+ }
+}
+
+- (void)onThumbnailUpdate:(NSNotification *)notif
+{
+ // sanity check
+ if ([notif.object isKindOfClass:[NSString class]])
+ {
+ NSString* contactID = notif.object;
+
+ if ([contactID isEqualToString:self.contact.contactID])
+ {
+ if (_contact.thumbnail)
+ {
+ [self updatePictureButton:_contact.thumbnail];
+ }
+ else
+ {
+ [self updatePictureButton:self.picturePlaceholder];
+ }
+ }
+ }
+}
+
+@end
diff --git a/Riot/Modules/MatrixKit/Controllers/MXKContactDetailsViewController.xib b/Riot/Modules/MatrixKit/Controllers/MXKContactDetailsViewController.xib
new file mode 100644
index 000000000..d202e2856
--- /dev/null
+++ b/Riot/Modules/MatrixKit/Controllers/MXKContactDetailsViewController.xib
@@ -0,0 +1,65 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Lorem ipsum dolor sit er elit lamet, consectetaur cillium adipisicing pecu, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Nam liber te conscient to factor tum poen legum odioque civiuda.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Riot/Modules/MatrixKit/Controllers/MXKContactListViewController.h b/Riot/Modules/MatrixKit/Controllers/MXKContactListViewController.h
new file mode 100644
index 000000000..e516aa15d
--- /dev/null
+++ b/Riot/Modules/MatrixKit/Controllers/MXKContactListViewController.h
@@ -0,0 +1,122 @@
+/*
+ Copyright 2015 OpenMarket 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 "MXKTableViewController.h"
+
+#import "MXKContactManager.h"
+#import "MXKContact.h"
+#import "MXKContactTableCell.h"
+
+@class MXKContactListViewController;
+
+/**
+ `MXKContactListViewController` delegate.
+ */
+@protocol MXKContactListViewControllerDelegate
+
+/**
+ Tells the delegate that the user selected a contact.
+
+ @param contactListViewController the `MXKContactListViewController` instance.
+ @param contactId the id of the selected contact.
+ */
+- (void)contactListViewController:(MXKContactListViewController *)contactListViewController didSelectContact:(NSString*)contactId;
+
+/**
+ Tells the delegate that the user tapped a contact thumbnail.
+
+ @param contactListViewController the `MXKContactListViewController` instance.
+ @param contactId the id of the tapped contact.
+ */
+- (void)contactListViewController:(MXKContactListViewController *)contactListViewController didTapContactThumbnail:(NSString*)contactId;
+
+@end
+
+/**
+ 'MXKContactListViewController' instance displays constact list.
+ This view controller support multi sessions by collecting all matrix users (only one occurrence is kept by user).
+ */
+@interface MXKContactListViewController : MXKTableViewController
+
+/**
+ The segmented control used to handle separatly matrix users and local contacts.
+ User's actions are handled by [MXKContactListViewController onSegmentValueChange:].
+ */
+@property (weak, nonatomic) IBOutlet UISegmentedControl* contactsControls;
+
+/**
+ The delegate for the view controller.
+ */
+@property (nonatomic, weak) id delegate;
+
+/**
+ Enable the search option by adding a navigation item in the navigation bar (YES by default).
+ Set NO this property to disable this option and hide the related bar button.
+ */
+@property (nonatomic) BOOL enableBarButtonSearch;
+
+/**
+ Tell whether an action is already in progress.
+ */
+@property (nonatomic, readonly) BOOL hasPendingAction;
+
+/**
+ The class used in creating new contact table cells.
+ Only MXKContactTableCell classes or sub-classes are accepted.
+ */
+@property (nonatomic) Class contactTableViewCellClass;
+
+#pragma mark - Class methods
+
+/**
+ Returns the `UINib` object initialized for a `MXKContactListViewController`.
+
+ @return The initialized `UINib` object or `nil` if there were errors during initialization
+ or the nib file could not be located.
+
+ @discussion You may override this method to provide a customized nib. If you do,
+ you should also override `contactListViewController` to return your
+ view controller loaded from your custom nib.
+ */
++ (UINib *)nib;
+
+/**
+ Creates and returns a new `MXKContactListViewController` object.
+
+ @discussion This is the designated initializer for programmatic instantiation.
+ @return An initialized `MXKContactListViewController` object if successful, `nil` otherwise.
+ */
++ (instancetype)contactListViewController;
+
+/**
+ The action registered on 'value changed' event of the 'UISegmentedControl' contactControls.
+ */
+- (IBAction)onSegmentValueChange:(id)sender;
+
+/**
+ Add a mask in overlay to prevent a new contact selection (used when an action is on progress).
+ */
+- (void)addPendingActionMask;
+
+/**
+ Remove the potential overlay mask
+ */
+- (void)removePendingActionMask;
+
+@end
+
diff --git a/Riot/Modules/MatrixKit/Controllers/MXKContactListViewController.m b/Riot/Modules/MatrixKit/Controllers/MXKContactListViewController.m
new file mode 100644
index 000000000..6bcb30b42
--- /dev/null
+++ b/Riot/Modules/MatrixKit/Controllers/MXKContactListViewController.m
@@ -0,0 +1,663 @@
+/*
+ Copyright 2015 OpenMarket 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 "MXKContactListViewController.h"
+
+#import "MXKSectionedContacts.h"
+
+#import "NSBundle+MatrixKit.h"
+
+#import "MXKSwiftHeader.h"
+
+@interface MXKContactListViewController ()
+{
+ // YES -> only matrix users
+ // NO -> display local contacts
+ BOOL displayMatrixUsers;
+
+ // screenshot of the local contacts
+ NSArray* localContactsArray;
+ MXKSectionedContacts* sectionedLocalContacts;
+
+ // screenshot of the matrix users
+ NSArray* matrixContactsArray;
+ MXKSectionedContacts* sectionedMatrixContacts;
+
+ // Search
+ UIBarButtonItem *searchButton;
+ UISearchBar *contactsSearchBar;
+ NSMutableArray *filteredContacts;
+ MXKSectionedContacts* sectionedFilteredContacts;
+ BOOL searchBarShouldEndEditing;
+ BOOL ignoreSearchRequest;
+ NSString* latestSearchedPattern;
+
+ NSArray* collationTitles;
+
+ // mask view while processing a request
+ UIActivityIndicatorView * pendingMaskSpinnerView;
+}
+
+@end
+
+@implementation MXKContactListViewController
+
+#pragma mark - Class methods
+
++ (UINib *)nib
+{
+ return [UINib nibWithNibName:NSStringFromClass([MXKContactListViewController class])
+ bundle:[NSBundle bundleForClass:[MXKContactListViewController class]]];
+}
+
++ (instancetype)contactListViewController
+{
+ return [[[self class] alloc] initWithNibName:NSStringFromClass([MXKContactListViewController class])
+ bundle:[NSBundle bundleForClass:[MXKContactListViewController class]]];
+}
+
+- (void)finalizeInit
+{
+ [super finalizeInit];
+
+ _enableBarButtonSearch = YES;
+
+ // get the system collation titles
+ collationTitles = [[UILocalizedIndexedCollation currentCollation] sectionTitles];
+}
+
+- (void)dealloc
+{
+ searchButton = nil;
+}
+
+- (void)destroy
+{
+ [self removePendingActionMask];
+
+ [super destroy];
+}
+
+- (void)viewDidLoad
+{
+ [super viewDidLoad];
+
+ // Check whether the view controller has been pushed via storyboard
+ if (!_contactsControls)
+ {
+ // Instantiate view controller objects
+ [[[self class] nib] instantiateWithOwner:self options:nil];
+ }
+
+ // global init
+ displayMatrixUsers = (0 == self.contactsControls.selectedSegmentIndex);
+
+ [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(onContactsRefresh:) name:kMXKContactManagerDidUpdateMatrixContactsNotification object:nil];
+
+ [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(onContactsRefresh:) name:kMXKContactManagerDidUpdateLocalContactsNotification object:nil];
+ [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(onContactsRefresh:) name:kMXKContactManagerDidUpdateLocalContactMatrixIDsNotification object:nil];
+
+ if (!_contactTableViewCellClass)
+ {
+ // Set default table view cell class
+ self.contactTableViewCellClass = [MXKContactTableCell class];
+ }
+
+ // Localize string
+ [_contactsControls setTitle:[MatrixKitL10n contactMxUsers] forSegmentAtIndex:0];
+ [_contactsControls setTitle:[MatrixKitL10n contactLocalContacts] forSegmentAtIndex:1];
+
+ // Apply search option in navigation bar
+ self.enableBarButtonSearch = _enableBarButtonSearch;
+}
+
+- (void)viewWillAppear:(BOOL)animated
+{
+ [super viewWillAppear:animated];
+
+ // Restore search mechanism (if enabled)
+ ignoreSearchRequest = NO;
+}
+
+- (void)viewWillDisappear:(BOOL)animated
+{
+ [super viewWillDisappear:animated];
+
+ // The user may still press search button whereas the view disappears
+ ignoreSearchRequest = YES;
+
+ // Leave potential search session
+ if (contactsSearchBar)
+ {
+ [self searchBarCancelButtonClicked:contactsSearchBar];
+ }
+}
+
+- (void)scrollToTop
+{
+ // stop any scrolling effect
+ [UIView setAnimationsEnabled:NO];
+ // before scrolling to the tableview top
+ self.tableView.contentOffset = CGPointMake(-self.tableView.adjustedContentInset.left, -self.tableView.adjustedContentInset.top);
+ [UIView setAnimationsEnabled:YES];
+}
+
+#pragma mark -
+
+-(void)setContactTableViewCellClass:(Class)contactTableViewCellClass
+{
+ // Sanity check: accept only MXKContactTableCell classes or sub-classes
+ NSParameterAssert([contactTableViewCellClass isSubclassOfClass:MXKContactTableCell.class]);
+
+ _contactTableViewCellClass = contactTableViewCellClass;
+ [self.tableView registerClass:contactTableViewCellClass forCellReuseIdentifier:[contactTableViewCellClass defaultReuseIdentifier]];
+}
+
+- (void)setEnableBarButtonSearch:(BOOL)enableBarButtonSearch
+{
+ _enableBarButtonSearch = enableBarButtonSearch;
+
+ if (enableBarButtonSearch)
+ {
+ if (!searchButton)
+ {
+ searchButton = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemSearch target:self action:@selector(search:)];
+ }
+
+ // Add it in right bar items
+ NSArray *rightBarButtonItems = self.navigationItem.rightBarButtonItems;
+ self.navigationItem.rightBarButtonItems = rightBarButtonItems ? [rightBarButtonItems arrayByAddingObject:searchButton] : @[searchButton];
+ }
+ else
+ {
+ NSMutableArray *rightBarButtonItems = [NSMutableArray arrayWithArray: self.navigationItem.rightBarButtonItems];
+ [rightBarButtonItems removeObject:searchButton];
+ self.navigationItem.rightBarButtonItems = rightBarButtonItems;
+ }
+}
+
+#pragma mark - Internals
+
+- (void)updateSectionedLocalContacts:(BOOL)force
+{
+ [self stopActivityIndicator];
+
+ MXKContactManager* sharedManager = [MXKContactManager sharedManager];
+
+ if (force || !localContactsArray)
+ {
+ localContactsArray = sharedManager.localContacts;
+ sectionedLocalContacts = [sharedManager getSectionedContacts:localContactsArray];
+ }
+}
+
+- (void)updateSectionedMatrixContacts:(BOOL)force
+{
+ [self stopActivityIndicator];
+
+ MXKContactManager* sharedManager = [MXKContactManager sharedManager];
+
+ if (force || !matrixContactsArray)
+ {
+ matrixContactsArray = sharedManager.matrixContacts;
+ sectionedMatrixContacts = [sharedManager getSectionedContacts:matrixContactsArray];
+ }
+}
+
+- (BOOL)hasPendingAction
+{
+ return nil != pendingMaskSpinnerView;
+}
+
+- (void)addPendingActionMask
+{
+ // add a spinner above the tableview to avoid that the user tap on any other button
+ pendingMaskSpinnerView = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhiteLarge];
+ pendingMaskSpinnerView.backgroundColor = [UIColor colorWithRed:0.5 green:0.5 blue:0.5 alpha:0.5];
+ pendingMaskSpinnerView.frame = self.tableView.frame;
+ pendingMaskSpinnerView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleTopMargin;
+
+ // append it
+ [self.tableView.superview addSubview:pendingMaskSpinnerView];
+
+ // animate it
+ [pendingMaskSpinnerView startAnimating];
+}
+
+- (void)removePendingActionMask
+{
+ if (pendingMaskSpinnerView)
+ {
+ [pendingMaskSpinnerView removeFromSuperview];
+ pendingMaskSpinnerView = nil;
+ [self.tableView reloadData];
+ }
+}
+
+#pragma mark - UITableView dataSource
+
+- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
+{
+ NSInteger sectionNb;
+
+ // search in progress
+ if (contactsSearchBar)
+ {
+ sectionNb = sectionedFilteredContacts.sectionedContacts.count;
+ if (!sectionNb)
+ {
+ // Keep at least one section to display the search bar
+ sectionNb = 1;
+ }
+ }
+ else if (displayMatrixUsers)
+ {
+ [self updateSectionedMatrixContacts:NO];
+ sectionNb = sectionedMatrixContacts.sectionedContacts.count;
+
+ }
+ else
+ {
+ [self updateSectionedLocalContacts:NO];
+ sectionNb = sectionedLocalContacts.sectionedContacts.count;
+ }
+
+ return sectionNb;
+}
+
+- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
+{
+ MXKSectionedContacts* sectionedContacts = contactsSearchBar ? sectionedFilteredContacts : (displayMatrixUsers ? sectionedMatrixContacts : sectionedLocalContacts);
+
+ if (section < sectionedContacts.sectionedContacts.count)
+ {
+ return [sectionedContacts.sectionedContacts[section] count];
+ }
+ return 0;
+}
+
+- (NSString *)tableView:(UITableView *)aTableView titleForHeaderInSection:(NSInteger)section
+{
+ if (contactsSearchBar)
+ {
+ // Hide section titles during search session
+ return nil;
+ }
+
+ MXKSectionedContacts* sectionedContacts = displayMatrixUsers ? sectionedMatrixContacts : sectionedLocalContacts;
+ if (section < sectionedContacts.sectionTitles.count)
+ {
+ return (NSString*)[sectionedContacts.sectionTitles objectAtIndex:section];
+ }
+
+ return nil;
+}
+
+- (NSArray *)sectionIndexTitlesForTableView:(UITableView *)aTableView
+{
+ // do not display the collation during a search
+ if (contactsSearchBar)
+ {
+ return nil;
+ }
+
+ return [[UILocalizedIndexedCollation currentCollation] sectionIndexTitles];
+}
+
+- (NSInteger)tableView:(UITableView *)aTableView sectionForSectionIndexTitle:(NSString *)title atIndex:(NSInteger)index
+{
+ MXKSectionedContacts* sectionedContacts = displayMatrixUsers ? sectionedMatrixContacts : sectionedLocalContacts;
+ NSInteger section = [sectionedContacts.sectionTitles indexOfObject:title];
+
+ // undefined title -> jump to the first valid non empty section
+ if (NSNotFound == section)
+ {
+ NSInteger systemCollationIndex = [collationTitles indexOfObject:title];
+
+ // find in the system collation
+ if (NSNotFound != systemCollationIndex)
+ {
+ systemCollationIndex--;
+
+ while ((systemCollationIndex >= 0) && (NSNotFound == section))
+ {
+ NSString* systemTitle = [collationTitles objectAtIndex:systemCollationIndex];
+ section = [sectionedContacts.sectionTitles indexOfObject:systemTitle];
+ systemCollationIndex--;
+ }
+ }
+ }
+
+ return section;
+}
+
+- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
+{
+ MXKContactTableCell* cell = [tableView dequeueReusableCellWithIdentifier:[_contactTableViewCellClass defaultReuseIdentifier] forIndexPath:indexPath];
+ cell.thumbnailDisplayBoxType = MXKTableViewCellDisplayBoxTypeCircle;
+
+ MXKSectionedContacts* sectionedContacts = contactsSearchBar ? sectionedFilteredContacts : (displayMatrixUsers ? sectionedMatrixContacts : sectionedLocalContacts);
+
+ MXKContact* contact = nil;
+
+ if (indexPath.section < sectionedContacts.sectionedContacts.count)
+ {
+ NSArray *thisSection = [sectionedContacts.sectionedContacts objectAtIndex:indexPath.section];
+
+ if (indexPath.row < thisSection.count)
+ {
+ contact = [thisSection objectAtIndex:indexPath.row];
+ }
+ }
+
+ if (contact)
+ {
+ cell.contactAccessoryViewType = MXKContactTableCellAccessoryMatrixIcon;
+ [cell render:contact];
+ cell.delegate = self;
+ }
+
+ return cell;
+}
+
+#pragma mark - UITableView delegate
+
+- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
+{
+ MXKSectionedContacts* sectionedContacts = contactsSearchBar ? sectionedFilteredContacts : (displayMatrixUsers ? sectionedMatrixContacts : sectionedLocalContacts);
+
+ MXKContact* contact = nil;
+
+ if (indexPath.section < sectionedContacts.sectionedContacts.count)
+ {
+ NSArray *thisSection = [sectionedContacts.sectionedContacts objectAtIndex:indexPath.section];
+
+ if (indexPath.row < thisSection.count)
+ {
+ contact = [thisSection objectAtIndex:indexPath.row];
+ }
+ }
+
+ return [((Class)_contactTableViewCellClass) heightForCellData:contact withMaximumWidth:tableView.frame.size.width];
+}
+
+- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section
+{
+ // In case of search, the section titles are hidden and the search bar is displayed in first section header.
+ if (contactsSearchBar)
+ {
+ if (section == 0)
+ {
+ return contactsSearchBar.frame.size.height;
+ }
+ return 0;
+ }
+
+ // Default section header height
+ return 22;
+}
+
+- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section
+{
+ if (contactsSearchBar && section == 0)
+ {
+ return contactsSearchBar;
+ }
+ return nil;
+}
+
+- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
+{
+ [tableView deselectRowAtIndexPath:indexPath animated:YES];
+
+ MXKSectionedContacts* sectionedContacts = contactsSearchBar ? sectionedFilteredContacts : (displayMatrixUsers ? sectionedMatrixContacts : sectionedLocalContacts);
+
+ MXKContact* contact = nil;
+
+ if (indexPath.section < sectionedContacts.sectionedContacts.count)
+ {
+ NSArray *thisSection = [sectionedContacts.sectionedContacts objectAtIndex:indexPath.section];
+
+ if (indexPath.row < thisSection.count)
+ {
+ contact = [thisSection objectAtIndex:indexPath.row];
+ }
+ }
+
+ if (self.delegate) {
+ [self.delegate contactListViewController:self didSelectContact:contact.contactID];
+ }
+}
+
+- (void)tableView:(UITableView *)tableView didEndDisplayingCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath*)indexPath
+{
+ // Release here resources, and restore reusable cells
+ if ([cell respondsToSelector:@selector(didEndDisplay)])
+ {
+ [(id)cell didEndDisplay];
+ }
+}
+
+#pragma mark - Actions
+
+- (void)onContactsRefresh:(NSNotification *)notif
+{
+ if ([notif.name isEqualToString:kMXKContactManagerDidUpdateMatrixContactsNotification])
+ {
+ [self updateSectionedMatrixContacts:YES];
+ }
+ else if ([notif.name isEqualToString:kMXKContactManagerDidUpdateLocalContactsNotification])
+ {
+ [self updateSectionedLocalContacts:YES];
+ }
+ else //if ([notif.name isEqualToString:kMXKContactManagerDidUpdateLocalContactMatrixIDsNotification])
+ {
+ // Consider here only global notifications, ignore notifications related to a specific contact.
+ if (notif.object)
+ {
+ return;
+ }
+
+ [self updateSectionedLocalContacts:YES];
+ }
+
+ if (contactsSearchBar)
+ {
+ latestSearchedPattern = nil;
+ [self searchBar:contactsSearchBar textDidChange:contactsSearchBar.text];
+ }
+ else
+ {
+ [self.tableView reloadData];
+ }
+}
+
+- (IBAction)onSegmentValueChange:(id)sender
+{
+ if (sender == self.contactsControls)
+ {
+ displayMatrixUsers = (0 == self.contactsControls.selectedSegmentIndex);
+
+ // Leave potential search session
+ if (contactsSearchBar)
+ {
+ [self searchBarCancelButtonClicked:contactsSearchBar];
+ }
+
+ [self.tableView reloadData];
+ }
+}
+
+#pragma mark Search management
+
+- (void)search:(id)sender
+{
+ // The user may have pressed search button whereas the view controller was disappearing
+ if (ignoreSearchRequest)
+ {
+ return;
+ }
+
+ if (!contactsSearchBar)
+ {
+ MXKSectionedContacts* sectionedContacts = displayMatrixUsers ? sectionedMatrixContacts : sectionedLocalContacts;
+
+ // Check whether there are data in which search
+ if (sectionedContacts.sectionedContacts.count > 0)
+ {
+ // Create search bar
+ contactsSearchBar = [[UISearchBar alloc] initWithFrame:CGRectMake(0, 0, self.view.frame.size.width, 44)];
+ contactsSearchBar.autoresizingMask = UIViewAutoresizingFlexibleWidth;
+ contactsSearchBar.showsCancelButton = YES;
+ contactsSearchBar.returnKeyType = UIReturnKeyDone;
+ contactsSearchBar.delegate = self;
+ searchBarShouldEndEditing = NO;
+
+ // init the table content
+ latestSearchedPattern = @"";
+ filteredContacts = [(displayMatrixUsers ? matrixContactsArray : localContactsArray) mutableCopy];
+ sectionedFilteredContacts = [[MXKContactManager sharedManager] getSectionedContacts:filteredContacts];
+
+ [self.tableView reloadData];
+
+ dispatch_async(dispatch_get_main_queue(), ^{
+ [self->contactsSearchBar becomeFirstResponder];
+ });
+ }
+ }
+ else
+ {
+ [self searchBarCancelButtonClicked:contactsSearchBar];
+ }
+}
+
+#pragma mark - UISearchBarDelegate
+
+- (BOOL)searchBarShouldBeginEditing:(UISearchBar *)searchBar
+{
+ searchBarShouldEndEditing = NO;
+ return YES;
+}
+
+- (BOOL)searchBarShouldEndEditing:(UISearchBar *)searchBar
+{
+ return searchBarShouldEndEditing;
+}
+
+- (NSArray*)patternsFromText:(NSString*)text
+{
+ NSArray* items = [text componentsSeparatedByString:@" "];
+
+ if (items.count <= 1)
+ {
+ return items;
+ }
+
+ NSMutableArray* patterns = [[NSMutableArray alloc] init];
+
+ for (NSString* item in items)
+ {
+ if (item.length > 0)
+ {
+ [patterns addObject:item];
+ }
+ }
+
+ return patterns;
+}
+
+- (void)searchBar:(UISearchBar *)searchBar textDidChange:(NSString *)searchText
+{
+ if ((contactsSearchBar == searchBar) && (![latestSearchedPattern isEqualToString:searchText]))
+ {
+ latestSearchedPattern = searchText;
+
+ // contacts
+ NSArray* contacts = displayMatrixUsers ? matrixContactsArray : localContactsArray;
+
+ // Update filtered list
+ if (searchText.length && contacts.count)
+ {
+ filteredContacts = [[NSMutableArray alloc] init];
+
+ NSArray* patterns = [self patternsFromText:searchText];
+ for(MXKContact* contact in contacts)
+ {
+ if ([contact matchedWithPatterns:patterns])
+ {
+ [filteredContacts addObject:contact];
+ }
+ }
+ }
+ else
+ {
+ filteredContacts = [contacts mutableCopy];
+ }
+
+ sectionedFilteredContacts = [[MXKContactManager sharedManager] getSectionedContacts:filteredContacts];
+
+ // Refresh display
+ [self.tableView reloadData];
+ [self scrollToTop];
+ }
+}
+
+- (void)searchBarSearchButtonClicked:(UISearchBar *)searchBar
+{
+ if (contactsSearchBar == searchBar)
+ {
+ // "Done" key has been pressed
+ searchBarShouldEndEditing = YES;
+ [contactsSearchBar resignFirstResponder];
+ }
+}
+
+- (void)searchBarCancelButtonClicked:(UISearchBar *)searchBar
+{
+ if (contactsSearchBar == searchBar)
+ {
+ // Leave search
+ searchBarShouldEndEditing = YES;
+ [contactsSearchBar resignFirstResponder];
+ [contactsSearchBar removeFromSuperview];
+ contactsSearchBar = nil;
+ filteredContacts = nil;
+ sectionedFilteredContacts = nil;
+ latestSearchedPattern = nil;
+ [self.tableView reloadData];
+ [self scrollToTop];
+ }
+}
+
+#pragma mark - MXKCellRendering delegate
+
+- (void)cell:(id)cell didRecognizeAction:(NSString*)actionIdentifier userInfo:(NSDictionary *)userInfo
+{
+ if ([actionIdentifier isEqualToString:kMXKContactCellTapOnThumbnailView])
+ {
+ if (self.delegate) {
+ [self.delegate contactListViewController:self didTapContactThumbnail:userInfo[kMXKContactCellContactIdKey]];
+ }
+ }
+}
+
+- (BOOL)cell:(id)cell shouldDoAction:(NSString *)actionIdentifier userInfo:(NSDictionary *)userInfo defaultValue:(BOOL)defaultValue
+{
+ // No such action yet on contacts
+ return defaultValue;
+}
+
+@end
diff --git a/Riot/Modules/MatrixKit/Controllers/MXKContactListViewController.xib b/Riot/Modules/MatrixKit/Controllers/MXKContactListViewController.xib
new file mode 100644
index 000000000..6d638f106
--- /dev/null
+++ b/Riot/Modules/MatrixKit/Controllers/MXKContactListViewController.xib
@@ -0,0 +1,49 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Riot/Modules/MatrixKit/Controllers/MXKCountryPickerViewController.h b/Riot/Modules/MatrixKit/Controllers/MXKCountryPickerViewController.h
new file mode 100644
index 000000000..83e92f2ae
--- /dev/null
+++ b/Riot/Modules/MatrixKit/Controllers/MXKCountryPickerViewController.h
@@ -0,0 +1,81 @@
+/*
+ Copyright 2017 Vector Creations 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 "MXKTableViewController.h"
+
+@class MXKCountryPickerViewController;
+
+/**
+ `MXKCountryPickerViewController` delegate.
+ */
+@protocol MXKCountryPickerViewControllerDelegate
+
+/**
+ Tells the delegate that the user selected a country.
+
+ @param countryPickerViewController the `MXKCountryPickerViewController` instance.
+ @param isoCountryCode the ISO 3166-1 country code representation.
+ */
+- (void)countryPickerViewController:(MXKCountryPickerViewController*)countryPickerViewController didSelectCountry:(NSString*)isoCountryCode;
+
+@end
+
+/**
+ 'MXKCountryPickerViewController' instance displays the list of supported countries.
+ */
+@interface MXKCountryPickerViewController : MXKTableViewController
+
+/**
+The searchController used to manage search.
+*/
+@property (nonatomic, strong) UISearchController *searchController;
+
+/**
+ The delegate for the view controller.
+ */
+@property (nonatomic, weak) id delegate;
+
+#pragma mark - Class methods
+
+/**
+ Returns the `UINib` object initialized for a `MXKCountryPickerViewController`.
+
+ @return The initialized `UINib` object or `nil` if there were errors during initialization
+ or the nib file could not be located.
+
+ @discussion You may override this method to provide a customized nib. If you do,
+ you should also override `countryPickerViewController` to return your
+ view controller loaded from your custom nib.
+ */
++ (UINib *)nib;
+
+/**
+ Creates and returns a new `MXKCountryPickerViewController` object.
+
+ @discussion This is the designated initializer for programmatic instantiation.
+ @return An initialized `MXKCountryPickerViewController` object if successful, `nil` otherwise.
+ */
++ (instancetype)countryPickerViewController;
+
+/**
+ Show/Hide the international dialing code for each country (NO by default).
+ */
+@property (nonatomic) BOOL showCountryCallingCode;
+
+@end
+
diff --git a/Riot/Modules/MatrixKit/Controllers/MXKCountryPickerViewController.m b/Riot/Modules/MatrixKit/Controllers/MXKCountryPickerViewController.m
new file mode 100644
index 000000000..273046244
--- /dev/null
+++ b/Riot/Modules/MatrixKit/Controllers/MXKCountryPickerViewController.m
@@ -0,0 +1,299 @@
+/*
+ Copyright 2017 Vector Creations 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 "MXKCountryPickerViewController.h"
+
+@import libPhoneNumber_iOS;
+
+#import "NSBundle+MatrixKit.h"
+#import "MXKSwiftHeader.h"
+
+
+NSString* const kMXKCountryPickerViewControllerCountryCellId = @"kMXKCountryPickerViewControllerCountryCellId";
+
+@interface MXKCountryPickerViewController ()
+{
+ NSMutableDictionary *isoCountryCodesByCountryName;
+
+ NSArray *countryNames;
+ NSMutableArray *filteredCountryNames;
+
+ NSString *previousSearchPattern;
+
+ NSMutableDictionary *callingCodesByCountryName;
+}
+
+@end
+
+@implementation MXKCountryPickerViewController
+
+#pragma mark - Class methods
+
++ (UINib *)nib
+{
+ return [UINib nibWithNibName:NSStringFromClass([MXKCountryPickerViewController class])
+ bundle:[NSBundle bundleForClass:[MXKCountryPickerViewController class]]];
+}
+
++ (instancetype)countryPickerViewController
+{
+ return [[[self class] alloc] initWithNibName:NSStringFromClass([MXKCountryPickerViewController class])
+ bundle:[NSBundle bundleForClass:[MXKCountryPickerViewController class]]];
+}
+
+- (void)finalizeInit
+{
+ [super finalizeInit];
+
+ NSArray *isoCountryCodes = [NSLocale ISOCountryCodes];
+ NSMutableArray *countries;
+
+ isoCountryCodesByCountryName = [NSMutableDictionary dictionaryWithCapacity:isoCountryCodes.count];
+ countries = [NSMutableArray arrayWithCapacity:isoCountryCodes.count];
+
+ NSLocale *local = [[NSLocale alloc] initWithLocaleIdentifier:[[[NSBundle mainBundle] preferredLocalizations] objectAtIndex:0]];
+
+ for (NSString *isoCountryCode in isoCountryCodes)
+ {
+ NSString *country = [local displayNameForKey:NSLocaleCountryCode value:isoCountryCode];
+ if (country)
+ {
+ [countries addObject: country];
+ isoCountryCodesByCountryName[country] = isoCountryCode;
+ }
+ }
+
+ countryNames = [countries sortedArrayUsingSelector:@selector(localizedCaseInsensitiveCompare:)];
+
+ previousSearchPattern = nil;
+ filteredCountryNames = nil;
+
+ _showCountryCallingCode = NO;
+}
+
+- (void)destroy
+{
+ [super destroy];
+
+ isoCountryCodesByCountryName = nil;
+
+ countryNames = nil;
+ filteredCountryNames = nil;
+
+ callingCodesByCountryName = nil;
+
+ previousSearchPattern = nil;
+}
+
+- (void)viewDidLoad
+{
+ [super viewDidLoad];
+
+ // Check whether the view controller has been pushed via storyboard
+ if (!self.tableView)
+ {
+ // Instantiate view controller objects
+ [[[self class] nib] instantiateWithOwner:self options:nil];
+ }
+
+ self.navigationItem.title = [MatrixKitL10n countryPickerTitle];
+
+ [self setupSearchController];
+}
+
+- (void)viewDidAppear:(BOOL)animated
+{
+ [super viewDidAppear:animated];
+
+ self.navigationItem.hidesSearchBarWhenScrolling = YES;
+}
+
+#pragma mark -
+
+- (void)setShowCountryCallingCode:(BOOL)showCountryCallingCode
+{
+ if (_showCountryCallingCode != showCountryCallingCode)
+ {
+ _showCountryCallingCode = showCountryCallingCode;
+
+ if (_showCountryCallingCode && !callingCodesByCountryName)
+ {
+ callingCodesByCountryName = [NSMutableDictionary dictionary];
+
+ for (NSString *countryName in countryNames)
+ {
+ NSString *isoCountryCode = isoCountryCodesByCountryName[countryName];
+ NSNumber *callingCode = [[NBPhoneNumberUtil sharedInstance] getCountryCodeForRegion:isoCountryCode];
+
+ callingCodesByCountryName[countryName] = callingCode;
+ }
+ }
+
+ [self.tableView reloadData];
+ }
+}
+
+#pragma mark - Private
+
+- (void)setupSearchController
+{
+ UISearchController *searchController = [[UISearchController alloc]
+ initWithSearchResultsController:nil];
+ searchController.dimsBackgroundDuringPresentation = NO;
+ searchController.hidesNavigationBarDuringPresentation = NO;
+ searchController.searchResultsUpdater = self;
+
+ self.navigationItem.searchController = searchController;
+ // Make the search bar visible on first view appearance
+ self.navigationItem.hidesSearchBarWhenScrolling = NO;
+
+ self.definesPresentationContext = YES;
+
+ self.searchController = searchController;
+}
+
+#pragma mark - UITableView dataSource
+
+- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
+{
+ if (filteredCountryNames)
+ {
+ return filteredCountryNames.count;
+ }
+ return countryNames.count;
+}
+
+- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
+{
+ UITableViewCell* cell = [tableView dequeueReusableCellWithIdentifier:kMXKCountryPickerViewControllerCountryCellId];
+ if (!cell)
+ {
+ cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:kMXKCountryPickerViewControllerCountryCellId];
+ }
+
+ NSInteger index = indexPath.row;
+ NSString *countryName;
+
+ if (filteredCountryNames)
+ {
+ if (index < filteredCountryNames.count)
+ {
+ countryName = filteredCountryNames[index];
+ }
+ }
+ else if (index < countryNames.count)
+ {
+ countryName = countryNames[index];
+ }
+
+ if (countryName)
+ {
+ cell.textLabel.text = countryName;
+
+ if (self.showCountryCallingCode)
+ {
+ cell.detailTextLabel.text = [NSString stringWithFormat:@"+%@", [callingCodesByCountryName[countryName] stringValue]];
+ }
+ }
+
+ return cell;
+}
+
+#pragma mark - UITableView delegate
+
+- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
+{
+ [tableView deselectRowAtIndexPath:indexPath animated:YES];
+
+ if (self.delegate)
+ {
+ NSInteger index = indexPath.row;
+ NSString *countryName;
+
+ if (filteredCountryNames)
+ {
+ if (index < filteredCountryNames.count)
+ {
+ countryName = filteredCountryNames[index];
+ }
+ }
+ else if (index < countryNames.count)
+ {
+ countryName = countryNames[index];
+ }
+
+ if (countryName)
+ {
+ NSString *isoCountryCode = isoCountryCodesByCountryName[countryName];
+
+ [self.delegate countryPickerViewController:self didSelectCountry:isoCountryCode];
+ }
+ }
+}
+
+#pragma mark - UISearchResultsUpdating
+
+- (void)updateSearchResultsForSearchController:(UISearchController *)searchController
+{
+ NSString *searchText = searchController.searchBar.text;
+
+ if (searchText.length)
+ {
+ searchText = [searchText lowercaseString];
+
+ if (previousSearchPattern && [searchText hasPrefix:previousSearchPattern])
+ {
+ for (NSUInteger index = 0; index < filteredCountryNames.count;)
+ {
+ NSString *countryName = [filteredCountryNames[index] lowercaseString];
+
+ if ([countryName hasPrefix:searchText] == NO)
+ {
+ [filteredCountryNames removeObjectAtIndex:index];
+ }
+ else
+ {
+ index++;
+ }
+ }
+ }
+ else
+ {
+ filteredCountryNames = [NSMutableArray array];
+
+ for (NSUInteger index = 0; index < countryNames.count; index++)
+ {
+ NSString *countryName = [countryNames[index] lowercaseString];
+
+ if ([countryName hasPrefix:searchText])
+ {
+ [filteredCountryNames addObject:countryNames[index]];
+ }
+ }
+ }
+
+ previousSearchPattern = searchText;
+ }
+ else
+ {
+ previousSearchPattern = nil;
+ filteredCountryNames = nil;
+ }
+
+ [self.tableView reloadData];
+}
+
+@end
diff --git a/Riot/Modules/MatrixKit/Controllers/MXKCountryPickerViewController.xib b/Riot/Modules/MatrixKit/Controllers/MXKCountryPickerViewController.xib
new file mode 100644
index 000000000..a9349f0b2
--- /dev/null
+++ b/Riot/Modules/MatrixKit/Controllers/MXKCountryPickerViewController.xib
@@ -0,0 +1,27 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Riot/Modules/MatrixKit/Controllers/MXKGroupListViewController.h b/Riot/Modules/MatrixKit/Controllers/MXKGroupListViewController.h
new file mode 100644
index 000000000..020f78549
--- /dev/null
+++ b/Riot/Modules/MatrixKit/Controllers/MXKGroupListViewController.h
@@ -0,0 +1,118 @@
+/*
+ Copyright 2017 Vector Creations 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 "MXKViewController.h"
+#import "MXKSessionGroupsDataSource.h"
+
+@class MXKGroupListViewController;
+
+/**
+ `MXKGroupListViewController` delegate.
+ */
+@protocol MXKGroupListViewControllerDelegate
+
+/**
+ Tells the delegate that the user selected a group.
+
+ @param groupListViewController the `MXKGroupListViewController` instance.
+ @param group the selected group.
+ @param mxSession the matrix session in which the group is defined.
+ */
+- (void)groupListViewController:(MXKGroupListViewController *)groupListViewController didSelectGroup:(MXGroup*)group inMatrixSession:(MXSession*)mxSession;
+
+@end
+
+
+/**
+ This view controller displays a group list.
+ */
+@interface MXKGroupListViewController : MXKViewController
+{
+@protected
+
+ /**
+ The fake top view displayed in case of vertical bounce.
+ */
+ __weak UIView *topview;
+}
+
+@property (weak, nonatomic) IBOutlet UISearchBar *groupsSearchBar;
+@property (weak, nonatomic) IBOutlet UITableView *groupsTableView;
+@property (weak, nonatomic) IBOutlet NSLayoutConstraint *groupsSearchBarTopConstraint;
+@property (weak, nonatomic) IBOutlet NSLayoutConstraint *groupsSearchBarHeightConstraint;
+@property (weak, nonatomic) IBOutlet NSLayoutConstraint *groupsTableViewBottomConstraint;
+
+/**
+ The current data source associated to the view controller.
+ */
+@property (nonatomic, readonly) MXKSessionGroupsDataSource *dataSource;
+
+/**
+ The delegate for the view controller.
+ */
+@property (nonatomic, weak) id delegate;
+
+/**
+ Enable the search option by adding a navigation item in the navigation bar (YES by default).
+ Set NO this property to disable this option and hide the related bar button.
+ */
+@property (nonatomic) BOOL enableBarButtonSearch;
+
+#pragma mark - Class methods
+
+/**
+ Returns the `UINib` object initialized for a `MXKGroupListViewController`.
+
+ @return The initialized `UINib` object or `nil` if there were errors during initialization
+ or the nib file could not be located.
+
+ @discussion You may override this method to provide a customized nib. If you do,
+ you should also override `groupListViewController` to return your
+ view controller loaded from your custom nib.
+ */
++ (UINib *)nib;
+
+/**
+ Creates and returns a new `MXKGroupListViewController` object.
+
+ @discussion This is the designated initializer for programmatic instantiation.
+ @return An initialized `MXKGroupListViewController` object if successful, `nil` otherwise.
+ */
++ (instancetype)groupListViewController;
+
+/**
+ Display the groups described in the provided data source.
+
+ Note: The provided data source will replace the current data source if any. The caller
+ should dispose properly this data source if it is not used anymore.
+
+ @param listDataSource the data source providing the groups list.
+ */
+- (void)displayList:(MXKSessionGroupsDataSource*)listDataSource;
+
+/**
+ Refresh the groups table display.
+ */
+- (void)refreshGroupsTable;
+
+/**
+ Hide/show the search bar at the top of the groups table view.
+ */
+- (void)hideSearchBar:(BOOL)hidden;
+
+@end
diff --git a/Riot/Modules/MatrixKit/Controllers/MXKGroupListViewController.m b/Riot/Modules/MatrixKit/Controllers/MXKGroupListViewController.m
new file mode 100644
index 000000000..e9950fa3f
--- /dev/null
+++ b/Riot/Modules/MatrixKit/Controllers/MXKGroupListViewController.m
@@ -0,0 +1,608 @@
+/*
+ Copyright 2017 Vector Creations 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 "MXKGroupListViewController.h"
+
+#import "MXKGroupTableViewCell.h"
+#import "MXKTableViewHeaderFooterWithLabel.h"
+
+@interface MXKGroupListViewController ()
+{
+ /**
+ The data source providing UITableViewCells
+ */
+ MXKSessionGroupsDataSource *dataSource;
+
+ /**
+ Search handling
+ */
+ UIBarButtonItem *searchButton;
+ BOOL ignoreSearchRequest;
+
+ /**
+ The reconnection animated view.
+ */
+ UIView* reconnectingView;
+
+ /**
+ The current table view header if any.
+ */
+ UIView* tableViewHeaderView;
+
+ /**
+ The latest server sync date
+ */
+ NSDate* latestServerSync;
+
+ /**
+ The restart the event connnection
+ */
+ BOOL restartConnection;
+}
+
+@end
+
+@implementation MXKGroupListViewController
+@synthesize dataSource;
+
+#pragma mark - Class methods
+
++ (UINib *)nib
+{
+ return [UINib nibWithNibName:NSStringFromClass([MXKGroupListViewController class])
+ bundle:[NSBundle bundleForClass:[MXKGroupListViewController class]]];
+}
+
++ (instancetype)groupListViewController
+{
+ return [[[self class] alloc] initWithNibName:NSStringFromClass([MXKGroupListViewController class])
+ bundle:[NSBundle bundleForClass:[MXKGroupListViewController class]]];
+}
+
+#pragma mark -
+
+- (void)finalizeInit
+{
+ [super finalizeInit];
+
+ _enableBarButtonSearch = YES;
+}
+
+- (void)viewDidLoad
+{
+ [super viewDidLoad];
+
+ // Check whether the view controller has been pushed via storyboard
+ if (!_groupsTableView)
+ {
+ // Instantiate view controller objects
+ [[[self class] nib] instantiateWithOwner:self options:nil];
+ }
+
+ // Adjust search bar Top constraint to take into account potential navBar.
+ if (_groupsSearchBarTopConstraint)
+ {
+ [NSLayoutConstraint deactivateConstraints:@[_groupsSearchBarTopConstraint]];
+
+ _groupsSearchBarTopConstraint = [NSLayoutConstraint constraintWithItem:self.topLayoutGuide
+ attribute:NSLayoutAttributeBottom
+ relatedBy:NSLayoutRelationEqual
+ toItem:self.groupsSearchBar
+ attribute:NSLayoutAttributeTop
+ multiplier:1.0f
+ constant:0.0f];
+
+ [NSLayoutConstraint activateConstraints:@[_groupsSearchBarTopConstraint]];
+ }
+
+ // Adjust table view Bottom constraint to take into account tabBar.
+ if (_groupsTableViewBottomConstraint)
+ {
+ [NSLayoutConstraint deactivateConstraints:@[_groupsTableViewBottomConstraint]];
+
+ _groupsTableViewBottomConstraint = [NSLayoutConstraint constraintWithItem:self.bottomLayoutGuide
+ attribute:NSLayoutAttributeTop
+ relatedBy:NSLayoutRelationEqual
+ toItem:self.groupsTableView
+ attribute:NSLayoutAttributeBottom
+ multiplier:1.0f
+ constant:0.0f];
+
+ [NSLayoutConstraint activateConstraints:@[_groupsTableViewBottomConstraint]];
+ }
+
+ // Hide search bar by default
+ [self hideSearchBar:YES];
+
+ // Apply search option in navigation bar
+ self.enableBarButtonSearch = _enableBarButtonSearch;
+
+ // Add an accessory view to the search bar in order to retrieve keyboard view.
+ self.groupsSearchBar.inputAccessoryView = [[UIView alloc] initWithFrame:CGRectZero];
+
+ // Finalize table view configuration
+ // Note: self-sizing cells and self-sizing section headers are enabled from the nib file.
+ self.groupsTableView.delegate = self;
+ self.groupsTableView.dataSource = dataSource; // Note: dataSource may be nil here
+ self.groupsTableView.estimatedSectionHeaderHeight = 30; // The value set in the nib seems not available for iOS version < 10.
+
+ // Set up classes to use for the cells and the section headers.
+ [self.groupsTableView registerNib:MXKGroupTableViewCell.nib forCellReuseIdentifier:MXKGroupTableViewCell.defaultReuseIdentifier];
+ [self.groupsTableView registerNib:MXKTableViewHeaderFooterWithLabel.nib forHeaderFooterViewReuseIdentifier:MXKTableViewHeaderFooterWithLabel.defaultReuseIdentifier];
+
+ // Add a top view which will be displayed in case of vertical bounce.
+ CGFloat height = self.groupsTableView.frame.size.height;
+ UIView *topview = [[UIView alloc] initWithFrame:CGRectMake(0,-height,self.groupsTableView.frame.size.width,height)];
+ topview.autoresizingMask = UIViewAutoresizingFlexibleWidth;
+ topview.backgroundColor = [UIColor groupTableViewBackgroundColor];
+ [self.groupsTableView addSubview:topview];
+ self->topview = topview;
+}
+
+- (void)viewWillAppear:(BOOL)animated
+{
+ [super viewWillAppear:animated];
+
+ // Restore search mechanism (if enabled)
+ ignoreSearchRequest = NO;
+
+ // Observe the server sync
+ [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(onSyncNotification) name:kMXSessionDidSyncNotification object:nil];
+
+ // Do a full reload
+ [self refreshGroupsTable];
+
+ // Refresh all groups summary
+ [self.dataSource refreshGroupsSummary:nil];
+}
+
+- (void)viewWillDisappear:(BOOL)animated
+{
+ [super viewWillDisappear:animated];
+
+ // The user may still press search button whereas the view disappears
+ ignoreSearchRequest = YES;
+
+ // Leave potential search session
+ if (!self.groupsSearchBar.isHidden)
+ {
+ [self searchBarCancelButtonClicked:self.groupsSearchBar];
+ }
+
+ [[NSNotificationCenter defaultCenter] removeObserver:self name:kMXSessionDidSyncNotification object:nil];
+
+ [self removeReconnectingView];
+}
+
+- (void)dealloc
+{
+ self.groupsSearchBar.inputAccessoryView = nil;
+
+ searchButton = nil;
+}
+
+- (void)didReceiveMemoryWarning
+{
+ [super didReceiveMemoryWarning];
+
+ // Dispose of any resources that can be recreated.
+}
+
+#pragma mark - Override MXKViewController
+
+- (void)onKeyboardShowAnimationComplete
+{
+ // Report the keyboard view in order to track keyboard frame changes
+ self.keyboardView = _groupsSearchBar.inputAccessoryView.superview;
+}
+
+- (void)setKeyboardHeight:(CGFloat)keyboardHeight
+{
+ // Deduce the bottom constraint for the table view (Don't forget the potential tabBar)
+ CGFloat tableViewBottomConst = keyboardHeight - self.bottomLayoutGuide.length;
+ // Check whether the keyboard is over the tabBar
+ if (tableViewBottomConst < 0)
+ {
+ tableViewBottomConst = 0;
+ }
+
+ // Update constraints
+ _groupsTableViewBottomConstraint.constant = tableViewBottomConst;
+
+ // Force layout immediately to take into account new constraint
+ [self.view layoutIfNeeded];
+}
+
+- (void)destroy
+{
+ self.groupsTableView.dataSource = nil;
+ self.groupsTableView.delegate = nil;
+ self.groupsTableView = nil;
+
+ dataSource.delegate = nil;
+ dataSource = nil;
+
+ _delegate = nil;
+
+ [topview removeFromSuperview];
+ topview = nil;
+
+ [super destroy];
+}
+
+#pragma mark -
+
+- (void)setEnableBarButtonSearch:(BOOL)enableBarButtonSearch
+{
+ _enableBarButtonSearch = enableBarButtonSearch;
+
+ if (enableBarButtonSearch)
+ {
+ if (!searchButton)
+ {
+ searchButton = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemSearch target:self action:@selector(search:)];
+ }
+
+ // Add it in right bar items
+ NSArray *rightBarButtonItems = self.navigationItem.rightBarButtonItems;
+ self.navigationItem.rightBarButtonItems = rightBarButtonItems ? [rightBarButtonItems arrayByAddingObject:searchButton] : @[searchButton];
+ }
+ else
+ {
+ NSMutableArray *rightBarButtonItems = [NSMutableArray arrayWithArray: self.navigationItem.rightBarButtonItems];
+ [rightBarButtonItems removeObject:searchButton];
+ self.navigationItem.rightBarButtonItems = rightBarButtonItems;
+ }
+}
+
+- (void)displayList:(MXKSessionGroupsDataSource *)listDataSource
+{
+ // Cancel registration on existing dataSource if any
+ if (dataSource)
+ {
+ dataSource.delegate = nil;
+
+ // Remove associated matrix sessions
+ NSArray *mxSessions = self.mxSessions;
+ for (MXSession *mxSession in mxSessions)
+ {
+ [self removeMatrixSession:mxSession];
+ }
+ }
+
+ dataSource = listDataSource;
+ dataSource.delegate = self;
+
+ // Report the matrix session at view controller level to update UI according to session state
+ [self addMatrixSession:listDataSource.mxSession];
+
+ if (self.groupsTableView)
+ {
+ // Set up table data source
+ self.groupsTableView.dataSource = dataSource;
+ }
+}
+
+- (void)refreshGroupsTable
+{
+ // For now, do a simple full reload
+ [self.groupsTableView reloadData];
+}
+
+- (void)hideSearchBar:(BOOL)hidden
+{
+ self.groupsSearchBar.hidden = hidden;
+ self.groupsSearchBarHeightConstraint.constant = hidden ? 0 : 44;
+ [self.view setNeedsUpdateConstraints];
+}
+
+#pragma mark - Action
+
+- (IBAction)search:(id)sender
+{
+ // The user may have pressed search button whereas the view controller was disappearing
+ if (ignoreSearchRequest)
+ {
+ return;
+ }
+
+ if (self.groupsSearchBar.isHidden)
+ {
+ // Check whether there are data in which search
+ if ([self.dataSource numberOfSectionsInTableView:self.groupsTableView])
+ {
+ [self hideSearchBar:NO];
+
+ // Create search bar
+ [self.groupsSearchBar becomeFirstResponder];
+ }
+ }
+ else
+ {
+ [self searchBarCancelButtonClicked: self.groupsSearchBar];
+ }
+}
+
+#pragma mark - MXKDataSourceDelegate
+
+- (Class)cellViewClassForCellData:(MXKCellData*)cellData
+{
+ // Return the default group table view cell
+ return MXKGroupTableViewCell.class;
+}
+
+- (NSString *)cellReuseIdentifierForCellData:(MXKCellData*)cellData
+{
+ // Return the default group table view cell
+ return MXKGroupTableViewCell.defaultReuseIdentifier;
+}
+
+- (void)dataSource:(MXKDataSource *)dataSource didCellChange:(id)changes
+{
+ // For now, do a simple full reload
+ [self refreshGroupsTable];
+}
+
+- (void)dataSource:(MXKDataSource *)dataSource didAddMatrixSession:(MXSession *)mxSession
+{
+ [self addMatrixSession:mxSession];
+}
+
+- (void)dataSource:(MXKDataSource *)dataSource didRemoveMatrixSession:(MXSession *)mxSession
+{
+ [self removeMatrixSession:mxSession];
+}
+
+#pragma mark - UITableView delegate
+
+- (CGFloat)tableView:(UITableView *)tableView estimatedHeightForRowAtIndexPath:(NSIndexPath *)indexPath
+{
+ return tableView.estimatedRowHeight;
+}
+
+- (CGFloat)tableView:(UITableView *)tableView estimatedHeightForHeaderInSection:(NSInteger)section
+{
+ if (tableView.numberOfSections > 1)
+ {
+ return tableView.estimatedSectionHeaderHeight;
+ }
+
+ return 0;
+}
+
+- (void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath
+{
+ // Refresh here the estimated row height
+ tableView.estimatedRowHeight = cell.frame.size.height;
+}
+
+- (void)tableView:(UITableView *)tableView willDisplayHeaderView:(nonnull UIView *)view forSection:(NSInteger)section
+{
+ // Refresh here the estimated header height
+ tableView.estimatedSectionHeaderHeight = view.frame.size.height;
+}
+
+- (nullable UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section
+{
+ MXKTableViewHeaderFooterWithLabel *sectionHeader;
+
+ if (tableView.numberOfSections > 1)
+ {
+ sectionHeader = [tableView dequeueReusableHeaderFooterViewWithIdentifier:MXKTableViewHeaderFooterWithLabel.defaultReuseIdentifier];
+
+ sectionHeader.mxkLabel.text = [self.dataSource tableView:tableView titleForHeaderInSection:section];
+ }
+
+ return sectionHeader;
+}
+
+- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
+{
+ if (_delegate)
+ {
+ UITableViewCell *selectedCell = [tableView cellForRowAtIndexPath:indexPath];
+
+ if ([selectedCell conformsToProtocol:@protocol(MXKCellRendering)])
+ {
+ id cell = (id)selectedCell;
+
+ if ([cell respondsToSelector:@selector(renderedCellData)])
+ {
+ MXKCellData *cellData = cell.renderedCellData;
+ if ([cellData conformsToProtocol:@protocol(MXKGroupCellDataStoring)])
+ {
+ id groupCellData = (id)cellData;
+ [_delegate groupListViewController:self didSelectGroup:groupCellData.group inMatrixSession:self.mainSession];
+ }
+ }
+ }
+ }
+
+ // Hide the keyboard when user select a room
+ // do not hide the searchBar until the view controller disappear
+ // on tablets / iphone 6+, the user could expect to search again while looking at a room
+ [self.groupsSearchBar resignFirstResponder];
+}
+
+- (void)tableView:(UITableView *)tableView didEndDisplayingCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath*)indexPath
+{
+ // Release here resources, and restore reusable cells
+ if ([cell respondsToSelector:@selector(didEndDisplay)])
+ {
+ [(id)cell didEndDisplay];
+ }
+}
+
+- (void)scrollViewWillEndDragging:(UIScrollView *)scrollView withVelocity:(CGPoint)velocity targetContentOffset:(inout CGPoint *)targetContentOffset
+{
+ // Detect vertical bounce at the top of the tableview to trigger reconnection.
+ if (scrollView == _groupsTableView)
+ {
+ [self detectPullToKick:scrollView];
+ }
+}
+
+- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView
+{
+ if (scrollView == _groupsTableView)
+ {
+ [self managePullToKick:scrollView];
+ }
+}
+
+- (void)scrollViewDidScroll:(UIScrollView *)scrollView
+{
+ if (scrollView == _groupsTableView)
+ {
+ if (scrollView.contentOffset.y + scrollView.adjustedContentInset.top == 0)
+ {
+ [self managePullToKick:scrollView];
+ }
+ }
+}
+
+#pragma mark - UISearchBarDelegate
+
+- (void)searchBar:(UISearchBar *)searchBar textDidChange:(NSString *)searchText
+{
+ // Apply filter
+ if (searchText.length)
+ {
+ [self.dataSource searchWithPatterns:@[searchText]];
+ }
+ else
+ {
+ [self.dataSource searchWithPatterns:nil];
+ }
+}
+
+- (void)searchBarSearchButtonClicked:(UISearchBar *)searchBar
+{
+ // "Done" key has been pressed
+ [searchBar resignFirstResponder];
+}
+
+- (void)searchBarCancelButtonClicked:(UISearchBar *)searchBar
+{
+ // Leave search
+ [searchBar resignFirstResponder];
+
+ [self hideSearchBar:YES];
+
+ self.groupsSearchBar.text = nil;
+
+ // Refresh display
+ [self.dataSource searchWithPatterns:nil];
+}
+
+#pragma mark - resync management
+
+- (void)onSyncNotification
+{
+ latestServerSync = [NSDate date];
+
+ MXWeakify(self);
+
+ // Refresh all groups summary
+ [self.dataSource refreshGroupsSummary:^{
+
+ MXStrongifyAndReturnIfNil(self);
+
+ [self removeReconnectingView];
+ }];
+}
+
+- (BOOL)canReconnect
+{
+ // avoid restarting connection if some data has been received within 1 second (1000 : latestServerSync is null)
+ NSTimeInterval interval = latestServerSync ? [[NSDate date] timeIntervalSinceDate:latestServerSync] : 1000;
+ return (interval > 1) && [self.mainSession reconnect];
+}
+
+- (void)addReconnectingView
+{
+ if (!reconnectingView)
+ {
+ UIActivityIndicatorView* spinner = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhiteLarge];
+ spinner.transform = CGAffineTransformMakeScale(0.75f, 0.75f);
+ CGRect frame = spinner.frame;
+ frame.size.height = 80; // 80 * 0.75 = 60
+ spinner.bounds = frame;
+ spinner.color = [UIColor darkGrayColor];
+ spinner.hidesWhenStopped = NO;
+ spinner.backgroundColor = _groupsTableView.backgroundColor;
+ [spinner startAnimating];
+
+ // no need to manage constraints here, IOS defines them.
+ tableViewHeaderView = _groupsTableView.tableHeaderView;
+ _groupsTableView.tableHeaderView = reconnectingView = spinner;
+ }
+}
+
+- (void)removeReconnectingView
+{
+ if (reconnectingView && !restartConnection)
+ {
+ _groupsTableView.tableHeaderView = tableViewHeaderView;
+ reconnectingView = nil;
+ }
+}
+
+/**
+ Detect if the current connection must be restarted.
+ The spinner is displayed until the overscroll ends (and scrollViewDidEndDecelerating is called).
+ */
+- (void)detectPullToKick:(UIScrollView *)scrollView
+{
+ if (!reconnectingView)
+ {
+ // detect if the user scrolls over the tableview top
+ restartConnection = (scrollView.contentOffset.y + scrollView.adjustedContentInset.top < -128);
+
+ if (restartConnection)
+ {
+ // wait that list decelerate to display / hide it
+ [self addReconnectingView];
+ }
+ }
+}
+
+/**
+ Restarts the current connection if it is required.
+ The 0.3s delay is added to avoid flickering if the connection does not require to be restarted.
+ */
+- (void)managePullToKick:(UIScrollView *)scrollView
+{
+ // the current connection must be restarted
+ if (restartConnection)
+ {
+ // display at least 0.3s the spinner to show to the user that something is pending
+ // else the UI is flickering
+ dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 0.3 * NSEC_PER_SEC), dispatch_get_main_queue(), ^{
+ self->restartConnection = NO;
+
+ if (![self canReconnect])
+ {
+ // if the event stream has not been restarted
+ // hide the spinner
+ [self removeReconnectingView];
+ }
+ // else wait that onSyncNotification is called.
+ });
+ }
+}
+
+@end
diff --git a/Riot/Modules/MatrixKit/Controllers/MXKGroupListViewController.xib b/Riot/Modules/MatrixKit/Controllers/MXKGroupListViewController.xib
new file mode 100644
index 000000000..fad906339
--- /dev/null
+++ b/Riot/Modules/MatrixKit/Controllers/MXKGroupListViewController.xib
@@ -0,0 +1,53 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Riot/Modules/MatrixKit/Controllers/MXKLanguagePickerViewController.h b/Riot/Modules/MatrixKit/Controllers/MXKLanguagePickerViewController.h
new file mode 100644
index 000000000..f30cd3d79
--- /dev/null
+++ b/Riot/Modules/MatrixKit/Controllers/MXKLanguagePickerViewController.h
@@ -0,0 +1,104 @@
+/*
+ Copyright 2017 Vector Creations 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 "MXKTableViewController.h"
+
+@class MXKLanguagePickerViewController;
+
+ /**
+ `MXKLanguagePickerViewController` delegate.
+ */
+ @protocol MXKLanguagePickerViewControllerDelegate
+
+ /**
+ Tells the delegate that the user has selected a language.
+
+ @param languagePickerViewController the `MXKLanguagePickerViewController` instance.
+ @param language the ISO language code. nil means use the language chosen by the OS.
+ */
+ - (void)languagePickerViewController:(MXKLanguagePickerViewController*)languagePickerViewController didSelectLangugage:(NSString*)language;
+
+ @end
+
+/**
+ 'MXKLanguagePickerViewController' instance displays the list of languages.
+ For the moment, it displays only languages available in the application bundle.
+ */
+@interface MXKLanguagePickerViewController : MXKTableViewController
+
+/**
+The searchController used to manage search.
+*/
+@property (nonatomic, strong) UISearchController *searchController;
+
+/**
+ The delegate for the view controller.
+ */
+@property (nonatomic, weak) id delegate;
+
+/**
+ The language marked in the list.
+ @"" by default.
+ */
+@property (nonatomic) NSString *selectedLanguage;
+
+#pragma mark - Class methods
+
+/**
+ Returns the `UINib` object initialized for a `MXKLanguagePickerViewController`.
+
+ @return The initialized `UINib` object or `nil` if there were errors during initialization
+ or the nib file could not be located.
+
+ @discussion You may override this method to provide a customized nib. If you do,
+ you should also override `listViewController` to return your
+ view controller loaded from your custom nib.
+ */
++ (UINib *)nib;
+
+/**
+ Creates and returns a new `MXKLanguagePickerViewController` object.
+
+ @discussion This is the designated initializer for programmatic instantiation.
+ @return An initialized `MXKLanguagePickerViewController` object if successful, `nil` otherwise.
+ */
++ (instancetype)languagePickerViewController;
+
+/**
+ Get the description string of a language defined by its ISO country code.
+ The description is localised in this language.
+
+ @param language the ISO country code of the language (ex: "en").
+ @return its description (ex: "English").
+ */
++ (NSString *)languageDescription:(NSString*)language;
+
+/**
+ Get the localised description string of a language defined by its ISO country code.
+
+ @param language the ISO country code of the language (ex: "en").
+ @return its localised description (ex: "Anglais" on a device running in French).
+ */
++ (NSString *)languageLocalisedDescription:(NSString *)language;
+
+/**
+ Get the ISO country code of the language selected by the OS according to
+ the device language and languages available in the app bundle.
+ */
++ (NSString *)defaultLanguage;
+
+@end
+
diff --git a/Riot/Modules/MatrixKit/Controllers/MXKLanguagePickerViewController.m b/Riot/Modules/MatrixKit/Controllers/MXKLanguagePickerViewController.m
new file mode 100644
index 000000000..92d1a065c
--- /dev/null
+++ b/Riot/Modules/MatrixKit/Controllers/MXKLanguagePickerViewController.m
@@ -0,0 +1,308 @@
+/*
+ Copyright 2017 Vector Creations 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 "MXKLanguagePickerViewController.h"
+
+@import libPhoneNumber_iOS;
+
+#import "NSBundle+MatrixKit.h"
+#import "MXKSwiftHeader.h"
+
+NSString* const kMXKLanguagePickerViewControllerCellId = @"kMXKLanguagePickerViewControllerCellId";
+
+NSString* const kMXKLanguagePickerCellDataKeyText = @"text";
+NSString* const kMXKLanguagePickerCellDataKeyDetailText = @"detailText";
+NSString* const kMXKLanguagePickerCellDataKeyLanguage = @"language";
+
+@interface MXKLanguagePickerViewController ()
+{
+ NSMutableArray *cellDataArray;
+ NSMutableArray *filteredCellDataArray;
+
+ NSString *previousSearchPattern;
+}
+
+@end
+
+@implementation MXKLanguagePickerViewController
+
+#pragma mark - Class methods
+
++ (UINib *)nib
+{
+ return [UINib nibWithNibName:NSStringFromClass([MXKLanguagePickerViewController class])
+ bundle:[NSBundle bundleForClass:[MXKLanguagePickerViewController class]]];
+}
+
++ (instancetype)languagePickerViewController
+{
+ return [[[self class] alloc] initWithNibName:NSStringFromClass([MXKLanguagePickerViewController class])
+ bundle:[NSBundle bundleForClass:[MXKLanguagePickerViewController class]]];
+}
+
++ (NSString *)languageDescription:(NSString *)language
+{
+ NSLocale *locale = [[NSLocale alloc] initWithLocaleIdentifier:language];
+
+ return [locale displayNameForKey:NSLocaleIdentifier value:language];
+}
+
++ (NSString *)languageLocalisedDescription:(NSString *)language
+{
+ NSLocale *locale = [[NSLocale alloc] initWithLocaleIdentifier:[NSBundle mainBundle].preferredLocalizations.firstObject];
+
+ return [locale displayNameForKey:NSLocaleIdentifier value:language];
+}
+
++ (NSString *)defaultLanguage
+{
+ return [NSBundle mainBundle].preferredLocalizations.firstObject;
+}
+
+- (void)finalizeInit
+{
+ [super finalizeInit];
+
+ cellDataArray = [NSMutableArray array];
+ filteredCellDataArray = nil;
+
+ previousSearchPattern = nil;
+
+ // Populate cellDataArray
+ // Start by the default language chosen by the OS
+ NSString *defaultLanguage = [MXKLanguagePickerViewController defaultLanguage];
+ NSString *languageDescription = [MatrixKitL10n languagePickerDefaultLanguage:[MXKLanguagePickerViewController languageDescription:defaultLanguage]];
+
+ [cellDataArray addObject:@{
+ kMXKLanguagePickerCellDataKeyText:languageDescription
+ }];
+
+ // Then, add languages available in the app bundle
+ NSArray *localizations = [[NSBundle mainBundle] localizations];
+ for (NSString *language in localizations)
+ {
+ // Do not duplicate the default lang
+ if (![language isEqualToString:defaultLanguage])
+ {
+ languageDescription = [MXKLanguagePickerViewController languageDescription:language];
+ NSString *localisedLanguageDescription = [MXKLanguagePickerViewController languageLocalisedDescription:language];
+
+ // Capitalise the description in the language locale
+ NSLocale *locale = [[NSLocale alloc] initWithLocaleIdentifier:language];
+ languageDescription = [languageDescription capitalizedStringWithLocale:locale];
+ localisedLanguageDescription = [localisedLanguageDescription capitalizedStringWithLocale:locale];
+
+ if (languageDescription)
+ {
+ [cellDataArray addObject:@{
+ kMXKLanguagePickerCellDataKeyText: languageDescription,
+ kMXKLanguagePickerCellDataKeyDetailText: localisedLanguageDescription,
+ kMXKLanguagePickerCellDataKeyLanguage: language
+ }];
+ }
+ }
+ }
+
+ // Default to "" in order to differentiate it from nil
+ _selectedLanguage = @"";
+}
+
+- (void)destroy
+{
+ [super destroy];
+
+ cellDataArray = nil;
+ filteredCellDataArray = nil;
+
+ previousSearchPattern = nil;
+}
+
+- (void)viewDidLoad
+{
+ [super viewDidLoad];
+
+ // Check whether the view controller has been pushed via storyboard
+ if (!self.tableView)
+ {
+ // Instantiate view controller objects
+ [[[self class] nib] instantiateWithOwner:self options:nil];
+ }
+
+ [self setupSearchController];
+
+ self.navigationItem.title = [MatrixKitL10n languagePickerTitle];
+
+}
+
+- (void)viewDidAppear:(BOOL)animated
+{
+ [super viewDidAppear:animated];
+
+ self.navigationItem.hidesSearchBarWhenScrolling = YES;
+}
+
+#pragma mark - Private
+
+- (void)setupSearchController
+{
+ UISearchController *searchController = [[UISearchController alloc]
+ initWithSearchResultsController:nil];
+ searchController.dimsBackgroundDuringPresentation = NO;
+ searchController.hidesNavigationBarDuringPresentation = NO;
+ searchController.searchResultsUpdater = self;
+
+ // Search bar is hidden for the moment, uncomment following line to enable it.
+ // TODO: Enable it once we have enough translations to fill pages and pages
+ // self.navigationItem.searchController = searchController;
+ // Make the search bar visible on first view appearance
+ self.navigationItem.hidesSearchBarWhenScrolling = NO;
+
+ self.definesPresentationContext = YES;
+
+ self.searchController = searchController;
+}
+
+#pragma mark - UITableView dataSource
+
+- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
+{
+ if (filteredCellDataArray)
+ {
+ return filteredCellDataArray.count;
+ }
+ return cellDataArray.count;
+}
+
+- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
+{
+ UITableViewCell* cell = [tableView dequeueReusableCellWithIdentifier:kMXKLanguagePickerViewControllerCellId];
+ if (!cell)
+ {
+ cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:kMXKLanguagePickerViewControllerCellId];
+ }
+
+ NSInteger index = indexPath.row;
+ NSDictionary *itemCellData;
+
+ if (filteredCellDataArray)
+ {
+ if (index < filteredCellDataArray.count)
+ {
+ itemCellData = filteredCellDataArray[index];
+ }
+ }
+ else if (index < cellDataArray.count)
+ {
+ itemCellData = cellDataArray[index];
+ }
+
+ if (itemCellData)
+ {
+ cell.textLabel.text = itemCellData[kMXKLanguagePickerCellDataKeyText];
+ cell.detailTextLabel.text = itemCellData[kMXKLanguagePickerCellDataKeyDetailText];
+
+ // Mark the cell with the selected language
+ if (_selectedLanguage == itemCellData[kMXKLanguagePickerCellDataKeyLanguage] || [_selectedLanguage isEqualToString:itemCellData[kMXKLanguagePickerCellDataKeyLanguage]])
+ {
+ cell.accessoryType = UITableViewCellAccessoryCheckmark;
+ }
+ else
+ {
+ cell.accessoryType = UITableViewCellAccessoryNone;
+ }
+ }
+
+ return cell;
+}
+
+#pragma mark - UITableView delegate
+
+- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
+{
+ [tableView deselectRowAtIndexPath:indexPath animated:YES];
+
+ if (self.delegate)
+ {
+ NSInteger index = indexPath.row;
+ NSString *language;
+
+ if (filteredCellDataArray)
+ {
+ if (index < filteredCellDataArray.count)
+ {
+ language = filteredCellDataArray[index][kMXKLanguagePickerCellDataKeyLanguage];
+ }
+ }
+ else if (index < cellDataArray.count)
+ {
+ language = cellDataArray[index][kMXKLanguagePickerCellDataKeyLanguage];
+ }
+
+ [self.delegate languagePickerViewController:self didSelectLangugage:language];
+ }
+}
+
+#pragma mark - UISearchResultsUpdating
+
+- (void)updateSearchResultsForSearchController:(UISearchController *)searchController
+{
+ NSString *searchText = searchController.searchBar.text;
+
+ if (searchText.length)
+ {
+ searchText = [searchText lowercaseString];
+
+ if (previousSearchPattern && [searchText hasPrefix:previousSearchPattern])
+ {
+ for (NSUInteger index = 0; index < filteredCellDataArray.count;)
+ {
+ NSString *text = [filteredCellDataArray[index][kMXKLanguagePickerCellDataKeyText] lowercaseString];
+
+ if ([text hasPrefix:searchText] == NO)
+ {
+ [filteredCellDataArray removeObjectAtIndex:index];
+ }
+ else
+ {
+ index++;
+ }
+ }
+ }
+ else
+ {
+ filteredCellDataArray = [NSMutableArray array];
+
+ for (NSUInteger index = 0; index < cellDataArray.count; index++)
+ {
+ NSString *text = [cellDataArray[index][kMXKLanguagePickerCellDataKeyText] lowercaseString];
+
+ if ([text hasPrefix:searchText])
+ {
+ [filteredCellDataArray addObject:cellDataArray[index]];
+ }
+ }
+ }
+
+ previousSearchPattern = searchText;
+ }
+ else
+ {
+ previousSearchPattern = nil;
+ filteredCellDataArray = nil;
+ }
+}
+
+@end
diff --git a/Riot/Modules/MatrixKit/Controllers/MXKLanguagePickerViewController.xib b/Riot/Modules/MatrixKit/Controllers/MXKLanguagePickerViewController.xib
new file mode 100644
index 000000000..2261ee450
--- /dev/null
+++ b/Riot/Modules/MatrixKit/Controllers/MXKLanguagePickerViewController.xib
@@ -0,0 +1,27 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Riot/Modules/MatrixKit/Controllers/MXKNotificationSettingsViewController.h b/Riot/Modules/MatrixKit/Controllers/MXKNotificationSettingsViewController.h
new file mode 100644
index 000000000..e8ff33015
--- /dev/null
+++ b/Riot/Modules/MatrixKit/Controllers/MXKNotificationSettingsViewController.h
@@ -0,0 +1,34 @@
+/*
+ Copyright 2015 OpenMarket 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 "MXKTableViewController.h"
+#import "MXKAccount.h"
+
+/**
+ 'MXKNotificationSettingsViewController' instance may be used to display the notification settings (account's push rules).
+ Presently only the Global notification settings are supported.
+ */
+@interface MXKNotificationSettingsViewController : MXKTableViewController
+
+/**
+ The account who owns the displayed notification settings.
+ */
+@property (nonatomic) MXKAccount *mxAccount;
+
+@end
+
diff --git a/Riot/Modules/MatrixKit/Controllers/MXKNotificationSettingsViewController.m b/Riot/Modules/MatrixKit/Controllers/MXKNotificationSettingsViewController.m
new file mode 100644
index 000000000..156b0c9c3
--- /dev/null
+++ b/Riot/Modules/MatrixKit/Controllers/MXKNotificationSettingsViewController.m
@@ -0,0 +1,637 @@
+/*
+ Copyright 2015 OpenMarket Ltd
+ Copyright 2018 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 "MXKNotificationSettingsViewController.h"
+
+#import "MXKTableViewCellWithButton.h"
+#import "MXKPushRuleTableViewCell.h"
+#import "MXKPushRuleCreationTableViewCell.h"
+#import "MXKTableViewCellWithTextView.h"
+
+#import "MXKConstants.h"
+
+#import "NSBundle+MatrixKit.h"
+
+#import "MXKSwiftHeader.h"
+
+#define MXKNOTIFICATIONSETTINGS_SECTION_INTRO_INDEX 0
+#define MXKNOTIFICATIONSETTINGS_SECTION_PER_WORD_INDEX 1
+#define MXKNOTIFICATIONSETTINGS_SECTION_PER_ROOM_INDEX 2
+#define MXKNOTIFICATIONSETTINGS_SECTION_PER_SENDER_INDEX 3
+#define MXKNOTIFICATIONSETTINGS_SECTION_OTHERS_INDEX 4
+#define MXKNOTIFICATIONSETTINGS_SECTION_DEFAULT_INDEX 5
+#define MXKNOTIFICATIONSETTINGS_SECTION_COUNT 6
+
+@interface MXKNotificationSettingsViewController ()
+{
+ /**
+ Handle master rule state
+ */
+ UIButton *ruleMasterButton;
+ BOOL areAllDisabled;
+
+ /**
+ */
+ NSInteger contentRuleCreationIndex;
+ NSInteger roomRuleCreationIndex;
+ NSInteger senderRuleCreationIndex;
+
+ /**
+ Predefined rules index
+ */
+ NSInteger ruleContainsUserNameIndex;
+ NSInteger ruleContainsDisplayNameIndex;
+ NSInteger ruleOneToOneRoomIndex;
+ NSInteger ruleInviteForMeIndex;
+ NSInteger ruleMemberEventIndex;
+ NSInteger ruleCallIndex;
+ NSInteger ruleSuppressBotsNotificationsIndex;
+
+ /**
+ Notification center observers
+ */
+ id notificationCenterWillUpdateObserver;
+ id notificationCenterDidUpdateObserver;
+ id notificationCenterDidFailObserver;
+}
+
+@end
+
+@implementation MXKNotificationSettingsViewController
+
+- (void)finalizeInit
+{
+ [super finalizeInit];
+}
+
+- (void)dealloc
+{
+ ruleMasterButton = nil;
+}
+
+- (void)viewDidLoad
+{
+ [super viewDidLoad];
+ // Do any additional setup after loading the view, typically from a nib.
+}
+
+- (void)didReceiveMemoryWarning
+{
+ [super didReceiveMemoryWarning];
+ // Dispose of any resources that can be recreated.
+}
+
+- (void)destroy
+{
+ if (notificationCenterWillUpdateObserver)
+ {
+ [[NSNotificationCenter defaultCenter] removeObserver:notificationCenterWillUpdateObserver];
+ notificationCenterWillUpdateObserver = nil;
+ }
+
+ if (notificationCenterDidUpdateObserver)
+ {
+ [[NSNotificationCenter defaultCenter] removeObserver:notificationCenterDidUpdateObserver];
+ notificationCenterDidUpdateObserver = nil;
+ }
+
+ if (notificationCenterDidFailObserver)
+ {
+ [[NSNotificationCenter defaultCenter] removeObserver:notificationCenterDidFailObserver];
+ notificationCenterDidFailObserver = nil;
+ }
+
+ [super destroy];
+}
+
+- (void)viewWillAppear:(BOOL)animated
+{
+ [super viewWillAppear:animated];
+
+ if (_mxAccount)
+ {
+ [self startActivityIndicator];
+
+ // Refresh existing notification rules
+ [_mxAccount.mxSession.notificationCenter refreshRules:^{
+
+ [self stopActivityIndicator];
+ [self.tableView reloadData];
+
+ } failure:^(NSError *error) {
+
+ [self stopActivityIndicator];
+
+ }];
+
+ notificationCenterWillUpdateObserver = [[NSNotificationCenter defaultCenter] addObserverForName:kMXNotificationCenterWillUpdateRules object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *note) {
+ [self startActivityIndicator];
+ }];
+
+ notificationCenterDidUpdateObserver = [[NSNotificationCenter defaultCenter] addObserverForName:kMXNotificationCenterDidUpdateRules object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *note) {
+ [self stopActivityIndicator];
+ [self.tableView reloadData];
+ }];
+
+ notificationCenterDidFailObserver = [[NSNotificationCenter defaultCenter] addObserverForName:kMXNotificationCenterDidFailRulesUpdate object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *note) {
+ [self stopActivityIndicator];
+
+ // Notify MatrixKit user
+ NSString *myUserId = self.mxAccount.mxCredentials.userId;
+ [[NSNotificationCenter defaultCenter] postNotificationName:kMXKErrorNotification object:note.userInfo[kMXNotificationCenterErrorKey] userInfo:myUserId ? @{kMXKErrorUserIdKey: myUserId} : nil];
+ }];
+ }
+
+ // Refresh display
+ [self.tableView reloadData];
+}
+
+- (void)viewWillDisappear:(BOOL)animated
+{
+ [super viewWillDisappear:animated];
+
+ if (notificationCenterWillUpdateObserver)
+ {
+ [[NSNotificationCenter defaultCenter] removeObserver:notificationCenterWillUpdateObserver];
+ notificationCenterWillUpdateObserver = nil;
+ }
+
+ if (notificationCenterDidUpdateObserver)
+ {
+ [[NSNotificationCenter defaultCenter] removeObserver:notificationCenterDidUpdateObserver];
+ notificationCenterDidUpdateObserver = nil;
+ }
+
+ if (notificationCenterDidFailObserver)
+ {
+ [[NSNotificationCenter defaultCenter] removeObserver:notificationCenterDidFailObserver];
+ notificationCenterDidFailObserver = nil;
+ }
+}
+
+#pragma mark - Actions
+
+- (IBAction)onButtonPressed:(id)sender
+{
+ if (sender == ruleMasterButton)
+ {
+ // Swap enable state for all noticiations
+ MXPushRule *pushRule = [_mxAccount.mxSession.notificationCenter ruleById:kMXNotificationCenterDisableAllNotificationsRuleID];
+ if (pushRule)
+ {
+ [_mxAccount.mxSession.notificationCenter enableRule:pushRule isEnabled:!areAllDisabled];
+ }
+ }
+}
+
+#pragma mark - UITableView data source
+
+- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
+{
+ // Check master rule state
+ MXPushRule *pushRule = [_mxAccount.mxSession.notificationCenter ruleById:kMXNotificationCenterDisableAllNotificationsRuleID];
+ if (pushRule.enabled)
+ {
+ areAllDisabled = YES;
+ return 1;
+ }
+ else
+ {
+ areAllDisabled = NO;
+ return MXKNOTIFICATIONSETTINGS_SECTION_COUNT;
+ }
+}
+
+- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
+{
+ NSInteger count = 0;
+
+ if (section == MXKNOTIFICATIONSETTINGS_SECTION_INTRO_INDEX)
+ {
+ count = 2;
+ }
+ else if (section == MXKNOTIFICATIONSETTINGS_SECTION_PER_WORD_INDEX)
+ {
+ // A first cell will display a user information
+ count = 1;
+
+ // Only removable content rules are listed in this section (we ignore here predefined rules)
+ for (MXPushRule *pushRule in _mxAccount.mxSession.notificationCenter.rules.global.content)
+ {
+ if (!pushRule.isDefault)
+ {
+ count++;
+ }
+ }
+
+ // Add one item to suggest new rule creation
+ contentRuleCreationIndex = count ++;
+ }
+ else if (section == MXKNOTIFICATIONSETTINGS_SECTION_PER_ROOM_INDEX)
+ {
+ count = _mxAccount.mxSession.notificationCenter.rules.global.room.count;
+
+ // Add one item to suggest new rule creation
+ roomRuleCreationIndex = count ++;
+ }
+ else if (section == MXKNOTIFICATIONSETTINGS_SECTION_PER_SENDER_INDEX)
+ {
+ count = _mxAccount.mxSession.notificationCenter.rules.global.sender.count;
+
+ // Add one item to suggest new rule creation
+ senderRuleCreationIndex = count ++;
+ }
+ else if (section == MXKNOTIFICATIONSETTINGS_SECTION_OTHERS_INDEX)
+ {
+ ruleContainsUserNameIndex = ruleContainsDisplayNameIndex = ruleOneToOneRoomIndex = ruleInviteForMeIndex = ruleMemberEventIndex = ruleCallIndex = ruleSuppressBotsNotificationsIndex = -1;
+
+ // Check whether each predefined rule is supported
+ if ([_mxAccount.mxSession.notificationCenter ruleById:kMXNotificationCenterContainUserNameRuleID])
+ {
+ ruleContainsUserNameIndex = count++;
+ }
+ if ([_mxAccount.mxSession.notificationCenter ruleById:kMXNotificationCenterContainDisplayNameRuleID])
+ {
+ ruleContainsDisplayNameIndex = count++;
+ }
+ if ([_mxAccount.mxSession.notificationCenter ruleById:kMXNotificationCenterOneToOneRoomRuleID])
+ {
+ ruleOneToOneRoomIndex = count++;
+ }
+ if ([_mxAccount.mxSession.notificationCenter ruleById:kMXNotificationCenterInviteMeRuleID])
+ {
+ ruleInviteForMeIndex = count++;
+ }
+ if ([_mxAccount.mxSession.notificationCenter ruleById:kMXNotificationCenterMemberEventRuleID])
+ {
+ ruleMemberEventIndex = count++;
+ }
+ if ([_mxAccount.mxSession.notificationCenter ruleById:kMXNotificationCenterCallRuleID])
+ {
+ ruleCallIndex = count++;
+ }
+ if ([_mxAccount.mxSession.notificationCenter ruleById:kMXNotificationCenterSuppressBotsNotificationsRuleID])
+ {
+ ruleSuppressBotsNotificationsIndex = count++;
+ }
+ }
+ else if (section == MXKNOTIFICATIONSETTINGS_SECTION_DEFAULT_INDEX)
+ {
+ if ([_mxAccount.mxSession.notificationCenter ruleById:kMXNotificationCenterAllOtherRoomMessagesRuleID])
+ {
+ count = 1;
+ }
+ }
+
+ return count;
+}
+
+- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
+{
+ UITableViewCell *cell = nil;
+ NSInteger rowIndex = indexPath.row;
+
+ if (indexPath.section == MXKNOTIFICATIONSETTINGS_SECTION_INTRO_INDEX)
+ {
+ if (indexPath.row == 0)
+ {
+ MXKTableViewCellWithButton *masterBtnCell = [tableView dequeueReusableCellWithIdentifier:[MXKTableViewCellWithButton defaultReuseIdentifier]];
+ if (!masterBtnCell)
+ {
+ masterBtnCell = [[MXKTableViewCellWithButton alloc] init];
+ }
+
+ if (areAllDisabled)
+ {
+ [masterBtnCell.mxkButton setTitle:[MatrixKitL10n notificationSettingsEnableNotifications] forState:UIControlStateNormal];
+ [masterBtnCell.mxkButton setTitle:[MatrixKitL10n notificationSettingsEnableNotifications] forState:UIControlStateHighlighted];
+ }
+ else
+ {
+ [masterBtnCell.mxkButton setTitle:[MatrixKitL10n notificationSettingsDisableAll] forState:UIControlStateNormal];
+ [masterBtnCell.mxkButton setTitle:[MatrixKitL10n notificationSettingsDisableAll] forState:UIControlStateHighlighted];
+ }
+
+ [masterBtnCell.mxkButton addTarget:self action:@selector(onButtonPressed:) forControlEvents:UIControlEventTouchUpInside];
+
+ ruleMasterButton = masterBtnCell.mxkButton;
+
+ cell = masterBtnCell;
+ }
+ else
+ {
+ MXKTableViewCellWithTextView *introCell = [tableView dequeueReusableCellWithIdentifier:[MXKTableViewCellWithTextView defaultReuseIdentifier]];
+ if (!introCell)
+ {
+ introCell = [[MXKTableViewCellWithTextView alloc] init];
+ }
+
+ if (areAllDisabled)
+ {
+ introCell.mxkTextView.text = [MatrixKitL10n notificationSettingsEnableNotificationsWarning];
+ introCell.mxkTextView.backgroundColor = [UIColor redColor];
+ }
+ else
+ {
+ introCell.mxkTextView.text = [MatrixKitL10n notificationSettingsGlobalInfo];
+ introCell.mxkTextView.backgroundColor = [UIColor clearColor];
+ }
+
+ introCell.mxkTextView.font = [UIFont systemFontOfSize:14];
+
+ cell = introCell;
+ }
+ }
+ else if (indexPath.section == MXKNOTIFICATIONSETTINGS_SECTION_PER_WORD_INDEX)
+ {
+ if (rowIndex == 0)
+ {
+ MXKTableViewCellWithTextView *introCell = [tableView dequeueReusableCellWithIdentifier:[MXKTableViewCellWithTextView defaultReuseIdentifier]];
+ if (!introCell)
+ {
+ introCell = [[MXKTableViewCellWithTextView alloc] init];
+ }
+ introCell.mxkTextView.text = [MatrixKitL10n notificationSettingsPerWordInfo];
+ introCell.mxkTextView.font = [UIFont systemFontOfSize:14];
+
+ cell = introCell;
+ }
+ else if (rowIndex == contentRuleCreationIndex)
+ {
+ MXKPushRuleCreationTableViewCell *pushRuleCreationCell = [tableView dequeueReusableCellWithIdentifier:[MXKPushRuleCreationTableViewCell defaultReuseIdentifier]];
+ if (!pushRuleCreationCell)
+ {
+ pushRuleCreationCell = [[MXKPushRuleCreationTableViewCell alloc] init];
+ }
+
+ pushRuleCreationCell.mxSession = _mxAccount.mxSession;
+ pushRuleCreationCell.mxPushRuleKind = MXPushRuleKindContent;
+ cell = pushRuleCreationCell;
+ }
+ else
+ {
+ // Only removable content rules are listed in this section
+ NSInteger count = 0;
+ for (MXPushRule *pushRule in _mxAccount.mxSession.notificationCenter.rules.global.content)
+ {
+ if (!pushRule.isDefault)
+ {
+ count++;
+
+ if (count == rowIndex)
+ {
+ MXKPushRuleTableViewCell *pushRuleCell = [tableView dequeueReusableCellWithIdentifier:[MXKPushRuleTableViewCell defaultReuseIdentifier]];
+ if (!pushRuleCell)
+ {
+ pushRuleCell = [[MXKPushRuleTableViewCell alloc] init];
+ }
+
+ pushRuleCell.mxSession = _mxAccount.mxSession;
+ pushRuleCell.mxPushRule = pushRule;
+
+ cell = pushRuleCell;
+ break;
+ }
+ }
+ }
+ }
+ }
+ else if (indexPath.section == MXKNOTIFICATIONSETTINGS_SECTION_PER_ROOM_INDEX)
+ {
+ if (rowIndex == roomRuleCreationIndex)
+ {
+ MXKPushRuleCreationTableViewCell *pushRuleCreationCell = [tableView dequeueReusableCellWithIdentifier:[MXKPushRuleCreationTableViewCell defaultReuseIdentifier]];
+ if (!pushRuleCreationCell)
+ {
+ pushRuleCreationCell = [[MXKPushRuleCreationTableViewCell alloc] init];
+ }
+
+ pushRuleCreationCell.mxSession = _mxAccount.mxSession;
+ pushRuleCreationCell.mxPushRuleKind = MXPushRuleKindRoom;
+ cell = pushRuleCreationCell;
+ }
+ else if (rowIndex < _mxAccount.mxSession.notificationCenter.rules.global.room.count)
+ {
+ MXKPushRuleTableViewCell *pushRuleCell = [tableView dequeueReusableCellWithIdentifier:[MXKPushRuleTableViewCell defaultReuseIdentifier]];
+ if (!pushRuleCell)
+ {
+ pushRuleCell = [[MXKPushRuleTableViewCell alloc] init];
+ }
+
+ pushRuleCell.mxSession = _mxAccount.mxSession;
+ pushRuleCell.mxPushRule = [_mxAccount.mxSession.notificationCenter.rules.global.room objectAtIndex:rowIndex];
+
+ cell = pushRuleCell;
+ }
+ }
+ else if (indexPath.section == MXKNOTIFICATIONSETTINGS_SECTION_PER_SENDER_INDEX)
+ {
+ if (rowIndex == senderRuleCreationIndex)
+ {
+ MXKPushRuleCreationTableViewCell *pushRuleCreationCell = [tableView dequeueReusableCellWithIdentifier:[MXKPushRuleCreationTableViewCell defaultReuseIdentifier]];
+ if (!pushRuleCreationCell)
+ {
+ pushRuleCreationCell = [[MXKPushRuleCreationTableViewCell alloc] init];
+ }
+
+ pushRuleCreationCell.mxSession = _mxAccount.mxSession;
+ pushRuleCreationCell.mxPushRuleKind = MXPushRuleKindSender;
+ cell = pushRuleCreationCell;
+ }
+ else if (rowIndex < _mxAccount.mxSession.notificationCenter.rules.global.sender.count)
+ {
+ MXKPushRuleTableViewCell *pushRuleCell = [tableView dequeueReusableCellWithIdentifier:[MXKPushRuleTableViewCell defaultReuseIdentifier]];
+ if (!pushRuleCell)
+ {
+ pushRuleCell = [[MXKPushRuleTableViewCell alloc] init];
+ }
+
+ pushRuleCell.mxSession = _mxAccount.mxSession;
+ pushRuleCell.mxPushRule = [_mxAccount.mxSession.notificationCenter.rules.global.sender objectAtIndex:rowIndex];
+
+ cell = pushRuleCell;
+ }
+ }
+ else if (indexPath.section == MXKNOTIFICATIONSETTINGS_SECTION_OTHERS_INDEX)
+ {
+ MXPushRule *pushRule;
+ NSString *ruleDescription;
+
+ if (rowIndex == ruleContainsUserNameIndex)
+ {
+ pushRule = [_mxAccount.mxSession.notificationCenter ruleById:kMXNotificationCenterContainUserNameRuleID];
+ ruleDescription = [MatrixKitL10n notificationSettingsContainMyUserName];
+ }
+ if (rowIndex == ruleContainsDisplayNameIndex)
+ {
+ pushRule = [_mxAccount.mxSession.notificationCenter ruleById:kMXNotificationCenterContainDisplayNameRuleID];
+ ruleDescription = [MatrixKitL10n notificationSettingsContainMyDisplayName];
+ }
+ if (rowIndex == ruleOneToOneRoomIndex)
+ {
+ pushRule = [_mxAccount.mxSession.notificationCenter ruleById:kMXNotificationCenterOneToOneRoomRuleID];
+ ruleDescription = [MatrixKitL10n notificationSettingsJustSentToMe];
+ }
+ if (rowIndex == ruleInviteForMeIndex)
+ {
+ pushRule = [_mxAccount.mxSession.notificationCenter ruleById:kMXNotificationCenterInviteMeRuleID];
+ ruleDescription = [MatrixKitL10n notificationSettingsInviteToANewRoom];
+ }
+ if (rowIndex == ruleMemberEventIndex)
+ {
+ pushRule = [_mxAccount.mxSession.notificationCenter ruleById:kMXNotificationCenterMemberEventRuleID];
+ ruleDescription = [MatrixKitL10n notificationSettingsPeopleJoinLeaveRooms];
+ }
+ if (rowIndex == ruleCallIndex)
+ {
+ pushRule = [_mxAccount.mxSession.notificationCenter ruleById:kMXNotificationCenterCallRuleID];
+ ruleDescription = [MatrixKitL10n notificationSettingsReceiveACall];
+ }
+ if (rowIndex == ruleSuppressBotsNotificationsIndex)
+ {
+ pushRule = [_mxAccount.mxSession.notificationCenter ruleById:kMXNotificationCenterSuppressBotsNotificationsRuleID];
+ ruleDescription = [MatrixKitL10n notificationSettingsSuppressFromBots];
+ }
+
+ if (pushRule)
+ {
+ MXKPushRuleTableViewCell *pushRuleCell = [tableView dequeueReusableCellWithIdentifier:[MXKPushRuleTableViewCell defaultReuseIdentifier]];
+ if (!pushRuleCell)
+ {
+ pushRuleCell = [[MXKPushRuleTableViewCell alloc] init];
+ }
+
+ pushRuleCell.mxSession = _mxAccount.mxSession;
+ pushRuleCell.mxPushRule = pushRule;
+ pushRuleCell.ruleDescription.text = ruleDescription;
+
+ cell = pushRuleCell;
+ }
+ }
+ else if (indexPath.section == MXKNOTIFICATIONSETTINGS_SECTION_DEFAULT_INDEX)
+ {
+ MXPushRule *pushRule = [_mxAccount.mxSession.notificationCenter ruleById:kMXNotificationCenterAllOtherRoomMessagesRuleID];
+
+ if (pushRule)
+ {
+ MXKPushRuleTableViewCell *pushRuleCell = [tableView dequeueReusableCellWithIdentifier:[MXKPushRuleTableViewCell defaultReuseIdentifier]];
+ if (!pushRuleCell)
+ {
+ pushRuleCell = [[MXKPushRuleTableViewCell alloc] init];
+ }
+
+ pushRuleCell.mxSession = _mxAccount.mxSession;
+ pushRuleCell.mxPushRule = pushRule;
+ pushRuleCell.ruleDescription.text = [MatrixKitL10n notificationSettingsNotifyAllOther];
+
+ cell = pushRuleCell;
+ }
+ }
+ else
+ {
+ // Return a fake cell to prevent app from crashing.
+ cell = [[UITableViewCell alloc] init];
+ }
+
+ return cell;
+}
+
+#pragma mark - UITableView delegate
+
+- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
+{
+ if (indexPath.section == MXKNOTIFICATIONSETTINGS_SECTION_INTRO_INDEX && indexPath.row == 1)
+ {
+ UITextView *textView = [[UITextView alloc] initWithFrame:CGRectMake(0, 0, tableView.frame.size.width, MAXFLOAT)];
+ textView.font = [UIFont systemFontOfSize:14];
+ textView.text = areAllDisabled ? [MatrixKitL10n notificationSettingsEnableNotificationsWarning] : [MatrixKitL10n notificationSettingsGlobalInfo];
+ CGSize contentSize = [textView sizeThatFits:textView.frame.size];
+ return contentSize.height + 1;
+ }
+
+ if (indexPath.section == MXKNOTIFICATIONSETTINGS_SECTION_PER_WORD_INDEX)
+ {
+ if (indexPath.row == 0)
+ {
+ UITextView *textView = [[UITextView alloc] initWithFrame:CGRectMake(0, 0, tableView.frame.size.width, MAXFLOAT)];
+ textView.font = [UIFont systemFontOfSize:14];
+ textView.text = [MatrixKitL10n notificationSettingsPerWordInfo];
+ CGSize contentSize = [textView sizeThatFits:textView.frame.size];
+ return contentSize.height + 1;
+ }
+ else if (indexPath.row == contentRuleCreationIndex)
+ {
+ return 120;
+ }
+ }
+
+ if (indexPath.section == MXKNOTIFICATIONSETTINGS_SECTION_PER_ROOM_INDEX && indexPath.row == roomRuleCreationIndex)
+ {
+ return 120;
+ }
+
+ if (indexPath.section == MXKNOTIFICATIONSETTINGS_SECTION_PER_SENDER_INDEX && indexPath.row == senderRuleCreationIndex)
+ {
+ return 120;
+ }
+
+ return 50;
+}
+
+- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section
+{
+ if (section != MXKNOTIFICATIONSETTINGS_SECTION_INTRO_INDEX)
+ {
+ return 30;
+ }
+ return 0;
+}
+
+- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section
+{
+ UIView *sectionHeader = [[UIView alloc] initWithFrame:[tableView rectForHeaderInSection:section]];
+ sectionHeader.backgroundColor = [UIColor colorWithRed:0.9 green:0.9 blue:0.9 alpha:1.0];
+ UILabel *sectionLabel = [[UILabel alloc] initWithFrame:CGRectMake(5, 5, sectionHeader.frame.size.width - 10, sectionHeader.frame.size.height - 10)];
+ sectionLabel.font = [UIFont boldSystemFontOfSize:16];
+ sectionLabel.backgroundColor = [UIColor clearColor];
+ [sectionHeader addSubview:sectionLabel];
+
+ if (section == MXKNOTIFICATIONSETTINGS_SECTION_PER_WORD_INDEX)
+ {
+ sectionLabel.text = [MatrixKitL10n notificationSettingsPerWordNotifications];
+ }
+ else if (section == MXKNOTIFICATIONSETTINGS_SECTION_PER_ROOM_INDEX)
+ {
+ sectionLabel.text = [MatrixKitL10n notificationSettingsPerRoomNotifications];
+ }
+ else if (section == MXKNOTIFICATIONSETTINGS_SECTION_PER_SENDER_INDEX)
+ {
+ sectionLabel.text = [MatrixKitL10n notificationSettingsPerSenderNotifications];
+ }
+ else if (section == MXKNOTIFICATIONSETTINGS_SECTION_OTHERS_INDEX)
+ {
+ sectionLabel.text = [MatrixKitL10n notificationSettingsOtherAlerts];
+ }
+ else if (section == MXKNOTIFICATIONSETTINGS_SECTION_DEFAULT_INDEX)
+ {
+ sectionLabel.text = [MatrixKitL10n notificationSettingsByDefault];
+ }
+
+ return sectionHeader;
+}
+
+@end
diff --git a/Riot/Modules/MatrixKit/Controllers/MXKPreviewViewController.h b/Riot/Modules/MatrixKit/Controllers/MXKPreviewViewController.h
new file mode 100644
index 000000000..127baaa9b
--- /dev/null
+++ b/Riot/Modules/MatrixKit/Controllers/MXKPreviewViewController.h
@@ -0,0 +1,74 @@
+/*
+ Copyright 2020 The Matrix.org Foundation C.I.C
+
+ 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
+
+NS_ASSUME_NONNULL_BEGIN
+
+@protocol MXKPreviewViewControllerDelegate;
+
+/**
+ @brief A view controller that previews, opens, or prints files whose file format cannot be handled directly by your app.
+
+ Use this class to present an appropriate user interface for previewing, opening, copying, or printing a specified file. For example, an email program might use this class to allow the user to preview attachments and open them in other apps.
+
+ After presenting its user interface, a document interaction controller handles all interactions needed to support file preview and menu display.
+
+ Unlike UIDocumentInteractionController, this view controller aims to be modal presented.
+ */
+@interface MXKPreviewViewController : UINavigationController
+
+/**
+ @brief presents a new instance of MXKPreviewViewController as modal.
+
+ @param presenting view controller that presents the MXKPreviewViewController
+ @param fileUrl URL of the file. This URL should point to a local file.
+ @param allowActions YES to display actions Button. NO otherwise
+ @param delegate delegate (optional) that receives some events about the lifecycle of the MXKPreviewViewController
+
+ @return the instance of MXKPreviewViewController
+ */
++ (MXKPreviewViewController *)presentFrom:(nonnull UIViewController *)presenting
+ fileUrl: (nonnull NSURL *)fileUrl
+ allowActions: (BOOL)allowActions
+ delegate: (nullable id)delegate;
+
+@end
+
+/**
+ A set of methods you can implement to respond to messages from a preview controller.
+ */
+@protocol MXKPreviewViewControllerDelegate
+
+@optional
+
+/**
+ The MXKPreviewViewController will present the preview
+
+ @param controller the instance of MXKPreviewViewController
+ */
+- (void)previewViewControllerWillBeginPreview:(MXKPreviewViewController *)controller;
+
+/**
+ The MXKPreviewViewController did end presenting the preview
+
+ @param controller the instance of MXKPreviewViewController
+ */
+- (void)previewViewControllerDidEndPreview:(MXKPreviewViewController *)controller;
+
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/Riot/Modules/MatrixKit/Controllers/MXKPreviewViewController.m b/Riot/Modules/MatrixKit/Controllers/MXKPreviewViewController.m
new file mode 100644
index 000000000..214ea6c17
--- /dev/null
+++ b/Riot/Modules/MatrixKit/Controllers/MXKPreviewViewController.m
@@ -0,0 +1,104 @@
+/*
+ Copyright 2020 Vector Creations 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 "MXKPreviewViewController.h"
+@import QuickLook;
+
+@interface MXKPreviewViewController ()
+
+/// A specialized view controller for previewing an item.
+@property (nonatomic, weak) QLPreviewController *previewController;
+
+/// URL of the file to preview
+@property (nonatomic, strong) NSURL *fileURL;
+
+/// YES to display actions Button. NO otherwise
+@property (nonatomic) BOOL allowActions;
+
+@property (nonatomic, weak) id previewDelegate;
+
+@end
+
+@implementation MXKPreviewViewController
+
++ (MXKPreviewViewController *)presentFrom:(UIViewController *)presenting fileUrl:(NSURL *)fileUrl allowActions:(BOOL)allowActions delegate:(nullable id)delegate
+{
+ MXKPreviewViewController *previewController = [[MXKPreviewViewController alloc] initWithFileUrl: fileUrl allowActions: allowActions];
+ previewController.previewDelegate = delegate;
+ if ([delegate respondsToSelector:@selector(previewViewControllerWillBeginPreview:)]) {
+ [delegate previewViewControllerWillBeginPreview:previewController];
+ }
+ [presenting presentViewController:previewController animated:YES completion:^{
+ }];
+
+ return previewController;
+}
+
+- (instancetype)initWithFileUrl: (NSURL *)fileUrl allowActions: (BOOL)allowActions
+{
+ QLPreviewController *previewController = [[QLPreviewController alloc] init];
+ self = [super initWithRootViewController:previewController];
+ self.previewController = previewController;
+
+ if (self)
+ {
+ self.modalPresentationStyle = UIModalPresentationFullScreen;
+ self.fileURL = fileUrl;
+ self.allowActions = allowActions;
+ self.previewController.dataSource = self;
+ self.previewController.navigationItem.leftBarButtonItem = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemDone target:self action:@selector(doneAction:)];
+ }
+
+ return self;
+}
+
+- (void)viewDidLayoutSubviews
+{
+ [super viewDidLayoutSubviews];
+
+ if (!self.allowActions)
+ {
+ NSMutableArray *items = [NSMutableArray arrayWithArray: self.previewController.navigationItem.rightBarButtonItems];
+ if (items.count > 0)
+ {
+ [items removeObjectAtIndex:0];
+ }
+ self.previewController.navigationItem.rightBarButtonItems = items;
+ }
+}
+
+- (IBAction)doneAction:(id)sender
+{
+ [self dismissViewControllerAnimated:YES completion:^{
+ if ([self.previewDelegate respondsToSelector:@selector(previewViewControllerDidEndPreview:)]) {
+ [self.previewDelegate previewViewControllerDidEndPreview:self];
+ }
+ }];
+}
+
+#pragma mark - QLPreviewControllerDataSource
+
+- (NSInteger)numberOfPreviewItemsInPreviewController:(nonnull QLPreviewController *)controller
+{
+ return self.fileURL ? 1 : 0;
+}
+
+- (nonnull id)previewController:(nonnull QLPreviewController *)controller previewItemAtIndex:(NSInteger)index
+{
+ return self.fileURL;
+}
+
+@end
diff --git a/Riot/Modules/MatrixKit/Controllers/MXKRecentListViewController.h b/Riot/Modules/MatrixKit/Controllers/MXKRecentListViewController.h
new file mode 100644
index 000000000..da8d193f3
--- /dev/null
+++ b/Riot/Modules/MatrixKit/Controllers/MXKRecentListViewController.h
@@ -0,0 +1,129 @@
+/*
+ Copyright 2015 OpenMarket Ltd
+ Copyright 2017 Vector Creations 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 "MXKViewController.h"
+#import "MXKRecentsDataSource.h"
+
+@class MXKRecentListViewController;
+
+/**
+ `MXKRecentListViewController` delegate.
+ */
+@protocol MXKRecentListViewControllerDelegate
+
+/**
+ Tells the delegate that the user selected a room.
+
+ @param recentListViewController the `MXKRecentListViewController` instance.
+ @param roomId the id of the selected room.
+ @param mxSession the matrix session in which the room is defined.
+ */
+- (void)recentListViewController:(MXKRecentListViewController *)recentListViewController didSelectRoom:(NSString*)roomId inMatrixSession:(MXSession*)mxSession;
+
+/**
+ Tells the delegate that the user selected a suggested room.
+
+ @param recentListViewController the `MXKRecentListViewController` instance.
+ @param childInfo the `MXSpaceChildInfo` instance that describes the selected room.
+ */
+-(void)recentListViewController:(MXKRecentListViewController *)recentListViewController didSelectSuggestedRoom:(MXSpaceChildInfo *)childInfo;
+
+@end
+
+
+/**
+ This view controller displays a room list.
+ */
+@interface MXKRecentListViewController : MXKViewController
+{
+@protected
+
+ /**
+ The fake top view displayed in case of vertical bounce.
+ */
+ __weak UIView *topview;
+}
+
+@property (weak, nonatomic) IBOutlet UISearchBar *recentsSearchBar;
+@property (weak, nonatomic) IBOutlet UITableView *recentsTableView;
+@property (weak, nonatomic) IBOutlet NSLayoutConstraint *recentsSearchBarTopConstraint;
+@property (weak, nonatomic) IBOutlet NSLayoutConstraint *recentsSearchBarHeightConstraint;
+@property (weak, nonatomic) IBOutlet NSLayoutConstraint *recentsTableViewBottomConstraint;
+
+/**
+ The current data source associated to the view controller.
+ */
+@property (nonatomic, readonly) MXKRecentsDataSource *dataSource;
+
+/**
+ The delegate for the view controller.
+ */
+@property (nonatomic, weak) id delegate;
+
+/**
+ Enable the search option by adding a navigation item in the navigation bar (YES by default).
+ Set NO this property to disable this option and hide the related bar button.
+ */
+@property (nonatomic) BOOL enableBarButtonSearch;
+
+#pragma mark - Class methods
+
+/**
+ Returns the `UINib` object initialized for a `MXKRecentListViewController`.
+
+ @return The initialized `UINib` object or `nil` if there were errors during initialization
+ or the nib file could not be located.
+
+ @discussion You may override this method to provide a customized nib. If you do,
+ you should also override `recentListViewController` to return your
+ view controller loaded from your custom nib.
+ */
++ (UINib *)nib;
+
+/**
+ Creates and returns a new `MXKRecentListViewController` object.
+
+ @discussion This is the designated initializer for programmatic instantiation.
+ @return An initialized `MXKRecentListViewController` object if successful, `nil` otherwise.
+ */
++ (instancetype)recentListViewController;
+
+/**
+ Display the recents described in the provided data source.
+
+ Note1: The provided data source will replace the current data source if any. The caller
+ should dispose properly this data source if it is not used anymore.
+
+ Note2: You may provide here a MXKInterleavedRecentsDataSource instance to display interleaved recents.
+
+ @param listDataSource the data source providing the recents list.
+ */
+- (void)displayList:(MXKRecentsDataSource*)listDataSource;
+
+/**
+ Refresh the recents table display.
+ */
+- (void)refreshRecentsTable;
+
+/**
+ Hide/show the search bar at the top of the recents table view.
+ */
+- (void)hideSearchBar:(BOOL)hidden;
+
+@end
diff --git a/Riot/Modules/MatrixKit/Controllers/MXKRecentListViewController.m b/Riot/Modules/MatrixKit/Controllers/MXKRecentListViewController.m
new file mode 100644
index 000000000..828f84825
--- /dev/null
+++ b/Riot/Modules/MatrixKit/Controllers/MXKRecentListViewController.m
@@ -0,0 +1,624 @@
+/*
+ Copyright 2015 OpenMarket Ltd
+ Copyright 2017 Vector Creations 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 "MXKRecentListViewController.h"
+
+#import "MXKRoomDataSourceManager.h"
+
+#import "MXKInterleavedRecentsDataSource.h"
+#import "MXKInterleavedRecentTableViewCell.h"
+
+#import "MXKSwiftHeader.h"
+
+@interface MXKRecentListViewController ()
+{
+ /**
+ The data source providing UITableViewCells
+ */
+ MXKRecentsDataSource *dataSource;
+
+ /**
+ Search handling
+ */
+ UIBarButtonItem *searchButton;
+ BOOL ignoreSearchRequest;
+
+ /**
+ The reconnection animated view.
+ */
+ __weak UIView* reconnectingView;
+
+ /**
+ The current table view header if any.
+ */
+ UIView* tableViewHeaderView;
+
+ /**
+ The latest server sync date
+ */
+ NSDate* latestServerSync;
+
+ /**
+ The restart the event connnection
+ */
+ BOOL restartConnection;
+}
+
+@end
+
+@implementation MXKRecentListViewController
+@synthesize dataSource;
+
+#pragma mark - Class methods
+
++ (UINib *)nib
+{
+ return [UINib nibWithNibName:NSStringFromClass([MXKRecentListViewController class])
+ bundle:[NSBundle bundleForClass:[MXKRecentListViewController class]]];
+}
+
++ (instancetype)recentListViewController
+{
+ return [[[self class] alloc] initWithNibName:NSStringFromClass([MXKRecentListViewController class])
+ bundle:[NSBundle bundleForClass:[MXKRecentListViewController class]]];
+}
+
+#pragma mark -
+
+- (void)finalizeInit
+{
+ [super finalizeInit];
+
+ _enableBarButtonSearch = YES;
+}
+
+- (void)viewDidLoad
+{
+ [super viewDidLoad];
+
+ // Check whether the view controller has been pushed via storyboard
+ if (!_recentsTableView)
+ {
+ // Instantiate view controller objects
+ [[[self class] nib] instantiateWithOwner:self options:nil];
+ }
+
+ // Adjust search bar Top constraint to take into account potential navBar.
+ if (_recentsSearchBarTopConstraint)
+ {
+ _recentsSearchBarTopConstraint.active = NO;
+ _recentsSearchBarTopConstraint = [NSLayoutConstraint constraintWithItem:self.topLayoutGuide
+ attribute:NSLayoutAttributeBottom
+ relatedBy:NSLayoutRelationEqual
+ toItem:self.recentsSearchBar
+ attribute:NSLayoutAttributeTop
+ multiplier:1.0f
+ constant:0.0f];
+
+ _recentsSearchBarTopConstraint.active = YES;
+ }
+
+ // Adjust table view Bottom constraint to take into account tabBar.
+ if (_recentsTableViewBottomConstraint)
+ {
+ _recentsTableViewBottomConstraint.active = NO;
+ _recentsTableViewBottomConstraint = [NSLayoutConstraint constraintWithItem:self.bottomLayoutGuide
+ attribute:NSLayoutAttributeTop
+ relatedBy:NSLayoutRelationEqual
+ toItem:self.recentsTableView
+ attribute:NSLayoutAttributeBottom
+ multiplier:1.0f
+ constant:0.0f];
+
+ _recentsTableViewBottomConstraint.active = YES;
+ }
+
+ // Hide search bar by default
+ [self hideSearchBar:YES];
+
+ // Apply search option in navigation bar
+ self.enableBarButtonSearch = _enableBarButtonSearch;
+
+ // Add an accessory view to the search bar in order to retrieve keyboard view.
+ self.recentsSearchBar.inputAccessoryView = [[UIView alloc] initWithFrame:CGRectZero];
+
+ // Finalize table view configuration
+ self.recentsTableView.delegate = self;
+ self.recentsTableView.dataSource = dataSource; // Note: dataSource may be nil here
+
+ // Set up classes to use for cells
+ [self.recentsTableView registerNib:MXKRecentTableViewCell.nib forCellReuseIdentifier:MXKRecentTableViewCell.defaultReuseIdentifier];
+ // Consider here the specific case where interleaved recents are supported
+ [self.recentsTableView registerNib:MXKInterleavedRecentTableViewCell.nib forCellReuseIdentifier:MXKInterleavedRecentTableViewCell.defaultReuseIdentifier];
+
+ // Add a top view which will be displayed in case of vertical bounce.
+ CGFloat height = self.recentsTableView.frame.size.height;
+ UIView *topview = [[UIView alloc] initWithFrame:CGRectMake(0,-height,self.recentsTableView.frame.size.width,height)];
+ topview.autoresizingMask = UIViewAutoresizingFlexibleWidth;
+ topview.backgroundColor = [UIColor groupTableViewBackgroundColor];
+ [self.recentsTableView addSubview:topview];
+ self->topview = topview;
+}
+
+- (void)viewWillAppear:(BOOL)animated
+{
+ [super viewWillAppear:animated];
+
+ // Restore search mechanism (if enabled)
+ ignoreSearchRequest = NO;
+
+ // Observe server sync at room data source level too
+ [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(onMatrixSessionChange) name:kMXKRoomDataSourceSyncStatusChanged object:nil];
+
+ // Observe the server sync
+ [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(onSyncNotification) name:kMXSessionDidSyncNotification object:nil];
+
+ // Do a full reload
+ [self refreshRecentsTable];
+}
+
+- (void)viewWillDisappear:(BOOL)animated
+{
+ [super viewWillDisappear:animated];
+
+ // The user may still press search button whereas the view disappears
+ ignoreSearchRequest = YES;
+
+ // Leave potential search session
+ if (!self.recentsSearchBar.isHidden)
+ {
+ [self searchBarCancelButtonClicked:self.recentsSearchBar];
+ }
+
+ [[NSNotificationCenter defaultCenter] removeObserver:self name:kMXKRoomDataSourceSyncStatusChanged object:nil];
+ [[NSNotificationCenter defaultCenter] removeObserver:self name:kMXSessionDidSyncNotification object:nil];
+
+ [self removeReconnectingView];
+}
+
+- (void)dealloc
+{
+ self.recentsSearchBar.inputAccessoryView = nil;
+
+ searchButton = nil;
+}
+
+- (void)didReceiveMemoryWarning
+{
+ [super didReceiveMemoryWarning];
+
+ // Dispose of any resources that can be recreated.
+}
+
+#pragma mark - Override MXKViewController
+
+- (void)onMatrixSessionChange
+{
+ [super onMatrixSessionChange];
+
+ // Check whether no server sync is in progress in room data sources
+ NSArray *mxSessions = self.mxSessions;
+ for (MXSession *mxSession in mxSessions)
+ {
+ if ([MXKRoomDataSourceManager sharedManagerForMatrixSession:mxSession].isServerSyncInProgress)
+ {
+ // sync is in progress for at least one data source, keep running the loading wheel
+ [self.activityIndicator startAnimating];
+ break;
+ }
+ }
+}
+
+- (void)onKeyboardShowAnimationComplete
+{
+ // Report the keyboard view in order to track keyboard frame changes
+ self.keyboardView = _recentsSearchBar.inputAccessoryView.superview;
+}
+
+- (void)setKeyboardHeight:(CGFloat)keyboardHeight
+{
+ // Deduce the bottom constraint for the table view (Don't forget the potential tabBar)
+ CGFloat tableViewBottomConst = keyboardHeight - self.bottomLayoutGuide.length;
+ // Check whether the keyboard is over the tabBar
+ if (tableViewBottomConst < 0)
+ {
+ tableViewBottomConst = 0;
+ }
+
+ // Update constraints
+ _recentsTableViewBottomConstraint.constant = tableViewBottomConst;
+
+ // Force layout immediately to take into account new constraint
+ [self.view layoutIfNeeded];
+}
+
+- (void)destroy
+{
+ self.recentsTableView.dataSource = nil;
+ self.recentsTableView.delegate = nil;
+ self.recentsTableView = nil;
+
+ dataSource.delegate = nil;
+ dataSource = nil;
+
+ _delegate = nil;
+
+ [topview removeFromSuperview];
+ topview = nil;
+
+ [super destroy];
+}
+
+#pragma mark -
+
+- (void)setEnableBarButtonSearch:(BOOL)enableBarButtonSearch
+{
+ _enableBarButtonSearch = enableBarButtonSearch;
+
+ if (enableBarButtonSearch)
+ {
+ if (!searchButton)
+ {
+ searchButton = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemSearch target:self action:@selector(search:)];
+ }
+
+ // Add it in right bar items
+ NSArray *rightBarButtonItems = self.navigationItem.rightBarButtonItems;
+ self.navigationItem.rightBarButtonItems = rightBarButtonItems ? [rightBarButtonItems arrayByAddingObject:searchButton] : @[searchButton];
+ }
+ else
+ {
+ NSMutableArray *rightBarButtonItems = [NSMutableArray arrayWithArray: self.navigationItem.rightBarButtonItems];
+ [rightBarButtonItems removeObject:searchButton];
+ self.navigationItem.rightBarButtonItems = rightBarButtonItems;
+ }
+}
+
+- (void)displayList:(MXKRecentsDataSource *)listDataSource
+{
+ // Cancel registration on existing dataSource if any
+ if (dataSource)
+ {
+ dataSource.delegate = nil;
+
+ // Remove associated matrix sessions
+ NSArray *mxSessions = self.mxSessions;
+ for (MXSession *mxSession in mxSessions)
+ {
+ [self removeMatrixSession:mxSession];
+ }
+ }
+
+ dataSource = listDataSource;
+ dataSource.delegate = self;
+
+ // Report all matrix sessions at view controller level to update UI according to sessions state
+ NSArray *mxSessions = listDataSource.mxSessions;
+ for (MXSession *mxSession in mxSessions)
+ {
+ [self addMatrixSession:mxSession];
+ }
+
+ if (self.recentsTableView)
+ {
+ // Set up table data source
+ self.recentsTableView.dataSource = dataSource;
+ }
+}
+
+- (void)refreshRecentsTable
+{
+ // For now, do a simple full reload
+ [self.recentsTableView reloadData];
+}
+
+- (void)hideSearchBar:(BOOL)hidden
+{
+ self.recentsSearchBar.hidden = hidden;
+ self.recentsSearchBarHeightConstraint.constant = hidden ? 0 : 44;
+ [self.view setNeedsUpdateConstraints];
+}
+
+#pragma mark - Action
+
+- (IBAction)search:(id)sender
+{
+ // The user may have pressed search button whereas the view controller was disappearing
+ if (ignoreSearchRequest)
+ {
+ return;
+ }
+
+ if (self.recentsSearchBar.isHidden)
+ {
+ // Check whether there are data in which search
+ if ([self.dataSource numberOfSectionsInTableView:self.recentsTableView])
+ {
+ [self hideSearchBar:NO];
+
+ // Create search bar
+ [self.recentsSearchBar becomeFirstResponder];
+ }
+ }
+ else
+ {
+ [self searchBarCancelButtonClicked: self.recentsSearchBar];
+ }
+}
+
+#pragma mark - MXKDataSourceDelegate
+
+- (Class)cellViewClassForCellData:(MXKCellData*)cellData
+{
+ // Consider here the specific case where interleaved recents are supported
+ if ([dataSource isKindOfClass:MXKInterleavedRecentsDataSource.class])
+ {
+ return MXKInterleavedRecentTableViewCell.class;
+ }
+
+ // Return the default recent table view cell
+ return MXKRecentTableViewCell.class;
+}
+
+- (NSString *)cellReuseIdentifierForCellData:(MXKCellData*)cellData
+{
+ // Consider here the specific case where interleaved recents are supported
+ if ([dataSource isKindOfClass:MXKInterleavedRecentsDataSource.class])
+ {
+ return MXKInterleavedRecentTableViewCell.defaultReuseIdentifier;
+ }
+
+ // Return the default recent table view cell
+ return MXKRecentTableViewCell.defaultReuseIdentifier;
+}
+
+- (void)dataSource:(MXKDataSource *)dataSource didCellChange:(id)changes
+{
+ // For now, do a simple full reload
+ [self refreshRecentsTable];
+}
+
+- (void)dataSource:(MXKDataSource *)dataSource didAddMatrixSession:(MXSession *)mxSession
+{
+ [self addMatrixSession:mxSession];
+}
+
+- (void)dataSource:(MXKDataSource *)dataSource didRemoveMatrixSession:(MXSession *)mxSession
+{
+ [self removeMatrixSession:mxSession];
+}
+
+#pragma mark - UITableView delegate
+- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
+{
+ return [dataSource cellHeightAtIndexPath:indexPath];
+}
+
+- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section
+{
+ // Section header is required only when several recent lists are displayed.
+ if (self.dataSource.displayedRecentsDataSourcesCount > 1)
+ {
+ return 35;
+ }
+ return 0;
+}
+
+- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section
+{
+ // Let dataSource provide the section header.
+ return [dataSource viewForHeaderInSection:section withFrame:[tableView rectForHeaderInSection:section]];
+}
+
+- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
+{
+ if (_delegate)
+ {
+ UITableViewCell *selectedCell = [tableView cellForRowAtIndexPath:indexPath];
+
+ if ([selectedCell conformsToProtocol:@protocol(MXKCellRendering)])
+ {
+ id cell = (id)selectedCell;
+
+ if ([cell respondsToSelector:@selector(renderedCellData)])
+ {
+ MXKCellData *cellData = cell.renderedCellData;
+ if ([cellData conformsToProtocol:@protocol(MXKRecentCellDataStoring)])
+ {
+ id recentCellData = (id)cellData;
+ if (recentCellData.isSuggestedRoom)
+ {
+ [_delegate recentListViewController:self
+ didSelectSuggestedRoom:recentCellData.roomSummary.spaceChildInfo];
+ }
+ else
+ {
+ [_delegate recentListViewController:self
+ didSelectRoom:recentCellData.roomIdentifier
+ inMatrixSession:recentCellData.mxSession];
+ }
+ }
+ }
+ }
+ }
+
+ // Hide the keyboard when user select a room
+ // do not hide the searchBar until the view controller disappear
+ // on tablets / iphone 6+, the user could expect to search again while looking at a room
+ [self.recentsSearchBar resignFirstResponder];
+}
+
+- (void)tableView:(UITableView *)tableView didEndDisplayingCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath*)indexPath
+{
+ // Release here resources, and restore reusable cells
+ if ([cell respondsToSelector:@selector(didEndDisplay)])
+ {
+ [(id)cell didEndDisplay];
+ }
+}
+
+- (void)scrollViewWillEndDragging:(UIScrollView *)scrollView withVelocity:(CGPoint)velocity targetContentOffset:(inout CGPoint *)targetContentOffset
+{
+ // Detect vertical bounce at the top of the tableview to trigger reconnection.
+ if (scrollView == _recentsTableView)
+ {
+ [self detectPullToKick:scrollView];
+ }
+}
+
+- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView
+{
+ if (scrollView == _recentsTableView)
+ {
+ [self managePullToKick:scrollView];
+ }
+}
+
+- (void)scrollViewDidScroll:(UIScrollView *)scrollView
+{
+ if (scrollView == _recentsTableView)
+ {
+ if (scrollView.contentOffset.y + scrollView.adjustedContentInset.top == 0)
+ {
+ [self managePullToKick:scrollView];
+ }
+ }
+}
+
+#pragma mark - UISearchBarDelegate
+
+- (void)searchBar:(UISearchBar *)searchBar textDidChange:(NSString *)searchText
+{
+ // Apply filter
+ if (searchText.length)
+ {
+ [self.dataSource searchWithPatterns:@[searchText]];
+ }
+ else
+ {
+ [self.dataSource searchWithPatterns:nil];
+ }
+}
+
+- (void)searchBarSearchButtonClicked:(UISearchBar *)searchBar
+{
+ // "Done" key has been pressed
+ [searchBar resignFirstResponder];
+}
+
+- (void)searchBarCancelButtonClicked:(UISearchBar *)searchBar
+{
+ // Leave search
+ [searchBar resignFirstResponder];
+
+ [self hideSearchBar:YES];
+
+ self.recentsSearchBar.text = nil;
+
+ // Refresh display
+ [self.dataSource searchWithPatterns:nil];
+}
+
+#pragma mark - resync management
+
+- (void)onSyncNotification
+{
+ latestServerSync = [NSDate date];
+ [self removeReconnectingView];
+}
+
+- (BOOL)canReconnect
+{
+ // avoid restarting connection if some data has been received within 1 second (1000 : latestServerSync is null)
+ NSTimeInterval interval = latestServerSync ? [[NSDate date] timeIntervalSinceDate:latestServerSync] : 1000;
+ return (interval > 1) && [self.mainSession reconnect];
+}
+
+- (void)addReconnectingView
+{
+ if (!reconnectingView)
+ {
+ UIActivityIndicatorView* spinner = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhiteLarge];
+ spinner.transform = CGAffineTransformMakeScale(0.75f, 0.75f);
+ CGRect frame = spinner.frame;
+ frame.size.height = 80; // 80 * 0.75 = 60
+ spinner.bounds = frame;
+ spinner.color = [UIColor darkGrayColor];
+ spinner.hidesWhenStopped = NO;
+ spinner.backgroundColor = _recentsTableView.backgroundColor;
+ [spinner startAnimating];
+
+ // no need to manage constraints here, IOS defines them.
+ tableViewHeaderView = _recentsTableView.tableHeaderView;
+ _recentsTableView.tableHeaderView = reconnectingView = spinner;
+ }
+}
+
+- (void)removeReconnectingView
+{
+ if (reconnectingView && !restartConnection)
+ {
+ _recentsTableView.tableHeaderView = tableViewHeaderView;
+ reconnectingView = nil;
+ }
+}
+
+/**
+ Detect if the current connection must be restarted.
+ The spinner is displayed until the overscroll ends (and scrollViewDidEndDecelerating is called).
+ */
+- (void)detectPullToKick:(UIScrollView *)scrollView
+{
+ if (!reconnectingView)
+ {
+ // detect if the user scrolls over the tableview top
+ restartConnection = (scrollView.contentOffset.y + scrollView.adjustedContentInset.top < -128);
+
+ if (restartConnection)
+ {
+ // wait that list decelerate to display / hide it
+ [self addReconnectingView];
+ }
+ }
+}
+
+/**
+ Restarts the current connection if it is required.
+ The 0.3s delay is added to avoid flickering if the connection does not require to be restarted.
+ */
+- (void)managePullToKick:(UIScrollView *)scrollView
+{
+ // the current connection must be restarted
+ if (restartConnection)
+ {
+ // display at least 0.3s the spinner to show to the user that something is pending
+ // else the UI is flickering
+ dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 0.3 * NSEC_PER_SEC), dispatch_get_main_queue(), ^{
+ self->restartConnection = NO;
+
+ if (![self canReconnect])
+ {
+ // if the event stream has not been restarted
+ // hide the spinner
+ [self removeReconnectingView];
+ }
+ // else wait that onSyncNotification is called.
+ });
+ }
+}
+
+@end
diff --git a/Riot/Modules/MatrixKit/Controllers/MXKRecentListViewController.xib b/Riot/Modules/MatrixKit/Controllers/MXKRecentListViewController.xib
new file mode 100644
index 000000000..441694c1f
--- /dev/null
+++ b/Riot/Modules/MatrixKit/Controllers/MXKRecentListViewController.xib
@@ -0,0 +1,53 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Riot/Modules/MatrixKit/Controllers/MXKRoomMemberDetailsViewController.h b/Riot/Modules/MatrixKit/Controllers/MXKRoomMemberDetailsViewController.h
new file mode 100644
index 000000000..6724f6998
--- /dev/null
+++ b/Riot/Modules/MatrixKit/Controllers/MXKRoomMemberDetailsViewController.h
@@ -0,0 +1,212 @@
+/*
+ Copyright 2015 OpenMarket Ltd
+ Copyright 2017 Vector Creations 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 "MXKViewController.h"
+#import "MXKImageView.h"
+
+/**
+ Available actions on room member
+ */
+typedef enum : NSUInteger
+{
+ MXKRoomMemberDetailsActionInvite,
+ MXKRoomMemberDetailsActionLeave,
+ MXKRoomMemberDetailsActionKick,
+ MXKRoomMemberDetailsActionBan,
+ MXKRoomMemberDetailsActionUnban,
+ MXKRoomMemberDetailsActionIgnore,
+ MXKRoomMemberDetailsActionUnignore,
+ MXKRoomMemberDetailsActionSetDefaultPowerLevel,
+ MXKRoomMemberDetailsActionSetModerator,
+ MXKRoomMemberDetailsActionSetAdmin,
+ MXKRoomMemberDetailsActionSetCustomPowerLevel,
+ MXKRoomMemberDetailsActionStartChat,
+ MXKRoomMemberDetailsActionStartVoiceCall,
+ MXKRoomMemberDetailsActionStartVideoCall,
+ MXKRoomMemberDetailsActionMention,
+ MXKRoomMemberDetailsActionSecurity,
+ MXKRoomMemberDetailsActionSecurityInformation
+
+} MXKRoomMemberDetailsAction;
+
+@class MXKRoomMemberDetailsViewController;
+
+/**
+ `MXKRoomMemberDetailsViewController` delegate.
+ */
+@protocol MXKRoomMemberDetailsViewControllerDelegate
+
+/**
+ Tells the delegate that the user wants to start a one-to-one chat with the room member.
+
+ @param roomMemberDetailsViewController the `MXKRoomMemberDetailsViewController` instance.
+ @param matrixId the member's matrix id
+ @param completion the block to execute at the end of the operation (independently if it succeeded or not).
+ */
+- (void)roomMemberDetailsViewController:(MXKRoomMemberDetailsViewController *)roomMemberDetailsViewController startChatWithMemberId:(NSString*)matrixId completion:(void (^)(void))completion;
+
+@optional
+/**
+ Tells the delegate that the user wants to mention the room member.
+
+ @discussion the `MXKRoomMemberDetailsViewController` instance is withdrawn automatically.
+
+ @param roomMemberDetailsViewController the `MXKRoomMemberDetailsViewController` instance.
+ @param member the room member to mention.
+ */
+- (void)roomMemberDetailsViewController:(MXKRoomMemberDetailsViewController *)roomMemberDetailsViewController mention:(MXRoomMember*)member;
+
+/**
+ Tells the delegate that the user wants to place a voip call with the room member.
+
+ @param roomMemberDetailsViewController the `MXKRoomMemberDetailsViewController` instance.
+ @param matrixId the member's matrix id
+ @param isVideoCall the type of the call: YES for video call / NO for voice call.
+ */
+- (void)roomMemberDetailsViewController:(MXKRoomMemberDetailsViewController *)roomMemberDetailsViewController placeVoipCallWithMemberId:(NSString*)matrixId andVideo:(BOOL)isVideoCall;
+
+@end
+
+/**
+ Whereas the main item of this view controller is a table view, the 'MXKRoomMemberDetailsViewController' class inherits
+ from 'MXKViewController' instead of 'MXKTableViewController' in order to ease the customization.
+ Indeed some items like header may be added at the same level than the table.
+ */
+@interface MXKRoomMemberDetailsViewController : MXKViewController
+{
+@protected
+ /**
+ Current alert (if any).
+ */
+ UIAlertController *currentAlert;
+
+ /**
+ List of the allowed actions on this member.
+ */
+ NSMutableArray *actionsArray;
+}
+
+@property (weak, nonatomic) IBOutlet UITableView *tableView;
+
+@property (weak, nonatomic) IBOutlet MXKImageView *memberThumbnail;
+@property (weak, nonatomic) IBOutlet UITextView *roomMemberMatrixInfo;
+
+/**
+ The default account picture displayed when no picture is defined.
+ */
+@property (nonatomic) UIImage *picturePlaceholder;
+
+/**
+ The displayed member and the corresponding room
+ */
+@property (nonatomic, readonly) MXRoomMember *mxRoomMember;
+@property (nonatomic, readonly) MXRoom *mxRoom;
+@property (nonatomic, readonly) MXEventTimeline *mxRoomLiveTimeline;
+
+/**
+ Enable mention option. NO by default
+ */
+@property (nonatomic) BOOL enableMention;
+
+/**
+ Enable voip call (voice/video). NO by default
+ */
+@property (nonatomic) BOOL enableVoipCall;
+
+/**
+ Enable leave this room. YES by default
+ */
+@property (nonatomic) BOOL enableLeave;
+
+/**
+ Tell whether an action is already in progress.
+ */
+@property (nonatomic, readonly) BOOL hasPendingAction;
+
+/**
+ The delegate for the view controller.
+ */
+@property (nonatomic, weak) id delegate;
+
+#pragma mark - Class methods
+
+/**
+ Returns the `UINib` object initialized for a `MXKRoomMemberDetailsViewController`.
+
+ @return The initialized `UINib` object or `nil` if there were errors during initialization
+ or the nib file could not be located.
+
+ @discussion You may override this method to provide a customized nib. If you do,
+ you should also override `roomMemberDetailsViewController` to return your
+ view controller loaded from your custom nib.
+ */
++ (UINib *)nib;
+
+/**
+ Creates and returns a new `MXKRoomMemberDetailsViewController` object.
+
+ @discussion This is the designated initializer for programmatic instantiation.
+ @return An initialized `MXKRoomMemberDetailsViewController` object if successful, `nil` otherwise.
+ */
++ (instancetype)roomMemberDetailsViewController;
+
+/**
+ Set the room member to display. Provide the actual room in order to handle member changes.
+
+ @param roomMember the matrix room member
+ @param room the matrix room to which this member belongs.
+ */
+- (void)displayRoomMember:(MXRoomMember*)roomMember withMatrixRoom:(MXRoom*)room;
+
+/**
+ Refresh the member information.
+ */
+- (void)updateMemberInfo;
+
+/**
+ The following method is registered on `UIControlEventTouchUpInside` event for all displayed action buttons.
+
+ The start chat and mention options are transferred to the delegate.
+ All the other actions are handled by the current implementation.
+
+ If the delegate responds to selector: @selector(roomMemberDetailsViewController:placeVoipCallWithMemberId:andVideo:), the voip options
+ are transferred to the delegate.
+ */
+- (IBAction)onActionButtonPressed:(id)sender;
+
+/**
+ Set the power level of the room member
+
+ @param value the value to set.
+ @param promptUser prompt the user if they ops a member with the same power level.
+ */
+- (void)setPowerLevel:(NSInteger)value promptUser:(BOOL)promptUser;
+
+/**
+ Add a mask in overlay to prevent a new contact selection (used when an action is on progress).
+ */
+- (void)addPendingActionMask;
+
+/**
+ Remove the potential overlay mask
+ */
+- (void)removePendingActionMask;
+
+@end
+
diff --git a/Riot/Modules/MatrixKit/Controllers/MXKRoomMemberDetailsViewController.m b/Riot/Modules/MatrixKit/Controllers/MXKRoomMemberDetailsViewController.m
new file mode 100644
index 000000000..67623bc18
--- /dev/null
+++ b/Riot/Modules/MatrixKit/Controllers/MXKRoomMemberDetailsViewController.m
@@ -0,0 +1,1037 @@
+/*
+ Copyright 2015 OpenMarket Ltd
+ Copyright 2017 Vector Creations Ltd
+ Copyright 2018 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 "MXKRoomMemberDetailsViewController.h"
+
+@import MatrixSDK.MXMediaManager;
+
+#import "MXKTableViewCellWithButtons.h"
+
+#import "NSBundle+MatrixKit.h"
+
+#import "MXKAppSettings.h"
+
+#import "MXKConstants.h"
+
+#import "MXKSwiftHeader.h"
+
+@interface MXKRoomMemberDetailsViewController ()
+{
+ id membersListener;
+
+ // mask view while processing a request
+ UIActivityIndicatorView * pendingMaskSpinnerView;
+
+ // Observe left rooms
+ id leaveRoomNotificationObserver;
+
+ // Observe kMXRoomDidFlushDataNotification to take into account the updated room members when the room history is flushed.
+ id roomDidFlushDataNotificationObserver;
+
+ // Cache for the room live timeline
+ MXEventTimeline *mxRoomLiveTimeline;
+}
+
+@end
+
+@implementation MXKRoomMemberDetailsViewController
+@synthesize mxRoom;
+
++ (UINib *)nib
+{
+ return [UINib nibWithNibName:NSStringFromClass([MXKRoomMemberDetailsViewController class])
+ bundle:[NSBundle bundleForClass:[MXKRoomMemberDetailsViewController class]]];
+}
+
++ (instancetype)roomMemberDetailsViewController
+{
+ return [[[self class] alloc] initWithNibName:NSStringFromClass([MXKRoomMemberDetailsViewController class])
+ bundle:[NSBundle bundleForClass:[MXKRoomMemberDetailsViewController class]]];
+}
+
+- (void)finalizeInit
+{
+ [super finalizeInit];
+
+ actionsArray = [[NSMutableArray alloc] init];
+ _enableLeave = YES;
+}
+
+- (void)viewDidLoad
+{
+ [super viewDidLoad];
+
+ // Check whether the view controller has been pushed via storyboard
+ if (!self.tableView)
+ {
+ // Instantiate view controller objects
+ [[[self class] nib] instantiateWithOwner:self options:nil];
+ }
+
+ // ignore useless update
+ if (_mxRoomMember)
+ {
+ [self initObservers];
+ }
+}
+
+- (void)viewWillAppear:(BOOL)animated
+{
+ [super viewWillAppear:animated];
+
+ [self initObservers];
+}
+
+- (void)viewWillDisappear:(BOOL)animated
+{
+ [super viewWillDisappear:animated];
+
+ [self removeObservers];
+}
+
+- (void)destroy
+{
+ // close any pending actionsheet
+ if (currentAlert)
+ {
+ [currentAlert dismissViewControllerAnimated:NO completion:nil];
+ currentAlert = nil;
+ }
+
+ [self removePendingActionMask];
+
+ [self removeObservers];
+
+ _delegate = nil;
+ _mxRoomMember = nil;
+
+ actionsArray = nil;
+
+ [super destroy];
+}
+
+#pragma mark -
+
+- (void)displayRoomMember:(MXRoomMember*)roomMember withMatrixRoom:(MXRoom*)room
+{
+ [self removeObservers];
+
+ mxRoom = room;
+
+ MXWeakify(self);
+ [mxRoom liveTimeline:^(MXEventTimeline *liveTimeline) {
+ MXStrongifyAndReturnIfNil(self);
+
+ self->mxRoomLiveTimeline = liveTimeline;
+
+ // Update matrix session associated to the view controller
+ NSArray *mxSessions = self.mxSessions;
+ for (MXSession *mxSession in mxSessions) {
+ [self removeMatrixSession:mxSession];
+ }
+ [self addMatrixSession:room.mxSession];
+
+ self->_mxRoomMember = roomMember;
+
+ [self initObservers];
+ }];
+}
+
+- (MXEventTimeline *)mxRoomLiveTimeline
+{
+ // @TODO(async-state): Just here for dev
+ NSAssert(mxRoomLiveTimeline, @"[MXKRoomMemberDetailsViewController] Room live timeline must be preloaded before accessing to MXKRoomMemberDetailsViewController.mxRoomLiveTimeline");
+ return mxRoomLiveTimeline;
+}
+
+- (UIImage*)picturePlaceholder
+{
+ return [NSBundle mxk_imageFromMXKAssetsBundleWithName:@"default-profile"];
+}
+
+- (void)setEnableMention:(BOOL)enableMention
+{
+ if (_enableMention != enableMention)
+ {
+ _enableMention = enableMention;
+
+ [self updateMemberInfo];
+ }
+}
+
+- (void)setEnableVoipCall:(BOOL)enableVoipCall
+{
+ if (_enableVoipCall != enableVoipCall)
+ {
+ _enableVoipCall = enableVoipCall;
+
+ [self updateMemberInfo];
+ }
+}
+
+- (void)setEnableLeave:(BOOL)enableLeave
+{
+ if (_enableLeave != enableLeave)
+ {
+ _enableLeave = enableLeave;
+
+ [self updateMemberInfo];
+ }
+}
+
+- (IBAction)onActionButtonPressed:(id)sender
+{
+ if ([sender isKindOfClass:[UIButton class]])
+ {
+ // Check whether an action is already in progress
+ if ([self hasPendingAction])
+ {
+ return;
+ }
+
+ UIButton *button = (UIButton*)sender;
+
+ switch (button.tag)
+ {
+ case MXKRoomMemberDetailsActionInvite:
+ {
+ [self addPendingActionMask];
+ [mxRoom inviteUser:_mxRoomMember.userId
+ success:^{
+
+ [self removePendingActionMask];
+
+ } failure:^(NSError *error) {
+
+ [self removePendingActionMask];
+ MXLogDebug(@"[MXKRoomMemberDetailsVC] Invite %@ failed", self->_mxRoomMember.userId);
+ // Notify MatrixKit user
+ NSString *myUserId = self.mainSession.myUser.userId;
+ [[NSNotificationCenter defaultCenter] postNotificationName:kMXKErrorNotification object:error userInfo:myUserId ? @{kMXKErrorUserIdKey: myUserId} : nil];
+
+ }];
+ break;
+ }
+ case MXKRoomMemberDetailsActionLeave:
+ {
+ [self addPendingActionMask];
+ [self.mxRoom leave:^{
+
+ [self removePendingActionMask];
+ [self withdrawViewControllerAnimated:YES completion:nil];
+
+ } failure:^(NSError *error) {
+
+ [self removePendingActionMask];
+ MXLogDebug(@"[MXKRoomMemberDetailsVC] Leave room %@ failed", self->mxRoom.roomId);
+ // Notify MatrixKit user
+ NSString *myUserId = self.mainSession.myUser.userId;
+ [[NSNotificationCenter defaultCenter] postNotificationName:kMXKErrorNotification object:error userInfo:myUserId ? @{kMXKErrorUserIdKey: myUserId} : nil];
+
+ }];
+ break;
+ }
+ case MXKRoomMemberDetailsActionKick:
+ {
+ [self addPendingActionMask];
+ [mxRoom kickUser:_mxRoomMember.userId
+ reason:nil
+ success:^{
+
+ [self removePendingActionMask];
+ // Pop/Dismiss the current view controller if the left members are hidden
+ if (![[MXKAppSettings standardAppSettings] showLeftMembersInRoomMemberList])
+ {
+ [self withdrawViewControllerAnimated:YES completion:nil];
+ }
+
+ } failure:^(NSError *error) {
+
+ [self removePendingActionMask];
+ MXLogDebug(@"[MXKRoomMemberDetailsVC] Kick %@ failed", self->_mxRoomMember.userId);
+ // Notify MatrixKit user
+ NSString *myUserId = self.mainSession.myUser.userId;
+ [[NSNotificationCenter defaultCenter] postNotificationName:kMXKErrorNotification object:error userInfo:myUserId ? @{kMXKErrorUserIdKey: myUserId} : nil];
+
+ }];
+ break;
+ }
+ case MXKRoomMemberDetailsActionBan:
+ {
+ [self addPendingActionMask];
+ [mxRoom banUser:_mxRoomMember.userId
+ reason:nil
+ success:^{
+
+ [self removePendingActionMask];
+
+ } failure:^(NSError *error) {
+
+ [self removePendingActionMask];
+ MXLogDebug(@"[MXKRoomMemberDetailsVC] Ban %@ failed", self->_mxRoomMember.userId);
+ // Notify MatrixKit user
+ NSString *myUserId = self.mainSession.myUser.userId;
+ [[NSNotificationCenter defaultCenter] postNotificationName:kMXKErrorNotification object:error userInfo:myUserId ? @{kMXKErrorUserIdKey: myUserId} : nil];
+
+ }];
+ break;
+ }
+ case MXKRoomMemberDetailsActionUnban:
+ {
+ [self addPendingActionMask];
+ [mxRoom unbanUser:_mxRoomMember.userId
+ success:^{
+
+ [self removePendingActionMask];
+
+ } failure:^(NSError *error) {
+
+ [self removePendingActionMask];
+ MXLogDebug(@"[MXKRoomMemberDetailsVC] Unban %@ failed", self->_mxRoomMember.userId);
+ // Notify MatrixKit user
+ NSString *myUserId = self.mainSession.myUser.userId;
+ [[NSNotificationCenter defaultCenter] postNotificationName:kMXKErrorNotification object:error userInfo:myUserId ? @{kMXKErrorUserIdKey: myUserId} : nil];
+
+ }];
+ break;
+ }
+ case MXKRoomMemberDetailsActionIgnore:
+ {
+ // Prompt user to ignore content from this user
+ MXWeakify(self);
+
+ if (currentAlert)
+ {
+ [currentAlert dismissViewControllerAnimated:NO completion:nil];
+ }
+
+ currentAlert = [UIAlertController alertControllerWithTitle:[MatrixKitL10n roomMemberIgnorePrompt] message:nil preferredStyle:UIAlertControllerStyleAlert];
+
+ [currentAlert addAction:[UIAlertAction actionWithTitle:[MatrixKitL10n yes]
+ style:UIAlertActionStyleDefault
+ handler:^(UIAlertAction * action) {
+
+ MXStrongifyAndReturnIfNil(self);
+
+ self->currentAlert = nil;
+
+ // Add the user to the blacklist: ignored users
+ [self addPendingActionMask];
+
+ MXWeakify(self);
+
+ [self.mainSession ignoreUsers:@[self.mxRoomMember.userId]
+ success:^{
+
+ MXStrongifyAndReturnIfNil(self);
+
+ [self removePendingActionMask];
+
+ } failure:^(NSError *error) {
+
+ MXStrongifyAndReturnIfNil(self);
+
+ [self removePendingActionMask];
+ MXLogDebug(@"[MXKRoomMemberDetailsVC] Ignore %@ failed", self.mxRoomMember.userId);
+
+ // Notify MatrixKit user
+ NSString *myUserId = self.mainSession.myUser.userId;
+ [[NSNotificationCenter defaultCenter] postNotificationName:kMXKErrorNotification object:error userInfo:myUserId ? @{kMXKErrorUserIdKey: myUserId} : nil];
+
+ }];
+
+ }]];
+
+ [currentAlert addAction:[UIAlertAction actionWithTitle:[MatrixKitL10n no]
+ style:UIAlertActionStyleDefault
+ handler:^(UIAlertAction * action) {
+
+ MXStrongifyAndReturnIfNil(self);
+
+ self->currentAlert = nil;
+ }]];
+
+ [self presentViewController:currentAlert animated:YES completion:nil];
+ break;
+ }
+ case MXKRoomMemberDetailsActionUnignore:
+ {
+ // Remove the member from the ignored user list.
+ [self addPendingActionMask];
+
+ MXWeakify(self);
+
+ [self.mainSession unIgnoreUsers:@[self.mxRoomMember.userId]
+ success:^{
+
+ MXStrongifyAndReturnIfNil(self);
+ [self removePendingActionMask];
+
+ } failure:^(NSError *error) {
+
+ MXStrongifyAndReturnIfNil(self);
+
+ [self removePendingActionMask];
+ MXLogDebug(@"[MXKRoomMemberDetailsVC] Unignore %@ failed", self.mxRoomMember.userId);
+
+ // Notify MatrixKit user
+ NSString *myUserId = self.mainSession.myUser.userId;
+ [[NSNotificationCenter defaultCenter] postNotificationName:kMXKErrorNotification object:error userInfo:myUserId ? @{kMXKErrorUserIdKey: myUserId} : nil];
+
+ }];
+ break;
+ }
+ case MXKRoomMemberDetailsActionSetDefaultPowerLevel:
+ {
+ break;
+ }
+ case MXKRoomMemberDetailsActionSetModerator:
+ {
+ break;
+ }
+ case MXKRoomMemberDetailsActionSetAdmin:
+ {
+ break;
+ }
+ case MXKRoomMemberDetailsActionSetCustomPowerLevel:
+ {
+ [self updateUserPowerLevel];
+ break;
+ }
+ case MXKRoomMemberDetailsActionStartChat:
+ {
+ if (self.delegate)
+ {
+ [self addPendingActionMask];
+
+ [self.delegate roomMemberDetailsViewController:self startChatWithMemberId:_mxRoomMember.userId completion:^{
+
+ [self removePendingActionMask];
+ }];
+ }
+ break;
+ }
+ case MXKRoomMemberDetailsActionStartVoiceCall:
+ case MXKRoomMemberDetailsActionStartVideoCall:
+ {
+ BOOL isVideoCall = (button.tag == MXKRoomMemberDetailsActionStartVideoCall);
+
+ if (self.delegate && [self.delegate respondsToSelector:@selector(roomMemberDetailsViewController:placeVoipCallWithMemberId:andVideo:)])
+ {
+ [self addPendingActionMask];
+
+ [self.delegate roomMemberDetailsViewController:self placeVoipCallWithMemberId:_mxRoomMember.userId andVideo:isVideoCall];
+
+ [self removePendingActionMask];
+ }
+ else
+ {
+ [self addPendingActionMask];
+
+ MXRoom* directRoom = [self.mainSession directJoinedRoomWithUserId:_mxRoomMember.userId];
+
+ // Place the call directly if the room exists
+ if (directRoom)
+ {
+ [directRoom placeCallWithVideo:isVideoCall success:nil failure:nil];
+ [self removePendingActionMask];
+ }
+ else
+ {
+ // Create a new room
+ MXRoomCreationParameters *roomCreationParameters = [MXRoomCreationParameters parametersForDirectRoomWithUser:_mxRoomMember.userId];
+ [self.mainSession createRoomWithParameters:roomCreationParameters success:^(MXRoom *room) {
+
+ // Delay the call in order to be sure that the room is ready
+ dispatch_async(dispatch_get_main_queue(), ^{
+ [room placeCallWithVideo:isVideoCall success:nil failure:nil];
+ [self removePendingActionMask];
+ });
+
+ } failure:^(NSError *error) {
+
+ MXLogDebug(@"[MXKRoomMemberDetailsVC] Create room failed");
+ [self removePendingActionMask];
+ // Notify MatrixKit user
+ NSString *myUserId = self.mainSession.myUser.userId;
+ [[NSNotificationCenter defaultCenter] postNotificationName:kMXKErrorNotification object:error userInfo:myUserId ? @{kMXKErrorUserIdKey: myUserId} : nil];
+
+ }];
+ }
+ }
+ break;
+ }
+ case MXKRoomMemberDetailsActionMention:
+ {
+ // Sanity check
+ if (_delegate && [_delegate respondsToSelector:@selector(roomMemberDetailsViewController:mention:)])
+ {
+ id delegate = _delegate;
+ MXRoomMember *member = _mxRoomMember;
+
+ // Withdraw the current view controller, and let the delegate mention the member
+ [self withdrawViewControllerAnimated:YES completion:^{
+
+ [delegate roomMemberDetailsViewController:self mention:member];
+
+ }];
+ }
+ break;
+ }
+ default:
+ break;
+ }
+ }
+}
+
+#pragma mark - Internals
+
+- (void)initObservers
+{
+ // Remove any pending observers
+ [self removeObservers];
+
+ if (mxRoom)
+ {
+ // Observe room's members update
+ NSArray *mxMembersEvents = @[kMXEventTypeStringRoomMember, kMXEventTypeStringRoomPowerLevels];
+ self->membersListener = [mxRoom listenToEventsOfTypes:mxMembersEvents onEvent:^(MXEvent *event, MXTimelineDirection direction, id customObject) {
+
+ // consider only live event
+ if (direction == MXTimelineDirectionForwards)
+ {
+ [self refreshRoomMember];
+ }
+ }];
+
+ // Observe kMXSessionWillLeaveRoomNotification to be notified if the user leaves the current room.
+ leaveRoomNotificationObserver = [[NSNotificationCenter defaultCenter] addObserverForName:kMXSessionWillLeaveRoomNotification object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *notif) {
+
+ // Check whether the user will leave the room related to the displayed member
+ if (notif.object == self.mainSession)
+ {
+ NSString *roomId = notif.userInfo[kMXSessionNotificationRoomIdKey];
+ if (roomId && [roomId isEqualToString:self->mxRoom.roomId])
+ {
+ // We must remove the current view controller.
+ [self withdrawViewControllerAnimated:YES completion:nil];
+ }
+ }
+ }];
+
+ // Observe room history flush (sync with limited timeline, or state event redaction)
+ roomDidFlushDataNotificationObserver = [[NSNotificationCenter defaultCenter] addObserverForName:kMXRoomDidFlushDataNotification object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *notif) {
+
+ MXRoom *room = notif.object;
+ if (self.mainSession == room.mxSession && [self->mxRoom.roomId isEqualToString:room.roomId])
+ {
+ // The existing room history has been flushed during server sync.
+ // Take into account the updated room members list by updating the room member instance
+ [self refreshRoomMember];
+ }
+
+ }];
+ }
+
+ [self updateMemberInfo];
+}
+
+- (void)removeObservers
+{
+ if (leaveRoomNotificationObserver)
+ {
+ [[NSNotificationCenter defaultCenter] removeObserver:leaveRoomNotificationObserver];
+ leaveRoomNotificationObserver = nil;
+ }
+ if (roomDidFlushDataNotificationObserver)
+ {
+ [[NSNotificationCenter defaultCenter] removeObserver:roomDidFlushDataNotificationObserver];
+ roomDidFlushDataNotificationObserver = nil;
+ }
+
+ [[NSNotificationCenter defaultCenter] removeObserver:self];
+
+ if (membersListener && mxRoom)
+ {
+ MXWeakify(self);
+ [mxRoom liveTimeline:^(MXEventTimeline *liveTimeline) {
+ MXStrongifyAndReturnIfNil(self);
+
+ [liveTimeline removeListener:self->membersListener];
+ self->membersListener = nil;
+ }];
+ }
+}
+
+- (void)refreshRoomMember
+{
+ // Hide potential action sheet
+ if (currentAlert)
+ {
+ [currentAlert dismissViewControllerAnimated:NO completion:nil];
+ currentAlert = nil;
+ }
+
+ MXRoomMember* nextRoomMember = nil;
+
+ // get the updated memmber
+ NSArray *membersList = self.mxRoomLiveTimeline.state.members.members;
+ for (MXRoomMember* member in membersList)
+ {
+ if ([member.userId isEqualToString:_mxRoomMember.userId])
+ {
+ nextRoomMember = member;
+ break;
+ }
+ }
+
+ // does the member still exist ?
+ if (nextRoomMember)
+ {
+ // Refresh member
+ _mxRoomMember = nextRoomMember;
+ [self updateMemberInfo];
+ }
+ else
+ {
+ [self withdrawViewControllerAnimated:YES completion:nil];
+ }
+}
+
+- (void)updateMemberInfo
+{
+ self.title = _mxRoomMember.displayname ? _mxRoomMember.displayname : _mxRoomMember.userId;
+
+ // set the thumbnail info
+ self.memberThumbnail.contentMode = UIViewContentModeScaleAspectFill;
+ self.memberThumbnail.defaultBackgroundColor = [UIColor clearColor];
+ [self.memberThumbnail.layer setCornerRadius:self.memberThumbnail.frame.size.width / 2];
+ [self.memberThumbnail setClipsToBounds:YES];
+
+ self.memberThumbnail.mediaFolder = kMXMediaManagerAvatarThumbnailFolder;
+ self.memberThumbnail.enableInMemoryCache = YES;
+ [self.memberThumbnail setImageURI:_mxRoomMember.avatarUrl
+ withType:nil
+ andImageOrientation:UIImageOrientationUp
+ toFitViewSize:self.memberThumbnail.frame.size
+ withMethod:MXThumbnailingMethodCrop
+ previewImage:self.picturePlaceholder
+ mediaManager:self.mainSession.mediaManager];
+
+ self.roomMemberMatrixInfo.text = _mxRoomMember.userId;
+
+ [self.tableView reloadData];
+}
+
+#pragma mark - Table view data source
+
+- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
+{
+ return 1;
+}
+
+- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
+{
+ // Check user's power level before allowing an action (kick, ban, ...)
+ MXRoomPowerLevels *powerLevels = [self.mxRoomLiveTimeline.state powerLevels];
+ NSInteger memberPowerLevel = [powerLevels powerLevelOfUserWithUserID:_mxRoomMember.userId];
+ NSInteger oneSelfPowerLevel = [powerLevels powerLevelOfUserWithUserID:self.mainSession.myUser.userId];
+
+ [actionsArray removeAllObjects];
+
+ // Consider the case of the user himself
+ if ([_mxRoomMember.userId isEqualToString:self.mainSession.myUser.userId])
+ {
+ if (_enableLeave)
+ {
+ [actionsArray addObject:@(MXKRoomMemberDetailsActionLeave)];
+ }
+
+ if (oneSelfPowerLevel >= [powerLevels minimumPowerLevelForSendingEventAsStateEvent:kMXEventTypeStringRoomPowerLevels])
+ {
+ [actionsArray addObject:@(MXKRoomMemberDetailsActionSetCustomPowerLevel)];
+ }
+ }
+ else if (_mxRoomMember)
+ {
+ if (_enableVoipCall)
+ {
+ // Offer voip call options
+ [actionsArray addObject:@(MXKRoomMemberDetailsActionStartVoiceCall)];
+ [actionsArray addObject:@(MXKRoomMemberDetailsActionStartVideoCall)];
+ }
+
+ // Consider membership of the selected member
+ switch (_mxRoomMember.membership)
+ {
+ case MXMembershipInvite:
+ case MXMembershipJoin:
+ {
+ // Check conditions to be able to kick someone
+ if (oneSelfPowerLevel >= [powerLevels kick] && oneSelfPowerLevel > memberPowerLevel)
+ {
+ [actionsArray addObject:@(MXKRoomMemberDetailsActionKick)];
+ }
+ // Check conditions to be able to ban someone
+ if (oneSelfPowerLevel >= [powerLevels ban] && oneSelfPowerLevel > memberPowerLevel)
+ {
+ [actionsArray addObject:@(MXKRoomMemberDetailsActionBan)];
+ }
+
+ // Check whether the option Ignore may be presented
+ if (_mxRoomMember.membership == MXMembershipJoin)
+ {
+ // is he already ignored ?
+ if (![self.mainSession isUserIgnored:_mxRoomMember.userId])
+ {
+ [actionsArray addObject:@(MXKRoomMemberDetailsActionIgnore)];
+ }
+ else
+ {
+ [actionsArray addObject:@(MXKRoomMemberDetailsActionUnignore)];
+ }
+ }
+ break;
+ }
+ case MXMembershipLeave:
+ {
+ // Check conditions to be able to invite someone
+ if (oneSelfPowerLevel >= [powerLevels invite])
+ {
+ [actionsArray addObject:@(MXKRoomMemberDetailsActionInvite)];
+ }
+ // Check conditions to be able to ban someone
+ if (oneSelfPowerLevel >= [powerLevels ban] && oneSelfPowerLevel > memberPowerLevel)
+ {
+ [actionsArray addObject:@(MXKRoomMemberDetailsActionBan)];
+ }
+ break;
+ }
+ case MXMembershipBan:
+ {
+ // Check conditions to be able to unban someone
+ if (oneSelfPowerLevel >= [powerLevels ban] && oneSelfPowerLevel > memberPowerLevel)
+ {
+ [actionsArray addObject:@(MXKRoomMemberDetailsActionUnban)];
+ }
+ break;
+ }
+ default:
+ {
+ break;
+ }
+ }
+
+ // update power level
+ if (oneSelfPowerLevel >= [powerLevels minimumPowerLevelForSendingEventAsStateEvent:kMXEventTypeStringRoomPowerLevels] && oneSelfPowerLevel > memberPowerLevel)
+ {
+ [actionsArray addObject:@(MXKRoomMemberDetailsActionSetCustomPowerLevel)];
+ }
+
+ // offer to start a new chat only if the room is not the first direct chat with this user
+ // it does not make sense : it would open the same room
+ MXRoom* directRoom = [self.mainSession directJoinedRoomWithUserId:_mxRoomMember.userId];
+ if (!directRoom || (![directRoom.roomId isEqualToString:mxRoom.roomId]))
+ {
+ [actionsArray addObject:@(MXKRoomMemberDetailsActionStartChat)];
+ }
+ }
+
+ if (_enableMention)
+ {
+ // Add mention option
+ [actionsArray addObject:@(MXKRoomMemberDetailsActionMention)];
+ }
+
+ return (actionsArray.count + 1) / 2;
+}
+
+- (NSString*)actionButtonTitle:(MXKRoomMemberDetailsAction)action
+{
+ NSString *title;
+
+ switch (action)
+ {
+ case MXKRoomMemberDetailsActionInvite:
+ title = [MatrixKitL10n invite];
+ break;
+ case MXKRoomMemberDetailsActionLeave:
+ title = [MatrixKitL10n leave];
+ break;
+ case MXKRoomMemberDetailsActionKick:
+ title = [MatrixKitL10n kick];
+ break;
+ case MXKRoomMemberDetailsActionBan:
+ title = [MatrixKitL10n ban];
+ break;
+ case MXKRoomMemberDetailsActionUnban:
+ title = [MatrixKitL10n unban];
+ break;
+ case MXKRoomMemberDetailsActionIgnore:
+ title = [MatrixKitL10n ignore];
+ break;
+ case MXKRoomMemberDetailsActionUnignore:
+ title = [MatrixKitL10n unignore];
+ break;
+ case MXKRoomMemberDetailsActionSetDefaultPowerLevel:
+ title = [MatrixKitL10n setDefaultPowerLevel];
+ break;
+ case MXKRoomMemberDetailsActionSetModerator:
+ title = [MatrixKitL10n setModerator];
+ break;
+ case MXKRoomMemberDetailsActionSetAdmin:
+ title = [MatrixKitL10n setAdmin];
+ break;
+ case MXKRoomMemberDetailsActionSetCustomPowerLevel:
+ title = [MatrixKitL10n setPowerLevel];
+ break;
+ case MXKRoomMemberDetailsActionStartChat:
+ title = [MatrixKitL10n startChat];
+ break;
+ case MXKRoomMemberDetailsActionStartVoiceCall:
+ title = [MatrixKitL10n startVoiceCall];
+ break;
+ case MXKRoomMemberDetailsActionStartVideoCall:
+ title = [MatrixKitL10n startVideoCall];
+ break;
+ case MXKRoomMemberDetailsActionMention:
+ title = [MatrixKitL10n mention];
+ break;
+ default:
+ break;
+ }
+
+ return title;
+}
+
+- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
+{
+ if (self.tableView == tableView)
+ {
+ NSInteger row = indexPath.row;
+
+ MXKTableViewCellWithButtons *cell = [tableView dequeueReusableCellWithIdentifier:[MXKTableViewCellWithButtons defaultReuseIdentifier]];
+ if (!cell)
+ {
+ cell = [[MXKTableViewCellWithButtons alloc] init];
+ }
+
+ cell.mxkButtonNumber = 2;
+ NSArray *buttons = cell.mxkButtons;
+ NSInteger index = row * 2;
+ NSString *text = nil;
+ for (UIButton *button in buttons)
+ {
+ NSNumber *actionNumber;
+ if (index < actionsArray.count)
+ {
+ actionNumber = [actionsArray objectAtIndex:index];
+ }
+
+ text = (actionNumber ? [self actionButtonTitle:actionNumber.unsignedIntegerValue] : nil);
+
+ button.hidden = (text.length == 0);
+
+ button.layer.borderColor = button.tintColor.CGColor;
+ button.layer.borderWidth = 1;
+ button.layer.cornerRadius = 5;
+
+ [button setTitle:text forState:UIControlStateNormal];
+ [button setTitle:text forState:UIControlStateHighlighted];
+
+ [button addTarget:self action:@selector(onActionButtonPressed:) forControlEvents:UIControlEventTouchUpInside];
+
+ button.tag = (actionNumber ? actionNumber.unsignedIntegerValue : -1);
+
+ index ++;
+ }
+
+ return cell;
+ }
+
+ // Return a fake cell to prevent app from crashing.
+ return [[UITableViewCell alloc] init];
+}
+
+
+#pragma mark - button management
+
+- (BOOL)hasPendingAction
+{
+ return nil != pendingMaskSpinnerView;
+}
+
+- (void)addPendingActionMask
+{
+ // add a spinner above the tableview to avoid that the user tap on any other button
+ pendingMaskSpinnerView = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhiteLarge];
+ pendingMaskSpinnerView.backgroundColor = [UIColor colorWithRed:0.5 green:0.5 blue:0.5 alpha:0.5];
+ pendingMaskSpinnerView.frame = self.tableView.frame;
+ pendingMaskSpinnerView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleTopMargin;
+
+ // append it
+ [self.tableView.superview addSubview:pendingMaskSpinnerView];
+
+ // animate it
+ [pendingMaskSpinnerView startAnimating];
+}
+
+- (void)removePendingActionMask
+{
+ if (pendingMaskSpinnerView)
+ {
+ [pendingMaskSpinnerView removeFromSuperview];
+ pendingMaskSpinnerView = nil;
+ [self.tableView reloadData];
+ }
+}
+
+- (void)setPowerLevel:(NSInteger)value promptUser:(BOOL)promptUser
+{
+ NSInteger currentPowerLevel = [self.mxRoomLiveTimeline.state.powerLevels powerLevelOfUserWithUserID:_mxRoomMember.userId];
+
+ // check if the power level has not yet been set to 0
+ if (value != currentPowerLevel)
+ {
+ __weak typeof(self) weakSelf = self;
+
+ if (promptUser && value == [self.mxRoomLiveTimeline.state.powerLevels powerLevelOfUserWithUserID:self.mainSession.myUser.userId])
+ {
+ // If the user is setting the same power level as his to another user, ask him for a confirmation
+ if (currentAlert)
+ {
+ [currentAlert dismissViewControllerAnimated:NO completion:nil];
+ }
+
+ currentAlert = [UIAlertController alertControllerWithTitle:[MatrixKitL10n roomMemberPowerLevelPrompt] message:nil preferredStyle:UIAlertControllerStyleAlert];
+
+ [currentAlert addAction:[UIAlertAction actionWithTitle:[MatrixKitL10n no]
+ style:UIAlertActionStyleDefault
+ handler:^(UIAlertAction * action) {
+
+ if (weakSelf)
+ {
+ typeof(self) self = weakSelf;
+ self->currentAlert = nil;
+ }
+
+ }]];
+
+ [currentAlert addAction:[UIAlertAction actionWithTitle:[MatrixKitL10n yes]
+ style:UIAlertActionStyleDefault
+ handler:^(UIAlertAction * action) {
+
+ if (weakSelf)
+ {
+ typeof(self) self = weakSelf;
+ self->currentAlert = nil;
+
+ // The user confirms. Apply the power level
+ [self setPowerLevel:value promptUser:NO];
+ }
+
+ }]];
+
+ [self presentViewController:currentAlert animated:YES completion:nil];
+ }
+ else
+ {
+ [self addPendingActionMask];
+
+ // Reset user power level
+ [self.mxRoom setPowerLevelOfUserWithUserID:_mxRoomMember.userId powerLevel:value success:^{
+
+ __strong __typeof(weakSelf)strongSelf = weakSelf;
+ [strongSelf removePendingActionMask];
+
+ } failure:^(NSError *error) {
+
+ __strong __typeof(weakSelf)strongSelf = weakSelf;
+ [strongSelf removePendingActionMask];
+ MXLogDebug(@"[MXKRoomMemberDetailsVC] Set user power (%@) failed", strongSelf.mxRoomMember.userId);
+
+ // Notify MatrixKit user
+ NSString *myUserId = strongSelf.mainSession.myUser.userId;
+ [[NSNotificationCenter defaultCenter] postNotificationName:kMXKErrorNotification object:error userInfo:myUserId ? @{kMXKErrorUserIdKey: myUserId} : nil];
+
+ }];
+ }
+ }
+}
+
+- (void)updateUserPowerLevel
+{
+ __weak typeof(self) weakSelf = self;
+
+ if (currentAlert)
+ {
+ [currentAlert dismissViewControllerAnimated:NO completion:nil];
+ }
+
+ currentAlert = [UIAlertController alertControllerWithTitle:[MatrixKitL10n powerLevel] message:nil preferredStyle:UIAlertControllerStyleAlert];
+
+
+ if (![self.mainSession.myUser.userId isEqualToString:_mxRoomMember.userId])
+ {
+ [currentAlert addAction:[UIAlertAction actionWithTitle:[MatrixKitL10n resetToDefault]
+ style:UIAlertActionStyleDefault
+ handler:^(UIAlertAction * action) {
+
+ if (weakSelf)
+ {
+ typeof(self) self = weakSelf;
+ self->currentAlert = nil;
+
+ [self setPowerLevel:self.mxRoomLiveTimeline.state.powerLevels.usersDefault promptUser:YES];
+ }
+
+ }]];
+ }
+
+ [currentAlert addTextFieldWithConfigurationHandler:^(UITextField *textField)
+ {
+ typeof(self) self = weakSelf;
+
+ textField.secureTextEntry = NO;
+ textField.text = [NSString stringWithFormat:@"%ld", (long)[self.mxRoomLiveTimeline.state.powerLevels powerLevelOfUserWithUserID:self.mxRoomMember.userId]];
+ textField.placeholder = nil;
+ textField.keyboardType = UIKeyboardTypeDecimalPad;
+ }];
+
+ [currentAlert addAction:[UIAlertAction actionWithTitle:[MatrixKitL10n ok]
+ style:UIAlertActionStyleDefault
+ handler:^(UIAlertAction * action) {
+
+ if (weakSelf)
+ {
+ typeof(self) self = weakSelf;
+ NSString *text = [self->currentAlert textFields].firstObject.text;
+ self->currentAlert = nil;
+
+ if (text.length > 0)
+ {
+ [self setPowerLevel:[text integerValue] promptUser:YES];
+ }
+ }
+
+ }]];
+
+ [self presentViewController:currentAlert animated:YES completion:nil];
+}
+
+@end
diff --git a/Riot/Modules/MatrixKit/Controllers/MXKRoomMemberDetailsViewController.xib b/Riot/Modules/MatrixKit/Controllers/MXKRoomMemberDetailsViewController.xib
new file mode 100644
index 000000000..238c3d370
--- /dev/null
+++ b/Riot/Modules/MatrixKit/Controllers/MXKRoomMemberDetailsViewController.xib
@@ -0,0 +1,73 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Lorem ipsum dolor sit er elit lamet, consectetaur cillium adipisicing pecu, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Nam liber te conscient to factor tum poen legum odioque civiuda.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Riot/Modules/MatrixKit/Controllers/MXKRoomMemberListViewController.h b/Riot/Modules/MatrixKit/Controllers/MXKRoomMemberListViewController.h
new file mode 100644
index 000000000..d35eb497b
--- /dev/null
+++ b/Riot/Modules/MatrixKit/Controllers/MXKRoomMemberListViewController.h
@@ -0,0 +1,116 @@
+/*
+ Copyright 2015 OpenMarket 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 "MXKViewController.h"
+#import "MXKRoomMemberListDataSource.h"
+
+@class MXKRoomMemberListViewController;
+
+/**
+ `MXKRoomMemberListViewController` delegate.
+ */
+@protocol MXKRoomMemberListViewControllerDelegate
+
+/**
+ Tells the delegate that the user selected a member.
+
+ @param roomMemberListViewController the `MXKRoomMemberListViewController` instance.
+ @param member the selected member.
+ */
+- (void)roomMemberListViewController:(MXKRoomMemberListViewController *)roomMemberListViewController didSelectMember:(MXRoomMember*)member;
+
+@end
+
+
+/**
+ This view controller displays members of a room. Only one matrix session is handled by this view controller.
+ */
+@interface MXKRoomMemberListViewController : MXKViewController
+{
+@protected
+ /**
+ Used to auto scroll at the top when search session is started or cancelled.
+ */
+ BOOL shouldScrollToTopOnRefresh;
+}
+
+@property (weak, nonatomic) IBOutlet UISearchBar *membersSearchBar;
+@property (weak, nonatomic) IBOutlet UITableView *membersTableView;
+@property (weak, nonatomic) IBOutlet NSLayoutConstraint *membersSearchBarTopConstraint;
+@property (weak, nonatomic) IBOutlet NSLayoutConstraint *membersSearchBarHeightConstraint;
+@property (weak, nonatomic) IBOutlet NSLayoutConstraint *membersTableViewBottomConstraint;
+
+/**
+ The current data source associated to the view controller.
+ */
+@property (nonatomic, readonly) MXKRoomMemberListDataSource *dataSource;
+
+/**
+ The delegate for the view controller.
+ */
+@property (nonatomic, weak) id delegate;
+
+/**
+ Enable the search in room members list according to the member's display name (YES by default).
+ Set NO this property to disable this option and hide the related bar button.
+ */
+@property (nonatomic) BOOL enableMemberSearch;
+
+/**
+ Enable the invitation of a new member (YES by default).
+ Set NO this property to disable this option and hide the related bar button.
+ */
+@property (nonatomic) BOOL enableMemberInvitation;
+
+#pragma mark - Class methods
+
+/**
+ Returns the `UINib` object initialized for a `MXKRoomMemberListViewController`.
+
+ @return The initialized `UINib` object or `nil` if there were errors during initialization
+ or the nib file could not be located.
+
+ @discussion You may override this method to provide a customized nib. If you do,
+ you should also override `roomMemberListViewController` to return your
+ view controller loaded from your custom nib.
+ */
++ (UINib *)nib;
+
+/**
+ Creates and returns a new `MXKRoomMemberListViewController` object.
+
+ @discussion This is the designated initializer for programmatic instantiation.
+ @return An initialized `MXKRoomMemberListViewController` object if successful, `nil` otherwise.
+ */
++ (instancetype)roomMemberListViewController;
+
+/**
+ Display the members list.
+
+ @param listDataSource the data source providing the members list.
+ */
+- (void)displayList:(MXKRoomMemberListDataSource*)listDataSource;
+
+/**
+ Scroll the members list to the top.
+
+ @param animated YES to animate the transition at a constant velocity to the new offset, NO to make the transition immediate.
+ */
+- (void)scrollToTop:(BOOL)animated;
+
+@end
diff --git a/Riot/Modules/MatrixKit/Controllers/MXKRoomMemberListViewController.m b/Riot/Modules/MatrixKit/Controllers/MXKRoomMemberListViewController.m
new file mode 100644
index 000000000..217fb4758
--- /dev/null
+++ b/Riot/Modules/MatrixKit/Controllers/MXKRoomMemberListViewController.m
@@ -0,0 +1,571 @@
+/*
+ Copyright 2015 OpenMarket Ltd
+ Copyright 2017 Vector Creations Ltd
+ Copyright 2018 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 "MXKRoomMemberListViewController.h"
+
+#import "MXKRoomMemberTableViewCell.h"
+
+#import "MXKConstants.h"
+
+#import "NSBundle+MatrixKit.h"
+
+#import "MXKSwiftHeader.h"
+
+@interface MXKRoomMemberListViewController ()
+{
+ /**
+ The data source providing UITableViewCells
+ */
+ MXKRoomMemberListDataSource *dataSource;
+
+ /**
+ Timer used to update members presence
+ */
+ NSTimer* presenceUpdateTimer;
+
+ /**
+ Optional bar buttons
+ */
+ UIBarButtonItem *searchBarButton;
+ UIBarButtonItem *addBarButton;
+
+ /**
+ The current displayed alert (if any).
+ */
+ UIAlertController *currentAlert;
+
+ /**
+ Search bar
+ */
+ BOOL ignoreSearchRequest;
+
+ /**
+ Observe kMXSessionWillLeaveRoomNotification to be notified if the user leaves the current room.
+ */
+ id leaveRoomNotificationObserver;
+}
+
+@end
+
+@implementation MXKRoomMemberListViewController
+@synthesize dataSource;
+
+#pragma mark - Class methods
+
++ (UINib *)nib
+{
+ return [UINib nibWithNibName:NSStringFromClass([MXKRoomMemberListViewController class])
+ bundle:[NSBundle bundleForClass:[MXKRoomMemberListViewController class]]];
+}
+
++ (instancetype)roomMemberListViewController
+{
+ return [[[self class] alloc] initWithNibName:NSStringFromClass([MXKRoomMemberListViewController class])
+ bundle:[NSBundle bundleForClass:[MXKRoomMemberListViewController class]]];
+}
+
+
+#pragma mark -
+
+- (void)finalizeInit
+{
+ [super finalizeInit];
+
+ // Enable both bar button by default.
+ _enableMemberInvitation = YES;
+ _enableMemberSearch = YES;
+}
+
+- (void)viewDidLoad
+{
+ [super viewDidLoad];
+
+ // Check whether the view controller has been pushed via storyboard
+ if (!self.membersTableView)
+ {
+ // Instantiate view controller objects
+ [[[self class] nib] instantiateWithOwner:self options:nil];
+ }
+
+ // Adjust Top and Bottom constraints to take into account potential navBar and tabBar.
+ [NSLayoutConstraint deactivateConstraints:@[_membersSearchBarTopConstraint, _membersTableViewBottomConstraint]];
+
+ _membersSearchBarTopConstraint = [NSLayoutConstraint constraintWithItem:self.topLayoutGuide
+ attribute:NSLayoutAttributeBottom
+ relatedBy:NSLayoutRelationEqual
+ toItem:self.membersSearchBar
+ attribute:NSLayoutAttributeTop
+ multiplier:1.0f
+ constant:0.0f];
+
+ _membersTableViewBottomConstraint = [NSLayoutConstraint constraintWithItem:self.bottomLayoutGuide
+ attribute:NSLayoutAttributeTop
+ relatedBy:NSLayoutRelationEqual
+ toItem:self.membersTableView
+ attribute:NSLayoutAttributeBottom
+ multiplier:1.0f
+ constant:0.0f];
+
+ [NSLayoutConstraint activateConstraints:@[_membersSearchBarTopConstraint, _membersTableViewBottomConstraint]];
+
+ // Hide search bar by default
+ self.membersSearchBar.hidden = YES;
+ self.membersSearchBarHeightConstraint.constant = 0;
+ [self.view setNeedsUpdateConstraints];
+
+ searchBarButton = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemSearch target:self action:@selector(search:)];
+ addBarButton = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemAdd target:self action:@selector(inviteNewMember:)];
+
+ // Refresh bar button display.
+ [self refreshUIBarButtons];
+
+ // Add an accessory view to the search bar in order to retrieve keyboard view.
+ self.membersSearchBar.inputAccessoryView = [[UIView alloc] initWithFrame:CGRectZero];
+
+ // Finalize table view configuration
+ self.membersTableView.delegate = self;
+ self.membersTableView.dataSource = dataSource; // Note datasource may be nil here.
+
+ // Set up default table view cell class
+ [self.membersTableView registerNib:MXKRoomMemberTableViewCell.nib forCellReuseIdentifier:MXKRoomMemberTableViewCell.defaultReuseIdentifier];
+}
+
+- (void)viewDidAppear:(BOOL)animated
+{
+ [super viewDidAppear:animated];
+
+ // Check whether the user still belongs to the room's members.
+ if (self.dataSource && [self.mainSession roomWithRoomId:self.dataSource.roomId])
+ {
+ [self refreshUIBarButtons];
+
+ // Observe kMXSessionWillLeaveRoomNotification to be notified if the user leaves the current room.
+ leaveRoomNotificationObserver = [[NSNotificationCenter defaultCenter] addObserverForName:kMXSessionWillLeaveRoomNotification object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *notif)
+ {
+
+ // Check whether the user will leave the room related to the displayed member list
+ if (notif.object == self.mainSession)
+ {
+ NSString *roomId = notif.userInfo[kMXSessionNotificationRoomIdKey];
+ if (roomId && [roomId isEqualToString:self.dataSource.roomId])
+ {
+ // We remove the current view controller.
+ [self withdrawViewControllerAnimated:YES completion:nil];
+ }
+ }
+ }];
+ }
+ else
+ {
+ // We remove the current view controller.
+ [self withdrawViewControllerAnimated:YES completion:nil];
+ }
+}
+
+- (void)viewWillAppear:(BOOL)animated
+{
+ [super viewWillAppear:animated];
+
+ // Restore search mechanism (if enabled)
+ ignoreSearchRequest = NO;
+}
+
+- (void)viewWillDisappear:(BOOL)animated
+{
+ [super viewWillDisappear:animated];
+
+ // The user may still press search button whereas the view disappears
+ ignoreSearchRequest = YES;
+
+ if (leaveRoomNotificationObserver)
+ {
+ [[NSNotificationCenter defaultCenter] removeObserver:leaveRoomNotificationObserver];
+ leaveRoomNotificationObserver = nil;
+ }
+
+ // Leave potential search session
+ if (!self.membersSearchBar.isHidden)
+ {
+ [self searchBarCancelButtonClicked:self.membersSearchBar];
+ }
+}
+
+- (void)dealloc
+{
+ self.membersSearchBar.inputAccessoryView = nil;
+}
+
+- (void)didReceiveMemoryWarning
+{
+ [super didReceiveMemoryWarning];
+
+ // Dispose of any resources that can be recreated.
+}
+
+#pragma mark - Override MXKTableViewController
+
+- (void)onKeyboardShowAnimationComplete
+{
+ // Report the keyboard view in order to track keyboard frame changes
+ self.keyboardView = _membersSearchBar.inputAccessoryView.superview;
+}
+
+- (void)setKeyboardHeight:(CGFloat)keyboardHeight
+{
+ // Deduce the bottom constraint for the table view (Don't forget the potential tabBar)
+ CGFloat tableViewBottomConst = keyboardHeight - self.bottomLayoutGuide.length;
+ // Check whether the keyboard is over the tabBar
+ if (tableViewBottomConst < 0)
+ {
+ tableViewBottomConst = 0;
+ }
+
+ // Update constraints
+ _membersTableViewBottomConstraint.constant = tableViewBottomConst;
+
+ // Force layout immediately to take into account new constraint
+ [self.view layoutIfNeeded];
+}
+
+- (void)destroy
+{
+ if (presenceUpdateTimer)
+ {
+ [presenceUpdateTimer invalidate];
+ presenceUpdateTimer = nil;
+ }
+
+ self.membersTableView.dataSource = nil;
+ self.membersTableView.delegate = nil;
+ self.membersTableView = nil;
+ dataSource.delegate = nil;
+ dataSource = nil;
+
+ if (currentAlert)
+ {
+ [currentAlert dismissViewControllerAnimated:NO completion:nil];
+ currentAlert = nil;
+ }
+
+ searchBarButton = nil;
+ addBarButton = nil;
+
+ _delegate = nil;
+
+ [super destroy];
+}
+
+#pragma mark - Internal methods
+
+- (void)updateMembersActivityInfo
+{
+ for (id memberCell in self.membersTableView.visibleCells)
+ {
+ if ([memberCell respondsToSelector:@selector(updateActivityInfo)])
+ {
+ [memberCell updateActivityInfo];
+ }
+ }
+}
+
+#pragma mark - UIBarButton handling
+
+- (void)setEnableMemberSearch:(BOOL)enableMemberSearch
+{
+ _enableMemberSearch = enableMemberSearch;
+ [self refreshUIBarButtons];
+}
+
+- (void)setEnableMemberInvitation:(BOOL)enableMemberInvitation
+{
+ _enableMemberInvitation = enableMemberInvitation;
+ [self refreshUIBarButtons];
+}
+
+- (void)refreshUIBarButtons
+{
+ MXRoom *mxRoom = [self.mainSession roomWithRoomId:dataSource.roomId];
+
+ MXWeakify(self);
+ [mxRoom state:^(MXRoomState *roomState) {
+ MXStrongifyAndReturnIfNil(self);
+
+ BOOL showInvitationOption = self.enableMemberInvitation;
+
+ if (showInvitationOption && self->dataSource)
+ {
+ // Check conditions to be able to invite someone
+ NSInteger oneSelfPowerLevel = [roomState.powerLevels powerLevelOfUserWithUserID:self.mainSession.myUser.userId];
+ if (oneSelfPowerLevel < [roomState.powerLevels invite])
+ {
+ showInvitationOption = NO;
+ }
+ }
+
+ if (showInvitationOption)
+ {
+ if (self.enableMemberSearch)
+ {
+ self.navigationItem.rightBarButtonItems = @[self->searchBarButton, self->addBarButton];
+ }
+ else
+ {
+ self.navigationItem.rightBarButtonItems = @[self->addBarButton];
+ }
+ }
+ else if (self.enableMemberSearch)
+ {
+ self.navigationItem.rightBarButtonItems = @[self->searchBarButton];
+ }
+ else
+ {
+ self.navigationItem.rightBarButtonItems = nil;
+ }
+ }];
+}
+
+#pragma mark -
+- (void)displayList:(MXKRoomMemberListDataSource *)listDataSource
+{
+ if (dataSource)
+ {
+ dataSource.delegate = nil;
+ dataSource = nil;
+ [self removeMatrixSession:self.mainSession];
+ }
+
+ dataSource = listDataSource;
+ dataSource.delegate = self;
+
+ // Report the matrix session at view controller level to update UI according to session state
+ [self addMatrixSession:dataSource.mxSession];
+
+ if (self.membersTableView)
+ {
+ // Set up table data source
+ self.membersTableView.dataSource = dataSource;
+ }
+}
+
+- (void)scrollToTop:(BOOL)animated
+{
+ [self.membersTableView setContentOffset:CGPointMake(-self.membersTableView.adjustedContentInset.left, -self.membersTableView.adjustedContentInset.top) animated:animated];
+}
+
+#pragma mark - MXKDataSourceDelegate
+
+- (Class)cellViewClassForCellData:(MXKCellData*)cellData
+{
+ // Return the default member table view cell
+ return MXKRoomMemberTableViewCell.class;
+}
+
+- (NSString *)cellReuseIdentifierForCellData:(MXKCellData*)cellData
+{
+ // Consider the default member table view cell
+ return MXKRoomMemberTableViewCell.defaultReuseIdentifier;
+}
+
+- (void)dataSource:(MXKDataSource *)dataSource didCellChange:(id)changes
+{
+ if (presenceUpdateTimer)
+ {
+ [presenceUpdateTimer invalidate];
+ presenceUpdateTimer = nil;
+ }
+
+ // For now, do a simple full reload
+ [self.membersTableView reloadData];
+
+ if (shouldScrollToTopOnRefresh)
+ {
+ [self scrollToTop:NO];
+ shouldScrollToTopOnRefresh = NO;
+ }
+
+ // Place a timer to update members's activity information
+ presenceUpdateTimer = [NSTimer scheduledTimerWithTimeInterval:5 target:self selector:@selector(updateMembersActivityInfo) userInfo:self repeats:YES];
+}
+
+#pragma mark - UITableView delegate
+- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
+{
+ return [dataSource cellHeightAtIndex:indexPath.row];
+}
+
+- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
+{
+ if (_delegate)
+ {
+ id cellData = [dataSource cellDataAtIndex:indexPath.row];
+
+ [_delegate roomMemberListViewController:self didSelectMember:cellData.roomMember];
+ }
+ [tableView deselectRowAtIndexPath:indexPath animated:NO];
+}
+
+- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section
+{
+ return 0;
+}
+
+- (void)tableView:(UITableView *)tableView didEndDisplayingCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath*)indexPath
+{
+ // Release here resources, and restore reusable cells
+ if ([cell respondsToSelector:@selector(didEndDisplay)])
+ {
+ [(id)cell didEndDisplay];
+ }
+}
+
+#pragma mark - UISearchBarDelegate
+
+- (void)searchBar:(UISearchBar *)searchBar textDidChange:(NSString *)searchText
+{
+ // Apply filter
+ shouldScrollToTopOnRefresh = YES;
+ if (searchText.length)
+ {
+ [self.dataSource searchWithPatterns:@[searchText]];
+ }
+ else
+ {
+ [self.dataSource searchWithPatterns:nil];
+ }
+}
+
+- (void)searchBarSearchButtonClicked:(UISearchBar *)searchBar
+{
+ // "Done" key has been pressed
+ [searchBar resignFirstResponder];
+}
+
+- (void)searchBarCancelButtonClicked:(UISearchBar *)searchBar
+{
+ // Leave search
+ [searchBar resignFirstResponder];
+
+ self.membersSearchBar.hidden = YES;
+ self.membersSearchBarHeightConstraint.constant = 0;
+ [self.view setNeedsUpdateConstraints];
+
+ self.membersSearchBar.text = nil;
+
+ // Refresh display
+ shouldScrollToTopOnRefresh = YES;
+ [self.dataSource searchWithPatterns:nil];
+}
+
+#pragma mark - Actions
+
+- (void)search:(id)sender
+{
+ // The user may have pressed search button whereas the view controller was disappearing
+ if (ignoreSearchRequest)
+ {
+ return;
+ }
+
+ if (self.membersSearchBar.isHidden)
+ {
+ // Check whether there are data in which search
+ if ([self.dataSource tableView:self.membersTableView numberOfRowsInSection:0])
+ {
+ self.membersSearchBar.hidden = NO;
+ self.membersSearchBarHeightConstraint.constant = 44;
+ [self.view setNeedsUpdateConstraints];
+
+ // Create search bar
+ [self.membersSearchBar becomeFirstResponder];
+ }
+ }
+ else
+ {
+ [self searchBarCancelButtonClicked: self.membersSearchBar];
+ }
+}
+
+- (void)inviteNewMember:(id)sender
+{
+ __weak typeof(self) weakSelf = self;
+
+ if (currentAlert)
+ {
+ [currentAlert dismissViewControllerAnimated:NO completion:nil];
+ }
+
+ // Ask for userId to invite
+ currentAlert = [UIAlertController alertControllerWithTitle:[MatrixKitL10n userIdTitle] message:nil preferredStyle:UIAlertControllerStyleAlert];
+
+
+ [currentAlert addAction:[UIAlertAction actionWithTitle:[MatrixKitL10n cancel]
+ style:UIAlertActionStyleDefault
+ handler:^(UIAlertAction * action) {
+
+ if (weakSelf)
+ {
+ typeof(self) self = weakSelf;
+ self->currentAlert = nil;
+ }
+
+ }]];
+
+
+ [currentAlert addTextFieldWithConfigurationHandler:^(UITextField *textField)
+ {
+ textField.secureTextEntry = NO;
+ textField.placeholder = [MatrixKitL10n userIdPlaceholder];
+ }];
+
+ [currentAlert addAction:[UIAlertAction actionWithTitle:[MatrixKitL10n invite]
+ style:UIAlertActionStyleDefault
+ handler:^(UIAlertAction * action) {
+
+ if (weakSelf)
+ {
+ typeof(self) self = weakSelf;
+
+ NSString *userId = [self->currentAlert textFields].firstObject.text;
+
+ self->currentAlert = nil;
+
+ if (userId.length)
+ {
+ MXRoom *mxRoom = [self.mainSession roomWithRoomId:self.dataSource.roomId];
+ if (mxRoom)
+ {
+ [mxRoom inviteUser:userId success:^{
+
+ } failure:^(NSError *error) {
+
+ MXLogDebug(@"[MXKRoomVC] Invite %@ failed", userId);
+ // Notify MatrixKit user
+ NSString *myUserId = self.mainSession.myUser.userId;
+ [[NSNotificationCenter defaultCenter] postNotificationName:kMXKErrorNotification object:error userInfo:myUserId ? @{kMXKErrorUserIdKey: myUserId} : nil];
+
+ }];
+ }
+ }
+ }
+
+ }]];
+
+ [self presentViewController:currentAlert animated:YES completion:nil];
+}
+
+@end
diff --git a/Riot/Modules/MatrixKit/Controllers/MXKRoomMemberListViewController.xib b/Riot/Modules/MatrixKit/Controllers/MXKRoomMemberListViewController.xib
new file mode 100644
index 000000000..4a339bdad
--- /dev/null
+++ b/Riot/Modules/MatrixKit/Controllers/MXKRoomMemberListViewController.xib
@@ -0,0 +1,53 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Riot/Modules/MatrixKit/Controllers/MXKRoomSettingsViewController.h b/Riot/Modules/MatrixKit/Controllers/MXKRoomSettingsViewController.h
new file mode 100644
index 000000000..663101028
--- /dev/null
+++ b/Riot/Modules/MatrixKit/Controllers/MXKRoomSettingsViewController.h
@@ -0,0 +1,81 @@
+/*
+ Copyright 2015 OpenMarket 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 "MXKTableViewController.h"
+
+/**
+ This view controller displays the room settings.
+ */
+@interface MXKRoomSettingsViewController : MXKTableViewController
+{
+@protected
+ // the dedicated room
+ MXRoom* mxRoom;
+
+ // the room state
+ MXRoomState* mxRoomState;
+}
+
+/**
+ The dedicated roomId.
+ */
+@property (nonatomic, readonly) NSString *roomId;
+
+
+#pragma mark - Class methods
+
+/**
+ Returns the `UINib` object initialized for a `MXKRoomSettingsViewController`.
+
+ @return The initialized `UINib` object or `nil` if there were errors during initialization
+ or the nib file could not be located.
+
+ @discussion You may override this method to provide a customized nib. If you do,
+ you should also override `roomViewController` to return your
+ view controller loaded from your custom nib.
+ */
++ (UINib *)nib;
+
+/**
+ Creates and returns a new `MXKRoomSettingsViewController` object.
+
+ @discussion This is the designated initializer for programmatic instantiation.
+ @return An initialized `MXKRoomSettingsViewController` object if successful, `nil` otherwise.
+ */
++ (instancetype)roomSettingsViewController;
+
+/**
+ Set the dedicated session and the room Id
+ */
+- (void)initWithSession:(MXSession*)session andRoomId:(NSString*)roomId;
+
+/**
+ Refresh the displayed room settings. By default this method reload the table view.
+
+ @discusion You may override this method to handle the table refresh.
+ */
+- (void)refreshRoomSettings;
+
+/**
+ Updates the display with a new room state.
+
+ @param newRoomState the new room state.
+ */
+- (void)updateRoomState:(MXRoomState*)newRoomState;
+
+@end
diff --git a/Riot/Modules/MatrixKit/Controllers/MXKRoomSettingsViewController.m b/Riot/Modules/MatrixKit/Controllers/MXKRoomSettingsViewController.m
new file mode 100644
index 000000000..8f729e9b9
--- /dev/null
+++ b/Riot/Modules/MatrixKit/Controllers/MXKRoomSettingsViewController.m
@@ -0,0 +1,215 @@
+/*
+ Copyright 2015 OpenMarket 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 "MXKRoomSettingsViewController.h"
+
+#import "NSBundle+MatrixKit.h"
+
+#import "MXKSwiftHeader.h"
+
+@interface MXKRoomSettingsViewController()
+{
+ // the room events listener
+ id roomListener;
+
+ // Observe kMXSessionWillLeaveRoomNotification to be notified if the user leaves the current room.
+ id leaveRoomNotificationObserver;
+
+ // Observe kMXRoomDidFlushDataNotification to take into account the updated room state when the room history is flushed.
+ id roomDidFlushDataNotificationObserver;
+}
+@end
+
+@implementation MXKRoomSettingsViewController
+
+#pragma mark - Class methods
+
++ (UINib *)nib
+{
+ return [UINib nibWithNibName:NSStringFromClass([MXKRoomSettingsViewController class])
+ bundle:[NSBundle bundleForClass:[MXKRoomSettingsViewController class]]];
+}
+
++ (instancetype)roomSettingsViewController
+{
+ return [[[self class] alloc] initWithNibName:NSStringFromClass([MXKRoomSettingsViewController class])
+ bundle:[NSBundle bundleForClass:[MXKRoomSettingsViewController class]]];
+}
+
+#pragma mark -
+
+- (void)viewWillAppear:(BOOL)animated
+{
+ [super viewWillAppear:animated];
+
+ [self refreshRoomSettings];
+}
+
+#pragma mark - Override MXKTableViewController
+
+- (void)finalizeInit
+{
+ [super finalizeInit];
+}
+
+- (void)destroy
+{
+ if (roomListener)
+ {
+ MXWeakify(self);
+ [mxRoom liveTimeline:^(MXEventTimeline *liveTimeline) {
+ MXStrongifyAndReturnIfNil(self);
+
+ [liveTimeline removeListener:self->roomListener];
+ self->roomListener = nil;
+ }];
+ }
+
+ if (leaveRoomNotificationObserver)
+ {
+ [[NSNotificationCenter defaultCenter] removeObserver:leaveRoomNotificationObserver];
+ leaveRoomNotificationObserver = nil;
+ }
+
+ if (roomDidFlushDataNotificationObserver)
+ {
+ [[NSNotificationCenter defaultCenter] removeObserver:roomDidFlushDataNotificationObserver];
+ roomDidFlushDataNotificationObserver = nil;
+ }
+
+ mxRoom = nil;
+ mxRoomState = nil;
+
+ [super destroy];
+}
+
+- (void)onMatrixSessionStateDidChange:(NSNotification *)notif;
+{
+ // Check this is our Matrix session that has changed
+ if (notif.object == self.mainSession)
+ {
+ [super onMatrixSessionStateDidChange:notif];
+
+ // refresh when the session sync is done.
+ if (MXSessionStateRunning == self.mainSession.state)
+ {
+ [self refreshRoomSettings];
+ }
+ }
+}
+
+#pragma mark - Public API
+
+/**
+ Set the dedicated session and the room Id
+ */
+- (void)initWithSession:(MXSession*)mxSession andRoomId:(NSString*)roomId
+{
+ // Update the matrix session
+ if (self.mainSession)
+ {
+ [self removeMatrixSession:self.mainSession];
+ }
+ mxRoom = nil;
+
+ // Sanity checks
+ if (mxSession && roomId)
+ {
+ [self addMatrixSession:mxSession];
+
+ // Report the room identifier
+ _roomId = roomId;
+ mxRoom = [mxSession roomWithRoomId:roomId];
+ }
+
+ if (mxRoom)
+ {
+ // Register a listener to handle messages related to room name, topic...
+ MXWeakify(self);
+ [mxRoom liveTimeline:^(MXEventTimeline *liveTimeline) {
+ MXStrongifyAndReturnIfNil(self);
+
+ self->roomListener = [liveTimeline listenToEventsOfTypes:@[kMXEventTypeStringRoomName, kMXEventTypeStringRoomTopic, kMXEventTypeStringRoomAliases, kMXEventTypeStringRoomAvatar, kMXEventTypeStringRoomPowerLevels, kMXEventTypeStringRoomCanonicalAlias, kMXEventTypeStringRoomJoinRules, kMXEventTypeStringRoomGuestAccess, kMXEventTypeStringRoomHistoryVisibility] onEvent:^(MXEvent *event, MXTimelineDirection direction, MXRoomState *roomState) {
+
+ // Consider only live events
+ if (direction == MXTimelineDirectionForwards)
+ {
+ [self updateRoomState:liveTimeline.state];
+ }
+ }];
+
+ // Observe kMXSessionWillLeaveRoomNotification to be notified if the user leaves the current room.
+ self->leaveRoomNotificationObserver = [[NSNotificationCenter defaultCenter] addObserverForName:kMXSessionWillLeaveRoomNotification object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *notif) {
+
+ // Check whether the user will leave the room related to the displayed participants
+ if (notif.object == self.mainSession)
+ {
+ NSString *roomId = notif.userInfo[kMXSessionNotificationRoomIdKey];
+ if (roomId && [roomId isEqualToString:self.roomId])
+ {
+ // We remove the current view controller.
+ [self withdrawViewControllerAnimated:YES completion:nil];
+ }
+ }
+ }];
+
+ // Observe room history flush (sync with limited timeline, or state event redaction)
+ self->roomDidFlushDataNotificationObserver = [[NSNotificationCenter defaultCenter] addObserverForName:kMXRoomDidFlushDataNotification object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *notif) {
+
+ MXRoom *room = notif.object;
+ if (self.mainSession == room.mxSession && [self.roomId isEqualToString:room.roomId])
+ {
+ // The existing room history has been flushed during server sync. Take into account the updated room state.
+ [self updateRoomState:liveTimeline.state];
+ }
+
+ }];
+
+ [self updateRoomState:liveTimeline.state];
+ }];
+ }
+
+ self.title = [MatrixKitL10n roomDetailsTitle];
+}
+
+- (void)refreshRoomSettings
+{
+ [self.tableView reloadData];
+}
+
+- (void)updateRoomState:(MXRoomState*)newRoomState
+{
+ mxRoomState = newRoomState.copy;
+
+ [self refreshRoomSettings];
+}
+
+#pragma mark - UITableViewDataSource
+
+// empty by default
+
+- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
+{
+ return 0;
+}
+
+- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
+{
+ // Return a fake cell to prevent app from crashing.
+ return [[UITableViewCell alloc] init];
+}
+
+@end
diff --git a/Riot/Modules/MatrixKit/Controllers/MXKRoomSettingsViewController.xib b/Riot/Modules/MatrixKit/Controllers/MXKRoomSettingsViewController.xib
new file mode 100644
index 000000000..4dbf0f338
--- /dev/null
+++ b/Riot/Modules/MatrixKit/Controllers/MXKRoomSettingsViewController.xib
@@ -0,0 +1,27 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Riot/Modules/MatrixKit/Controllers/MXKRoomViewController.h b/Riot/Modules/MatrixKit/Controllers/MXKRoomViewController.h
new file mode 100644
index 000000000..8ae86eccb
--- /dev/null
+++ b/Riot/Modules/MatrixKit/Controllers/MXKRoomViewController.h
@@ -0,0 +1,440 @@
+/*
+ Copyright 2015 OpenMarket Ltd
+ Copyright 2019 The Matrix.org Foundation C.I.C
+
+ 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 "MXKViewController.h"
+#import "MXKRoomDataSource.h"
+#import "MXKRoomTitleView.h"
+#import "MXKRoomInputToolbarView.h"
+#import "MXKRoomActivitiesView.h"
+#import "MXKEventDetailsView.h"
+
+#import "MXKAttachmentsViewController.h"
+#import "MXKAttachmentAnimator.h"
+
+typedef NS_ENUM(NSUInteger, MXKRoomViewControllerJoinRoomResult) {
+ MXKRoomViewControllerJoinRoomResultSuccess,
+ MXKRoomViewControllerJoinRoomResultFailureRoomEmpty,
+ MXKRoomViewControllerJoinRoomResultFailureJoinInProgress,
+ MXKRoomViewControllerJoinRoomResultFailureGeneric
+};
+
+/**
+ This view controller displays messages of a room. Only one matrix session is handled by this view controller.
+ */
+@interface MXKRoomViewController : MXKViewController
+{
+@protected
+ /**
+ The identifier of the current event displayed at the bottom of the table (just above the toolbar).
+ Use to anchor the message displayed at the bottom during table refresh.
+ */
+ NSString *currentEventIdAtTableBottom;
+
+ /**
+ Boolean value used to scroll to bottom the bubble history after refresh.
+ */
+ BOOL shouldScrollToBottomOnTableRefresh;
+
+ /**
+ Potential event details view.
+ */
+ __weak MXKEventDetailsView *eventDetailsView;
+
+ /**
+ Current alert (if any).
+ */
+ __weak UIAlertController *currentAlert;
+
+ /**
+ The document interaction Controller used to share attachment
+ */
+ UIDocumentInteractionController *documentInteractionController;
+
+ /**
+ The current shared attachment.
+ */
+ MXKAttachment *currentSharedAttachment;
+
+ /**
+ The potential text input placeholder is saved when it is replaced temporarily
+ */
+ NSString *savedInputToolbarPlaceholder;
+
+ /**
+ Tell whether the input toolbar required to run an animation indicator.
+ */
+ BOOL isInputToolbarProcessing;
+
+ /**
+ Tell whether a device rotation is in progress
+ */
+ BOOL isSizeTransitionInProgress;
+
+ /**
+ The current visibility of the status bar in this view controller.
+ */
+ BOOL isStatusBarHidden;
+
+ /**
+ YES to prevent `bubblesTableView` scrolling when calling -[setBubbleTableViewContentOffset:animated:]
+ */
+ BOOL preventBubblesTableViewScroll;
+}
+
+/**
+ The current data source associated to the view controller.
+ */
+@property (nonatomic, readonly) MXKRoomDataSource *roomDataSource;
+
+/**
+ Flag indicating if this instance has the memory ownership of its `roomDataSource`.
+ If YES, it will release it on [self destroy] call;
+ Default is NO.
+ */
+@property (nonatomic) BOOL hasRoomDataSourceOwnership;
+
+/**
+ Tell whether the bubbles table view display is in transition. Its display is not warranty during the transition.
+ */
+@property (nonatomic, getter=isBubbleTableViewDisplayInTransition) BOOL bubbleTableViewDisplayInTransition;
+
+/**
+ Tell whether the automatic events acknowledgement (based on read receipt) is enabled.
+ Default is YES.
+ */
+@property (nonatomic, getter=isEventsAcknowledgementEnabled) BOOL eventsAcknowledgementEnabled;
+
+/**
+ Tell whether the room read marker must be updated when an event is acknowledged with a read receipt.
+ Default is NO.
+ */
+@property (nonatomic) BOOL updateRoomReadMarker;
+
+/**
+ When the room view controller displays a room data source based on a timeline with an initial event,
+ the bubble table view content is scrolled by default to display the top of this event at the center of the screen
+ the first time it appears.
+ Use this property to force the table view to center its content on the bottom part of the event.
+ Default is NO.
+ */
+@property (nonatomic) BOOL centerBubblesTableViewContentOnTheInitialEventBottom;
+
+/**
+ The current title view defined into the view controller.
+ */
+@property (nonatomic, weak, readonly) MXKRoomTitleView* titleView;
+
+/**
+ The current input toolbar view defined into the view controller.
+ */
+@property (nonatomic, weak, readonly) MXKRoomInputToolbarView* inputToolbarView;
+
+/**
+ The current extra info view defined into the view controller.
+ */
+@property (nonatomic, readonly) MXKRoomActivitiesView* activitiesView;
+
+/**
+ The threshold used to trigger inconspicuous back pagination, or forwards pagination
+ for non live timeline. A pagination is triggered when the vertical content offset
+ is lower this threshold.
+ Default is 300.
+ */
+@property (nonatomic) NSUInteger paginationThreshold;
+
+/**
+ The maximum number of messages to retrieve during a pagination. Default is 30.
+ */
+@property (nonatomic) NSUInteger paginationLimit;
+
+/**
+ Enable/disable saving of the current typed text in message composer when view disappears.
+ The message composer is prefilled with this text when the room is opened again.
+ This property value is YES by default.
+ */
+@property BOOL saveProgressTextInput;
+
+/**
+ The invited rooms can be automatically joined when the data source is ready.
+ This property enable/disable this option. Its value is YES by default.
+ */
+@property BOOL autoJoinInvitedRoom;
+
+/**
+ Tell whether the room history is automatically scrolled to the most recent messages
+ when a keyboard is presented. YES by default.
+ This option is ignored when an alert is presented.
+ */
+@property BOOL scrollHistoryToTheBottomOnKeyboardPresentation;
+
+/**
+ YES (default) to show actions button in document preview. NO otherwise.
+ */
+@property BOOL allowActionsInDocumentPreview;
+
+/**
+ Duration of the animation in case of the composer needs to be resized (default 0.3s)
+ */
+@property NSTimeInterval resizeComposerAnimationDuration;
+
+/**
+ This object is defined when the displayed room is left. It is added into the bubbles table header.
+ This label is used to display the reason why the room has been left.
+ */
+@property (nonatomic, weak, readonly) UILabel *leftRoomReasonLabel;
+
+@property (weak, nonatomic) IBOutlet UITableView *bubblesTableView;
+@property (weak, nonatomic) IBOutlet UIView *roomTitleViewContainer;
+@property (weak, nonatomic) IBOutlet UIView *roomInputToolbarContainer;
+@property (weak, nonatomic) IBOutlet UIView *roomActivitiesContainer;
+
+@property (weak, nonatomic) IBOutlet NSLayoutConstraint *bubblesTableViewTopConstraint;
+@property (weak, nonatomic) IBOutlet NSLayoutConstraint *bubblesTableViewBottomConstraint;
+@property (weak, nonatomic) IBOutlet NSLayoutConstraint *roomActivitiesContainerHeightConstraint;
+@property (weak, nonatomic) IBOutlet NSLayoutConstraint *roomInputToolbarContainerHeightConstraint;
+@property (weak, nonatomic) IBOutlet NSLayoutConstraint *roomInputToolbarContainerBottomConstraint;
+
+#pragma mark - Class methods
+
+/**
+ Returns the `UINib` object initialized for a `MXKRoomViewController`.
+
+ @return The initialized `UINib` object or `nil` if there were errors during initialization
+ or the nib file could not be located.
+
+ @discussion You may override this method to provide a customized nib. If you do,
+ you should also override `roomViewController` to return your
+ view controller loaded from your custom nib.
+ */
++ (UINib *)nib;
+
+/**
+ Creates and returns a new `MXKRoomViewController` object.
+
+ @discussion This is the designated initializer for programmatic instantiation.
+ @return An initialized `MXKRoomViewController` object if successful, `nil` otherwise.
+ */
++ (instancetype)roomViewController;
+
+/**
+ Display a room.
+
+ @param dataSource the data source .
+ */
+- (void)displayRoom:(MXKRoomDataSource*)dataSource;
+
+/**
+ This method is called when the associated data source is ready.
+
+ By default this operation triggers the initial back pagination when the user is an actual
+ member of the room (membership = join).
+
+ The invited rooms are automatically joined during this operation if 'autoJoinInvitedRoom' is YES.
+ When the room is successfully joined, an initial back pagination is triggered too.
+ Else nothing is done for the invited rooms.
+
+ Override it to customize the view controller behavior when the data source is ready.
+ */
+- (void)onRoomDataSourceReady;
+
+/**
+ Update view controller appearance according to the state of its associated data source.
+ This method is called in the following use cases:
+ - on data source change (see `[MXKRoomViewController displayRoom:]`).
+ - on data source state change (see `[MXKDataSourceDelegate dataSource:didStateChange:]`)
+ - when view did appear.
+
+ The default implementation:
+ - show input toolbar view if the dataSource is defined and ready (`MXKDataSourceStateReady`), hide toolbar in others use cases.
+ - stop activity indicator if the dataSource is defined and ready (`MXKDataSourceStateReady`).
+ - update view controller title with room information.
+
+ Override it to customize view appearance according to data source state.
+ */
+- (void)updateViewControllerAppearanceOnRoomDataSourceState;
+
+/**
+ This method is called when the associated data source has encountered an error on the timeline.
+
+ Override it to customize the view controller behavior.
+
+ @param notif the notification data sent with kMXKRoomDataSourceTimelineError notif.
+ */
+- (void)onTimelineError:(NSNotification *)notif;
+
+/**
+ Join the current displayed room.
+
+ This operation fails if the user has already joined the room, or if the data source is not ready.
+ It fails if a join request is already running too.
+
+ @param completion the block to execute at the end of the operation.
+ You may specify nil for this parameter.
+ */
+- (void)joinRoom:(void(^)(MXKRoomViewControllerJoinRoomResult result))completion;
+
+/**
+ Join a room with a room id or an alias.
+
+ This operation fails if the user has already joined the room, or if the data source is not ready,
+ or if the access to the room is forbidden to the user.
+ It fails if a join request is already running too.
+
+ @param roomIdOrAlias the id or the alias of the room to join.
+ @param viaServers The server names to try and join through in addition to those that are automatically chosen. It is optional and can be nil.
+ @param signUrl the signurl paramater passed with a 3PID invitation. It is optional and can be nil.
+
+ @param completion the block to execute at the end of the operation.
+ You may specify nil for this parameter.
+ */
+- (void)joinRoomWithRoomIdOrAlias:(NSString*)roomIdOrAlias
+ viaServers:(NSArray*)viaServers
+ andSignUrl:(NSString*)signUrl
+ completion:(void(^)(MXKRoomViewControllerJoinRoomResult result))completion;
+
+/**
+ Update view controller appearance when the user is about to leave the displayed room.
+ This method is called when the user will leave the current room (see `kMXSessionWillLeaveRoomNotification`).
+
+ The default implementation:
+ - discard `roomDataSource`
+ - hide input toolbar view
+ - freeze the room title display
+ - add a label (`leftRoomReasonLabel`) in bubbles table header to display the reason why the room has been left.
+
+ Override it to customize view appearance, or to withdraw the view controller.
+
+ @param event the MXEvent responsible for the leaving.
+ */
+- (void)leaveRoomOnEvent:(MXEvent*)event;
+
+/**
+ Register the class used to instantiate the title view which will handle the room name display.
+
+ The resulting view is added into 'roomTitleViewContainer' view, which must be defined before calling this method.
+
+ Note: By default the room name is displayed by using 'navigationItem.title' field of the view controller.
+
+ @param roomTitleViewClass a MXKRoomTitleView-inherited class.
+ */
+- (void)setRoomTitleViewClass:(Class)roomTitleViewClass;
+
+/**
+ Register the class used to instantiate the input toolbar view which will handle message composer
+ and attachments selection for the room.
+
+ The resulting view is added into 'roomInputToolbarContainer' view, which must be defined before calling this method.
+
+ @param roomInputToolbarViewClass a MXKRoomInputToolbarView-inherited class, or nil to remove the current view.
+ */
+- (void)setRoomInputToolbarViewClass:(Class)roomInputToolbarViewClass;
+
+/**
+ Register the class used to instantiate the extra info view.
+
+ The resulting view is added into 'roomActivitiesContainer' view, which must be defined before calling this method.
+
+ @param roomActivitiesViewClass a MXKRoomActivitiesViewClass-inherited class, or nil to remove the current view.
+ */
+- (void)setRoomActivitiesViewClass:(Class)roomActivitiesViewClass;
+
+/**
+ Register the class used to instantiate the viewer dedicated to the attachments with thumbnail.
+ By default 'MXKAttachmentsViewController' class is used.
+
+ @param attachmentsViewerClass a MXKAttachmentsViewController-inherited class, or nil to restore the default class.
+ */
+- (void)setAttachmentsViewerClass:(Class)attachmentsViewerClass;
+
+/**
+ Register the view class used to display the details of an event.
+ MXKEventDetailsView is used by default.
+
+ @param eventDetailsViewClass a MXKEventDetailsView-inherited class.
+ */
+- (void)setEventDetailsViewClass:(Class)eventDetailsViewClass;
+
+/**
+ Detect and process potential IRC command in provided string.
+
+ @param string to analyse
+ @return YES if IRC style command has been detected and interpreted.
+ */
+- (BOOL)isIRCStyleCommand:(NSString*)string;
+
+/**
+ Mention the member display name in the current text of the message composer.
+ The message composer becomes then the first responder.
+ */
+- (void)mention:(MXRoomMember*)roomMember;
+
+/**
+ Force to dismiss keyboard if any
+ */
+- (void)dismissKeyboard;
+
+/**
+ Tell whether the most recent message of the room history is visible.
+ */
+- (BOOL)isBubblesTableScrollViewAtTheBottom;
+
+/**
+ Scroll the room history until the most recent message.
+ */
+- (void)scrollBubblesTableViewToBottomAnimated:(BOOL)animated;
+
+/**
+ Dismiss the keyboard and all the potential subviews.
+ */
+- (void)dismissTemporarySubViews;
+
+/**
+ Display a popup with the event detais.
+
+ @param event the event to inspect.
+ */
+- (void)showEventDetails:(MXEvent *)event;
+
+/**
+ Present the attachments viewer by displaying the attachment of the provided cell.
+
+ @param cell the table view cell with attachment
+ */
+- (void)showAttachmentInCell:(UITableViewCell*)cell;
+
+/**
+ Force a refresh of the room history display.
+
+ You should not call this method directly.
+ You may override it in inherited 'MXKRoomViewController' class.
+
+ @param useBottomAnchor tells whether the updated history must keep display the same event at the bottom.
+ @return a boolean value which tells whether the table has been scrolled to the bottom.
+ */
+- (BOOL)reloadBubblesTable:(BOOL)useBottomAnchor;
+
+/**
+ Sets the offset from the content `bubblesTableView`'s origin. Take into account `preventBubblesTableViewScroll` value.
+
+ @param contentOffset Offset from the content `bubblesTableView`’s origin.
+ @param animated YES to animate the transition.
+ */
+- (void)setBubbleTableViewContentOffset:(CGPoint)contentOffset animated:(BOOL)animated;
+
+@end
diff --git a/Riot/Modules/MatrixKit/Controllers/MXKRoomViewController.m b/Riot/Modules/MatrixKit/Controllers/MXKRoomViewController.m
new file mode 100644
index 000000000..382373a92
--- /dev/null
+++ b/Riot/Modules/MatrixKit/Controllers/MXKRoomViewController.m
@@ -0,0 +1,4066 @@
+/*
+ Copyright 2015 OpenMarket Ltd
+ Copyright 2017 Vector Creations Ltd
+ Copyright 2018 New Vector Ltd
+ Copyright 2019 The Matrix.org Foundation C.I.C
+
+ 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.
+ */
+
+#define MXKROOMVIEWCONTROLLER_DEFAULT_TYPING_TIMEOUT_SEC 10
+#define MXKROOMVIEWCONTROLLER_MESSAGES_TABLE_MINIMUM_HEIGHT 50
+
+#import "MXKRoomViewController.h"
+
+#import
+
+#import "MXKRoomBubbleTableViewCell.h"
+#import "MXKSearchTableViewCell.h"
+#import "MXKImageView.h"
+
+#import "MXKRoomDataSourceManager.h"
+
+#import "MXKRoomInputToolbarViewWithSimpleTextView.h"
+
+#import "MXKConstants.h"
+
+#import "MXKRoomBubbleCellData.h"
+
+#import "MXKRoomIncomingTextMsgBubbleCell.h"
+#import "MXKRoomIncomingTextMsgWithoutSenderInfoBubbleCell.h"
+#import "MXKRoomIncomingAttachmentBubbleCell.h"
+#import "MXKRoomIncomingAttachmentWithoutSenderInfoBubbleCell.h"
+
+#import "MXKRoomOutgoingTextMsgBubbleCell.h"
+#import "MXKRoomOutgoingTextMsgWithoutSenderInfoBubbleCell.h"
+#import "MXKRoomOutgoingAttachmentBubbleCell.h"
+#import "MXKRoomOutgoingAttachmentWithoutSenderInfoBubbleCell.h"
+
+#import "MXKEncryptionKeysImportView.h"
+
+#import "NSBundle+MatrixKit.h"
+#import "MXKSlashCommands.h"
+#import "MXKSwiftHeader.h"
+
+#import "MXKPreviewViewController.h"
+
+@interface MXKRoomViewController ()
+{
+ /**
+ YES once the view has appeared
+ */
+ BOOL hasAppearedOnce;
+
+ /**
+ YES if scrolling to bottom is in progress
+ */
+ BOOL isScrollingToBottom;
+
+ /**
+ Date of the last observed typing
+ */
+ NSDate *lastTypingDate;
+
+ /**
+ Local typing timout
+ */
+ NSTimer *typingTimer;
+
+ /**
+ YES when pagination is in progress.
+ */
+ BOOL isPaginationInProgress;
+
+ /**
+ The back pagination spinner view.
+ */
+ UIView* backPaginationActivityView;
+
+ /**
+ Store the height of the first bubble before back pagination.
+ */
+ CGFloat backPaginationSavedFirstBubbleHeight;
+
+ /**
+ Potential request in progress to join the selected room
+ */
+ MXHTTPOperation *joinRoomRequest;
+
+ /**
+ Text selection
+ */
+ NSString *selectedText;
+
+ /**
+ The class used to instantiate attachments viewer for image and video..
+ */
+ Class attachmentsViewerClass;
+
+ /**
+ The class used to display event details.
+ */
+ Class customEventDetailsViewClass;
+
+ /**
+ The reconnection animated view.
+ */
+ UIView* reconnectingView;
+
+ /**
+ The view to import e2e keys.
+ */
+ MXKEncryptionKeysImportView *importView;
+
+ /**
+ The latest server sync date
+ */
+ NSDate* latestServerSync;
+
+ /**
+ The restart the event connnection
+ */
+ BOOL restartConnection;
+}
+
+/**
+ The eventId of the Attachment that was used to open the Attachments ViewController
+ */
+@property (nonatomic) NSString *openedAttachmentEventId;
+
+/**
+ The eventId of the Attachment from which the Attachments ViewController was closed
+ */
+@property (nonatomic) NSString *closedAttachmentEventId;
+
+@property (nonatomic) UIImageView *openedAttachmentImageView;
+
+/**
+ Observe kMXSessionWillLeaveRoomNotification to be notified if the user leaves the current room.
+ */
+@property (nonatomic, weak) id mxSessionWillLeaveRoomNotificationObserver;
+
+/**
+ Observe UIApplicationDidBecomeActiveNotification to refresh bubbles when app leaves the background state.
+ */
+@property (nonatomic, weak) id uiApplicationDidBecomeActiveNotificationObserver;
+
+/**
+ Observe UIMenuControllerDidHideMenuNotification to cancel text selection
+ */
+@property (nonatomic, weak) id uiMenuControllerDidHideMenuNotificationObserver;
+
+/**
+ The attachments viewer for image and video.
+ */
+@property (nonatomic, weak) MXKAttachmentsViewController *attachmentsViewer;
+
+@end
+
+@implementation MXKRoomViewController
+@synthesize roomDataSource, titleView, inputToolbarView, activitiesView;
+
+#pragma mark - Class methods
+
++ (UINib *)nib
+{
+ return [UINib nibWithNibName:NSStringFromClass([MXKRoomViewController class])
+ bundle:[NSBundle bundleForClass:[MXKRoomViewController class]]];
+}
+
++ (instancetype)roomViewController
+{
+ return [[[self class] alloc] initWithNibName:NSStringFromClass([MXKRoomViewController class])
+ bundle:[NSBundle bundleForClass:[MXKRoomViewController class]]];
+}
+
+#pragma mark -
+
+- (void)finalizeInit
+{
+ [super finalizeInit];
+
+ // Scroll to bottom the bubble history at first display
+ shouldScrollToBottomOnTableRefresh = YES;
+
+ // Default pagination settings
+ _paginationThreshold = 300;
+ _paginationLimit = 30;
+
+ // Save progress text input by default
+ _saveProgressTextInput = YES;
+
+ // Enable auto join option by default
+ _autoJoinInvitedRoom = YES;
+
+ // Do not take ownership of room data source by default
+ _hasRoomDataSourceOwnership = NO;
+
+ // Turn on the automatic events acknowledgement.
+ _eventsAcknowledgementEnabled = YES;
+
+ // Do not update the read marker by default.
+ _updateRoomReadMarker = NO;
+
+ // Center the table content on the initial event top by default.
+ _centerBubblesTableViewContentOnTheInitialEventBottom = NO;
+
+ // Scroll to the bottom when a keyboard is presented
+ _scrollHistoryToTheBottomOnKeyboardPresentation = YES;
+
+ // Keep visible the status bar by default.
+ isStatusBarHidden = NO;
+
+ // By default actions button is shown in document preview
+ _allowActionsInDocumentPreview = YES;
+
+ // By default the duration of the composer resizing is 0.3s
+ _resizeComposerAnimationDuration = 0.3;
+}
+
+- (void)viewDidLoad
+{
+ [super viewDidLoad];
+
+ // Check whether the view controller has been pushed via storyboard
+ if (!_bubblesTableView)
+ {
+ // Instantiate view controller objects
+ [[[self class] nib] instantiateWithOwner:self options:nil];
+ }
+
+ // Adjust bottom constraint of the input toolbar container in order to take into account potential tabBar
+ _roomInputToolbarContainerBottomConstraint.active = NO;
+ _roomInputToolbarContainerBottomConstraint = [NSLayoutConstraint constraintWithItem:self.bottomLayoutGuide
+ attribute:NSLayoutAttributeTop
+ relatedBy:NSLayoutRelationEqual
+ toItem:self.roomInputToolbarContainer
+ attribute:NSLayoutAttributeBottom
+ multiplier:1.0f
+ constant:0.0f];
+ _roomInputToolbarContainerBottomConstraint.active = YES;
+ [self.view setNeedsUpdateConstraints];
+
+ // Hide bubbles table by default in order to hide initial scrolling to the bottom
+ _bubblesTableView.hidden = YES;
+
+ // Ensure that the titleView will be scaled when it will be required
+ // during a screen rotation for example.
+ _roomTitleViewContainer.autoresizingMask = UIViewAutoresizingFlexibleWidth;
+
+ // Set default input toolbar view
+ [self setRoomInputToolbarViewClass:MXKRoomInputToolbarViewWithSimpleTextView.class];
+
+ // set the default extra
+ [self setRoomActivitiesViewClass:MXKRoomActivitiesView.class];
+
+ // Finalize table view configuration
+ [self configureBubblesTableView];
+
+ // Observe UIApplicationDidBecomeActiveNotification to refresh bubbles when app leaves the background state.
+ MXWeakify(self);
+ _uiApplicationDidBecomeActiveNotificationObserver = [[NSNotificationCenter defaultCenter] addObserverForName:UIApplicationDidBecomeActiveNotification object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *notif) {
+
+ MXStrongifyAndReturnIfNil(self);
+ if (self->roomDataSource.state == MXKDataSourceStateReady && [self->roomDataSource tableView:self->_bubblesTableView numberOfRowsInSection:0])
+ {
+ // Reload the full table
+ self.bubbleTableViewDisplayInTransition = YES;
+ [self reloadBubblesTable:YES];
+ self.bubbleTableViewDisplayInTransition = NO;
+ }
+ }];
+
+ if ([MXKAppSettings standardAppSettings].outboundGroupSessionKeyPreSharingStrategy == MXKKeyPreSharingWhenEnteringRoom)
+ {
+ [self shareEncryptionKeys];
+ }
+}
+
+- (BOOL)prefersStatusBarHidden
+{
+ // Return the current status bar visibility.
+ // Caution: Enable [UIViewController prefersStatusBarHidden] use at application level
+ // by turning on UIViewControllerBasedStatusBarAppearance in Info.plist.
+ return isStatusBarHidden;
+}
+
+- (void)viewWillAppear:(BOOL)animated
+{
+ [super viewWillAppear:animated];
+
+ // Observe server sync process at room data source level too
+ [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(onMatrixSessionChange) name:kMXKRoomDataSourceSyncStatusChanged object:nil];
+
+ // Observe timeline failure
+ [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(onTimelineError:) name:kMXKRoomDataSourceTimelineError object:nil];
+
+ // Observe the server sync
+ [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(onSyncNotification) name:kMXSessionDidSyncNotification object:nil];
+
+ // Be sure to display the activity indicator during back pagination
+ if (isPaginationInProgress)
+ {
+ [self startActivityIndicator];
+ }
+
+ // Finalize view controller appearance
+ [self updateViewControllerAppearanceOnRoomDataSourceState];
+
+ // no need to reload the tableview at this stage
+ // IOS is going to load it after calling this method
+ // so give a breath to scroll to the bottom if required
+ if (shouldScrollToBottomOnTableRefresh)
+ {
+ self.bubbleTableViewDisplayInTransition = YES;
+
+ dispatch_async(dispatch_get_main_queue(), ^{
+
+ [self scrollBubblesTableViewToBottomAnimated:NO];
+
+ // Show bubbles table after initial scrolling to the bottom
+ // Patch: We need to delay this operation to wait for the end of scrolling.
+ dispatch_after(dispatch_walltime(DISPATCH_TIME_NOW, 0.3 * NSEC_PER_SEC), dispatch_get_main_queue(), ^{
+
+ self->_bubblesTableView.hidden = NO;
+ self.bubbleTableViewDisplayInTransition = NO;
+
+ });
+
+ });
+ }
+ else
+ {
+ _bubblesTableView.hidden = NO;
+ }
+}
+
+- (void)viewDidAppear:(BOOL)animated
+{
+ [super viewDidAppear:animated];
+
+ // Remove the rounded bottom unsafe area of the iPhone X
+ _bubblesTableViewBottomConstraint.constant += self.view.safeAreaInsets.bottom;
+
+ if (_saveProgressTextInput && roomDataSource)
+ {
+ // Retrieve the potential message partially typed during last room display.
+ // Note: We have to wait for viewDidAppear before updating growingTextView (viewWillAppear is too early)
+ inputToolbarView.textMessage = roomDataSource.partialTextMessage;
+ }
+
+ if (!hasAppearedOnce)
+ {
+ hasAppearedOnce = YES;
+ }
+
+ // Mark all messages as read when the room is displayed
+ [self.roomDataSource.room.summary markAllAsReadLocally];
+}
+
+- (void)viewWillDisappear:(BOOL)animated
+{
+ [super viewWillDisappear:animated];
+
+ [[NSNotificationCenter defaultCenter] removeObserver:self name:kMXKRoomDataSourceSyncStatusChanged object:nil];
+ [[NSNotificationCenter defaultCenter] removeObserver:self name:kMXKRoomDataSourceTimelineError object:nil];
+ [[NSNotificationCenter defaultCenter] removeObserver:self name:kMXSessionDidSyncNotification object:nil];
+
+ [self removeReconnectingView];
+}
+
+- (void)dealloc
+{
+ if (_mxSessionWillLeaveRoomNotificationObserver)
+ {
+ [[NSNotificationCenter defaultCenter] removeObserver:_mxSessionWillLeaveRoomNotificationObserver];
+ }
+
+ if (_uiApplicationDidBecomeActiveNotificationObserver)
+ {
+ [[NSNotificationCenter defaultCenter] removeObserver:_uiApplicationDidBecomeActiveNotificationObserver];
+ }
+
+ if (_uiMenuControllerDidHideMenuNotificationObserver)
+ {
+ [[NSNotificationCenter defaultCenter] removeObserver:_uiMenuControllerDidHideMenuNotificationObserver];
+ }
+
+ [self destroy];
+}
+
+- (void)didReceiveMemoryWarning
+{
+ [super didReceiveMemoryWarning];
+
+ // Dispose of any resources that can be recreated.
+}
+
+- (void)viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(id )coordinator
+{
+ isSizeTransitionInProgress = YES;
+ shouldScrollToBottomOnTableRefresh = [self isBubblesTableScrollViewAtTheBottom];
+
+ [super viewWillTransitionToSize:size withTransitionCoordinator:coordinator];
+
+ dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(coordinator.transitionDuration * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
+
+ if (!self.keyboardView)
+ {
+ [self updateMessageTextViewFrame];
+ }
+
+ // Force full table refresh to take into account cell width change.
+ self.bubbleTableViewDisplayInTransition = YES;
+ [self reloadBubblesTable:YES invalidateBubblesCellDataCache:YES];
+ self.bubbleTableViewDisplayInTransition = NO;
+
+ self->shouldScrollToBottomOnTableRefresh = NO;
+ self->isSizeTransitionInProgress = NO;
+ });
+}
+
+// The 2 following methods are deprecated since iOS 8
+- (void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration
+{
+ isSizeTransitionInProgress = YES;
+ shouldScrollToBottomOnTableRefresh = [self isBubblesTableScrollViewAtTheBottom];
+
+ [super willRotateToInterfaceOrientation:toInterfaceOrientation duration:duration];
+}
+- (void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation
+{
+ [super didRotateFromInterfaceOrientation:fromInterfaceOrientation];
+
+ if (!self.keyboardView)
+ {
+ [self updateMessageTextViewFrame];
+ }
+
+ dispatch_async(dispatch_get_main_queue(), ^{
+ // Force full table refresh to take into account cell width change.
+ self.bubbleTableViewDisplayInTransition = YES;
+ [self reloadBubblesTable:YES];
+ self.bubbleTableViewDisplayInTransition = NO;
+
+ self->shouldScrollToBottomOnTableRefresh = NO;
+ self->isSizeTransitionInProgress = NO;
+ });
+}
+
+- (void)viewDidLayoutSubviews
+{
+ [super viewDidLayoutSubviews];
+
+ CGFloat bubblesTableViewBottomConst = self.roomInputToolbarContainerBottomConstraint.constant + self.roomInputToolbarContainerHeightConstraint.constant + self.roomActivitiesContainerHeightConstraint.constant;
+
+ if (self.bubblesTableViewBottomConstraint.constant != bubblesTableViewBottomConst)
+ {
+ self.bubblesTableViewBottomConstraint.constant = bubblesTableViewBottomConst;
+ }
+
+}
+
+#pragma mark - Override MXKViewController
+
+- (void)onMatrixSessionChange
+{
+ [super onMatrixSessionChange];
+
+ // Check dataSource state
+ if (self.roomDataSource && (self.roomDataSource.state == MXKDataSourceStatePreparing || self.roomDataSource.serverSyncEventCount))
+ {
+ // dataSource is not ready, keep running the loading wheel
+ [self startActivityIndicator];
+ }
+}
+
+- (void)onKeyboardShowAnimationComplete
+{
+ // Check first if the first responder belongs to title view
+ UIView *keyboardView = titleView.inputAccessoryView.superview;
+ if (!keyboardView)
+ {
+ // Check whether the first responder is the input tool bar text composer
+ keyboardView = inputToolbarView.inputAccessoryView.superview;
+ }
+
+ // Report the keyboard view in order to track keyboard frame changes
+ self.keyboardView = keyboardView;
+}
+
+- (void)setKeyboardHeight:(CGFloat)keyboardHeight
+{
+ // Deduce the bottom constraint for the input toolbar view (Don't forget the potential tabBar)
+ CGFloat inputToolbarViewBottomConst = keyboardHeight - self.bottomLayoutGuide.length;
+ // Check whether the keyboard is over the tabBar
+ if (inputToolbarViewBottomConst < 0)
+ {
+ inputToolbarViewBottomConst = 0;
+ }
+
+ // Update constraints
+ _roomInputToolbarContainerBottomConstraint.constant = inputToolbarViewBottomConst;
+ _bubblesTableViewBottomConstraint.constant = inputToolbarViewBottomConst + _roomInputToolbarContainerHeightConstraint.constant + _roomActivitiesContainerHeightConstraint.constant;
+
+ // Remove the rounded bottom unsafe area of the iPhone X
+ _bubblesTableViewBottomConstraint.constant += self.view.safeAreaInsets.bottom;
+
+ // Invalidate the current layout to take into account the new constraints in the next update cycle.
+ [self.view setNeedsLayout];
+
+ // Compute the visible area (tableview + toolbar) at the end of animation
+ CGFloat visibleArea = self.view.frame.size.height - _bubblesTableView.adjustedContentInset.top - keyboardHeight;
+ // Deduce max height of the message text input by considering the minimum height of the table view.
+ inputToolbarView.maxHeight = visibleArea - MXKROOMVIEWCONTROLLER_MESSAGES_TABLE_MINIMUM_HEIGHT;
+
+ // Check conditions before scrolling the tableview content when a new keyboard is presented.
+ if ((_scrollHistoryToTheBottomOnKeyboardPresentation || [self isBubblesTableScrollViewAtTheBottom]) && !super.keyboardHeight && keyboardHeight && !currentAlert)
+ {
+ self.bubbleTableViewDisplayInTransition = YES;
+
+ // Force here the layout update to scroll correctly the table content.
+ [self.view layoutIfNeeded];
+ [self scrollBubblesTableViewToBottomAnimated:NO];
+
+ self.bubbleTableViewDisplayInTransition = NO;
+ }
+ else
+ {
+ [self updateCurrentEventIdAtTableBottom:NO];
+ }
+
+ super.keyboardHeight = keyboardHeight;
+}
+
+- (void)destroy
+{
+ if (documentInteractionController)
+ {
+ [documentInteractionController dismissPreviewAnimated:NO];
+ [documentInteractionController dismissMenuAnimated:NO];
+ documentInteractionController = nil;
+ }
+
+ if (currentSharedAttachment)
+ {
+ [currentSharedAttachment onShareEnded];
+ currentSharedAttachment = nil;
+ }
+
+ [self dismissTemporarySubViews];
+
+ _bubblesTableView.dataSource = nil;
+ _bubblesTableView.delegate = nil;
+ _bubblesTableView = nil;
+
+ if (roomDataSource.delegate == self)
+ {
+ roomDataSource.delegate = nil;
+ }
+
+ if (_hasRoomDataSourceOwnership)
+ {
+ // Release the room data source
+ [roomDataSource destroy];
+ }
+ roomDataSource = nil;
+
+ if (titleView)
+ {
+ [titleView removeFromSuperview];
+ [titleView destroy];
+ titleView = nil;
+ }
+
+ if (inputToolbarView)
+ {
+ [inputToolbarView removeFromSuperview];
+ [inputToolbarView destroy];
+ inputToolbarView = nil;
+ }
+
+ if (activitiesView)
+ {
+ [activitiesView removeFromSuperview];
+ [activitiesView destroy];
+ activitiesView = nil;
+ }
+
+ [typingTimer invalidate];
+ typingTimer = nil;
+
+ if (joinRoomRequest)
+ {
+ [joinRoomRequest cancel];
+ joinRoomRequest = nil;
+ }
+
+ [super destroy];
+}
+
+#pragma mark -
+
+- (void)configureBubblesTableView
+{
+ // Set up table delegates
+ _bubblesTableView.delegate = self;
+ _bubblesTableView.dataSource = roomDataSource; // Note: data source may be nil here, it will be set during [displayRoom:] call.
+
+ // Set up default classes to use for cells
+ [_bubblesTableView registerClass:MXKRoomIncomingTextMsgBubbleCell.class forCellReuseIdentifier:MXKRoomIncomingTextMsgBubbleCell.defaultReuseIdentifier];
+ [_bubblesTableView registerClass:MXKRoomIncomingTextMsgWithoutSenderInfoBubbleCell.class forCellReuseIdentifier:MXKRoomIncomingTextMsgWithoutSenderInfoBubbleCell.defaultReuseIdentifier];
+ [_bubblesTableView registerClass:MXKRoomIncomingAttachmentBubbleCell.class forCellReuseIdentifier:MXKRoomIncomingAttachmentBubbleCell.defaultReuseIdentifier];
+ [_bubblesTableView registerClass:MXKRoomIncomingAttachmentWithoutSenderInfoBubbleCell.class forCellReuseIdentifier:MXKRoomIncomingAttachmentWithoutSenderInfoBubbleCell.defaultReuseIdentifier];
+
+ [_bubblesTableView registerClass:MXKRoomOutgoingTextMsgBubbleCell.class forCellReuseIdentifier:MXKRoomOutgoingTextMsgBubbleCell.defaultReuseIdentifier];
+ [_bubblesTableView registerClass:MXKRoomOutgoingTextMsgWithoutSenderInfoBubbleCell.class forCellReuseIdentifier:MXKRoomOutgoingTextMsgWithoutSenderInfoBubbleCell.defaultReuseIdentifier];
+ [_bubblesTableView registerClass:MXKRoomOutgoingAttachmentBubbleCell.class forCellReuseIdentifier:MXKRoomOutgoingAttachmentBubbleCell.defaultReuseIdentifier];
+ [_bubblesTableView registerClass:MXKRoomOutgoingAttachmentWithoutSenderInfoBubbleCell.class forCellReuseIdentifier:MXKRoomOutgoingAttachmentWithoutSenderInfoBubbleCell.defaultReuseIdentifier];
+
+ // Observe kMXSessionWillLeaveRoomNotification to be notified if the user leaves the current room.
+ MXWeakify(self);
+ _mxSessionWillLeaveRoomNotificationObserver = [[NSNotificationCenter defaultCenter] addObserverForName:kMXSessionWillLeaveRoomNotification object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *notif) {
+
+ MXStrongifyAndReturnIfNil(self);
+ // Check whether the user will leave the current room
+ if (notif.object == self.mainSession)
+ {
+ NSString *roomId = notif.userInfo[kMXSessionNotificationRoomIdKey];
+ if (roomId && [roomId isEqualToString:self->roomDataSource.roomId])
+ {
+ // Update view controller appearance
+ [self leaveRoomOnEvent:notif.userInfo[kMXSessionNotificationEventKey]];
+ }
+ }
+ }];
+}
+
+- (void)updateMessageTextViewFrame
+{
+ if (!self.keyboardView)
+ {
+ // Compute the visible area (tableview + toolbar)
+ CGFloat visibleArea = self.view.frame.size.height - _bubblesTableView.adjustedContentInset.top;
+ // Deduce max height of the message text input by considering the minimum height of the table view.
+ inputToolbarView.maxHeight = visibleArea - MXKROOMVIEWCONTROLLER_MESSAGES_TABLE_MINIMUM_HEIGHT;
+ }
+}
+
+- (CGFloat)tableViewSafeAreaWidth
+{
+ CGFloat safeAreaInsetsWidth;
+
+ // Take safe area into account
+ safeAreaInsetsWidth = self.bubblesTableView.safeAreaInsets.left + self.bubblesTableView.safeAreaInsets.right;
+
+ return self.bubblesTableView.frame.size.width - safeAreaInsetsWidth;
+}
+
+#pragma mark - Public API
+
+- (void)displayRoom:(MXKRoomDataSource *)dataSource
+{
+ if (roomDataSource)
+ {
+ if (self.hasRoomDataSourceOwnership)
+ {
+ // Release the room data source
+ [roomDataSource destroy];
+ }
+ else if (roomDataSource.delegate == self)
+ {
+ roomDataSource.delegate = nil;
+ }
+ roomDataSource = nil;
+
+ [self removeMatrixSession:self.mainSession];
+ }
+
+ // Reset the current event id
+ currentEventIdAtTableBottom = nil;
+
+ if (dataSource)
+ {
+ if (!dataSource.isLive || dataSource.isPeeking)
+ {
+ // Remove the input toolbar if the displayed timeline is not a live one or in case of peeking.
+ // We do not let the user type message in this case.
+ [self setRoomInputToolbarViewClass:nil];
+ }
+
+ roomDataSource = dataSource;
+ roomDataSource.delegate = self;
+ roomDataSource.paginationLimitAroundInitialEvent = _paginationLimit;
+
+ // Report the matrix session at view controller level to update UI according to session state
+ [self addMatrixSession:roomDataSource.mxSession];
+
+ if (_bubblesTableView)
+ {
+ [self dismissTemporarySubViews];
+
+ // Set up table data source
+ _bubblesTableView.dataSource = roomDataSource;
+ }
+
+ // When ready, do the initial back pagination
+ if (roomDataSource.state == MXKDataSourceStateReady)
+ {
+ [self onRoomDataSourceReady];
+ }
+ }
+
+ [self updateViewControllerAppearanceOnRoomDataSourceState];
+}
+
+- (void)onRoomDataSourceReady
+{
+ // If the user is only invited, auto-join the room if this option is enabled
+ if (roomDataSource.room.summary.membership == MXMembershipInvite)
+ {
+ if (_autoJoinInvitedRoom)
+ {
+ [self joinRoom:nil];
+ }
+ }
+ else
+ {
+ [self triggerInitialBackPagination];
+ }
+}
+
+- (void)updateViewControllerAppearanceOnRoomDataSourceState
+{
+ // Update UI by considering dataSource state
+ if (roomDataSource && roomDataSource.state == MXKDataSourceStateReady)
+ {
+ [self stopActivityIndicator];
+
+ if (titleView)
+ {
+ titleView.mxRoom = roomDataSource.room;
+ titleView.editable = YES;
+ titleView.hidden = NO;
+ }
+ else
+ {
+ // set default title
+ self.navigationItem.title = roomDataSource.room.summary.displayname;
+ }
+
+ // Show input tool bar
+ inputToolbarView.hidden = NO;
+ }
+ else
+ {
+ // Update the title except if the room has just been left
+ if (!_leftRoomReasonLabel)
+ {
+ if (roomDataSource && roomDataSource.state == MXKDataSourceStatePreparing)
+ {
+ if (titleView)
+ {
+ titleView.mxRoom = roomDataSource.room;
+ titleView.hidden = (!titleView.mxRoom);
+ }
+ else
+ {
+ self.navigationItem.title = roomDataSource.room.summary.displayname;
+ }
+ }
+ else
+ {
+ if (titleView)
+ {
+ titleView.mxRoom = nil;
+ titleView.hidden = NO;
+ }
+ else
+ {
+ self.navigationItem.title = nil;
+ }
+ }
+ }
+ titleView.editable = NO;
+
+ // Hide input tool bar
+ inputToolbarView.hidden = YES;
+ }
+
+ // Finalize room title refresh
+ [titleView refreshDisplay];
+
+ if (activitiesView)
+ {
+ // Hide by default the activity view when no room is displayed
+ activitiesView.hidden = (roomDataSource == nil);
+ }
+}
+
+- (void)onTimelineError:(NSNotification *)notif
+{
+ if (notif.object == roomDataSource)
+ {
+ [self stopActivityIndicator];
+
+ // Compute the message to display to the end user
+ NSString *errorTitle;
+ NSString *errorMessage;
+
+ NSError *error = notif.userInfo[kMXKRoomDataSourceTimelineErrorErrorKey];
+ if ([MXError isMXError:error])
+ {
+ MXError *mxError = [[MXError alloc] initWithNSError:error];
+ if ([mxError.errcode isEqualToString:kMXErrCodeStringNotFound])
+ {
+ errorTitle = [MatrixKitL10n roomErrorTimelineEventNotFoundTitle];
+ errorMessage = [MatrixKitL10n roomErrorTimelineEventNotFound];
+ }
+ else
+ {
+ errorTitle = [MatrixKitL10n roomErrorCannotLoadTimeline];
+ errorMessage = mxError.error;
+ }
+ }
+ else
+ {
+ errorTitle = [MatrixKitL10n roomErrorCannotLoadTimeline];
+ }
+
+ // And show it
+ [currentAlert dismissViewControllerAnimated:NO completion:nil];
+
+ __weak typeof(self) weakSelf = self;
+ UIAlertController *errorAlert = [UIAlertController alertControllerWithTitle:errorTitle
+ message:errorMessage
+ preferredStyle:UIAlertControllerStyleAlert];
+
+ [errorAlert addAction:[UIAlertAction actionWithTitle:[MatrixKitL10n ok]
+ style:UIAlertActionStyleDefault
+ handler:^(UIAlertAction * action) {
+
+ typeof(self) self = weakSelf;
+ self->currentAlert = nil;
+
+ }]];
+
+ [self presentViewController:errorAlert animated:YES completion:nil];
+ currentAlert = errorAlert;
+ }
+}
+
+- (void)joinRoom:(void(^)(MXKRoomViewControllerJoinRoomResult result))completion
+{
+ if (joinRoomRequest != nil)
+ {
+ if (completion)
+ {
+ completion(MXKRoomViewControllerJoinRoomResultFailureJoinInProgress);
+ }
+ return;
+ }
+
+ [self startActivityIndicator];
+
+ joinRoomRequest = [roomDataSource.room join:^{
+
+ self->joinRoomRequest = nil;
+ [self stopActivityIndicator];
+
+ [self triggerInitialBackPagination];
+
+ if (completion)
+ {
+ completion(MXKRoomViewControllerJoinRoomResultSuccess);
+ }
+
+ } failure:^(NSError *error) {
+ MXLogDebug(@"[MXKRoomVC] Failed to join room (%@)", self->roomDataSource.room.summary.displayname);
+ [self processRoomJoinFailureWithError:error completion:completion];
+ }];
+}
+
+- (void)joinRoomWithRoomIdOrAlias:(NSString*)roomIdOrAlias
+ viaServers:(NSArray*)viaServers
+ andSignUrl:(NSString*)signUrl
+ completion:(void(^)(MXKRoomViewControllerJoinRoomResult result))completion
+{
+ if (joinRoomRequest != nil)
+ {
+ if (completion)
+ {
+ completion(MXKRoomViewControllerJoinRoomResultFailureJoinInProgress);
+ }
+
+ return;
+ }
+
+ [self startActivityIndicator];
+
+ void (^success)(MXRoom *room) = ^(MXRoom *room) {
+
+ self->joinRoomRequest = nil;
+ [self stopActivityIndicator];
+
+ MXWeakify(self);
+
+ // The room is now part of the user's room
+ MXKRoomDataSourceManager *roomDataSourceManager = [MXKRoomDataSourceManager sharedManagerForMatrixSession:self.mainSession];
+
+ [roomDataSourceManager roomDataSourceForRoom:room.roomId create:YES onComplete:^(MXKRoomDataSource *newRoomDataSource) {
+
+ MXStrongifyAndReturnIfNil(self);
+
+ // And can be displayed
+ [self displayRoom:newRoomDataSource];
+
+ if (completion)
+ {
+ completion(MXKRoomViewControllerJoinRoomResultSuccess);
+ }
+ }];
+ };
+
+ void (^failure)(NSError *error) = ^(NSError *error) {
+ MXLogDebug(@"[MXKRoomVC] Failed to join room (%@)", roomIdOrAlias);
+ [self processRoomJoinFailureWithError:error completion:completion];
+ };
+
+ // Does the join need to be validated before?
+ if (signUrl)
+ {
+ joinRoomRequest = [self.mainSession joinRoom:roomIdOrAlias viaServers:viaServers withSignUrl:signUrl success:success failure:failure];
+ }
+ else
+ {
+ joinRoomRequest = [self.mainSession joinRoom:roomIdOrAlias viaServers:viaServers success:success failure:failure];
+ }
+}
+
+- (void)processRoomJoinFailureWithError:(NSError *)error completion:(void(^)(MXKRoomViewControllerJoinRoomResult result))completion
+{
+ self->joinRoomRequest = nil;
+ [self stopActivityIndicator];
+
+ // Show the error to the end user
+ NSString *msg = [error.userInfo valueForKey:NSLocalizedDescriptionKey];
+
+ // FIXME: We should hide this inside the SDK and expose it as a domain specific error
+ BOOL isRoomEmpty = [msg isEqualToString:@"No known servers"];
+ if (isRoomEmpty)
+ {
+ // minging kludge until https://matrix.org/jira/browse/SYN-678 is fixed
+ // 'Error when trying to join an empty room should be more explicit'
+ msg = [MatrixKitL10n roomErrorJoinFailedEmptyRoom];
+ }
+
+ MXWeakify(self);
+ [self->currentAlert dismissViewControllerAnimated:NO completion:nil];
+
+ UIAlertController *errorAlert = [UIAlertController alertControllerWithTitle:[MatrixKitL10n roomErrorJoinFailedTitle]
+ message:msg
+ preferredStyle:UIAlertControllerStyleAlert];
+
+ [errorAlert addAction:[UIAlertAction actionWithTitle:[MatrixKitL10n ok]
+ style:UIAlertActionStyleDefault
+ handler:^(UIAlertAction * action) {
+
+ MXStrongifyAndReturnIfNil(self);
+ self->currentAlert = nil;
+
+ if (completion)
+ {
+ completion((isRoomEmpty ? MXKRoomViewControllerJoinRoomResultFailureRoomEmpty : MXKRoomViewControllerJoinRoomResultFailureGeneric));
+ }
+ }]];
+
+ [self presentViewController:errorAlert animated:YES completion:nil];
+ currentAlert = errorAlert;
+}
+
+- (void)leaveRoomOnEvent:(MXEvent*)event
+{
+ [self dismissTemporarySubViews];
+
+ NSString *reason = nil;
+ if (event)
+ {
+ MXKEventFormatterError error;
+ reason = [roomDataSource.eventFormatter stringFromEvent:event withRoomState:roomDataSource.roomState error:&error];
+ if (error != MXKEventFormatterErrorNone)
+ {
+ reason = nil;
+ }
+ }
+
+ if (!reason.length)
+ {
+ if (self.roomDataSource.room.isDirect)
+ {
+ reason = [MatrixKitL10n roomLeftForDm];
+ }
+ else
+ {
+ reason = [MatrixKitL10n roomLeft];
+ }
+ }
+
+
+ _bubblesTableView.dataSource = nil;
+ _bubblesTableView.delegate = nil;
+
+ if (self.hasRoomDataSourceOwnership)
+ {
+ // Release the room data source
+ [roomDataSource destroy];
+ }
+ else if (roomDataSource.delegate == self)
+ {
+ roomDataSource.delegate = nil;
+ }
+ roomDataSource = nil;
+
+ // Add reason label
+ _leftRoomReasonLabel = [[UILabel alloc] initWithFrame:CGRectMake(10, 5, self.view.frame.size.width - 20, 70)];
+ _leftRoomReasonLabel.numberOfLines = 0;
+ _leftRoomReasonLabel.text = reason;
+ _leftRoomReasonLabel.autoresizingMask = UIViewAutoresizingFlexibleWidth;
+ _bubblesTableView.tableHeaderView = [[UILabel alloc] initWithFrame:CGRectMake(0, 0, self.view.frame.size.width, 80)];
+ [_bubblesTableView.tableHeaderView addSubview:_leftRoomReasonLabel];
+ [_bubblesTableView reloadData];
+
+ [self updateViewControllerAppearanceOnRoomDataSourceState];
+}
+
+- (void)setPaginationLimit:(NSUInteger)paginationLimit
+{
+ _paginationLimit = paginationLimit;
+
+ // Use the same value when loading messages around the initial event
+ roomDataSource.paginationLimitAroundInitialEvent = _paginationLimit;
+}
+
+- (void)setRoomTitleViewClass:(Class)roomTitleViewClass
+{
+ // Sanity check: accept only MXKRoomTitleView classes or sub-classes
+ NSParameterAssert([roomTitleViewClass isSubclassOfClass:MXKRoomTitleView.class]);
+
+ // Remove potential title view
+ if (titleView)
+ {
+ [NSLayoutConstraint deactivateConstraints:titleView.constraints];
+
+ [titleView dismissKeyboard];
+ [titleView removeFromSuperview];
+ [titleView destroy];
+ }
+
+ titleView = self.navigationItem.titleView = [roomTitleViewClass roomTitleView];
+ titleView.delegate = self;
+
+ // Define directly the navigation titleView with the custom title view instance. Do not use anymore a container.
+ self.navigationItem.titleView = titleView;
+
+ [self updateViewControllerAppearanceOnRoomDataSourceState];
+}
+
+- (void)setRoomInputToolbarViewClass:(Class)roomInputToolbarViewClass
+{
+ if (!_roomInputToolbarContainer)
+ {
+ MXLogDebug(@"[MXKRoomVC] Set roomInputToolbarViewClass failed: container is missing");
+ return;
+ }
+
+ // Remove potential toolbar
+ if (inputToolbarView)
+ {
+ MXLogDebug(@"[MXKRoomVC] setRoomInputToolbarViewClass: Set inputToolbarView with class %@ to nil", [self.inputToolbarView class]);
+
+ [NSLayoutConstraint deactivateConstraints:inputToolbarView.constraints];
+ [inputToolbarView dismissKeyboard];
+ [inputToolbarView removeFromSuperview];
+ [inputToolbarView destroy];
+ inputToolbarView = nil;
+ }
+
+ if (roomDataSource && (!roomDataSource.isLive || roomDataSource.isPeeking))
+ {
+ // Do not show the input toolbar if the displayed timeline is not a live one, or in case of peeking.
+ // We do not let the user type message in this case.
+ roomInputToolbarViewClass = nil;
+ }
+
+ if (roomInputToolbarViewClass)
+ {
+ // Sanity check: accept only MXKRoomInputToolbarView classes or sub-classes
+ NSParameterAssert([roomInputToolbarViewClass isSubclassOfClass:MXKRoomInputToolbarView.class]);
+
+ MXLogDebug(@"[MXKRoomVC] setRoomInputToolbarViewClass: Set inputToolbarView to class %@", roomInputToolbarViewClass);
+
+ id inputToolbarView = [roomInputToolbarViewClass roomInputToolbarView];
+ self->inputToolbarView = inputToolbarView;
+ self->inputToolbarView.delegate = self;
+
+ // Add the input toolbar view and define edge constraints
+ [_roomInputToolbarContainer addSubview:inputToolbarView];
+ [_roomInputToolbarContainer addConstraint:[NSLayoutConstraint constraintWithItem:_roomInputToolbarContainer
+ attribute:NSLayoutAttributeBottom
+ relatedBy:NSLayoutRelationEqual
+ toItem:inputToolbarView
+ attribute:NSLayoutAttributeBottom
+ multiplier:1.0f
+ constant:0.0f]];
+ [_roomInputToolbarContainer addConstraint:[NSLayoutConstraint constraintWithItem:_roomInputToolbarContainer
+ attribute:NSLayoutAttributeTop
+ relatedBy:NSLayoutRelationEqual
+ toItem:inputToolbarView
+ attribute:NSLayoutAttributeTop
+ multiplier:1.0f
+ constant:0.0f]];
+ [_roomInputToolbarContainer addConstraint:[NSLayoutConstraint constraintWithItem:_roomInputToolbarContainer
+ attribute:NSLayoutAttributeLeading
+ relatedBy:NSLayoutRelationEqual
+ toItem:inputToolbarView
+ attribute:NSLayoutAttributeLeading
+ multiplier:1.0f
+ constant:0.0f]];
+ [_roomInputToolbarContainer addConstraint:[NSLayoutConstraint constraintWithItem:_roomInputToolbarContainer
+ attribute:NSLayoutAttributeTrailing
+ relatedBy:NSLayoutRelationEqual
+ toItem:inputToolbarView
+ attribute:NSLayoutAttributeTrailing
+ multiplier:1.0f
+ constant:0.0f]];
+ }
+
+ [_roomInputToolbarContainer setNeedsUpdateConstraints];
+}
+
+
+- (void)setRoomActivitiesViewClass:(Class)roomActivitiesViewClass
+{
+ if (!_roomActivitiesContainer)
+ {
+ MXLogDebug(@"[MXKRoomVC] Set RoomActivitiesViewClass failed: container is missing");
+ return;
+ }
+
+ // Remove potential toolbar
+ if (activitiesView)
+ {
+ [NSLayoutConstraint deactivateConstraints:activitiesView.constraints];
+ [activitiesView removeFromSuperview];
+ [activitiesView destroy];
+ activitiesView = nil;
+ }
+
+ if (roomActivitiesViewClass)
+ {
+ // Sanity check: accept only MXKRoomExtraInfoView classes or sub-classes
+ NSParameterAssert([roomActivitiesViewClass isSubclassOfClass:MXKRoomActivitiesView.class]);
+
+ activitiesView = [roomActivitiesViewClass roomActivitiesView];
+
+ // Add the view and define edge constraints
+ activitiesView.translatesAutoresizingMaskIntoConstraints = NO;
+ [_roomActivitiesContainer addSubview:activitiesView];
+
+ NSLayoutConstraint* topConstraint = [NSLayoutConstraint constraintWithItem:_roomActivitiesContainer
+ attribute:NSLayoutAttributeTop
+ relatedBy:NSLayoutRelationEqual
+ toItem:activitiesView
+ attribute:NSLayoutAttributeTop
+ multiplier:1.0f
+ constant:0.0f];
+
+
+ NSLayoutConstraint* leadingConstraint = [NSLayoutConstraint constraintWithItem:_roomActivitiesContainer
+ attribute:NSLayoutAttributeLeading
+ relatedBy:NSLayoutRelationEqual
+ toItem:activitiesView
+ attribute:NSLayoutAttributeLeading
+ multiplier:1.0f
+ constant:0.0f];
+
+ NSLayoutConstraint* widthConstraint = [NSLayoutConstraint constraintWithItem:_roomActivitiesContainer
+ attribute:NSLayoutAttributeWidth
+ relatedBy:NSLayoutRelationEqual
+ toItem:activitiesView
+ attribute:NSLayoutAttributeWidth
+ multiplier:1.0f
+ constant:0.0f];
+
+ NSLayoutConstraint* heightConstraint = [NSLayoutConstraint constraintWithItem:_roomActivitiesContainer
+ attribute:NSLayoutAttributeHeight
+ relatedBy:NSLayoutRelationEqual
+ toItem:activitiesView
+ attribute:NSLayoutAttributeHeight
+ multiplier:1.0f
+ constant:0.0f];
+
+
+ [NSLayoutConstraint activateConstraints:@[topConstraint, leadingConstraint, widthConstraint, heightConstraint]];
+
+ // let the provide view to define a height.
+ // it could have no constrainst if there is no defined xib
+ _roomActivitiesContainerHeightConstraint.constant = activitiesView.height;
+
+ // Listen to activities view change
+ activitiesView.delegate = self;
+ }
+ else
+ {
+ _roomActivitiesContainerHeightConstraint.constant = 0;
+ }
+
+ _bubblesTableViewBottomConstraint.constant = _roomInputToolbarContainerBottomConstraint.constant + _roomInputToolbarContainerHeightConstraint.constant +_roomActivitiesContainerHeightConstraint.constant;
+
+ [_roomActivitiesContainer setNeedsUpdateConstraints];
+}
+
+- (void)setAttachmentsViewerClass:(Class)theAttachmentsViewerClass
+{
+ if (theAttachmentsViewerClass)
+ {
+ // Sanity check: accept only MXKAttachmentsViewController classes or sub-classes
+ NSParameterAssert([theAttachmentsViewerClass isSubclassOfClass:MXKAttachmentsViewController.class]);
+ }
+
+ attachmentsViewerClass = theAttachmentsViewerClass;
+}
+
+- (void)setEventDetailsViewClass:(Class)eventDetailsViewClass
+{
+ if (eventDetailsViewClass)
+ {
+ // Sanity check: accept only MXKEventDetailsView classes or sub-classes
+ NSParameterAssert([eventDetailsViewClass isSubclassOfClass:MXKEventDetailsView.class]);
+ }
+
+ customEventDetailsViewClass = eventDetailsViewClass;
+}
+
+- (BOOL)isIRCStyleCommand:(NSString*)string
+{
+ // Check whether the provided text may be an IRC-style command
+ if ([string hasPrefix:@"/"] == NO || [string hasPrefix:@"//"] == YES)
+ {
+ return NO;
+ }
+
+ // Parse command line
+ NSArray *components = [string componentsSeparatedByString:@" "];
+ NSString *cmd = [components objectAtIndex:0];
+ NSUInteger index = 1;
+
+ // TODO: display an alert with the cmd usage in case of error or unrecognized cmd.
+ NSString *cmdUsage;
+
+ if ([cmd isEqualToString:kMXKSlashCmdEmote])
+ {
+ // send message as an emote
+ [self sendTextMessage:string];
+ }
+ else if ([string hasPrefix:kMXKSlashCmdChangeDisplayName])
+ {
+ // Change display name
+ NSString *displayName;
+
+ // Sanity check
+ if (string.length > kMXKSlashCmdChangeDisplayName.length)
+ {
+ displayName = [string substringFromIndex:kMXKSlashCmdChangeDisplayName.length + 1];
+
+ // Remove white space from both ends
+ displayName = [displayName stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]];
+ }
+
+ if (displayName.length)
+ {
+ [roomDataSource.mxSession.matrixRestClient setDisplayName:displayName success:^{
+
+ } failure:^(NSError *error) {
+
+ MXLogDebug(@"[MXKRoomVC] Set displayName failed");
+ // Notify MatrixKit user
+ NSString *myUserId = self->roomDataSource.mxSession.myUser.userId;
+ [[NSNotificationCenter defaultCenter] postNotificationName:kMXKErrorNotification object:error userInfo:myUserId ? @{kMXKErrorUserIdKey: myUserId} : nil];
+
+ }];
+ }
+ else
+ {
+ // Display cmd usage in text input as placeholder
+ cmdUsage = @"Usage: /nick