diff --git a/CHANGES.rst b/CHANGES.rst index 88b0ed703..ea5a451b8 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -1,16 +1,22 @@ -Changes in MatrixKit in 0.11.5 (2020-xx-xx) +Changes in MatrixKit in 0.12.0 (2020-04-17) ========================================== Improvements: + * Upgrade MatrixSDK version ([v0.16.0](https://github.com/matrix-org/matrix-ios-sdk/releases/tag/v0.16.0)). * MXKRoomBubbleTableViewCell: Handle content view tap and long press when there is no `messageTextView` or `attachmentView` properties. - * MXKEventFormatter: E2E, hide duplicate message warnings (vector-im/riot-ios#2910). - * MXKEventFormatter: E2E, hide the algo used when turning on encryption (vector-im/riot-ios#2939). * MXKRoomBubbleComponent: Add a property to indicate if an encryption badge should be shown. * MXKRoomBubbleCellData: Add a property to indicate if a bubble component needs to show encryption badge. + * MXKEventFormatter: E2E, hide duplicate message warnings (vector-im/riot-ios#2910). + * MXKEventFormatter: E2E, hide the algo used when turning on encryption (vector-im/riot-ios#2939). + * Push notifications: Implement logic to use also a secondary appId for VoIP pusher on debug builds, like for APNS pusher. + * SwiftUTI: Remove the no more maintained pod. Embed code instead. API break: * MXKRoomBubbleComponent: Add session parameter to init and update method. +Bug fix: + * MXKImageView: Consider safe area insets when displayed fullscreen (PR #649). + Changes in MatrixKit in 0.11.4 (2020-04-01) ========================================== diff --git a/Libs/README.md b/Libs/README.md new file mode 100644 index 000000000..e3d1f8ba8 --- /dev/null +++ b/Libs/README.md @@ -0,0 +1,4 @@ +# MatrixKit Libs folder + +This folder contains third-party libraries that do not use (or do not use anymore) CocoaPods as dependency manager. +Each folder contains a README giving library origin and why we dediced to embed it. diff --git a/Libs/SwiftUTI/LICENSE b/Libs/SwiftUTI/LICENSE new file mode 100644 index 000000000..a0bf476a7 --- /dev/null +++ b/Libs/SwiftUTI/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2017 + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/Libs/SwiftUTI/README.md b/Libs/SwiftUTI/README.md new file mode 100644 index 000000000..d23bf7763 --- /dev/null +++ b/Libs/SwiftUTI/README.md @@ -0,0 +1,4 @@ +Original source: +https://github.com/mkeiser/SwiftUTI + +The pod of this library is no more maintained. At the time of writing this README, the official pod version is 1.0.6 whereas the last release of the library is 2.0.3. This last release has a podspec that points to 2.0.2. diff --git a/Libs/SwiftUTI/UTI.swift b/Libs/SwiftUTI/UTI.swift new file mode 100644 index 000000000..c3d66c481 --- /dev/null +++ b/Libs/SwiftUTI/UTI.swift @@ -0,0 +1,517 @@ +// +// UTI.swift +// fseventstool +// +// Created by Matthias Keiser on 09.01.17. +// Copyright © 2017 Tristan Inc. All rights reserved. +// + +import Foundation + +#if os(iOS) || os(watchOS) + import MobileCoreServices +#elseif os(macOS) + import CoreServices +#endif + +/// Instances of the UTI class represent a specific Universal Type Identifier, e.g. kUTTypeMPEG4. + +public class UTI: RawRepresentable, Equatable { + + /** + The TagClass enum represents the supported tag classes. + + - fileExtension: kUTTagClassFilenameExtension + - mimeType: kUTTagClassMIMEType + - pbType: kUTTagClassNSPboardType + - osType: kUTTagClassOSType + */ + public enum TagClass: String { + + /// Equivalent to kUTTagClassFilenameExtension + case fileExtension = "public.filename-extension" + + /// Equivalent to kUTTagClassMIMEType + case mimeType = "public.mime-type" + + #if os (macOS) + + /// Equivalent to kUTTagClassNSPboardType + case pbType = "com.apple.nspboard-type" + + /// Equivalent to kUTTagClassOSType + case osType = "com.apple.ostype" + #endif + + /// Convenience variable for internal use. + + fileprivate var rawCFValue: CFString { + return self.rawValue as CFString + } + } + + public typealias RawValue = String + public let rawValue: String + + + /// Convenience variable for internal use. + + private var rawCFValue: CFString { + + return self.rawValue as CFString + } + + // MARK: Initialization + + + /** + + This is the designated initializer of the UTI class. + + - Parameters: + - rawValue: A string that is a Universal Type Identifier, i.e. "com.foobar.baz" or a constant like kUTTypeMP3. + - Returns: + An UTI instance representing the specified rawValue. + - Note: + You should rarely use this method. The preferred way to initialize a known UTI is to use its static variable (i.e. UTI.pdf). You should make an extension to make your own types available as static variables. + + */ + + public required init(rawValue: UTI.RawValue) { + + self.rawValue = rawValue + } + + /** + + Initialize an UTI with a tag of a specified class. + + - Parameters: + - tagClass: The class of the tag. + - value: The value of the tag. + - conformingTo: If specified, the returned UTI must conform to this UTI. If nil is specified, this parameter is ignored. The default is nil. + - Returns: + An UTI instance representing the specified rawValue. If no known UTI with the specified tags is found, a dynamic UTI is created. + - Note: + You should rarely need this method. It's usually simpler to use one of the specialized initialzers like + ```convenience init?(withExtension fileExtension: String, conformingTo conforming: UTI? = nil)``` + */ + + public convenience init(withTagClass tagClass: TagClass, value: String, conformingTo conforming: UTI? = nil) { + + let unmanagedIdentifier = UTTypeCreatePreferredIdentifierForTag(tagClass.rawCFValue, value as CFString, conforming?.rawCFValue) + + // UTTypeCreatePreferredIdentifierForTag only returns nil if the tag class is unknwown, which can't happen to us since we use an + // enum of known values. Hence we can force-cast the result. + + let identifier = (unmanagedIdentifier?.takeRetainedValue() as String?)! + + self.init(rawValue: identifier) + } + + /** + + Initialize an UTI with a file extension. + + - Parameters: + - withExtension: The file extension (e.g. "txt"). + - conformingTo: If specified, the returned UTI must conform to this UTI. If nil is specified, this parameter is ignored. The default is nil. + - Returns: + An UTI corresponding to the specified values. + **/ + + public convenience init(withExtension fileExtension: String, conformingTo conforming: UTI? = nil) { + + self.init(withTagClass:.fileExtension, value: fileExtension, conformingTo: conforming) + } + + /** + + Initialize an UTI with a MIME type. + + - Parameters: + - mimeType: The MIME type (e.g. "text/plain"). + - conformingTo: If specified, the returned UTI must conform to this UTI. If nil is specified, this parameter is ignored. The default is nil. + - Returns: + An UTI corresponding to the specified values. + */ + + public convenience init(withMimeType mimeType: String, conformingTo conforming: UTI? = nil) { + + self.init(withTagClass:.mimeType, value: mimeType, conformingTo: conforming) + } + + #if os(macOS) + + /** + + Initialize an UTI with a pasteboard type. + - Important: **This function is de-facto deprecated!** The old cocoa pasteboard types ( `NSStringPboardType`, `NSPDFPboardType`, etc) have been deprecated in favour of actual UTIs, and the constants are not available anymore in Swift. This function only works correctly with the values of these old constants, but _not_ with the replacement values (like `NSPasteboardTypeString` etc), since these already are UTIs. + - Parameters: + - pbType: The pasteboard type (e.g. NSPDFPboardType). + - conformingTo: If specified, the returned UTI must conform to this UTI. If nil is specified, this parameter is ignored. The default is nil. + - Returns: + An UTI corresponding to the specified values. + */ + public convenience init(withPBType pbType: String, conformingTo conforming: UTI? = nil) { + + self.init(withTagClass:.pbType, value: pbType, conformingTo: conforming) + } + + /** + Initialize an UTI with a OSType. + + - Parameters: + - osType: The OSType type as a string (e.g. "PDF "). + - conformingTo: If specified, the returned UTI must conform to this UTI. If nil is specified, this parameter is ignored. The default is nil. + - Returns: + An UTI corresponding to the specified values. + - Note: + You can use the variable ```OSType.string``` to get a string from an actual OSType. + */ + + public convenience init(withOSType osType: String, conformingTo conforming: UTI? = nil) { + + self.init(withTagClass:.osType, value: osType, conformingTo: conforming) + } + + #endif + + // MARK: Accessing Tags + + /** + + Returns the tag with the specified class. + + - Parameters: + - tagClass: The tag class to return. + - Returns: + The requested tag, or nil if there is no tag of the specified class. + */ + + public func tag(with tagClass: TagClass) -> String? { + + let unmanagedTag = UTTypeCopyPreferredTagWithClass(self.rawCFValue, tagClass.rawCFValue) + + guard let tag = unmanagedTag?.takeRetainedValue() as String? else { + return nil + } + + return tag + } + + /// Return the file extension that corresponds the the UTI. Returns nil if not available. + + public var fileExtension: String? { + + return self.tag(with: .fileExtension) + } + + /// Return the MIME type that corresponds the the UTI. Returns nil if not available. + + public var mimeType: String? { + + return self.tag(with: .mimeType) + } + + #if os(macOS) + + /// Return the pasteboard type that corresponds the the UTI. Returns nil if not available. + + public var pbType: String? { + + return self.tag(with: .pbType) + } + + /// Return the OSType as a string that corresponds the the UTI. Returns nil if not available. + /// - Note: you can use the ```init(with string: String)``` initializer to construct an actual OSType from the returnes string. + + public var osType: String? { + + return self.tag(with: .osType) + } + + #endif + + /** + + Returns all tags of the specified tag class. + + - Parameters: + - tagClass: The class of the requested tags. + - Returns: + An array of all tags of the receiver of the specified class. + */ + + public func tags(with tagClass: TagClass) -> Array { + + let unmanagedTags = UTTypeCopyAllTagsWithClass(self.rawCFValue, tagClass.rawCFValue) + + guard let tags = unmanagedTags?.takeRetainedValue() as? Array else { + return [] + } + + return tags as Array + } + + // MARK: List all UTIs associated with a tag + + + /** + Returns all UTIs that are associated with a specified tag. + + - Parameters: + - tag: The class of the specified tag. + - value: The value of the tag. + - conforming: If specified, the returned UTIs must conform to this UTI. If nil is specified, this parameter is ignored. The default is nil. + - Returns: + An array of all UTIs that satisfy the specified parameters. + */ + + public static func utis(for tag: TagClass, value: String, conformingTo conforming: UTI? = nil) -> Array { + + let unmanagedIdentifiers = UTTypeCreateAllIdentifiersForTag(tag.rawCFValue, value as CFString, conforming?.rawCFValue) + + + guard let identifiers = unmanagedIdentifiers?.takeRetainedValue() as? Array else { + return [] + } + + return identifiers.compactMap { UTI(rawValue: $0 as String) } + } + + // MARK: Equality and Conformance to other UTIs + + /** + + Checks if the receiver conforms to a specified UTI. + + - Parameters: + - otherUTI: The UTI to which the receiver is compared. + - Returns: + ```true``` if the receiver conforms to the specified UTI, ```false```otherwise. + */ + + public func conforms(to otherUTI: UTI) -> Bool { + + return UTTypeConformsTo(self.rawCFValue, otherUTI.rawCFValue) as Bool + } + + public static func ==(lhs: UTI, rhs: UTI) -> Bool { + + return UTTypeEqual(lhs.rawCFValue, rhs.rawCFValue) as Bool + } + + // MARK: Accessing Information about an UTI + + /// Returns the localized, user-readable type description string associated with a uniform type identifier. + + public var description: String? { + + let unmanagedDescription = UTTypeCopyDescription(self.rawCFValue) + + guard let description = unmanagedDescription?.takeRetainedValue() as String? else { + return nil + } + + return description + } + + /// Returns a uniform type’s declaration as a Dictionary, or nil if if no declaration for that type can be found. + + public var declaration: [AnyHashable:Any]? { + + let unmanagedDeclaration = UTTypeCopyDeclaration(self.rawCFValue) + + guard let declaration = unmanagedDeclaration?.takeRetainedValue() as? [AnyHashable:Any] else { + return nil + } + + return declaration + } + + /// Returns the location of a bundle containing the declaration for a type, or nil if the bundle could not be located. + + public var declaringBundleURL: URL? { + + let unmanagedURL = UTTypeCopyDeclaringBundleURL(self.rawCFValue) + + guard let url = unmanagedURL?.takeRetainedValue() as URL? else { + return nil + } + + return url + } + + /// Returns ```true``` if the receiver is a dynamic UTI. + + public var isDynamic: Bool { + + return UTTypeIsDynamic(self.rawCFValue) + } +} + + +// MARK: System defined UTIs + +public extension UTI { + + static let item = UTI(rawValue: kUTTypeItem as String) + static let content = UTI(rawValue: kUTTypeContent as String) + static let compositeContent = UTI(rawValue: kUTTypeCompositeContent as String) + static let message = UTI(rawValue: kUTTypeMessage as String) + static let contact = UTI(rawValue: kUTTypeContact as String) + static let archive = UTI(rawValue: kUTTypeArchive as String) + static let diskImage = UTI(rawValue: kUTTypeDiskImage as String) + static let data = UTI(rawValue: kUTTypeData as String) + static let directory = UTI(rawValue: kUTTypeDirectory as String) + static let resolvable = UTI(rawValue: kUTTypeResolvable as String) + static let symLink = UTI(rawValue: kUTTypeSymLink as String) + static let executable = UTI(rawValue: kUTTypeExecutable as String) + static let mountPoint = UTI(rawValue: kUTTypeMountPoint as String) + static let aliasFile = UTI(rawValue: kUTTypeAliasFile as String) + static let aliasRecord = UTI(rawValue: kUTTypeAliasRecord as String) + static let urlBookmarkData = UTI(rawValue: kUTTypeURLBookmarkData as String) + static let url = UTI(rawValue: kUTTypeURL as String) + static let fileURL = UTI(rawValue: kUTTypeFileURL as String) + static let text = UTI(rawValue: kUTTypeText as String) + static let plainText = UTI(rawValue: kUTTypePlainText as String) + static let utf8PlainText = UTI(rawValue: kUTTypeUTF8PlainText as String) + static let utf16ExternalPlainText = UTI(rawValue: kUTTypeUTF16ExternalPlainText as String) + static let utf16PlainText = UTI(rawValue: kUTTypeUTF16PlainText as String) + static let delimitedText = UTI(rawValue: kUTTypeDelimitedText as String) + static let commaSeparatedText = UTI(rawValue: kUTTypeCommaSeparatedText as String) + static let tabSeparatedText = UTI(rawValue: kUTTypeTabSeparatedText as String) + static let utf8TabSeparatedText = UTI(rawValue: kUTTypeUTF8TabSeparatedText as String) + static let rtf = UTI(rawValue: kUTTypeRTF as String) + static let html = UTI(rawValue: kUTTypeHTML as String) + static let xml = UTI(rawValue: kUTTypeXML as String) + static let sourceCode = UTI(rawValue: kUTTypeSourceCode as String) + static let assemblyLanguageSource = UTI(rawValue: kUTTypeAssemblyLanguageSource as String) + static let cSource = UTI(rawValue: kUTTypeCSource as String) + static let objectiveCSource = UTI(rawValue: kUTTypeObjectiveCSource as String) + @available( OSX 10.11, iOS 9.0, * ) + static let swiftSource = UTI(rawValue: kUTTypeSwiftSource as String) + static let cPlusPlusSource = UTI(rawValue: kUTTypeCPlusPlusSource as String) + static let objectiveCPlusPlusSource = UTI(rawValue: kUTTypeObjectiveCPlusPlusSource as String) + static let cHeader = UTI(rawValue: kUTTypeCHeader as String) + static let cPlusPlusHeader = UTI(rawValue: kUTTypeCPlusPlusHeader as String) + static let javaSource = UTI(rawValue: kUTTypeJavaSource as String) + static let script = UTI(rawValue: kUTTypeScript as String) + static let appleScript = UTI(rawValue: kUTTypeAppleScript as String) + static let osaScript = UTI(rawValue: kUTTypeOSAScript as String) + static let osaScriptBundle = UTI(rawValue: kUTTypeOSAScriptBundle as String) + static let javaScript = UTI(rawValue: kUTTypeJavaScript as String) + static let shellScript = UTI(rawValue: kUTTypeShellScript as String) + static let perlScript = UTI(rawValue: kUTTypePerlScript as String) + static let pythonScript = UTI(rawValue: kUTTypePythonScript as String) + static let rubyScript = UTI(rawValue: kUTTypeRubyScript as String) + static let phpScript = UTI(rawValue: kUTTypePHPScript as String) + static let json = UTI(rawValue: kUTTypeJSON as String) + static let propertyList = UTI(rawValue: kUTTypePropertyList as String) + static let xmlPropertyList = UTI(rawValue: kUTTypeXMLPropertyList as String) + static let binaryPropertyList = UTI(rawValue: kUTTypeBinaryPropertyList as String) + static let pdf = UTI(rawValue: kUTTypePDF as String) + static let rtfd = UTI(rawValue: kUTTypeRTFD as String) + static let flatRTFD = UTI(rawValue: kUTTypeFlatRTFD as String) + static let txnTextAndMultimediaData = UTI(rawValue: kUTTypeTXNTextAndMultimediaData as String) + static let webArchive = UTI(rawValue: kUTTypeWebArchive as String) + static let image = UTI(rawValue: kUTTypeImage as String) + static let jpeg = UTI(rawValue: kUTTypeJPEG as String) + static let jpeg2000 = UTI(rawValue: kUTTypeJPEG2000 as String) + static let tiff = UTI(rawValue: kUTTypeTIFF as String) + static let pict = UTI(rawValue: kUTTypePICT as String) + static let gif = UTI(rawValue: kUTTypeGIF as String) + static let png = UTI(rawValue: kUTTypePNG as String) + static let quickTimeImage = UTI(rawValue: kUTTypeQuickTimeImage as String) + static let appleICNS = UTI(rawValue: kUTTypeAppleICNS as String) + static let bmp = UTI(rawValue: kUTTypeBMP as String) + static let ico = UTI(rawValue: kUTTypeICO as String) + static let rawImage = UTI(rawValue: kUTTypeRawImage as String) + static let scalableVectorGraphics = UTI(rawValue: kUTTypeScalableVectorGraphics as String) + @available(OSX 10.12, iOS 9.1, watchOS 2.1, *) + static let livePhoto = UTI(rawValue: kUTTypeLivePhoto as String) + @available(OSX 10.12, iOS 9.1, *) + static let audiovisualContent = UTI(rawValue: kUTTypeAudiovisualContent as String) + static let movie = UTI(rawValue: kUTTypeMovie as String) + static let video = UTI(rawValue: kUTTypeVideo as String) + static let audio = UTI(rawValue: kUTTypeAudio as String) + static let quickTimeMovie = UTI(rawValue: kUTTypeQuickTimeMovie as String) + static let mpeg = UTI(rawValue: kUTTypeMPEG as String) + static let mpeg2Video = UTI(rawValue: kUTTypeMPEG2Video as String) + static let mpeg2TransportStream = UTI(rawValue: kUTTypeMPEG2TransportStream as String) + static let mp3 = UTI(rawValue: kUTTypeMP3 as String) + static let mpeg4 = UTI(rawValue: kUTTypeMPEG4 as String) + static let mpeg4Audio = UTI(rawValue: kUTTypeMPEG4Audio as String) + static let appleProtectedMPEG4Audio = UTI(rawValue: kUTTypeAppleProtectedMPEG4Audio as String) + static let appleProtectedMPEG4Video = UTI(rawValue: kUTTypeAppleProtectedMPEG4Video as String) + static let aviMovie = UTI(rawValue: kUTTypeAVIMovie as String) + static let audioInterchangeFileFormat = UTI(rawValue: kUTTypeAudioInterchangeFileFormat as String) + static let waveformAudio = UTI(rawValue: kUTTypeWaveformAudio as String) + static let midiAudio = UTI(rawValue: kUTTypeMIDIAudio as String) + static let playlist = UTI(rawValue: kUTTypePlaylist as String) + static let m3UPlaylist = UTI(rawValue: kUTTypeM3UPlaylist as String) + static let folder = UTI(rawValue: kUTTypeFolder as String) + static let volume = UTI(rawValue: kUTTypeVolume as String) + static let package = UTI(rawValue: kUTTypePackage as String) + static let bundle = UTI(rawValue: kUTTypeBundle as String) + static let pluginBundle = UTI(rawValue: kUTTypePluginBundle as String) + static let spotlightImporter = UTI(rawValue: kUTTypeSpotlightImporter as String) + static let quickLookGenerator = UTI(rawValue: kUTTypeQuickLookGenerator as String) + static let xpcService = UTI(rawValue: kUTTypeXPCService as String) + static let framework = UTI(rawValue: kUTTypeFramework as String) + static let application = UTI(rawValue: kUTTypeApplication as String) + static let applicationBundle = UTI(rawValue: kUTTypeApplicationBundle as String) + static let applicationFile = UTI(rawValue: kUTTypeApplicationFile as String) + static let unixExecutable = UTI(rawValue: kUTTypeUnixExecutable as String) + static let windowsExecutable = UTI(rawValue: kUTTypeWindowsExecutable as String) + static let javaClass = UTI(rawValue: kUTTypeJavaClass as String) + static let javaArchive = UTI(rawValue: kUTTypeJavaArchive as String) + static let systemPreferencesPane = UTI(rawValue: kUTTypeSystemPreferencesPane as String) + static let gnuZipArchive = UTI(rawValue: kUTTypeGNUZipArchive as String) + static let bzip2Archive = UTI(rawValue: kUTTypeBzip2Archive as String) + static let zipArchive = UTI(rawValue: kUTTypeZipArchive as String) + static let spreadsheet = UTI(rawValue: kUTTypeSpreadsheet as String) + static let presentation = UTI(rawValue: kUTTypePresentation as String) + static let database = UTI(rawValue: kUTTypeDatabase as String) + static let vCard = UTI(rawValue: kUTTypeVCard as String) + static let toDoItem = UTI(rawValue: kUTTypeToDoItem as String) + static let calendarEvent = UTI(rawValue: kUTTypeCalendarEvent as String) + static let emailMessage = UTI(rawValue: kUTTypeEmailMessage as String) + static let internetLocation = UTI(rawValue: kUTTypeInternetLocation as String) + static let inkText = UTI(rawValue: kUTTypeInkText as String) + static let font = UTI(rawValue: kUTTypeFont as String) + static let bookmark = UTI(rawValue: kUTTypeBookmark as String) + static let _3DContent = UTI(rawValue: kUTType3DContent as String) + static let pkcs12 = UTI(rawValue: kUTTypePKCS12 as String) + static let x509Certificate = UTI(rawValue: kUTTypeX509Certificate as String) + static let electronicPublication = UTI(rawValue: kUTTypeElectronicPublication as String) + static let log = UTI(rawValue: kUTTypeLog as String) +} + +#if os(OSX) + + extension OSType { + + + /// Returns the OSType encoded as a String. + + var string: String { + + let unmanagedString = UTCreateStringForOSType(self) + + return unmanagedString.takeRetainedValue() as String + } + + + /// Initializes a OSType from a String. + /// + /// - Parameter string: A String representing an OSType. + + init(with string: String) { + + self = UTGetOSTypeFromString(string as CFString) + } + } + +#endif diff --git a/MatrixKit.podspec b/MatrixKit.podspec index b560c7bcc..67263b047 100644 --- a/MatrixKit.podspec +++ b/MatrixKit.podspec @@ -1,7 +1,7 @@ Pod::Spec.new do |s| s.name = "MatrixKit" - s.version = "0.11.4" + s.version = "0.12.0" s.summary = "The Matrix reusable UI library for iOS based on MatrixSDK." s.description = <<-DESC @@ -23,23 +23,22 @@ Pod::Spec.new do |s| s.swift_version = '5.0' - s.dependency 'MatrixSDK', '0.15.2' + s.dependency 'MatrixSDK', '0.16.0' s.dependency 'HPGrowingTextView', '~> 1.1' s.dependency 'libPhoneNumber-iOS', '~> 0.9.13' s.dependency 'DTCoreText', '~> 1.6.21' s.dependency 'cmark', '~> 0.24.1' - s.dependency 'SwiftUTI', '~> 1.0.6' s.default_subspec = 'Core' s.subspec 'Core' do |core| - core.source_files = "MatrixKit", "MatrixKit/**/*.{h,m,swift}" + core.source_files = "MatrixKit", "MatrixKit/**/*.{h,m,swift}", "Libs/**/*.{h,m,swift}" core.resources = ["MatrixKit/**/*.{xib}", "MatrixKit/Assets/MatrixKitAssets.bundle"] core.dependency 'DTCoreText' end s.subspec 'AppExtension' do |ext| - ext.source_files = "MatrixKit", "MatrixKit/**/*.{h,m,swift}" + ext.source_files = "MatrixKit", "MatrixKit/**/*.{h,m,swift}", "Libs/**/*.{h,m,swift}" ext.resources = ["MatrixKit/**/*.{xib}", "MatrixKit/Assets/MatrixKitAssets.bundle"] ext.dependency 'DTCoreText/Extension' end diff --git a/MatrixKit.xcodeproj/project.pbxproj b/MatrixKit.xcodeproj/project.pbxproj index f6f99389a..3b3a2c3e2 100644 --- a/MatrixKit.xcodeproj/project.pbxproj +++ b/MatrixKit.xcodeproj/project.pbxproj @@ -24,6 +24,7 @@ 3235CD801C33C6640084EA40 /* MXKSearchTableViewCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 3235CD7F1C33C6640084EA40 /* MXKSearchTableViewCell.m */; }; 3235CD821C33D3EA0084EA40 /* MXKSearchTableViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 3235CD811C33D3EA0084EA40 /* MXKSearchTableViewCell.xib */; }; 323F9AF61F1E53730013AFDE /* NSBundle+MXKLanguage.m in Sources */ = {isa = PBXBuildFile; fileRef = 323F9AF51F1E53730013AFDE /* NSBundle+MXKLanguage.m */; }; + 324978F4243F444C000D0384 /* UTI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 324978F3243F444C000D0384 /* UTI.swift */; }; 32538D081D2EA100009FE744 /* MXKEventFormatterTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 32538D071D2EA100009FE744 /* MXKEventFormatterTests.m */; }; 327DCDC71AC1774000397E5E /* MXKRecentTableViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 327DCDC61AC1774000397E5E /* MXKRecentTableViewCell.xib */; }; 328AC4861AA75E000044A6FB /* MXKRoomBubbleTableViewCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 328AC4851AA75E000044A6FB /* MXKRoomBubbleTableViewCell.m */; }; @@ -295,6 +296,10 @@ 3235CD811C33D3EA0084EA40 /* MXKSearchTableViewCell.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; name = MXKSearchTableViewCell.xib; path = Search/MXKSearchTableViewCell.xib; sourceTree = ""; }; 323F9AF41F1E53730013AFDE /* NSBundle+MXKLanguage.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSBundle+MXKLanguage.h"; sourceTree = ""; }; 323F9AF51F1E53730013AFDE /* NSBundle+MXKLanguage.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSBundle+MXKLanguage.m"; sourceTree = ""; }; + 324978E6243F431D000D0384 /* README.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = ""; }; + 324978E8243F431D000D0384 /* LICENSE */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = LICENSE; sourceTree = ""; }; + 324978E9243F431D000D0384 /* README.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = ""; }; + 324978F3243F444C000D0384 /* UTI.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UTI.swift; sourceTree = ""; }; 32538D071D2EA100009FE744 /* MXKEventFormatterTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MXKEventFormatterTests.m; sourceTree = ""; }; 327DCDC61AC1774000397E5E /* MXKRecentTableViewCell.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = MXKRecentTableViewCell.xib; sourceTree = ""; }; 328AC4841AA75E000044A6FB /* MXKRoomBubbleTableViewCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MXKRoomBubbleTableViewCell.h; sourceTree = ""; }; @@ -734,6 +739,7 @@ 3203F2691D2E9CAE0021F170 /* MatrixKitTests */, 3219CB691AA6F2780027D7E2 /* Products */, 69833E0CA8F7B8F25C6CC7C7 /* Pods */, + 324978E5243F431D000D0384 /* Libs */, CC4E6F6D4101B9B731F29FB7 /* Frameworks */, ); sourceTree = ""; @@ -925,6 +931,25 @@ name = Search; sourceTree = ""; }; + 324978E5243F431D000D0384 /* Libs */ = { + isa = PBXGroup; + children = ( + 324978E6243F431D000D0384 /* README.md */, + 324978E7243F431D000D0384 /* SwiftUTI */, + ); + path = Libs; + sourceTree = ""; + }; + 324978E7243F431D000D0384 /* SwiftUTI */ = { + isa = PBXGroup; + children = ( + 324978F3243F444C000D0384 /* UTI.swift */, + 324978E8243F431D000D0384 /* LICENSE */, + 324978E9243F431D000D0384 /* README.md */, + ); + path = SwiftUTI; + sourceTree = ""; + }; 328AC47A1AA75D140044A6FB /* RoomBubbleList */ = { isa = PBXGroup; children = ( @@ -1705,7 +1730,6 @@ "${BUILT_PRODUCTS_DIR}/MatrixSDK/MatrixSDK.framework", "${BUILT_PRODUCTS_DIR}/OLMKit/OLMKit.framework", "${BUILT_PRODUCTS_DIR}/Realm/Realm.framework", - "${BUILT_PRODUCTS_DIR}/SwiftUTI/SwiftUTI.framework", "${BUILT_PRODUCTS_DIR}/cmark/cmark.framework", "${BUILT_PRODUCTS_DIR}/libPhoneNumber-iOS/libPhoneNumber_iOS.framework", "${BUILT_PRODUCTS_DIR}/libbase58/libbase58.framework", @@ -1722,7 +1746,6 @@ "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/MatrixSDK.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/OLMKit.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Realm.framework", - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SwiftUTI.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/cmark.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/libPhoneNumber_iOS.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/libbase58.framework", @@ -1749,7 +1772,6 @@ "${BUILT_PRODUCTS_DIR}/MatrixSDK/MatrixSDK.framework", "${BUILT_PRODUCTS_DIR}/OLMKit/OLMKit.framework", "${BUILT_PRODUCTS_DIR}/Realm/Realm.framework", - "${BUILT_PRODUCTS_DIR}/SwiftUTI/SwiftUTI.framework", "${BUILT_PRODUCTS_DIR}/cmark/cmark.framework", "${BUILT_PRODUCTS_DIR}/libPhoneNumber-iOS/libPhoneNumber_iOS.framework", "${BUILT_PRODUCTS_DIR}/libbase58/libbase58.framework", @@ -1766,7 +1788,6 @@ "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/MatrixSDK.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/OLMKit.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Realm.framework", - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SwiftUTI.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/cmark.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/libPhoneNumber_iOS.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/libbase58.framework", @@ -1823,6 +1844,7 @@ F0868E0D1B18FC01004CBE80 /* MXKRoomCreationView.m in Sources */, CE14CA671E80122600E329A3 /* MXKAttachmentInteractionController.m in Sources */, B125D10C22D7414400570CA4 /* MXKVideoThumbnailGenerator.swift in Sources */, + 324978F4243F444C000D0384 /* UTI.swift in Sources */, 3235CD7B1C32DEBF0084EA40 /* MXKSearchViewController.m in Sources */, F079333E1F38A6A600F04D4C /* MXKView.m in Sources */, F00FA8791C08A51B00E25826 /* MXKRoomOutgoingTextMsgWithoutSenderInfoBubbleCell.m in Sources */, diff --git a/MatrixKit/Assets/MatrixKitAssets.bundle/cy.lproj/MatrixKit.strings b/MatrixKit/Assets/MatrixKitAssets.bundle/cy.lproj/MatrixKit.strings index ca06f5f38..bf43ef3d8 100644 --- a/MatrixKit/Assets/MatrixKitAssets.bundle/cy.lproj/MatrixKit.strings +++ b/MatrixKit/Assets/MatrixKitAssets.bundle/cy.lproj/MatrixKit.strings @@ -74,7 +74,7 @@ "ignore" = "Anwybyddu"; "unignore" = "Dad-anwybyddu"; // Events formatter -"notice_avatar_changed_too" = "(newidiwyd cynrychlun hefyd)"; +"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" = ""; @@ -280,7 +280,7 @@ "notice_room_ban" = "Gwaharddod %@ %@"; "notice_room_withdraw" = "Tynnodd %@ wahoddiad %@ yn ôl"; "notice_room_reason" = ". Rheswm: %@"; -"notice_avatar_url_changed" = "Newidiodd %@ eu cynrychlun"; +"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"; diff --git a/MatrixKit/Assets/MatrixKitAssets.bundle/en.lproj/MatrixKit.strings b/MatrixKit/Assets/MatrixKitAssets.bundle/en.lproj/MatrixKit.strings index bb9d09847..22c451e1c 100644 --- a/MatrixKit/Assets/MatrixKitAssets.bundle/en.lproj/MatrixKit.strings +++ b/MatrixKit/Assets/MatrixKitAssets.bundle/en.lproj/MatrixKit.strings @@ -113,7 +113,8 @@ "notice_room_aliases" = "The room aliases are: %@"; "notice_room_related_groups" = "The groups associated with this room are: %@"; "notice_encrypted_message" = "Encrypted message"; -"notice_encryption_enabled" = "%@ turned on end-to-end encryption (algorithm %@)"; +"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"; @@ -131,7 +132,7 @@ "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_joined_point" = "%@ made future room history visible to all room members, from the point they joined."; "notice_crypto_unable_to_decrypt" = "** Unable to decrypt: %@ **"; -"notice_crypto_error_unknown_inbound_session_id" = "The sender's device has not sent us the keys for this message."; +"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"; @@ -149,13 +150,13 @@ "notification_settings_room_rule_title" = "Room: '%@'"; // Devices -"device_details_title" = "Device information\n"; +"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" = "Device Name"; -"device_details_rename_prompt_message" = "A device's public name is visible to people you communicate with"; +"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."; @@ -170,8 +171,8 @@ "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 device information\n"; -"room_event_encryption_info_device_unknown" = "unknown device\n"; +"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"; @@ -183,8 +184,8 @@ "room_event_encryption_info_unverify" = "Unverify"; "room_event_encryption_info_block" = "Blacklist"; "room_event_encryption_info_unblock" = "Unblacklist"; -"room_event_encryption_verify_title" = "Verify device\n\n"; -"room_event_encryption_verify_message" = "To verify that this device 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 device matches the key below:\n\n\tDevice name: %@\n\tDevice ID: %@\n\tDevice key: %@\n\nIf it matches, press the verify button below. If it doesnt, then someone else is intercepting this device and you probably want to press the blacklist button instead.\n\nIn future this verification process will be more sophisticated."; +"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 diff --git a/MatrixKit/Assets/MatrixKitAssets.bundle/hu.lproj/MatrixKit.strings b/MatrixKit/Assets/MatrixKitAssets.bundle/hu.lproj/MatrixKit.strings index 4d39cca92..180399f1d 100644 --- a/MatrixKit/Assets/MatrixKitAssets.bundle/hu.lproj/MatrixKit.strings +++ b/MatrixKit/Assets/MatrixKitAssets.bundle/hu.lproj/MatrixKit.strings @@ -28,7 +28,7 @@ "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 linkre még nem kattintottál"; +"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"; @@ -40,36 +40,36 @@ // Action "no" = "Nem"; "yes" = "Igen"; -"abort" = "Megszakít"; +"abort" = "Megszakítás"; "close" = "Bezár"; -"discard" = "Elvet"; -"dismiss" = "Elutasít"; -"retry" = "Újrapróbál"; -"sign_up" = "Regisztrál"; -"submit" = "Elküld"; +"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" = "Moderátornak beállít"; -"set_admin" = "Adminisztrátornak beállít"; +"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"; +"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"; +"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" = "Mindet kijelöl"; +"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"; -"unignore" = "Figyelembe vesz"; +"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"; @@ -87,18 +87,18 @@ "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ép csatolmány"; -"notice_audio_attachment" = "hang csatolmány"; -"notice_video_attachment" = "videó csatolmány"; -"notice_location_attachment" = "helyadat csatolmány"; -"notice_file_attachment" = "fájl csatolmány"; -"notice_invalid_attachment" = "érvénytelen csatolmány"; -"notice_unsupported_attachment" = "Nem támogatott csatolmány: %@"; +"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ény típus"; +"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á."; @@ -110,32 +110,32 @@ // Settings "settings" = "Beállítások"; "settings_enable_inapp_notifications" = "Alkalmazáson belüli értesítések engedélyezése"; -"settings_enable_push_notifications" = "„Push” értesítések engedélyezése"; -"settings_enter_validation_token_for" = "Hitelesítési kód megadása (%@):"; -"notification_settings_room_rule_title" = "Szoba: '%@'"; +"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" = "Eszköz információ\n"; -"device_details_name" = "Nyilvános Név\n"; +"device_details_title" = "Eszközinformá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" = "Az eszköz nyilvános neve megjelenik azoknál az embereknél akikkel beszélgetsz"; -"device_details_delete_prompt_title" = "Azonosítás"; -"device_details_delete_prompt_message" = "A művelethet további azonosítás szükséges.\nA továbblépéshez add meg a jelszavadat."; +"device_details_rename_prompt_message" = "Az eszköz 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égponttól végpontig titkosítási információ\n\n"; +"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" = "Kapcsolat azonosító\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ő eszköz információ\n"; "room_event_encryption_info_device_unknown" = "ismeretlen eszköz\n"; -"room_event_encryption_info_device_name" = "Nyilvános Név\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"; @@ -144,20 +144,20 @@ "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ólista"; -"room_event_encryption_info_unblock" = "Tiltólistából töröl"; +"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" = "Eszköz ellenőrzése\n\n"; -"room_event_encryption_verify_ok" = "Ellenőriz"; +"room_event_encryption_verify_ok" = "Ellenőrzés"; // Account -"account_save_changes" = "Változások elmentése"; -"account_link_email" = "E-mail cím összekötés"; +"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" = "Matrix kapcsolat nincs megnyitva"; +"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"; @@ -176,8 +176,8 @@ "room_error_join_failed_empty_room" = "Üres szobába jelenleg nem lehet újra 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 az eszköz 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 az eszközhöz tartozó kulcs az megegyezik az alábbi kulccsal:\n\n\tEszköz neve: %@\n\tEszköz azonosító: %@\n\tEszköz kulcs: %@\n\nHa egyezik akkor nyomd meg az ellenőrizve gombot alább. Ha nem egyezik, akkor valaki jogosulatlanul akarja használni az eszközt; 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ő linkre. 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ő linkre. Ha az megvan akkor kattints itt a tovább gombra"; +"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"; @@ -367,7 +367,7 @@ "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_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"; @@ -378,4 +378,4 @@ "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" = "Eszköz Neve"; +"device_details_rename_prompt_title" = "Eszköznév"; diff --git a/MatrixKit/Assets/MatrixKitAssets.bundle/it.lproj/MatrixKit.strings b/MatrixKit/Assets/MatrixKitAssets.bundle/it.lproj/MatrixKit.strings index 7a63cc492..ce487ee69 100644 --- a/MatrixKit/Assets/MatrixKitAssets.bundle/it.lproj/MatrixKit.strings +++ b/MatrixKit/Assets/MatrixKitAssets.bundle/it.lproj/MatrixKit.strings @@ -326,7 +326,7 @@ "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 canale"; +"room_details_title" = "Dettagli stanza"; // Settings screen "settings_title_config" = "Configurazione"; "settings_title_notifications" = "Notifiche"; diff --git a/MatrixKit/Controllers/MXKRoomMemberDetailsViewController.h b/MatrixKit/Controllers/MXKRoomMemberDetailsViewController.h index e4747d47a..c2976f897 100644 --- a/MatrixKit/Controllers/MXKRoomMemberDetailsViewController.h +++ b/MatrixKit/Controllers/MXKRoomMemberDetailsViewController.h @@ -39,7 +39,9 @@ typedef enum : NSUInteger MXKRoomMemberDetailsActionStartChat, MXKRoomMemberDetailsActionStartVoiceCall, MXKRoomMemberDetailsActionStartVideoCall, - MXKRoomMemberDetailsActionMention + MXKRoomMemberDetailsActionMention, + MXKRoomMemberDetailsActionSecurity, + MXKRoomMemberDetailsActionSecurityInformation } MXKRoomMemberDetailsAction; diff --git a/MatrixKit/Controllers/MXKRoomMemberDetailsViewController.m b/MatrixKit/Controllers/MXKRoomMemberDetailsViewController.m index 2d8e138d6..cb9b32c73 100644 --- a/MatrixKit/Controllers/MXKRoomMemberDetailsViewController.m +++ b/MatrixKit/Controllers/MXKRoomMemberDetailsViewController.m @@ -441,31 +441,24 @@ - (IBAction)onActionButtonPressed:(id)sender else { // Create a new room - [self.mainSession createRoom:nil - visibility:kMXRoomDirectoryVisibilityPrivate - roomAlias:nil - topic:nil - invite:@[_mxRoomMember.userId] - invite3PID:nil - isDirect:YES - preset:kMXRoomPresetTrustedPrivateChat - 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) { - - NSLog(@"[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]; - - }]; + 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) { + + NSLog(@"[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; diff --git a/MatrixKit/Controllers/MXKRoomViewController.m b/MatrixKit/Controllers/MXKRoomViewController.m index 101e6c8a1..5febd9d35 100644 --- a/MatrixKit/Controllers/MXKRoomViewController.m +++ b/MatrixKit/Controllers/MXKRoomViewController.m @@ -546,7 +546,10 @@ - (void)destroy _bubblesTableView.delegate = nil; _bubblesTableView = nil; - roomDataSource.delegate = nil; + if (roomDataSource.delegate == self) + { + roomDataSource.delegate = nil; + } if (_hasRoomDataSourceOwnership) { @@ -657,13 +660,15 @@ - (void)displayRoom:(MXKRoomDataSource *)dataSource { if (roomDataSource) { - roomDataSource.delegate = nil; - if (self.hasRoomDataSourceOwnership) { // Release the room data source [roomDataSource destroy]; } + else if (roomDataSource.delegate == self) + { + roomDataSource.delegate = nil; + } roomDataSource = nil; [self removeMatrixSession:self.mainSession]; @@ -1006,13 +1011,15 @@ - (void)leaveRoomOnEvent:(MXEvent*)event _bubblesTableView.dataSource = nil; _bubblesTableView.delegate = nil; - roomDataSource.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 diff --git a/MatrixKit/Models/Account/MXKAccount.m b/MatrixKit/Models/Account/MXKAccount.m index 5eb1b7308..c57a6eba8 100644 --- a/MatrixKit/Models/Account/MXKAccount.m +++ b/MatrixKit/Models/Account/MXKAccount.m @@ -1266,7 +1266,14 @@ - (void)enablePushKitPusher:(BOOL)enabled success:(void (^)(void))success failur { NSLog(@"[MXKAccount][Push] enablePushKitPusher: %@", @(enabled)); - NSString *appId = [[NSUserDefaults standardUserDefaults] objectForKey:@"pushKitAppIdProd"]; + NSString *appIdKey; + #ifdef DEBUG + appIdKey = @"pushKitAppIdDev"; + #else + appIdKey = @"pushKitAppIdProd"; + #endif + + NSString *appId = [[NSUserDefaults standardUserDefaults] objectForKey:appIdKey]; NSMutableDictionary *pushData = [NSMutableDictionary dictionaryWithDictionary:@{@"url": self.pushGatewayURL}]; diff --git a/MatrixKit/Models/MXKAppSettings.m b/MatrixKit/Models/MXKAppSettings.m index 51568ed53..50593b911 100644 --- a/MatrixKit/Models/MXKAppSettings.m +++ b/MatrixKit/Models/MXKAppSettings.m @@ -131,8 +131,11 @@ -(instancetype)init kMXEventTypeStringCallInvite, kMXEventTypeStringCallAnswer, kMXEventTypeStringCallHangup, - kMXEventTypeStringSticker + kMXEventTypeStringSticker, + kMXEventTypeStringKeyVerificationCancel, + kMXEventTypeStringKeyVerificationDone ]]; + // List all the event types, except kMXEventTypeStringPresence which are not related to a specific room. allEventTypesForMessages = [NSMutableArray arrayWithArray:@[ @@ -155,7 +158,9 @@ -(instancetype)init kMXEventTypeStringCallInvite, kMXEventTypeStringCallAnswer, kMXEventTypeStringCallHangup, - kMXEventTypeStringSticker + kMXEventTypeStringSticker, + kMXEventTypeStringKeyVerificationCancel, + kMXEventTypeStringKeyVerificationDone ]]; } return self; diff --git a/MatrixKit/Models/Room/MXKRoomBubbleCellData.h b/MatrixKit/Models/Room/MXKRoomBubbleCellData.h index ccf7c3bdb..0fc71f4f7 100644 --- a/MatrixKit/Models/Room/MXKRoomBubbleCellData.h +++ b/MatrixKit/Models/Room/MXKRoomBubbleCellData.h @@ -105,6 +105,11 @@ @property (nonatomic) NSString *uploadId; @property (nonatomic) CGFloat uploadProgress; +/** + Indicate a bubble component needs to show encryption badge. + */ +@property (nonatomic, readonly) BOOL containsBubbleComponentWithEncryptionBadge; + /** Check and refresh the position of each component. */ @@ -136,4 +141,11 @@ */ - (NSInteger)bubbleComponentIndexForEventId:(NSString *)eventId; +/** + Get the first visible component. + + @return First visible component or nil. + */ +- (MXKRoomBubbleComponent*)getFirstBubbleComponentWithDisplay; + @end diff --git a/MatrixKit/Models/Room/MXKRoomBubbleCellData.m b/MatrixKit/Models/Room/MXKRoomBubbleCellData.m index 747aadcd5..b26c9cc22 100644 --- a/MatrixKit/Models/Room/MXKRoomBubbleCellData.m +++ b/MatrixKit/Models/Room/MXKRoomBubbleCellData.m @@ -46,7 +46,7 @@ - (instancetype)initWithEvent:(MXEvent *)event andRoomState:(MXRoomState *)roomS self.readReceipts = [NSMutableDictionary dictionary]; // Create the bubble component based on matrix event - MXKRoomBubbleComponent *firstComponent = [[MXKRoomBubbleComponent alloc] initWithEvent:event andRoomState:roomState andEventFormatter:roomDataSource.eventFormatter]; + MXKRoomBubbleComponent *firstComponent = [[MXKRoomBubbleComponent alloc] initWithEvent:event roomState:roomState eventFormatter:roomDataSource.eventFormatter session:roomDataSource.mxSession]; if (firstComponent) { bubbleComponents = [NSMutableArray array]; @@ -111,7 +111,7 @@ - (NSUInteger)updateEvent:(NSString *)eventId withEvent:(MXEvent *)event MXKRoomBubbleComponent *roomBubbleComponent = [bubbleComponents objectAtIndex:index]; if ([roomBubbleComponent.event.eventId isEqualToString:eventId]) { - [roomBubbleComponent updateWithEvent:event andRoomState:roomDataSource.roomState]; + [roomBubbleComponent updateWithEvent:event roomState:roomDataSource.roomState session:self.mxSession]; if (!roomBubbleComponent.textMessage.length) { [bubbleComponents removeObjectAtIndex:index]; @@ -807,6 +807,25 @@ - (BOOL)showAntivirusScanStatus return eventScan != nil && eventScan.antivirusScanStatus != MXAntivirusScanStatusTrusted; } +- (BOOL)containsBubbleComponentWithEncryptionBadge +{ + BOOL containsBubbleComponentWithEncryptionBadge = NO; + + @synchronized(bubbleComponents) + { + for (MXKRoomBubbleComponent *component in bubbleComponents) + { + if (component.showEncryptionBadge) + { + containsBubbleComponentWithEncryptionBadge = YES; + break; + } + } + } + + return containsBubbleComponentWithEncryptionBadge; +} + #pragma mark - Bubble collapsing - (BOOL)collapseWith:(id)cellData diff --git a/MatrixKit/Models/Room/MXKRoomBubbleCellDataWithAppendingMode.m b/MatrixKit/Models/Room/MXKRoomBubbleCellDataWithAppendingMode.m index d4b6edb9b..9069ce1f7 100644 --- a/MatrixKit/Models/Room/MXKRoomBubbleCellDataWithAppendingMode.m +++ b/MatrixKit/Models/Room/MXKRoomBubbleCellDataWithAppendingMode.m @@ -72,7 +72,7 @@ - (BOOL)addEvent:(MXEvent*)event andRoomState:(MXRoomState*)roomState } // Create new message component - MXKRoomBubbleComponent *addedComponent = [[MXKRoomBubbleComponent alloc] initWithEvent:event andRoomState:roomState andEventFormatter:roomDataSource.eventFormatter]; + MXKRoomBubbleComponent *addedComponent = [[MXKRoomBubbleComponent alloc] initWithEvent:event roomState:roomState eventFormatter:roomDataSource.eventFormatter session:self.mxSession]; if (addedComponent) { [self addComponent:addedComponent]; diff --git a/MatrixKit/Models/Room/MXKRoomBubbleComponent.h b/MatrixKit/Models/Room/MXKRoomBubbleComponent.h index 7b40e244a..b41e614bb 100644 --- a/MatrixKit/Models/Room/MXKRoomBubbleComponent.h +++ b/MatrixKit/Models/Room/MXKRoomBubbleComponent.h @@ -80,23 +80,30 @@ typedef enum : NSUInteger { */ @property (nonatomic) MXEventScan *eventScan; +/** + Indicate if an encryption badge should be shown. + */ +@property (nonatomic, readonly) BOOL showEncryptionBadge; + /** Create a new `MXKRoomBubbleComponent` object based on a `MXEvent` instance. @param event the event used to compose the bubble component. @param roomState the room state when the event occured. @param eventFormatter object used to format event into displayable string. + @param session the related matrix session. @return the newly created instance. */ -- (instancetype)initWithEvent:(MXEvent*)event andRoomState:(MXRoomState*)roomState andEventFormatter:(MXKEventFormatter*)eventFormatter; +- (instancetype)initWithEvent:(MXEvent*)event roomState:(MXRoomState*)roomState eventFormatter:(MXKEventFormatter*)eventFormatter session:(MXSession*)session; /** Update the event because its sent state changed or it is has been redacted. @param event the new event data. @param roomState the up-to-date state of the room. + @param session the related matrix session. */ -- (void)updateWithEvent:(MXEvent*)event andRoomState:(MXRoomState*)roomState; +- (void)updateWithEvent:(MXEvent*)event roomState:(MXRoomState*)roomState session:(MXSession*)session; @end diff --git a/MatrixKit/Models/Room/MXKRoomBubbleComponent.m b/MatrixKit/Models/Room/MXKRoomBubbleComponent.m index d77848d4d..8b80e7649 100644 --- a/MatrixKit/Models/Room/MXKRoomBubbleComponent.m +++ b/MatrixKit/Models/Room/MXKRoomBubbleComponent.m @@ -20,12 +20,12 @@ @implementation MXKRoomBubbleComponent -- (instancetype)initWithEvent:(MXEvent*)event andRoomState:(MXRoomState*)roomState andEventFormatter:(MXKEventFormatter*)formatter +- (instancetype)initWithEvent:(MXEvent*)event roomState:(MXRoomState*)roomState eventFormatter:(MXKEventFormatter*)eventFormatter session:(MXSession*)session; { if (self = [super init]) { // Build text component related to this event - _eventFormatter = formatter; + _eventFormatter = eventFormatter; MXKEventFormatterError error; NSAttributedString *eventString = [_eventFormatter attributedStringFromEvent:event withRoomState:roomState error:&error]; @@ -57,6 +57,8 @@ - (instancetype)initWithEvent:(MXEvent*)event andRoomState:(MXRoomState*)roomSta _displayFix |= MXKRoomBubbleComponentDisplayFixHtmlBlockquote; } } + + _showEncryptionBadge = [self shouldShowWarningBadgeForEvent:event roomState:(MXRoomState*)roomState session:session]; } return self; } @@ -65,7 +67,7 @@ - (void)dealloc { } -- (void)updateWithEvent:(MXEvent*)event andRoomState:(MXRoomState*)roomState +- (void)updateWithEvent:(MXEvent*)event roomState:(MXRoomState*)roomState session:(MXSession*)session { // Report the new event _event = event; @@ -83,6 +85,8 @@ - (void)updateWithEvent:(MXEvent*)event andRoomState:(MXRoomState*)roomState MXKEventFormatterError error; _attributedTextMessage = [_eventFormatter attributedStringFromEvent:event withRoomState:roomState error:&error]; + + _showEncryptionBadge = [self shouldShowWarningBadgeForEvent:event roomState:roomState session:session]; } - (NSString *)textMessage @@ -94,5 +98,45 @@ - (NSString *)textMessage return _textMessage; } +- (BOOL)shouldShowWarningBadgeForEvent:(MXEvent*)event roomState:(MXRoomState*)roomState session:(MXSession*)session +{ + if (!roomState.isEncrypted) + { + return NO; + } + + BOOL shouldShowWarningBadge = NO; + + if (!event.isEncrypted) + { + if (event.isLocalEvent + || event.isState + || event.contentHasBeenEdited) // Local echo for an edit is clear but uses a true event id, the one of the edited event + { + shouldShowWarningBadge = NO; + } + else + { + shouldShowWarningBadge = YES; + } + } + else if (event.decryptionError) + { + shouldShowWarningBadge = YES; + } + else if (event.sender) + { + MXUserTrustLevel *userTrustLevel = [session.crypto trustLevelForUser:event.sender]; + MXDeviceInfo *deviceInfo = [session.crypto eventDeviceInfo:event]; + + if (userTrustLevel.isVerified && !deviceInfo.trustLevel.isVerified) + { + shouldShowWarningBadge = YES; + } + } + + return shouldShowWarningBadge; +} + @end diff --git a/MatrixKit/Models/Room/MXKRoomDataSource.m b/MatrixKit/Models/Room/MXKRoomDataSource.m index 0324434ee..cbd6a8a9d 100644 --- a/MatrixKit/Models/Room/MXKRoomDataSource.m +++ b/MatrixKit/Models/Room/MXKRoomDataSource.m @@ -3189,8 +3189,25 @@ - (void)updateCellDataReactions:(id)cellData forEv - (BOOL)canReactToEventWithId:(NSString*)eventId { + BOOL canReact = NO; + MXEvent *event = [self eventWithEventId:eventId]; - return [self canPerformActionOnEvent:event]; + + if ([self canPerformActionOnEvent:event]) + { + NSString *messageType = event.content[@"msgtype"]; + + if ([messageType isEqualToString:kMXMessageTypeKeyVerificationRequest]) + { + canReact = NO; + } + else + { + canReact = YES; + } + } + + return canReact; } - (void)addReaction:(NSString *)reaction forEventId:(NSString *)eventId success:(void (^)(void))success failure:(void (^)(NSError *))failure diff --git a/MatrixKit/Models/RoomList/MXKSessionRecentsDataSource.m b/MatrixKit/Models/RoomList/MXKSessionRecentsDataSource.m index 139539c70..1f2b618d7 100644 --- a/MatrixKit/Models/RoomList/MXKSessionRecentsDataSource.m +++ b/MatrixKit/Models/RoomList/MXKSessionRecentsDataSource.m @@ -21,6 +21,7 @@ #pragma mark - Constant definitions NSString *const kMXKRecentCellIdentifier = @"kMXKRecentCellIdentifier"; +static NSTimeInterval const roomSummaryChangeThrottlerDelay = .5; @interface MXKSessionRecentsDataSource () @@ -38,6 +39,11 @@ @interface MXKSessionRecentsDataSource () Store the current search patterns list. */ NSArray* searchPatternsList; + + /** + Do not react on every summary change + */ + MXThrottler *roomSummaryChangeThrottler; } @end @@ -56,6 +62,8 @@ - (instancetype)initWithMatrixSession:(MXSession *)matrixSession // Set default data and view classes [self registerCellDataClass:MXKRecentCellData.class forCellIdentifier:kMXKRecentCellIdentifier]; + + roomSummaryChangeThrottler = [[MXThrottler alloc] initWithMinimumDelay:roomSummaryChangeThrottlerDelay]; } return self; } @@ -68,6 +76,9 @@ - (void)destroy [[NSNotificationCenter defaultCenter] removeObserver:self name:kMXSessionDidLeaveRoomNotification object:nil]; [[NSNotificationCenter defaultCenter] removeObserver:self name:kMXSessionDirectRoomsDidChangeNotification object:nil]; + [roomSummaryChangeThrottler cancelAll]; + roomSummaryChangeThrottler = nil; + cellDataArray = nil; internalCellDataArray = nil; filteredCellDataArray = nil; @@ -260,9 +271,15 @@ - (void)didDirectRoomsChange:(NSNotification *)notif } - (void)didRoomSummaryChanged:(NSNotification *)notif +{ + [roomSummaryChangeThrottler throttle:^{ + [self didRoomSummaryChanged2:notif]; + }]; +} + +- (void)didRoomSummaryChanged2:(NSNotification *)notif { MXRoomSummary *roomSummary = notif.object; - if (roomSummary.mxSession == self.mxSession && internalCellDataArray.count) { // Find the index of the related cell data diff --git a/MatrixKit/Utils/EventFormatter/MXKEventFormatter.m b/MatrixKit/Utils/EventFormatter/MXKEventFormatter.m index 50b6a9982..2e2bb1dc7 100644 --- a/MatrixKit/Utils/EventFormatter/MXKEventFormatter.m +++ b/MatrixKit/Utils/EventFormatter/MXKEventFormatter.m @@ -766,12 +766,22 @@ - (NSAttributedString *)attributedStringFromEvent:(MXEvent *)event withRoomState // Make the unknown inbound session id error description more user friendly errorDescription = [NSBundle mxk_localizedStringForKey:@"notice_crypto_error_unknown_inbound_session_id"]; } + else if ([event.decryptionError.domain isEqualToString:MXDecryptingErrorDomain] + && event.decryptionError.code == MXDecryptingErrorDuplicateMessageIndexCode) + { + // Hide duplicate message warnings + NSLog(@"[MXKEventFormatter] Warning: Duplicate message with error description %@", event.decryptionError); + displayText = nil; + } else { errorDescription = event.decryptionError.localizedDescription; } - displayText = [NSString stringWithFormat:[NSBundle mxk_localizedStringForKey:@"notice_crypto_unable_to_decrypt"], errorDescription]; + if (errorDescription) + { + displayText = [NSString stringWithFormat:[NSBundle mxk_localizedStringForKey:@"notice_crypto_unable_to_decrypt"], errorDescription]; + } } else { @@ -796,7 +806,14 @@ - (NSAttributedString *)attributedStringFromEvent:(MXEvent *)event withRoomState algorithm = redactedInfo; } - displayText = [NSString stringWithFormat:[NSBundle mxk_localizedStringForKey:@"notice_encryption_enabled"], senderDisplayName, algorithm]; + if ([algorithm isEqualToString:kMXCryptoMegolmAlgorithm]) + { + displayText = [NSString stringWithFormat:[NSBundle mxk_localizedStringForKey:@"notice_encryption_enabled_ok"], senderDisplayName]; + } + else + { + displayText = [NSString stringWithFormat:[NSBundle mxk_localizedStringForKey:@"notice_encryption_enabled_unknown_algorithm"], senderDisplayName, algorithm]; + } break; } diff --git a/MatrixKit/Utils/MXKConstants.m b/MatrixKit/Utils/MXKConstants.m index 17c4e91d9..3c4d4d579 100644 --- a/MatrixKit/Utils/MXKConstants.m +++ b/MatrixKit/Utils/MXKConstants.m @@ -18,7 +18,7 @@ #import "MXKConstants.h" -NSString *const MatrixKitVersion = @"0.11.4"; +NSString *const MatrixKitVersion = @"0.12.0"; NSString *const kMXKErrorNotification = @"kMXKErrorNotification"; diff --git a/MatrixKit/Utils/MXKUTI.swift b/MatrixKit/Utils/MXKUTI.swift index 0bc00b6f1..b180805f5 100644 --- a/MatrixKit/Utils/MXKUTI.swift +++ b/MatrixKit/Utils/MXKUTI.swift @@ -15,8 +15,12 @@ */ import Foundation +import ImageIO import MobileCoreServices -import SwiftUTI + +// We do not use the SwiftUTI pod anymore +// The library is embedded in MatrixKit. See Libs/SwiftUTI/README.md for more details +//import SwiftUTI /// MXKUTI represents a Universal Type Identifier (e.g. kUTTypePNG). /// See https://developer.apple.com/library/archive/documentation/FileManagement/Conceptual/understanding_utis/understand_utis_conc/understand_utis_conc.html#//apple_ref/doc/uid/TP40001319-CH202-SW5 for more information. diff --git a/MatrixKit/Views/EncryptionInfoView/MXKEncryptionInfoView.m b/MatrixKit/Views/EncryptionInfoView/MXKEncryptionInfoView.m index 4068be174..11e45d9bb 100644 --- a/MatrixKit/Views/EncryptionInfoView/MXKEncryptionInfoView.m +++ b/MatrixKit/Views/EncryptionInfoView/MXKEncryptionInfoView.m @@ -153,7 +153,7 @@ - (void)updateTextViewText if (!_mxDeviceInfo) { // Trigger a server request to get the device information for the event sender - mxCurrentOperation = [_mxSession.crypto downloadKeys:@[senderId] forceDownload:NO success:^(MXUsersDevicesMap *usersDevicesInfoMap) { + mxCurrentOperation = [_mxSession.crypto downloadKeys:@[senderId] forceDownload:NO success:^(MXUsersDevicesMap *usersDevicesInfoMap, NSDictionary *crossSigningKeysMap) { self->mxCurrentOperation = nil; @@ -296,7 +296,7 @@ - (void)updateTextViewText // Display here the Verify and Block buttons except if the device is the current one. _verifyButton.hidden = _blockButton.hidden = [_mxDeviceInfo.deviceId isEqualToString:_mxSession.matrixRestClient.credentials.deviceId]; - switch (_mxDeviceInfo.verified) + switch (_mxDeviceInfo.trustLevel.localVerificationStatus) { case MXDeviceUnknown: case MXDeviceUnverified: @@ -414,7 +414,8 @@ - (IBAction)onButtonPressed:(id)sender { [_mxSession.crypto setDeviceVerification:MXDeviceVerified forDevice:_mxDeviceInfo.deviceId ofUser:_mxDeviceInfo.userId success:^{ - self.mxDeviceInfo.verified = MXDeviceVerified; + // Refresh data + _mxDeviceInfo = [self.mxSession.crypto eventDeviceInfo:self.mxEvent]; if (self->_delegate) { [self->_delegate encryptionInfoView:self didDeviceInfoVerifiedChange:self.mxDeviceInfo]; @@ -431,11 +432,11 @@ - (IBAction)onButtonPressed:(id)sender if (sender == _verifyButton) { - verificationStatus = ((_mxDeviceInfo.verified == MXDeviceVerified) ? MXDeviceUnverified : MXDeviceVerified); + verificationStatus = ((_mxDeviceInfo.trustLevel.localVerificationStatus == MXDeviceVerified) ? MXDeviceUnverified : MXDeviceVerified); } else if (sender == _blockButton) { - verificationStatus = ((_mxDeviceInfo.verified == MXDeviceBlocked) ? MXDeviceUnverified : MXDeviceBlocked); + verificationStatus = ((_mxDeviceInfo.trustLevel.localVerificationStatus == MXDeviceBlocked) ? MXDeviceUnverified : MXDeviceBlocked); } else { @@ -469,7 +470,9 @@ - (IBAction)onButtonPressed:(id)sender { [_mxSession.crypto setDeviceVerification:verificationStatus forDevice:_mxDeviceInfo.deviceId ofUser:_mxDeviceInfo.userId success:^{ - self.mxDeviceInfo.verified = verificationStatus; + // Refresh data + _mxDeviceInfo = [self.mxSession.crypto eventDeviceInfo:self.mxEvent]; + if (self->_delegate) { [self->_delegate encryptionInfoView:self didDeviceInfoVerifiedChange:self.mxDeviceInfo]; diff --git a/MatrixKit/Views/MXKImageView.m b/MatrixKit/Views/MXKImageView.m index 194ed2040..5b1cd5243 100644 --- a/MatrixKit/Views/MXKImageView.m +++ b/MatrixKit/Views/MXKImageView.m @@ -369,12 +369,17 @@ - (void)layoutSubviews CGRect tabBarFrame = CGRectZero; UITabBarController *tabBarController = nil; + UIEdgeInsets safeAreaInsets = UIEdgeInsetsZero; if (leftButtonTitle || rightButtonTitle) { UIApplication *sharedApplication = [UIApplication performSelector:@selector(sharedApplication)]; if (sharedApplication) { + if (@available(iOS 11.0, *)) + { + safeAreaInsets = [sharedApplication keyWindow].safeAreaInsets; + } UIViewController *rootViewController = [sharedApplication keyWindow].rootViewController; tabBarController = rootViewController.tabBarController; if (!tabBarController && [rootViewController isKindOfClass:[UITabBarController class]]) @@ -390,7 +395,7 @@ - (void)layoutSubviews else { // Define a default tabBar frame - tabBarFrame = CGRectMake(0, 0, self.frame.size.width, 44); + tabBarFrame = CGRectMake(0, 0, self.frame.size.width, 44 + safeAreaInsets.bottom); } } @@ -464,12 +469,12 @@ - (void)layoutSubviews if (leftButton) { - leftButton.frame = CGRectMake(0, 0, CUSTOM_IMAGE_VIEW_BUTTON_WIDTH, bottomBarView.frame.size.height); + leftButton.frame = CGRectMake(safeAreaInsets.left, 0, CUSTOM_IMAGE_VIEW_BUTTON_WIDTH, bottomBarView.frame.size.height - safeAreaInsets.bottom); } if (rightButton) { - rightButton.frame = CGRectMake(bottomBarView.frame.size.width - CUSTOM_IMAGE_VIEW_BUTTON_WIDTH, 0, CUSTOM_IMAGE_VIEW_BUTTON_WIDTH, bottomBarView.frame.size.height); + rightButton.frame = CGRectMake(bottomBarView.frame.size.width - CUSTOM_IMAGE_VIEW_BUTTON_WIDTH - safeAreaInsets.right, 0, CUSTOM_IMAGE_VIEW_BUTTON_WIDTH, bottomBarView.frame.size.height - safeAreaInsets.bottom); } } diff --git a/MatrixKit/Views/MXKRoomCreationView.m b/MatrixKit/Views/MXKRoomCreationView.m index b505ea470..e0cf1af83 100644 --- a/MatrixKit/Views/MXKRoomCreationView.m +++ b/MatrixKit/Views/MXKRoomCreationView.m @@ -504,43 +504,35 @@ - (IBAction)onButtonPressed:(id)sender { // Disable button to prevent multiple request self->_createRoomBtn.enabled = NO; + + MXRoomCreationParameters *roomCreationParameters = [MXRoomCreationParameters new]; - NSString *roomName = self->_roomNameTextField.text; - if (! roomName.length) + roomCreationParameters.name = self->_roomNameTextField.text; + if (!roomCreationParameters.name.length) { - roomName = nil; + roomCreationParameters.name = nil; } // Check whether some users must be invited - NSArray *invitedUsers = self.participantsList; + roomCreationParameters.inviteArray = self.participantsList; // Prepare room settings - MXRoomDirectoryVisibility visibility; - BOOL isDirect = NO; if (self->_roomVisibilityControl.selectedSegmentIndex == 0) { - visibility = kMXRoomDirectoryVisibilityPublic; + roomCreationParameters.visibility = kMXRoomDirectoryVisibilityPublic; } else { - visibility = kMXRoomDirectoryVisibilityPrivate; - isDirect = (invitedUsers.count == 1); + roomCreationParameters.visibility = kMXRoomDirectoryVisibilityPrivate; + roomCreationParameters.isDirect = (roomCreationParameters.inviteArray.count == 1); } // Ensure direct chat are created with equal ops on both sides (the trusted_private_chat preset) - MXRoomPreset preset = (isDirect ? kMXRoomPresetTrustedPrivateChat : nil); + roomCreationParameters.preset = (roomCreationParameters.isDirect ? kMXRoomPresetTrustedPrivateChat : nil); // Create new room - [selectedSession createRoom:roomName - visibility:visibility - roomAlias:self.alias - topic:nil - invite:invitedUsers - invite3PID:nil - isDirect:isDirect - preset:preset - success:^(MXRoom *room) { + [selectedSession createRoomWithParameters:roomCreationParameters success:^(MXRoom *room) { // Reset text fields self->_roomNameTextField.text = nil; diff --git a/MatrixKit/Views/RoomBubbleList/MXKRoomBubbleTableViewCell.h b/MatrixKit/Views/RoomBubbleList/MXKRoomBubbleTableViewCell.h index ec929f2eb..fea559148 100644 --- a/MatrixKit/Views/RoomBubbleList/MXKRoomBubbleTableViewCell.h +++ b/MatrixKit/Views/RoomBubbleList/MXKRoomBubbleTableViewCell.h @@ -121,6 +121,7 @@ extern NSString *const kMXKRoomBubbleCellShouldInteractWithURL; */ extern NSString *const kMXKRoomBubbleCellUserIdKey; extern NSString *const kMXKRoomBubbleCellEventKey; +extern NSString *const kMXKRoomBubbleCellEventIdKey; extern NSString *const kMXKRoomBubbleCellReceiptsContainerKey; extern NSString *const kMXKRoomBubbleCellUrl; extern NSString *const kMXKRoomBubbleCellUrlItemInteraction; @@ -312,4 +313,14 @@ extern NSString *const kMXKRoomBubbleCellUrlItemInteraction; */ - (void)resetAttachmentViewBottomConstraintConstant; +/** + Redeclare heightForCellData:withMaximumWidth: method from MXKCellRendering to use it as a class method in Swift and not a static method. + */ ++ (CGFloat)heightForCellData:(MXKCellData*)cellData withMaximumWidth:(CGFloat)maxWidth; + +/** + Setup outlets views. Useful to call when cell subclass does not use a xib otherwise this method is called automatically in `awakeFromNib`. + */ +- (void)setupViews; + @end diff --git a/MatrixKit/Views/RoomBubbleList/MXKRoomBubbleTableViewCell.m b/MatrixKit/Views/RoomBubbleList/MXKRoomBubbleTableViewCell.m index 9b96977b6..b29b2991d 100644 --- a/MatrixKit/Views/RoomBubbleList/MXKRoomBubbleTableViewCell.m +++ b/MatrixKit/Views/RoomBubbleList/MXKRoomBubbleTableViewCell.m @@ -48,6 +48,7 @@ NSString *const kMXKRoomBubbleCellUserIdKey = @"kMXKRoomBubbleCellUserIdKey"; NSString *const kMXKRoomBubbleCellEventKey = @"kMXKRoomBubbleCellEventKey"; +NSString *const kMXKRoomBubbleCellEventIdKey = @"kMXKRoomBubbleCellEventIdKey"; NSString *const kMXKRoomBubbleCellReceiptsContainerKey = @"kMXKRoomBubbleCellReceiptsContainerKey"; NSString *const kMXKRoomBubbleCellUrl = @"kMXKRoomBubbleCellUrl"; NSString *const kMXKRoomBubbleCellUrlItemInteraction = @"kMXKRoomBubbleCellUrlItemInteraction"; @@ -126,6 +127,11 @@ - (void)awakeFromNib { [super awakeFromNib]; + [self setupViews]; +} + +- (void)setupViews +{ if (self.userNameLabel) { // Listen to name tap @@ -1424,6 +1430,10 @@ - (IBAction)onContentViewTap:(UITapGestureRecognizer*)sender tappedComponent = [self closestBubbleComponentForGestureRecognizer:sender locationInView:self.messageTextView]; } } + else + { + tappedComponent = [self.bubbleData getFirstBubbleComponentWithDisplay]; + } [delegate cell:self didRecognizeAction:kMXKRoomBubbleCellTapOnContentView userInfo:(tappedComponent ? @{kMXKRoomBubbleCellEventKey:tappedComponent.event} : nil)]; } @@ -1469,6 +1479,10 @@ - (IBAction)onLongPressGesture:(UILongPressGestureRecognizer*)longPressGestureRe // Check whether the user tapped in front of a text component. tappedComponent = [self closestBubbleComponentForGestureRecognizer:longPressGestureRecognizer locationInView:self.messageTextView]; } + else + { + tappedComponent = [self.bubbleData getFirstBubbleComponentWithDisplay]; + } [delegate cell:self didRecognizeAction:kMXKRoomBubbleCellLongPressOnEvent userInfo:(tappedComponent ? @{kMXKRoomBubbleCellEventKey:tappedComponent.event} : nil)]; } @@ -1542,6 +1556,13 @@ - (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceive if ([recognizerView isDescendantOfView:self.contentView]) { + UIView *touchedView = touch.view; + + if ([touchedView isKindOfClass:[UIButton class]]) + { + return NO; + } + // Prevent gesture recognizer to be recognized by a custom view added to the cell contentView and with user interaction enabled for (UIView *tmpSubview in self.tmpSubviews) { @@ -1557,9 +1578,9 @@ - (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceive } // Prevent gesture recognizer to be recognized when user hits a link in a UITextView, let UITextViewDelegate handle links. - if ([touch.view isKindOfClass:[UITextView class]]) + if ([touchedView isKindOfClass:[UITextView class]]) { - UITextView *textView = (UITextView*)touch.view; + UITextView *textView = (UITextView*)touchedView; CGPoint touchLocation = [touch locationInView:textView]; return [textView isThereALinkNearPoint:touchLocation] == NO; diff --git a/MatrixKitTests/EncryptedAttachmentsTest.m b/MatrixKitTests/EncryptedAttachmentsTest.m index a4c1e058f..628cad139 100644 --- a/MatrixKitTests/EncryptedAttachmentsTest.m +++ b/MatrixKitTests/EncryptedAttachmentsTest.m @@ -18,6 +18,7 @@ #import "MXEncryptedAttachments.h" #import "MXEncryptedContentFile.h" +#import "MXBase64Tools.h" @interface EncryptedAttachmentsTest : XCTestCase @@ -94,7 +95,7 @@ - (void)testDecrypt { MXEncryptedContentFile *inputInfo = [MXEncryptedContentFile modelFromJSON:vector[1]]; NSString *want = vector[2]; - NSData *ctData = [[NSData alloc] initWithBase64EncodedString:[MXEncryptedAttachments padBase64:inputCiphertext] options:0]; + NSData *ctData = [[NSData alloc] initWithBase64EncodedString:[MXBase64Tools padBase64:inputCiphertext] options:0]; NSInputStream *inputStream = [NSInputStream inputStreamWithData:ctData]; NSOutputStream *outputStream = [NSOutputStream outputStreamToMemory]; @@ -102,7 +103,7 @@ - (void)testDecrypt { XCTAssertNil(err); NSData *gotData = [outputStream propertyForKey:NSStreamDataWrittenToMemoryStreamKey]; - NSData *wantData = [[NSData alloc] initWithBase64EncodedString:[MXEncryptedAttachments padBase64:want] options:0]; + NSData *wantData = [[NSData alloc] initWithBase64EncodedString:[MXBase64Tools padBase64:want] options:0]; XCTAssertEqualObjects(wantData, gotData, "Decrypted data did not match expectation."); } diff --git a/Podfile b/Podfile index 7b0a72258..f455112da 100644 --- a/Podfile +++ b/Podfile @@ -8,7 +8,7 @@ abstract_target 'MatrixKitSamplePods' do # Different flavours of pods to Matrix SDK # The tagged version on which this version of MatrixKit has been built - pod 'MatrixSDK', '0.15.2' + pod 'MatrixSDK', '0.16.0' # The lastest release available on the CocoaPods repository #pod 'MatrixSDK' @@ -25,10 +25,8 @@ abstract_target 'MatrixKitSamplePods' do pod 'JSQMessagesViewController', '~> 7.2.0' pod 'DTCoreText', '~> 1.6.21' pod 'cmark', '~> 0.24.1', :inhibit_warnings => true - - pod 'SwiftUTI', :git => 'https://github.com/speramusinc/SwiftUTI.git', :branch => 'master' - - + + target "MatrixKitSample" do end diff --git a/Podfile.lock b/Podfile.lock index 94dfbfc7d..0dc08b6d8 100644 --- a/Podfile.lock +++ b/Podfile.lock @@ -39,9 +39,9 @@ PODS: - JSQSystemSoundPlayer (2.0.1) - libbase58 (0.1.4) - libPhoneNumber-iOS (0.9.15) - - MatrixSDK (0.15.2): - - MatrixSDK/Core (= 0.15.2) - - MatrixSDK/Core (0.15.2): + - MatrixSDK (0.16.0): + - MatrixSDK/Core (= 0.16.0) + - MatrixSDK/Core (0.16.0): - AFNetworking (~> 3.2.0) - GZIP (~> 1.2.2) - libbase58 (~> 0.1.4) @@ -55,7 +55,6 @@ PODS: - Realm (3.17.3): - Realm/Headers (= 3.17.3) - Realm/Headers (3.17.3) - - SwiftUTI (1.0.7) DEPENDENCIES: - cmark (~> 0.24.1) @@ -63,8 +62,7 @@ DEPENDENCIES: - HPGrowingTextView (~> 1.1) - JSQMessagesViewController (~> 7.2.0) - libPhoneNumber-iOS (~> 0.9.13) - - MatrixSDK (= 0.15.2) - - SwiftUTI (from `https://github.com/speramusinc/SwiftUTI.git`, branch `master`) + - MatrixSDK (= 0.16.0) SPEC REPOS: trunk: @@ -82,16 +80,6 @@ SPEC REPOS: - OLMKit - Realm -EXTERNAL SOURCES: - SwiftUTI: - :branch: master - :git: https://github.com/speramusinc/SwiftUTI.git - -CHECKOUT OPTIONS: - SwiftUTI: - :commit: b6b46942fb3aad819610851f62a70e17a528444e - :git: https://github.com/speramusinc/SwiftUTI.git - SPEC CHECKSUMS: AFNetworking: b6f891fdfaed196b46c7a83cf209e09697b94057 cmark: 1d9ad0375e3b9fa281732e992467903606015520 @@ -103,11 +91,10 @@ SPEC CHECKSUMS: JSQSystemSoundPlayer: c5850e77a4363ffd374cd851154b9af93264ed8d libbase58: 7c040313537b8c44b6e2d15586af8e21f7354efd libPhoneNumber-iOS: 0a32a9525cf8744fe02c5206eb30d571e38f7d75 - MatrixSDK: f83bd3c5519c1cb9ac3998f6423574cf528f0250 + MatrixSDK: caad3d93c665163d9d414f6876e9a7d220c6c46f OLMKit: 4ee0159d63feeb86d836fdcfefe418e163511639 Realm: 5a1d9d47bfc101dd597668b1a8af4288a2557f6d - SwiftUTI: 917993c124f8eac25e88ced0202fc58d7eb50fa8 -PODFILE CHECKSUM: 5856db438bc5c3de4b39b48163542c87c3b67246 +PODFILE CHECKSUM: 26acd43fc53f1856e252bad2a404478a56835a51 -COCOAPODS: 1.8.4 +COCOAPODS: 1.9.1