Merge branch 'develop' into andy/4829_activity_indicators

This commit is contained in:
Andy Uhnak 2022-02-16 11:32:52 +00:00
commit 18af47ad34
66 changed files with 902 additions and 104 deletions

View file

@ -1,3 +1,15 @@
## Changes in 1.8.1 (2022-02-16)
🙌 Improvements
- Upgrade MatrixSDK version ([v0.22.1](https://github.com/matrix-org/matrix-ios-sdk/releases/tag/v0.22.1)).
🐛 Bugfixes
- Settings: Fix a bug where tapping a toggle could change multiple settings. ([#5463](https://github.com/vector-im/element-ios/issues/5463))
- Fix for images sometimes being sent unencrypted inside an encrypted room. ([#5564](https://github.com/vector-im/element-ios/issues/5564))
## Changes in 1.8.0 (2022-02-09) ## Changes in 1.8.0 (2022-02-09)
✨ Features ✨ Features

28
CommonKit/Common.xcconfig Normal file
View file

@ -0,0 +1,28 @@
//
// Copyright 2021 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.
//
// Configuration settings file format documentation can be found at:
// https://help.apple.com/xcode/#/dev745c5c974
#include "Config/AppIdentifiers.xcconfig"
#include "Config/AppVersion.xcconfig"
PRODUCT_NAME = CommonKit
PRODUCT_BUNDLE_IDENTIFIER = $(BASE_BUNDLE_IDENTIFIER).commonkit
INFOPLIST_FILE = CommonKit/Info.plist
SKIP_INSTALL = YES

27
CommonKit/CommonKit.h Normal file
View file

@ -0,0 +1,27 @@
//
// Copyright 2021 New Vector Ltd
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
#import <Foundation/Foundation.h>
//! Project version number for CommonKit.
FOUNDATION_EXPORT double CommonKitVersionNumber;
//! Project version string for CommonKit.
FOUNDATION_EXPORT const unsigned char CommonKitVersionString[];
// In this header, you should import all the public headers of your framework using statements like #import <CommonKit/PublicHeader.h>

20
CommonKit/Debug.xcconfig Normal file
View file

@ -0,0 +1,20 @@
//
// Copyright 2021 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.
//
// Configuration settings file format documentation can be found at:
// https://help.apple.com/xcode/#/dev745c5c974
#include "Common.xcconfig"

22
CommonKit/Info.plist Normal file
View file

@ -0,0 +1,22 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>$(PRODUCT_NAME)</string>
<key>CFBundlePackageType</key>
<string>$(PRODUCT_BUNDLE_PACKAGE_TYPE)</string>
<key>CFBundleShortVersionString</key>
<string>$(MARKETING_VERSION)</string>
<key>CFBundleVersion</key>
<string>$(CURRENT_PROJECT_VERSION)</string>
</dict>
</plist>

View file

@ -0,0 +1,20 @@
//
// Copyright 2021 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.
//
// Configuration settings file format documentation can be found at:
// https://help.apple.com/xcode/#/dev745c5c974
#include "Common.xcconfig"

View file

@ -0,0 +1,94 @@
//
// Copyright 2021 New Vector Ltd
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
import Foundation
import UIKit
/// An `Activity` represents the state of a temporary visual indicator, such as activity indicator, success notification or an error message. It does not directly manage the UI, instead it delegates to a `presenter`
/// whenever the UI should be shown or hidden.
///
/// More than one `Activity` may be requested by the system at the same time (e.g. global syncing vs local refresh),
/// and the `ActivityCenter` will ensure that only one activity is shown at a given time, putting the other in a pending queue.
///
/// A client that requests an activity can specify a default timeout after which the activity is dismissed, or it has to be manually
/// responsible for dismissing it via `cancel` method, or by deallocating itself.
public class Activity {
enum State {
case pending
case executing
case completed
}
private let request: ActivityRequest
private let completion: () -> Void
private(set) var state: State
public init(request: ActivityRequest, completion: @escaping () -> Void) {
self.request = request
self.completion = completion
state = .pending
}
deinit {
cancel()
}
internal func start() {
guard state == .pending else {
return
}
state = .executing
request.presenter.present()
switch request.dismissal {
case .manual:
break
case .timeout(let interval):
Timer.scheduledTimer(withTimeInterval: interval, repeats: false) { [weak self] _ in
self?.complete()
}
}
}
/// Cancel the activity, triggering any dismissal action / animation
///
/// Note: clients can call this method directly, if they have access to the `Activity`.
/// Once cancelled, `ActivityCenter` will automatically start the next `Activity` in the queue.
func cancel() {
complete()
}
private func complete() {
guard state != .completed else {
return
}
if state == .executing {
request.presenter.dismiss()
}
state = .completed
completion()
}
}
public extension Activity {
func store<C>(in collection: inout C) where C: RangeReplaceableCollection, C.Element == Activity {
collection.append(self)
}
}

View file

@ -0,0 +1,60 @@
//
// Copyright 2021 New Vector Ltd
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
import Foundation
/// A shared activity center with a single FIFO queue which will ensure only one activity is shown at a given time.
///
/// `ActivityCenter` offers a `shared` center that can be used by any clients, but clients are also allowed
/// to create local `ActivityCenter` if the context requres multiple simultaneous activities.
public class ActivityCenter {
private class Weak<T: AnyObject> {
weak var element: T?
init(_ element: T) {
self.element = element
}
}
public static let shared = ActivityCenter()
private var queue = [Weak<Activity>]()
/// Add a new activity to the queue by providing a request.
///
/// The queue will start the activity right away, if there are no currently running activities,
/// otherwise the activity will be put on hold.
public func add(_ request: ActivityRequest) -> Activity {
let activity = Activity(request: request) { [weak self] in
self?.startNextIfIdle()
}
queue.append(Weak(activity))
startNextIfIdle()
return activity
}
private func startNextIfIdle() {
cleanup()
if let activity = queue.first?.element, activity.state == .pending {
activity.start()
}
}
private func cleanup() {
queue.removeAll {
$0.element == nil || $0.element?.state == .completed
}
}
}

View file

@ -0,0 +1,25 @@
//
// Copyright 2021 New Vector Ltd
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
import Foundation
/// Different ways in which an `Activity` can be dismissed
public enum ActivityDismissal {
/// The `Activity` will not manage the dismissal, but will expect the calling client to do so manually
case manual
/// The `Activity` will be automatically dismissed after `TimeInterval`
case timeout(TimeInterval)
}

View file

@ -0,0 +1,25 @@
//
// Copyright 2021 New Vector Ltd
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
import Foundation
/// A presenter associated with and called by an `Activity`, and responsible for the underlying view shown on the screen.
public protocol ActivityPresentable {
/// Called when the `Activity` is started (manually or by the `ActivityCenter`)
func present()
/// Called when the `Activity` is manually cancelled or completed
func dismiss()
}

View file

@ -0,0 +1,28 @@
//
// Copyright 2021 New Vector Ltd
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
import Foundation
/// A request used to create an underlying `Activity`, allowing clients to only specify the visual aspects of an activity.
public struct ActivityRequest {
internal let presenter: ActivityPresentable
internal let dismissal: ActivityDismissal
public init(presenter: ActivityPresentable, dismissal: ActivityDismissal) {
self.presenter = presenter
self.dismissal = dismissal
}
}

View file

@ -0,0 +1,55 @@
//
// Copyright 2021 New Vector Ltd
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
import Foundation
import XCTest
class ActivityCenterTests: XCTestCase {
var activities: [Activity]!
var center: ActivityCenter!
override func setUp() {
activities = []
center = ActivityCenter()
}
func makeRequest() -> ActivityRequest {
return ActivityRequest(
presenter: ActivityPresenterSpy(),
dismissal: .manual
)
}
func testStartsActivityWhenAdded() {
let activity = center.add(makeRequest())
XCTAssertEqual(activity.state, .executing)
}
func testSecondActivityIsPending() {
center.add(makeRequest()).store(in: &activities)
let activity = center.add(makeRequest())
XCTAssertEqual(activity.state, .pending)
}
func testSecondActivityIsExecutingWhenFirstCompleted() {
let first = center.add(makeRequest())
let second = center.add(makeRequest())
first.cancel()
XCTAssertEqual(second.state, .executing)
}
}

View file

@ -0,0 +1,29 @@
//
// Copyright 2021 New Vector Ltd
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
import Foundation
class ActivityPresenterSpy: ActivityPresentable {
var intel = [String]()
func present() {
intel.append(#function)
}
func dismiss() {
intel.append(#function)
}
}

View file

@ -0,0 +1,127 @@
//
// Copyright 2021 New Vector Ltd
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
import Foundation
import XCTest
class ActivityTests: XCTestCase {
var presenter: ActivityPresenterSpy!
override func setUp() {
super.setUp()
presenter = ActivityPresenterSpy()
}
func makeActivity(dismissal: ActivityDismissal = .manual, callback: @escaping () -> Void = {}) -> Activity {
let request = ActivityRequest(
presenter: presenter,
dismissal: dismissal
)
return Activity(
request: request,
completion: callback
)
}
// MARK: - State
func testNewActivityIsPending() {
let activity = makeActivity()
XCTAssertEqual(activity.state, .pending)
}
func testStartedActivityIsExecuting() {
let activity = makeActivity()
activity.start()
XCTAssertEqual(activity.state, .executing)
}
func testCancelledActivityIsCompleted() {
let activity = makeActivity()
activity.cancel()
XCTAssertEqual(activity.state, .completed)
}
// MARK: - Presenter
func testStartingActivityPresentsUI() {
let activity = makeActivity()
activity.start()
XCTAssertEqual(presenter.intel, ["present()"])
}
func testAllowStartingOnlyOnce() {
let activity = makeActivity()
activity.start()
presenter.intel = []
activity.start()
XCTAssertEqual(presenter.intel, [])
}
func testCancellingActivityDismissesUI() {
let activity = makeActivity()
activity.start()
presenter.intel = []
activity.cancel()
XCTAssertEqual(presenter.intel, ["dismiss()"])
}
func testAllowCancellingOnlyOnce() {
let activity = makeActivity()
activity.start()
activity.cancel()
presenter.intel = []
activity.cancel()
XCTAssertEqual(presenter.intel, [])
}
// MARK: - Dismissal
func testDismissAfterTimeout() {
let interval: TimeInterval = 0.01
let activity = makeActivity(dismissal: .timeout(interval))
activity.start()
let exp = expectation(description: "")
DispatchQueue.main.asyncAfter(deadline: .now() + interval) {
exp.fulfill()
}
waitForExpectations(timeout: 1)
XCTAssertEqual(activity.state, .completed)
}
// MARK: - Completion callback
func testTriggersCallbackWhenCompleted() {
var didComplete = false
let activity = makeActivity {
didComplete = true
}
activity.start()
activity.cancel()
XCTAssertTrue(didComplete)
}
}

40
CommonKit/target.yml Normal file
View file

@ -0,0 +1,40 @@
name: CommonKit
schemes:
CommonKit:
analyze:
config: Debug
archive:
config: Release
build:
targets:
CommonKit:
- running
- testing
- profiling
- analyzing
- archiving
profile:
config: Release
run:
config: Debug
disableMainThreadChecker: true
test:
config: Debug
disableMainThreadChecker: true
targets:
- CommonKitUnitTests
targets:
CommonKit:
type: framework
platform: iOS
configFiles:
Debug: Debug.xcconfig
Release: Release.xcconfig
sources:
- path: .
excludes:
- "**/Tests/**"

View file

@ -0,0 +1,42 @@
name: CommonKitUnitTests
schemes:
CommonKitUnitTests:
analyze:
config: Debug
archive:
config: Release
build:
targets:
CommonKitUnitTests:
- running
- testing
- profiling
- analyzing
- archiving
profile:
config: Release
run:
config: Debug
disableMainThreadChecker: true
test:
config: Debug
disableMainThreadChecker: true
targets:
- CommonKitUnitTests
targets:
CommonKitUnitTests:
type: bundle.unit-test
platform: iOS
dependencies:
- target: CommonKit
configFiles:
Debug: Debug.xcconfig
Release: Release.xcconfig
sources:
- path: .

View file

@ -15,5 +15,5 @@
// //
// Version // Version
MARKETING_VERSION = 1.8.1 MARKETING_VERSION = 1.8.2
CURRENT_PROJECT_VERSION = 1.8.1 CURRENT_PROJECT_VERSION = 1.8.2

View file

@ -13,9 +13,9 @@ use_frameworks!
# - `{ :specHash => {sdk spec hash}` to depend on specific pod options (:git => …, :podspec => …) for MatrixSDK repo. Used by Fastfile during CI # - `{ :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 # Warning: our internal tooling depends on the name of this variable name, so be sure not to change it
# $matrixSDKVersion = '= 0.22.0' $matrixSDKVersion = '= 0.22.1'
# $matrixSDKVersion = :local # $matrixSDKVersion = :local
$matrixSDKVersion = { :branch => 'develop'} # $matrixSDKVersion = { :branch => 'develop'}
# $matrixSDKVersion = { :specHash => { git: 'https://git.io/fork123', branch: 'fix' } } # $matrixSDKVersion = { :specHash => { git: 'https://git.io/fork123', branch: 'fix' } }
######################################## ########################################

View file

@ -45,6 +45,7 @@ PODS:
- GBDeviceInfo/Core (= 6.6.0) - GBDeviceInfo/Core (= 6.6.0)
- GBDeviceInfo/Core (6.6.0) - GBDeviceInfo/Core (6.6.0)
- GZIP (1.3.0) - GZIP (1.3.0)
- HPGrowingTextView (1.1)
- Introspect (0.1.3) - Introspect (0.1.3)
- JitsiMeetSDK (3.10.2) - JitsiMeetSDK (3.10.2)
- KeychainAccess (4.2.2) - KeychainAccess (4.2.2)
@ -56,16 +57,16 @@ PODS:
- LoggerAPI (1.9.200): - LoggerAPI (1.9.200):
- Logging (~> 1.1) - Logging (~> 1.1)
- Logging (1.4.0) - Logging (1.4.0)
- MatrixSDK (0.22.0): - MatrixSDK (0.22.1):
- MatrixSDK/Core (= 0.22.0) - MatrixSDK/Core (= 0.22.1)
- MatrixSDK/Core (0.22.0): - MatrixSDK/Core (0.22.1):
- AFNetworking (~> 4.0.0) - AFNetworking (~> 4.0.0)
- GZIP (~> 1.3.0) - GZIP (~> 1.3.0)
- libbase58 (~> 0.1.4) - libbase58 (~> 0.1.4)
- OLMKit (~> 3.2.5) - OLMKit (~> 3.2.5)
- Realm (= 10.16.0) - Realm (= 10.16.0)
- SwiftyBeaver (= 1.9.5) - SwiftyBeaver (= 1.9.5)
- MatrixSDK/JingleCallStack (0.22.0): - MatrixSDK/JingleCallStack (0.22.1):
- JitsiMeetSDK (= 3.10.2) - JitsiMeetSDK (= 3.10.2)
- MatrixSDK/Core - MatrixSDK/Core
- OLMKit (3.2.5): - OLMKit (3.2.5):
@ -111,12 +112,13 @@ DEPENDENCIES:
- FLEX (~> 4.5.0) - FLEX (~> 4.5.0)
- FlowCommoniOS (~> 1.12.0) - FlowCommoniOS (~> 1.12.0)
- GBDeviceInfo (~> 6.6.0) - GBDeviceInfo (~> 6.6.0)
- HPGrowingTextView (~> 1.1)
- Introspect (~> 0.1) - Introspect (~> 0.1)
- KeychainAccess (~> 4.2.2) - KeychainAccess (~> 4.2.2)
- KTCenterFlowLayout (~> 1.3.1) - KTCenterFlowLayout (~> 1.3.1)
- libPhoneNumber-iOS (~> 0.9.13) - libPhoneNumber-iOS (~> 0.9.13)
- MatrixSDK (from `https://github.com/matrix-org/matrix-ios-sdk.git`, branch `develop`) - MatrixSDK (= 0.22.1)
- MatrixSDK/JingleCallStack (from `https://github.com/matrix-org/matrix-ios-sdk.git`, branch `develop`) - MatrixSDK/JingleCallStack (= 0.22.1)
- OLMKit - OLMKit
- PostHog (~> 1.4.4) - PostHog (~> 1.4.4)
- ReadMoreTextView (~> 3.0.1) - ReadMoreTextView (~> 3.0.1)
@ -147,6 +149,7 @@ SPEC REPOS:
- FlowCommoniOS - FlowCommoniOS
- GBDeviceInfo - GBDeviceInfo
- GZIP - GZIP
- HPGrowingTextView
- Introspect - Introspect
- JitsiMeetSDK - JitsiMeetSDK
- KeychainAccess - KeychainAccess
@ -156,6 +159,7 @@ SPEC REPOS:
- libPhoneNumber-iOS - libPhoneNumber-iOS
- LoggerAPI - LoggerAPI
- Logging - Logging
- MatrixSDK
- OLMKit - OLMKit
- PostHog - PostHog
- ReadMoreTextView - ReadMoreTextView
@ -176,17 +180,11 @@ EXTERNAL SOURCES:
AnalyticsEvents: AnalyticsEvents:
:branch: release/swift :branch: release/swift
:git: https://github.com/matrix-org/matrix-analytics-events.git :git: https://github.com/matrix-org/matrix-analytics-events.git
MatrixSDK:
:branch: develop
:git: https://github.com/matrix-org/matrix-ios-sdk.git
CHECKOUT OPTIONS: CHECKOUT OPTIONS:
AnalyticsEvents: AnalyticsEvents:
:commit: 8058dc6ec07ce0acfe5fdb19eb7e309b0c13845c :commit: 8058dc6ec07ce0acfe5fdb19eb7e309b0c13845c
:git: https://github.com/matrix-org/matrix-analytics-events.git :git: https://github.com/matrix-org/matrix-analytics-events.git
MatrixSDK:
:commit: 7be07981c3f2932b0205797f234982ca32da7dff
:git: https://github.com/matrix-org/matrix-ios-sdk.git
SPEC CHECKSUMS: SPEC CHECKSUMS:
AFNetworking: 7864c38297c79aaca1500c33288e429c3451fdce AFNetworking: 7864c38297c79aaca1500c33288e429c3451fdce
@ -204,6 +202,7 @@ SPEC CHECKSUMS:
FlowCommoniOS: ca92071ab526dc89905495a37844fd7e78d1a7f2 FlowCommoniOS: ca92071ab526dc89905495a37844fd7e78d1a7f2
GBDeviceInfo: ed0db16230d2fa280e1cbb39a5a7f60f6946aaec GBDeviceInfo: ed0db16230d2fa280e1cbb39a5a7f60f6946aaec
GZIP: 416858efbe66b41b206895ac6dfd5493200d95b3 GZIP: 416858efbe66b41b206895ac6dfd5493200d95b3
HPGrowingTextView: 88a716d97fb853bcb08a4a08e4727da17efc9b19
Introspect: 2be020f30f084ada52bb4387fff83fa52c5c400e Introspect: 2be020f30f084ada52bb4387fff83fa52c5c400e
JitsiMeetSDK: 2f118fa770f23e518f3560fc224fae3ac7062223 JitsiMeetSDK: 2f118fa770f23e518f3560fc224fae3ac7062223
KeychainAccess: c0c4f7f38f6fc7bbe58f5702e25f7bd2f65abf51 KeychainAccess: c0c4f7f38f6fc7bbe58f5702e25f7bd2f65abf51
@ -213,7 +212,7 @@ SPEC CHECKSUMS:
libPhoneNumber-iOS: 0a32a9525cf8744fe02c5206eb30d571e38f7d75 libPhoneNumber-iOS: 0a32a9525cf8744fe02c5206eb30d571e38f7d75
LoggerAPI: ad9c4a6f1e32f518fdb43a1347ac14d765ab5e3d LoggerAPI: ad9c4a6f1e32f518fdb43a1347ac14d765ab5e3d
Logging: beeb016c9c80cf77042d62e83495816847ef108b Logging: beeb016c9c80cf77042d62e83495816847ef108b
MatrixSDK: 21201cd007145d96beff24cc7f9727ced497c3fd MatrixSDK: 12c1a56e037f629e493cbcd615fd13cfc58cee3a
OLMKit: 9fb4799c4a044dd2c06bda31ec31a12191ad30b5 OLMKit: 9fb4799c4a044dd2c06bda31ec31a12191ad30b5
PostHog: 4b6321b521569092d4ef3a02238d9435dbaeb99f PostHog: 4b6321b521569092d4ef3a02238d9435dbaeb99f
ReadMoreTextView: 19147adf93abce6d7271e14031a00303fe28720d ReadMoreTextView: 19147adf93abce6d7271e14031a00303fe28720d
@ -230,6 +229,6 @@ SPEC CHECKSUMS:
zxcvbn-ios: fef98b7c80f1512ff0eec47ac1fa399fc00f7e3c zxcvbn-ios: fef98b7c80f1512ff0eec47ac1fa399fc00f7e3c
ZXingObjC: fdbb269f25dd2032da343e06f10224d62f537bdb ZXingObjC: fdbb269f25dd2032da343e06f10224d62f537bdb
PODFILE CHECKSUM: 9b4be35779b652e3d0ad333d84069240ec58dc96 PODFILE CHECKSUM: ae70a46e98aae87f130ad3d246711fc6b6ae7286
COCOAPODS: 1.11.2 COCOAPODS: 1.11.2

View file

@ -0,0 +1,23 @@
{
"images" : [
{
"filename" : "filter_off.png",
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "filter_off@2x.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"filename" : "filter_off@3x.png",
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 201 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 297 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 397 B

View file

@ -0,0 +1,23 @@
{
"images" : [
{
"filename" : "filter_on.png",
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "filter_on@2x.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"filename" : "filter_on@3x.png",
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 204 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 295 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 377 B

View file

@ -250,6 +250,7 @@ Tap the + to start adding people.";
"search_people" = "People"; "search_people" = "People";
"search_files" = "Files"; "search_files" = "Files";
"search_default_placeholder" = "Search"; "search_default_placeholder" = "Search";
"search_filter_placeholder" = "Filter";
"search_people_placeholder" = "Search by User ID, Name or email"; "search_people_placeholder" = "Search by User ID, Name or email";
"search_no_result" = "No results"; "search_no_result" = "No results";
"search_in_progress" = "Searching…"; "search_in_progress" = "Searching…";
@ -396,7 +397,7 @@ Tap the + to start adding people.";
"room_event_action_view_decrypted_source" = "View Decrypted Source"; "room_event_action_view_decrypted_source" = "View Decrypted Source";
"room_event_action_report" = "Report content"; "room_event_action_report" = "Report content";
"room_event_action_report_prompt_reason" = "Reason for reporting this content"; "room_event_action_report_prompt_reason" = "Reason for reporting this content";
"room_event_action_kick_prompt_reason" = "Reason for kicking this user"; "room_event_action_kick_prompt_reason" = "Reason for removing this user";
"room_event_action_ban_prompt_reason" = "Reason for banning this user"; "room_event_action_ban_prompt_reason" = "Reason for banning this user";
"room_event_action_report_prompt_ignore_user" = "Do you want to hide all messages from this user?"; "room_event_action_report_prompt_ignore_user" = "Do you want to hide all messages from this user?";
"room_event_action_save" = "Save"; "room_event_action_save" = "Save";

View file

@ -63,6 +63,8 @@ internal class Asset: NSObject {
internal static let disclosureIcon = ImageAsset(name: "disclosure_icon") internal static let disclosureIcon = ImageAsset(name: "disclosure_icon")
internal static let errorIcon = ImageAsset(name: "error_icon") internal static let errorIcon = ImageAsset(name: "error_icon")
internal static let faceidIcon = ImageAsset(name: "faceid_icon") internal static let faceidIcon = ImageAsset(name: "faceid_icon")
internal static let filterOff = ImageAsset(name: "filter_off")
internal static let filterOn = ImageAsset(name: "filter_on")
internal static let group = ImageAsset(name: "group") internal static let group = ImageAsset(name: "group")
internal static let informationButton = ImageAsset(name: "information_button") internal static let informationButton = ImageAsset(name: "information_button")
internal static let monitor = ImageAsset(name: "monitor") internal static let monitor = ImageAsset(name: "monitor")

View file

@ -467,7 +467,7 @@ public class MatrixKitL10n: NSObject {
public static var inviteUser: String { public static var inviteUser: String {
return MatrixKitL10n.tr("invite_user") return MatrixKitL10n.tr("invite_user")
} }
/// Kick /// Remove from chat
public static var kick: String { public static var kick: String {
return MatrixKitL10n.tr("kick") return MatrixKitL10n.tr("kick")
} }
@ -1059,11 +1059,11 @@ public class MatrixKitL10n: NSObject {
public static func noticeRoomJoinRulePublicForDm(_ p1: String) -> String { public static func noticeRoomJoinRulePublicForDm(_ p1: String) -> String {
return MatrixKitL10n.tr("notice_room_join_rule_public_for_dm", p1) return MatrixKitL10n.tr("notice_room_join_rule_public_for_dm", p1)
} }
/// %@ kicked %@ /// %@ removed %@
public static func noticeRoomKick(_ p1: String, _ p2: String) -> String { public static func noticeRoomKick(_ p1: String, _ p2: String) -> String {
return MatrixKitL10n.tr("notice_room_kick", p1, p2) return MatrixKitL10n.tr("notice_room_kick", p1, p2)
} }
/// You kicked %@ /// You removed %@
public static func noticeRoomKickByYou(_ p1: String) -> String { public static func noticeRoomKickByYou(_ p1: String) -> String {
return MatrixKitL10n.tr("notice_room_kick_by_you", p1) return MatrixKitL10n.tr("notice_room_kick_by_you", p1)
} }

View file

@ -3259,7 +3259,7 @@ public class VectorL10n: NSObject {
public static var roomEventActionForward: String { public static var roomEventActionForward: String {
return VectorL10n.tr("Vector", "room_event_action_forward") return VectorL10n.tr("Vector", "room_event_action_forward")
} }
/// Reason for kicking this user /// Reason for removing this user
public static var roomEventActionKickPromptReason: String { public static var roomEventActionKickPromptReason: String {
return VectorL10n.tr("Vector", "room_event_action_kick_prompt_reason") return VectorL10n.tr("Vector", "room_event_action_kick_prompt_reason")
} }
@ -4023,6 +4023,10 @@ public class VectorL10n: NSObject {
public static var searchFiles: String { public static var searchFiles: String {
return VectorL10n.tr("Vector", "search_files") return VectorL10n.tr("Vector", "search_files")
} }
/// Filter
public static var searchFilterPlaceholder: String {
return VectorL10n.tr("Vector", "search_filter_placeholder")
}
/// Searching /// Searching
public static var searchInProgress: String { public static var searchInProgress: String {
return VectorL10n.tr("Vector", "search_in_progress") return VectorL10n.tr("Vector", "search_in_progress")

View file

@ -123,7 +123,11 @@ NSString *const RecentsViewControllerDataReadyNotification = @"RecentsViewContro
tableSearchBar = [[UISearchBar alloc] initWithFrame:CGRectMake(0, 0, 600, 44)]; tableSearchBar = [[UISearchBar alloc] initWithFrame:CGRectMake(0, 0, 600, 44)];
tableSearchBar.autoresizingMask = UIViewAutoresizingFlexibleWidth; tableSearchBar.autoresizingMask = UIViewAutoresizingFlexibleWidth;
tableSearchBar.showsCancelButton = NO; tableSearchBar.showsCancelButton = NO;
tableSearchBar.placeholder = [VectorL10n searchDefaultPlaceholder]; tableSearchBar.placeholder = [VectorL10n searchFilterPlaceholder];
[tableSearchBar setImage:AssetImages.filterOff.image
forSearchBarIcon:UISearchBarIconSearch
state:UIControlStateNormal];
tableSearchBar.delegate = self; tableSearchBar.delegate = self;
displayedSectionHeaders = [NSMutableArray array]; displayedSectionHeaders = [NSMutableArray array];
@ -173,7 +177,10 @@ NSString *const RecentsViewControllerDataReadyNotification = @"RecentsViewContro
}]; }];
self.recentsSearchBar.autocapitalizationType = UITextAutocapitalizationTypeNone; self.recentsSearchBar.autocapitalizationType = UITextAutocapitalizationTypeNone;
self.recentsSearchBar.placeholder = [VectorL10n searchDefaultPlaceholder]; self.recentsSearchBar.placeholder = [VectorL10n searchFilterPlaceholder];
[self.recentsSearchBar setImage:AssetImages.filterOff.image
forSearchBarIcon:UISearchBarIconSearch
state:UIControlStateNormal];
// Observe user interface theme change. // Observe user interface theme change.
kThemeServiceDidChangeThemeNotificationObserver = [[NSNotificationCenter defaultCenter] addObserverForName:kThemeServiceDidChangeThemeNotification object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *notif) { kThemeServiceDidChangeThemeNotificationObserver = [[NSNotificationCenter defaultCenter] addObserverForName:kThemeServiceDidChangeThemeNotification object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *notif) {
@ -2197,6 +2204,16 @@ NSString *const RecentsViewControllerDataReadyNotification = @"RecentsViewContro
[self.recentsSearchBar setShowsCancelButton:NO animated:NO]; [self.recentsSearchBar setShowsCancelButton:NO animated:NO];
} }
- (void)searchBar:(UISearchBar *)searchBar textDidChange:(NSString *)searchText
{
[super searchBar:searchBar textDidChange:searchText];
UIImage *filterIcon = searchText.length > 0 ? AssetImages.filterOn.image : AssetImages.filterOff.image;
[self.recentsSearchBar setImage:filterIcon
forSearchBarIcon:UISearchBarIconSearch
state:UIControlStateNormal];
}
#pragma mark - CreateRoomCoordinatorBridgePresenterDelegate #pragma mark - CreateRoomCoordinatorBridgePresenterDelegate
- (void)createRoomCoordinatorBridgePresenterDelegate:(CreateRoomCoordinatorBridgePresenter *)coordinatorBridgePresenter didCreateNewRoom:(MXRoom *)room - (void)createRoomCoordinatorBridgePresenterDelegate:(CreateRoomCoordinatorBridgePresenter *)coordinatorBridgePresenter didCreateNewRoom:(MXRoom *)room

View file

@ -160,7 +160,7 @@
threadParameters = [[ThreadParameters alloc] initWithThreadId:event.threadId threadParameters = [[ThreadParameters alloc] initWithThreadId:event.threadId
stackRoomScreen:NO]; stackRoomScreen:NO];
} }
else if ([self.mainSession.threadingService isEventThreadRoot:event]) else if (event.unsignedData.relations.thread || [self.mainSession.threadingService isEventThreadRoot:event])
{ {
threadParameters = [[ThreadParameters alloc] initWithThreadId:event.eventId threadParameters = [[ThreadParameters alloc] initWithThreadId:event.eventId
stackRoomScreen:NO]; stackRoomScreen:NO];

View file

@ -89,6 +89,10 @@
{ {
continueBlock(); continueBlock();
} }
else if (result.result.unsignedData.relations.thread)
{
continueBlock();
}
else if (room) else if (room)
{ {
[room liveTimeline:^(id<MXEventTimeline> liveTimeline) { [room liveTimeline:^(id<MXEventTimeline> liveTimeline) {
@ -164,8 +168,9 @@
{ {
if (cellData.hasThreadRoot) if (cellData.hasThreadRoot)
{ {
MXThread *thread = cellData.bubbleComponents.firstObject.thread; id<MXThreadProtocol> thread = cellData.bubbleComponents.firstObject.thread;
ThreadSummaryView *threadSummaryView = [[ThreadSummaryView alloc] initWithThread:thread]; ThreadSummaryView *threadSummaryView = [[ThreadSummaryView alloc] initWithThread:thread
session:self.mxSession];
[bubbleCell.tmpSubviews addObject:threadSummaryView]; [bubbleCell.tmpSubviews addObject:threadSummaryView];
threadSummaryView.translatesAutoresizingMaskIntoConstraints = NO; threadSummaryView.translatesAutoresizingMaskIntoConstraints = NO;

View file

@ -167,7 +167,7 @@
threadParameters = [[ThreadParameters alloc] initWithThreadId:event.threadId threadParameters = [[ThreadParameters alloc] initWithThreadId:event.threadId
stackRoomScreen:NO]; stackRoomScreen:NO];
} }
else if ([self.mainSession.threadingService isEventThreadRoot:event]) else if (event.unsignedData.relations.thread || [self.mainSession.threadingService isEventThreadRoot:event])
{ {
threadParameters = [[ThreadParameters alloc] initWithThreadId:event.eventId threadParameters = [[ThreadParameters alloc] initWithThreadId:event.eventId
stackRoomScreen:NO]; stackRoomScreen:NO];

View file

@ -367,7 +367,7 @@
"notice_room_join" = "%@ joined"; "notice_room_join" = "%@ joined";
"notice_room_leave" = "%@ left"; "notice_room_leave" = "%@ left";
"notice_room_reject" = "%@ rejected the invitation"; "notice_room_reject" = "%@ rejected the invitation";
"notice_room_kick" = "%@ kicked %@"; "notice_room_kick" = "%@ removed %@";
"notice_room_unban" = "%@ unbanned %@"; "notice_room_unban" = "%@ unbanned %@";
"notice_room_ban" = "%@ banned %@"; "notice_room_ban" = "%@ banned %@";
"notice_room_withdraw" = "%@ withdrew %@'s invitation"; "notice_room_withdraw" = "%@ withdrew %@'s invitation";
@ -399,7 +399,7 @@
"notice_room_join_by_you" = "You joined"; "notice_room_join_by_you" = "You joined";
"notice_room_leave_by_you" = "You left"; "notice_room_leave_by_you" = "You left";
"notice_room_reject_by_you" = "You rejected the invitation"; "notice_room_reject_by_you" = "You rejected the invitation";
"notice_room_kick_by_you" = "You kicked %@"; "notice_room_kick_by_you" = "You removed %@";
"notice_room_unban_by_you" = "You unbanned %@"; "notice_room_unban_by_you" = "You unbanned %@";
"notice_room_ban_by_you" = "You banned %@"; "notice_room_ban_by_you" = "You banned %@";
"notice_room_withdraw_by_you" = "You withdrew %@'s invitation"; "notice_room_withdraw_by_you" = "You withdrew %@'s invitation";
@ -479,7 +479,7 @@
"num_members_one" = "%@ user"; "num_members_one" = "%@ user";
"num_members_other" = "%@ users"; "num_members_other" = "%@ users";
"invite" = "Invite"; "invite" = "Invite";
"kick" = "Kick"; "kick" = "Remove from chat";
"ban" = "Ban"; "ban" = "Ban";
"unban" = "Un-ban"; "unban" = "Un-ban";
"message_unsaved_changes" = "There are unsaved changes. Leaving will discard them."; "message_unsaved_changes" = "There are unsaved changes. Leaving will discard them.";

View file

@ -1426,25 +1426,27 @@ static const CGFloat kLocalPreviewMargin = 20;
{ {
BOOL inCall = (mxCall.state == MXCallStateConnected || mxCall.state == MXCallStateRinging || mxCall.state == MXCallStateInviteSent || mxCall.state == MXCallStateConnecting || mxCall.state == MXCallStateCreateOffer || mxCall.state == MXCallStateCreateAnswer); 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; BOOL isBuiltInReceiverUsed = self.isBuiltInReceiverAudioOuput;
// Enable the proximity monitoring when the built in receiver is used as the audio output. // Enable the proximity monitoring when the built in receiver is used as the audio output.
BOOL enableProxMonitoring = isBuiltInReceiverUsed; BOOL enableProxMonitoring = inCall && isBuiltInReceiverUsed;
[[UIDevice currentDevice] setProximityMonitoringEnabled:enableProxMonitoring];
UIDevice *device = [UIDevice currentDevice];
if (device && device.isProximityMonitoringEnabled != enableProxMonitoring)
{
[device setProximityMonitoringEnabled:enableProxMonitoring];
}
// Disable the idle timer during a video call, or during a voice call which is performed with the built-in receiver. // 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. // Note: if the device is locked, VoIP calling get dropped if an incoming GSM call is received.
BOOL disableIdleTimer = mxCall.isVideoCall || isBuiltInReceiverUsed; BOOL disableIdleTimer = inCall && (mxCall.isVideoCall || isBuiltInReceiverUsed);
UIApplication *sharedApplication = [UIApplication performSelector:@selector(sharedApplication)]; UIApplication *sharedApplication = [UIApplication performSelector:@selector(sharedApplication)];
if (sharedApplication) if (sharedApplication && sharedApplication.isIdleTimerDisabled != disableIdleTimer)
{ {
sharedApplication.idleTimerDisabled = disableIdleTimer; sharedApplication.idleTimerDisabled = disableIdleTimer;
} }
} }
}
- (UIView *)createIncomingCallView - (UIView *)createIncomingCallView
{ {

View file

@ -19,7 +19,7 @@
#import "MXKEventFormatter.h" #import "MXKEventFormatter.h"
#import "MXKURLPreviewDataProtocol.h" #import "MXKURLPreviewDataProtocol.h"
@class MXThread; @protocol MXThreadProtocol;
/** /**
Flags to indicate if a fix is required at the display time. Flags to indicate if a fix is required at the display time.
@ -108,7 +108,7 @@ typedef enum : NSUInteger {
/** /**
Thread for the bubble component. Should only exist for thread root events. Thread for the bubble component. Should only exist for thread root events.
*/ */
@property (nonatomic, readonly) MXThread *thread; @property (nonatomic, readonly) id<MXThreadProtocol> thread;
/** /**
Create a new `MXKRoomBubbleComponent` object based on a `MXEvent` instance. Create a new `MXKRoomBubbleComponent` object based on a `MXEvent` instance.

View file

@ -22,7 +22,7 @@
@interface MXKRoomBubbleComponent () @interface MXKRoomBubbleComponent ()
@property (nonatomic, readwrite) MXThread *thread; @property (nonatomic, readwrite) id<MXThreadProtocol> thread;
@end @end
@ -70,8 +70,17 @@
[self updateLinkWithRoomState:roomState]; [self updateLinkWithRoomState:roomState];
if (event.unsignedData.relations.thread)
{
self.thread = [[MXThreadModel alloc] initWithRootEvent:event
notificationCount:0
highlightCount:0];
}
else
{
self.thread = [session.threadingService threadWithId:event.eventId]; self.thread = [session.threadingService threadWithId:event.eventId];
} }
}
return self; return self;
} }

View file

@ -955,7 +955,7 @@ static NSString *const kHTMLATagRegexPattern = @"<a href=\"(.*?)\">([^<]*)</a>";
} }
if (event.content[@"kick"]) if (event.content[@"kick"])
{ {
displayText = [NSString stringWithFormat:@"%@\n\u2022 kick: %@", displayText, event.content[@"kick"]]; displayText = [NSString stringWithFormat:@"%@\n\u2022 remove: %@", displayText, event.content[@"kick"]];
} }
if (event.content[@"redact"]) if (event.content[@"redact"])
{ {

View file

@ -130,6 +130,6 @@
@param roomDataSource room data source instance @param roomDataSource room data source instance
*/ */
- (void)roomDataSource:(RoomDataSource * _Nonnull)roomDataSource - (void)roomDataSource:(RoomDataSource * _Nonnull)roomDataSource
didTapThread:(MXThread * _Nonnull)thread; didTapThread:(id<MXThreadProtocol> _Nonnull)thread;
@end @end

View file

@ -466,7 +466,8 @@ const CGFloat kTypingCellHeight = 24;
// display thread summary view if the component has a thread in the room timeline // display thread summary view if the component has a thread in the room timeline
if (RiotSettings.shared.enableThreads && component.thread && !self.threadId) if (RiotSettings.shared.enableThreads && component.thread && !self.threadId)
{ {
threadSummaryView = [[ThreadSummaryView alloc] initWithThread:component.thread]; threadSummaryView = [[ThreadSummaryView alloc] initWithThread:component.thread
session:self.mxSession];
threadSummaryView.delegate = self; threadSummaryView.delegate = self;
threadSummaryView.tag = index; threadSummaryView.tag = index;

View file

@ -438,10 +438,17 @@ typedef NS_ENUM(NSUInteger, MXKRoomViewControllerJoinRoomResult) {
- (void)setBubbleTableViewContentOffset:(CGPoint)contentOffset animated:(BOOL)animated; - (void)setBubbleTableViewContentOffset:(CGPoint)contentOffset animated:(BOOL)animated;
/** /**
Handle typing notification. Sends a typing notification with the specified timeout.
@param typing Flag indicating whether the user is typing or not. @param typing Flag indicating whether the user is typing or not.
@param notificationTimeoutMS The length of time the typing notification is valid for
*/ */
- (void)handleTypingNotification:(BOOL)typing; - (void)sendTypingNotification:(BOOL)typing timeout:(NSUInteger)notificationTimeoutMS;
/**
Share encryption keys in this room.
*/
- (void)shareEncryptionKeys;
@end @end

View file

@ -3326,7 +3326,7 @@
roomDataSource.partialTextMessage = inputToolbarView.textMessage; roomDataSource.partialTextMessage = inputToolbarView.textMessage;
} }
[self handleTypingNotification:typing]; [self handleTypingState:typing];
} }
- (void)roomInputToolbarView:(MXKRoomInputToolbarView*)toolbarView heightDidChanged:(CGFloat)height completion:(void (^)(BOOL finished))completion - (void)roomInputToolbarView:(MXKRoomInputToolbarView*)toolbarView heightDidChanged:(CGFloat)height completion:(void (^)(BOOL finished))completion
@ -3447,7 +3447,7 @@
} }
# pragma mark - Typing notification # pragma mark - Typing notification
- (void)handleTypingNotification:(BOOL)typing - (void)handleTypingState:(BOOL)typing
{ {
NSUInteger notificationTimeoutMS = -1; NSUInteger notificationTimeoutMS = -1;
if (typing) if (typing)
@ -3509,6 +3509,11 @@
lastTypingDate = nil; lastTypingDate = nil;
} }
[self sendTypingNotification:typing timeout:notificationTimeoutMS];
}
- (void)sendTypingNotification:(BOOL)typing timeout:(NSUInteger)notificationTimeoutMS
{
MXWeakify(self); MXWeakify(self);
// Send typing notification to server // Send typing notification to server
@ -3539,7 +3544,7 @@
// Check whether a new typing event has been observed // Check whether a new typing event has been observed
BOOL typing = (lastTypingDate != nil); BOOL typing = (lastTypingDate != nil);
// Post a new typing notification // Post a new typing notification
[self handleTypingNotification:typing]; [self handleTypingState:typing];
} }

View file

@ -4283,7 +4283,7 @@ const NSTimeInterval kResizeComposerAnimationDuration = .05;
[self updateTitleViewEncryptionDecoration]; [self updateTitleViewEncryptionDecoration];
} }
- (void)roomDataSource:(RoomDataSource *)roomDataSource didTapThread:(MXThread *)thread - (void)roomDataSource:(RoomDataSource *)roomDataSource didTapThread:(id<MXThreadProtocol>)thread
{ {
[self openThreadWithId:thread.id]; [self openThreadWithId:thread.id];
} }
@ -4564,6 +4564,15 @@ const NSTimeInterval kResizeComposerAnimationDuration = .05;
[self.userSuggestionCoordinator processTextMessage:toolbarView.textMessage]; [self.userSuggestionCoordinator processTextMessage:toolbarView.textMessage];
} }
- (void)roomInputToolbarViewDidOpenActionMenu:(MXKRoomInputToolbarView*)toolbarView
{
// Consider opening the action menu as beginning to type and share encryption keys if requested.
if ([MXKAppSettings standardAppSettings].outboundGroupSessionKeyPreSharingStrategy == MXKKeyPreSharingWhenTyping)
{
[self shareEncryptionKeys];
}
}
#pragma mark - MXKRoomMemberDetailsViewControllerDelegate #pragma mark - MXKRoomMemberDetailsViewControllerDelegate
- (void)roomMemberDetailsViewController:(MXKRoomMemberDetailsViewController *)roomMemberDetailsViewController startChatWithMemberId:(NSString *)matrixId completion:(void (^)(void))completion - (void)roomMemberDetailsViewController:(MXKRoomMemberDetailsViewController *)roomMemberDetailsViewController startChatWithMemberId:(NSString *)matrixId completion:(void (^)(void))completion

View file

@ -96,6 +96,10 @@
{ {
continueBlock(); continueBlock();
} }
else if (result.result.unsignedData.relations.thread)
{
continueBlock();
}
else else
{ {
[roomDataSource.room liveTimeline:^(id<MXEventTimeline> liveTimeline) { [roomDataSource.room liveTimeline:^(id<MXEventTimeline> liveTimeline) {
@ -143,8 +147,9 @@
{ {
if (cellData.hasThreadRoot) if (cellData.hasThreadRoot)
{ {
MXThread *thread = cellData.bubbleComponents.firstObject.thread; id<MXThreadProtocol> thread = cellData.bubbleComponents.firstObject.thread;
ThreadSummaryView *threadSummaryView = [[ThreadSummaryView alloc] initWithThread:thread]; ThreadSummaryView *threadSummaryView = [[ThreadSummaryView alloc] initWithThread:thread
session:self.mxSession];
[bubbleCell.tmpSubviews addObject:threadSummaryView]; [bubbleCell.tmpSubviews addObject:threadSummaryView];
threadSummaryView.translatesAutoresizingMaskIntoConstraints = NO; threadSummaryView.translatesAutoresizingMaskIntoConstraints = NO;

View file

@ -165,7 +165,7 @@
threadParameters = [[ThreadParameters alloc] initWithThreadId:event.threadId threadParameters = [[ThreadParameters alloc] initWithThreadId:event.threadId
stackRoomScreen:NO]; stackRoomScreen:NO];
} }
else if ([self.mainSession.threadingService isEventThreadRoot:event]) else if (event.unsignedData.relations.thread || [self.mainSession.threadingService isEventThreadRoot:event])
{ {
threadParameters = [[ThreadParameters alloc] initWithThreadId:event.eventId threadParameters = [[ThreadParameters alloc] initWithThreadId:event.eventId
stackRoomScreen:NO]; stackRoomScreen:NO];

View file

@ -47,6 +47,13 @@ typedef enum : NSUInteger
*/ */
- (void)roomInputToolbarViewDidChangeTextMessage:(MXKRoomInputToolbarView*)toolbarView; - (void)roomInputToolbarViewDidChangeTextMessage:(MXKRoomInputToolbarView*)toolbarView;
/**
Inform the delegate that the action menu was opened.
@param toolbarView the room input toolbar view
*/
- (void)roomInputToolbarViewDidOpenActionMenu:(MXKRoomInputToolbarView*)toolbarView;
@end @end
/** /**

View file

@ -429,6 +429,7 @@ static const NSTimeInterval kActionMenuComposerHeightAnimationDuration = .3;
if (_actionMenuOpened) { if (_actionMenuOpened) {
self.actionsBar.hidden = NO; self.actionsBar.hidden = NO;
[self.actionsBar animateWithShowIn:_actionMenuOpened completion:nil]; [self.actionsBar animateWithShowIn:_actionMenuOpened completion:nil];
[self.delegate roomInputToolbarViewDidOpenActionMenu:self];
} }
else else
{ {

View file

@ -38,7 +38,8 @@ class ThreadSummaryView: UIView {
@IBOutlet private weak var lastMessageContentLabel: UILabel! @IBOutlet private weak var lastMessageContentLabel: UILabel!
private var theme: Theme = ThemeService.shared().theme private var theme: Theme = ThemeService.shared().theme
private(set) var thread: MXThread? private(set) var thread: MXThreadProtocol?
private weak var session: MXSession?
private lazy var tapGestureRecognizer: UITapGestureRecognizer = { private lazy var tapGestureRecognizer: UITapGestureRecognizer = {
return UITapGestureRecognizer(target: self, action: #selector(tapped(_:))) return UITapGestureRecognizer(target: self, action: #selector(tapped(_:)))
@ -48,8 +49,9 @@ class ThreadSummaryView: UIView {
// MARK: - Setup // MARK: - Setup
init(withThread thread: MXThread) { init(withThread thread: MXThreadProtocol, session: MXSession) {
self.thread = thread self.thread = thread
self.session = session
super.init(frame: CGRect(origin: .zero, super.init(frame: CGRect(origin: .zero,
size: CGSize(width: Constants.viewDefaultWidth, size: CGSize(width: Constants.viewDefaultWidth,
height: RoomBubbleCellLayout.threadSummaryViewHeight))) height: RoomBubbleCellLayout.threadSummaryViewHeight)))
@ -59,7 +61,7 @@ class ThreadSummaryView: UIView {
translatesAutoresizingMaskIntoConstraints = false translatesAutoresizingMaskIntoConstraints = false
} }
static func contentViewHeight(forThread thread: MXThread?, fitting maxWidth: CGFloat) -> CGFloat { static func contentViewHeight(forThread thread: MXThreadProtocol?, fitting maxWidth: CGFloat) -> CGFloat {
return RoomBubbleCellLayout.threadSummaryViewHeight return RoomBubbleCellLayout.threadSummaryViewHeight
} }
@ -93,7 +95,7 @@ class ThreadSummaryView: UIView {
guard let thread = thread, guard let thread = thread,
let lastMessage = thread.lastMessage, let lastMessage = thread.lastMessage,
let session = thread.session, let session = session,
let eventFormatter = session.roomSummaryUpdateDelegate as? MXKEventFormatter, let eventFormatter = session.roomSummaryUpdateDelegate as? MXKEventFormatter,
let room = session.room(withRoomId: lastMessage.roomId) else { let room = session.room(withRoomId: lastMessage.roomId) else {
lastMessageAvatarView.avatarImageView.image = nil lastMessageAvatarView.avatarImageView.image = nil

View file

@ -1997,7 +1997,7 @@ TableViewSectionsDelegate>
labelAndSwitchCell.mxkSwitch.onTintColor = ThemeService.shared.theme.tintColor; labelAndSwitchCell.mxkSwitch.onTintColor = ThemeService.shared.theme.tintColor;
labelAndSwitchCell.mxkSwitch.enabled = YES; labelAndSwitchCell.mxkSwitch.enabled = YES;
[labelAndSwitchCell.mxkSwitch addTarget:self action:@selector(toggleEnableURLPreviews:) forControlEvents:UIControlEventValueChanged]; [labelAndSwitchCell.mxkSwitch addTarget:self action:@selector(toggleEnableURLPreviews:) forControlEvents:UIControlEventTouchUpInside];
cell = labelAndSwitchCell; cell = labelAndSwitchCell;
} }
@ -2442,7 +2442,7 @@ TableViewSectionsDelegate>
labelAndSwitchCell.mxkSwitch.on = RiotSettings.shared.enableRingingForGroupCalls; labelAndSwitchCell.mxkSwitch.on = RiotSettings.shared.enableRingingForGroupCalls;
labelAndSwitchCell.mxkSwitch.onTintColor = ThemeService.shared.theme.tintColor; labelAndSwitchCell.mxkSwitch.onTintColor = ThemeService.shared.theme.tintColor;
[labelAndSwitchCell.mxkSwitch addTarget:self action:@selector(toggleEnableRingingForGroupCalls:) forControlEvents:UIControlEventValueChanged]; [labelAndSwitchCell.mxkSwitch addTarget:self action:@selector(toggleEnableRingingForGroupCalls:) forControlEvents:UIControlEventTouchUpInside];
cell = labelAndSwitchCell; cell = labelAndSwitchCell;
} }
@ -2454,7 +2454,7 @@ TableViewSectionsDelegate>
labelAndSwitchCell.mxkSwitch.on = RiotSettings.shared.enableThreads; labelAndSwitchCell.mxkSwitch.on = RiotSettings.shared.enableThreads;
labelAndSwitchCell.mxkSwitch.onTintColor = ThemeService.shared.theme.tintColor; labelAndSwitchCell.mxkSwitch.onTintColor = ThemeService.shared.theme.tintColor;
[labelAndSwitchCell.mxkSwitch addTarget:self action:@selector(toggleEnableThreads:) forControlEvents:UIControlEventValueChanged]; [labelAndSwitchCell.mxkSwitch addTarget:self action:@selector(toggleEnableThreads:) forControlEvents:UIControlEventTouchUpInside];
cell = labelAndSwitchCell; cell = labelAndSwitchCell;
} }

View file

@ -70,7 +70,7 @@ class ThreadViewController: RoomViewController {
super.onButtonPressed(sender) super.onButtonPressed(sender)
} }
override func handleTypingNotification(_ typing: Bool) { override func sendTypingNotification(_ typing: Bool, timeout notificationTimeoutMS: UInt) {
// no-op // no-op
} }

View file

@ -65,11 +65,11 @@ extension ThreadListCoordinator: ThreadListViewModelCoordinatorDelegate {
self.delegate?.threadListCoordinatorDidLoadThreads(self) self.delegate?.threadListCoordinatorDidLoadThreads(self)
} }
func threadListViewModelDidSelectThread(_ viewModel: ThreadListViewModelProtocol, thread: MXThread) { func threadListViewModelDidSelectThread(_ viewModel: ThreadListViewModelProtocol, thread: MXThreadProtocol) {
self.delegate?.threadListCoordinatorDidSelectThread(self, thread: thread) self.delegate?.threadListCoordinatorDidSelectThread(self, thread: thread)
} }
func threadListViewModelDidSelectThreadViewInRoom(_ viewModel: ThreadListViewModelProtocol, thread: MXThread) { func threadListViewModelDidSelectThreadViewInRoom(_ viewModel: ThreadListViewModelProtocol, thread: MXThreadProtocol) {
self.delegate?.threadListCoordinatorDidSelectRoom(self, roomId: thread.roomId, eventId: thread.id) self.delegate?.threadListCoordinatorDidSelectRoom(self, roomId: thread.roomId, eventId: thread.id)
} }

View file

@ -20,7 +20,7 @@ import Foundation
protocol ThreadListCoordinatorDelegate: AnyObject { protocol ThreadListCoordinatorDelegate: AnyObject {
func threadListCoordinatorDidLoadThreads(_ coordinator: ThreadListCoordinatorProtocol) func threadListCoordinatorDidLoadThreads(_ coordinator: ThreadListCoordinatorProtocol)
func threadListCoordinatorDidSelectThread(_ coordinator: ThreadListCoordinatorProtocol, thread: MXThread) func threadListCoordinatorDidSelectThread(_ coordinator: ThreadListCoordinatorProtocol, thread: MXThreadProtocol)
func threadListCoordinatorDidSelectRoom(_ coordinator: ThreadListCoordinatorProtocol, roomId: String, eventId: String) func threadListCoordinatorDidSelectRoom(_ coordinator: ThreadListCoordinatorProtocol, roomId: String, eventId: String)
func threadListCoordinatorDidCancel(_ coordinator: ThreadListCoordinatorProtocol) func threadListCoordinatorDidCancel(_ coordinator: ThreadListCoordinatorProtocol)
} }

View file

@ -26,12 +26,12 @@ final class ThreadListViewModel: ThreadListViewModelProtocol {
private let session: MXSession private let session: MXSession
private let roomId: String private let roomId: String
private var threads: [MXThread] = [] private var threads: [MXThreadProtocol] = []
private var eventFormatter: MXKEventFormatter? private var eventFormatter: MXKEventFormatter?
private var roomState: MXRoomState? private var roomState: MXRoomState?
private var currentOperation: MXHTTPOperation? private var currentOperation: MXHTTPOperation?
private var longPressedThread: MXThread? private var longPressedThread: MXThreadProtocol?
// MARK: Public // MARK: Public
@ -144,7 +144,7 @@ final class ThreadListViewModel: ThreadListViewModelProtocol {
// MARK: - Private // MARK: - Private
private func model(forThread thread: MXThread) -> ThreadModel { private func model(forThread thread: MXThreadProtocol) -> ThreadModel {
let rootAvatarViewData: AvatarViewData? let rootAvatarViewData: AvatarViewData?
let rootMessageSender: MXUser? let rootMessageSender: MXUser?
let lastAvatarViewData: AvatarViewData? let lastAvatarViewData: AvatarViewData?
@ -199,7 +199,7 @@ final class ThreadListViewModel: ThreadListViewModelProtocol {
notificationStatus: notificationStatus) notificationStatus: notificationStatus)
} }
private func rootMessageText(forThread thread: MXThread) -> NSAttributedString? { private func rootMessageText(forThread thread: MXThreadProtocol) -> NSAttributedString? {
guard let eventFormatter = eventFormatter else { guard let eventFormatter = eventFormatter else {
return nil return nil
} }
@ -229,7 +229,7 @@ final class ThreadListViewModel: ThreadListViewModelProtocol {
error: formatterError) error: formatterError)
} }
private func lastMessageTextAndTime(forThread thread: MXThread) -> (NSAttributedString?, String?) { private func lastMessageTextAndTime(forThread thread: MXThreadProtocol) -> (NSAttributedString?, String?) {
guard let eventFormatter = eventFormatter else { guard let eventFormatter = eventFormatter else {
return (nil, nil) return (nil, nil)
} }
@ -251,22 +251,35 @@ final class ThreadListViewModel: ThreadListViewModelProtocol {
viewState = .loading viewState = .loading
} }
let onlyParticipated: Bool
switch selectedFilterType { switch selectedFilterType {
case .all: case .all:
threads = session.threadingService.threads(inRoom: roomId) onlyParticipated = false
case .myThreads: case .myThreads:
threads = session.threadingService.participatedThreads(inRoom: roomId) onlyParticipated = true
} }
session.threadingService.allThreads(inRoom: roomId,
onlyParticipated: onlyParticipated) { [weak self] response in
guard let self = self else { return }
switch response {
case .success(let threads):
self.threads = threads
self.threadsLoaded()
case .failure(let error):
MXLog.error("[ThreadListViewModel] loadData: error: \(error)")
self.viewState = .error(error)
}
}
}
private func threadsLoaded() {
if threads.isEmpty { if threads.isEmpty {
viewState = .empty(emptyViewModel) viewState = .empty(emptyViewModel)
return return
} }
threadsLoaded()
}
private func threadsLoaded() {
guard let eventFormatter = session.roomSummaryUpdateDelegate as? MXKEventFormatter, guard let eventFormatter = session.roomSummaryUpdateDelegate as? MXKEventFormatter,
let room = session.room(withRoomId: roomId) else { let room = session.room(withRoomId: roomId) else {
// go into loaded state // go into loaded state
@ -323,7 +336,7 @@ final class ThreadListViewModel: ThreadListViewModelProtocol {
private func actionShare() { private func actionShare() {
guard let thread = longPressedThread, guard let thread = longPressedThread,
let index = threads.firstIndex(of: thread) else { let index = threads.firstIndex(where: { thread.id == $0.id }) else {
return return
} }
if let permalink = MXTools.permalink(toEvent: thread.id, inRoom: thread.roomId), if let permalink = MXTools.permalink(toEvent: thread.id, inRoom: thread.roomId),

View file

@ -24,8 +24,8 @@ protocol ThreadListViewModelViewDelegate: AnyObject {
protocol ThreadListViewModelCoordinatorDelegate: AnyObject { protocol ThreadListViewModelCoordinatorDelegate: AnyObject {
func threadListViewModelDidLoadThreads(_ viewModel: ThreadListViewModelProtocol) func threadListViewModelDidLoadThreads(_ viewModel: ThreadListViewModelProtocol)
func threadListViewModelDidSelectThread(_ viewModel: ThreadListViewModelProtocol, thread: MXThread) func threadListViewModelDidSelectThread(_ viewModel: ThreadListViewModelProtocol, thread: MXThreadProtocol)
func threadListViewModelDidSelectThreadViewInRoom(_ viewModel: ThreadListViewModelProtocol, thread: MXThread) func threadListViewModelDidSelectThreadViewInRoom(_ viewModel: ThreadListViewModelProtocol, thread: MXThreadProtocol)
func threadListViewModelDidCancel(_ viewModel: ThreadListViewModelProtocol) func threadListViewModelDidCancel(_ viewModel: ThreadListViewModelProtocol)
} }

View file

@ -32,7 +32,7 @@ enum ThreadNotificationStatus {
case notified case notified
case highlighted case highlighted
init(withThread thread: MXThread) { init(withThread thread: MXThreadProtocol) {
if thread.highlightCount > 0 { if thread.highlightCount > 0 {
self = .highlighted self = .highlighted
} else if thread.isParticipated && thread.notificationCount > 0 { } else if thread.isParticipated && thread.notificationCount > 0 {

View file

@ -153,7 +153,7 @@ extension ThreadsCoordinator: ThreadListCoordinatorDelegate {
} }
func threadListCoordinatorDidSelectThread(_ coordinator: ThreadListCoordinatorProtocol, thread: MXThread) { func threadListCoordinatorDidSelectThread(_ coordinator: ThreadListCoordinatorProtocol, thread: MXThreadProtocol) {
let roomCoordinator = createThreadCoordinator(forThreadId: thread.id) let roomCoordinator = createThreadCoordinator(forThreadId: thread.id)
selectedThreadCoordinator = roomCoordinator selectedThreadCoordinator = roomCoordinator
roomCoordinator.start() roomCoordinator.start()

View file

@ -35,6 +35,7 @@ targets:
- target: SiriIntents - target: SiriIntents
- target: RiotNSE - target: RiotNSE
- target: DesignKit - target: DesignKit
- target: CommonKit
- package: Mapbox - package: Mapbox
configFiles: configFiles:

1
changelog.d/4103.bugfix Normal file
View file

@ -0,0 +1 @@
Fix proximity sensor staying on and sleep timer staying disabled after call ends

1
changelog.d/4829.change Normal file
View file

@ -0,0 +1 @@
CommonKit: Create a new framework with common functionality and create Activity and ActivityCenter

1
changelog.d/5250.change Normal file
View file

@ -0,0 +1 @@
Filter: update placeholder text and icon

1
changelog.d/5346.bugfix Normal file
View file

@ -0,0 +1 @@
Wordings: Replace "kick" and all affiliate word by "remove"

1
changelog.d/5540.change Normal file
View file

@ -0,0 +1 @@
ThreadListViewModel: Use new apis to fetch threads.

1
changelog.d/5562.change Normal file
View file

@ -0,0 +1 @@
Search: Use bundled aggregations if provided.

View file

@ -35,6 +35,8 @@ include:
- path: RiotSwiftUI/target.yml - path: RiotSwiftUI/target.yml
- path: RiotSwiftUI/targetUnitTests.yml - path: RiotSwiftUI/targetUnitTests.yml
- path: RiotSwiftUI/targetUITests.yml - path: RiotSwiftUI/targetUITests.yml
- path: CommonKit/target.yml
- path: CommonKit/targetUnitTests.yml
packages: packages:
Mapbox: Mapbox: