Merge branch 'develop' into ismail/5770_threads_notice

This commit is contained in:
ismailgulek 2022-03-30 13:35:14 +03:00
commit b12f6497d8
No known key found for this signature in database
GPG key ID: E96336D42D9470A9
276 changed files with 5596 additions and 1319 deletions

View file

@ -1,3 +1,82 @@
## Changes in 1.8.9 (2022-03-28)
🙌 Improvements
- Upgrade MatrixSDK version ([v0.23.1](https://github.com/matrix-org/matrix-ios-sdk/releases/tag/v0.23.1)).
- Update suggested room preview to behave the same way in all cases ([#5771](https://github.com/vector-im/element-ios/issues/5771))
- Add "Invite people" to the space menu in the left panel and update menu order ([#5810](https://github.com/vector-im/element-ios/issues/5810))
🐛 Bugfixes
- Sync Spaces order with web ([#5134](https://github.com/vector-im/element-ios/issues/5134))
- Fixed "Add Space" error message ([#5797](https://github.com/vector-im/element-ios/issues/5797))
- Authentication: Ensure the login button is always visible ([#5875](https://github.com/vector-im/element-ios/issues/5875))
- Room: Fix typing performance by avoiding expensive UI operations ([#5906](https://github.com/vector-im/element-ios/issues/5906))
- Push notifications: show space preview if user taps invite notification ([#5915](https://github.com/vector-im/element-ios/issues/5915))
## Changes in 1.8.8 (2022-03-22)
✨ Features
- Invite to Space in room landing ([#5225](https://github.com/vector-im/element-ios/issues/5225))
- Implement FAB journeys & rough edge warnings ([#5226](https://github.com/vector-im/element-ios/issues/5226))
- Space panel overflow journeys & rough edge warnings ([#5227](https://github.com/vector-im/element-ios/issues/5227))
- Let people know when rooms have moved. ([#5228](https://github.com/vector-im/element-ios/issues/5228))
- Room Settings bottom sheet ([#5229](https://github.com/vector-im/element-ios/issues/5229))
- Adding Rooms to Spaces ([#5230](https://github.com/vector-im/element-ios/issues/5230))
- Spaces: Update room settings for Spaces ([#5231](https://github.com/vector-im/element-ios/issues/5231))
- Spaces: Long press on rooms in space room lists ([#5232](https://github.com/vector-im/element-ios/issues/5232))
- Space Settings ([#5233](https://github.com/vector-im/element-ios/issues/5233))
🙌 Improvements
- Upgrade MatrixSDK version ([v0.23.0](https://github.com/matrix-org/matrix-ios-sdk/releases/tag/v0.23.0)).
- Space creation: Added entire space creation flow. ([#5224](https://github.com/vector-im/element-ios/issues/5224))
- Instrument metrics for the IA project. ([#5401](https://github.com/vector-im/element-ios/issues/5401))
- RoomDataSource: Reload thread screen for the first message. ([#5441](https://github.com/vector-im/element-ios/issues/5441))
- Change behaviour of avatar/self in left menu to match common paradigm and take user to their own profile/settings ([#5500](https://github.com/vector-im/element-ios/issues/5500))
- Secure Backup: Add support for mandatory backup/verification ([#5745](https://github.com/vector-im/element-ios/issues/5745))
- Thread Notifications: Open thread & reply to thread from notifications. ([#5749](https://github.com/vector-im/element-ios/issues/5749))
- IA Metrics: added trigger to JoinedRoom event and implemented ViewRoom event ([#5769](https://github.com/vector-im/element-ios/issues/5769))
- Activity Indicators: Replace user indicator presenting view controller with context ([#5780](https://github.com/vector-im/element-ios/issues/5780))
- MXKEventFormatter: Extend reply fallback for also non-thread events. ([#5816](https://github.com/vector-im/element-ios/issues/5816))
- Location sharing: Support multiple user annotation views on the map. ([#5827](https://github.com/vector-im/element-ios/issues/5827))
- MXKRoomDataSource: Pass threadId of room data source for replies. ([#5829](https://github.com/vector-im/element-ios/issues/5829))
- MXKEventFormatter: Fix edit fallback usage for edited events. ([#5841](https://github.com/vector-im/element-ios/issues/5841))
- RoomViewController: Remove thread list bar button item badge count. ([#5853](https://github.com/vector-im/element-ios/issues/5853))
🐛 Bugfixes
- Fix user suggestions not showing up when re-entering a room. ([#5876](https://github.com/vector-im/element-ios/pull/5876))
- Prevent the homescreen from resetting on every appearance. ([#5885](https://github.com/vector-im/element-ios/pull/5885))
- UserSuggestionViewModel: Fix retain cycle ([#5058](https://github.com/vector-im/element-ios/issues/5058))
- Green launch spinner is sometimes dismissed too early causing the incorrect onboarding screen to be displayed. ([#5472](https://github.com/vector-im/element-ios/issues/5472))
- Home: Fix crash when pressing tabs ([#5547](https://github.com/vector-im/element-ios/issues/5547))
- Selection impossible when filtering in add room screen. ([#5757](https://github.com/vector-im/element-ios/issues/5757))
- Room: Refresh header when call actions become available (member count changes) ([#5800](https://github.com/vector-im/element-ios/issues/5800))
- Share Extension: Stop logging crashes due to intentional exception that frees up memory and handle changes to MXRoom in the SDK. ([#5805](https://github.com/vector-im/element-ios/issues/5805))
- Crash after leaving last space. ([#5825](https://github.com/vector-im/element-ios/issues/5825))
- Authentication: Fix a crash that occurred when using the app with an account that had a soft logout. ([#5846](https://github.com/vector-im/element-ios/issues/5846))
- MXAccount: Do not clear cache if there are no stored filters ([#5873](https://github.com/vector-im/element-ios/issues/5873))
⚠️ API Changes
- Rename scrollEdgesAppearance → scrollEdgeAppearance to match UIKit. ([#5826](https://github.com/vector-im/element-ios/pull/5826))
🚧 In development 🚧
- Onboarding: Add screens for setting a display name and avatar when signing up for the first time. ([#5652](https://github.com/vector-im/element-ios/issues/5652))
- Location sharing: Handle live location banner view in room screen. ([#5857](https://github.com/vector-im/element-ios/issues/5857))
## Changes in 1.8.7 (2022-03-18)
🙌 Improvements
- Room: Allow ignoring invited users that have not joined a room yet ([#5866](https://github.com/vector-im/element-ios/issues/5866))
## Changes in 1.8.6 (2022-03-14)
🙌 Improvements

View file

@ -68,7 +68,8 @@ public class UserIndicator {
/// Cancel the indicator, triggering any dismissal action / animation
///
/// Note: clients can call this method directly, if they have access to the `UserIndicator`.
/// Note: clients can call this method directly, if they have access to the `UserIndicator`. Alternatively
/// deallocating the `UserIndicator` will call `cancel` automatically.
/// Once cancelled, `UserIndicatorQueue` will automatically start the next `UserIndicator` in the queue.
public func cancel() {
complete()

View file

@ -15,5 +15,5 @@
//
// Version
MARKETING_VERSION = 1.8.7
CURRENT_PROJECT_VERSION = 1.8.7
MARKETING_VERSION = 1.8.10
CURRENT_PROJECT_VERSION = 1.8.10

View file

@ -90,6 +90,12 @@ final class BuildSettings: NSObject {
static let applicationWebAppUrlString = "https://app.element.io"
// MARK: - Localization
/// Whether to allow the app to use a right to left layout or force left to right for all languages
static let disableRightToLeftLayout = true
// MARK: - Server configuration
// Default servers proposed on the authentication screen
@ -191,6 +197,7 @@ final class BuildSettings: NSObject {
static let bugReportEndpointUrlString = "https://riot.im/bugreports"
// Use the name allocated by the bug report server
static let bugReportApplicationId = "riot-ios"
static let bugReportUISIId = "element-auto-uisi"
// MARK: - Integrations
@ -204,8 +211,10 @@ final class BuildSettings: NSObject {
"https://scalar-staging.vector.im/api",
"https://scalar-staging.riot.im/scalar/api",
]
// Jitsi server used outside integrations to create conference calls from the call button in the timeline
static let jitsiServerUrl: URL = URL(string: "https://jitsi.riot.im")!
// Jitsi server used outside integrations to create conference calls from the call button in the timeline.
// Setting this to nil effectively disables Jitsi conference calls (given that there is no wellknown override).
// Note: this will not remove the conference call button, use roomScreenAllowVoIPForNonDirectRoom setting.
static let jitsiServerUrl: URL? = URL(string: "https://jitsi.riot.im")
// MARK: - Features
@ -287,7 +296,11 @@ final class BuildSettings: NSObject {
static let settingsSecurityScreenShowAdvancedUnverifiedDevices:Bool = true
// MARK: - Timeline settings
static let roomInputToolbarCompressionMode = MXKRoomInputToolbarCompressionModePrompt
static let roomInputToolbarCompressionMode: MediaCompressionMode = .prompt
enum MediaCompressionMode {
case prompt, small, medium, large, none
}
// MARK: - Room Creation Screen
@ -369,7 +382,7 @@ final class BuildSettings: NSObject {
static let authEnableRefreshTokens = false
// MARK: - Onboarding
static let onboardingShowAccountPersonalisation = false
static let onboardingShowAccountPersonalization = false
// MARK: - Unified Search
static let unifiedSearchScreenShowPublicDirectory = true
@ -377,6 +390,9 @@ final class BuildSettings: NSObject {
// MARK: - Secrets Recovery
static let secretsRecoveryAllowReset = true
// MARK: - UISI Autoreporting
static let cryptoUISIAutoReportingEnabled = false
// MARK: - Polls
static var pollsEnabled: Bool {

View file

@ -67,5 +67,4 @@ public protocol Colors {
/// - Names in chat timeline
/// - Avatars default states that include first name letter
var namesAndAvatars: [ColorType] { get }
}

View file

@ -47,7 +47,7 @@ import UIKit
public let background: UIColor
public let namesAndAvatars: [UIColor]
init(values: ColorValues) {
accent = values.accent
alert = values.alert

View file

@ -110,10 +110,10 @@ extension ElementFonts: Fonts {
public var title2: SharedFont {
let uiFont = self.font(forTextStyle: .title2)
if #available(iOS 13.0, *) {
return SharedFont(uiFont: uiFont, font: Font(uiFont))
} else if #available(iOS 14.0, *) {
if #available(iOS 14.0, *) {
return SharedFont(uiFont: uiFont, font: .title2)
} else if #available(iOS 13.0, *) {
return SharedFont(uiFont: uiFont, font: Font(uiFont))
} else {
return SharedFont(uiFont: uiFont)
}
@ -122,10 +122,10 @@ extension ElementFonts: Fonts {
public var title2B: SharedFont {
let uiFont = self.title2.uiFont.vc_bold
if #available(iOS 13.0, *) {
return SharedFont(uiFont: uiFont, font: Font(uiFont))
} else if #available(iOS 14.0, *) {
if #available(iOS 14.0, *) {
return SharedFont(uiFont: uiFont, font: .title2.bold())
} else if #available(iOS 13.0, *) {
return SharedFont(uiFont: uiFont, font: Font(uiFont))
} else {
return SharedFont(uiFont: uiFont)
}
@ -134,10 +134,10 @@ extension ElementFonts: Fonts {
public var title3: SharedFont {
let uiFont = self.font(forTextStyle: .title3)
if #available(iOS 13.0, *) {
return SharedFont(uiFont: uiFont, font: Font(uiFont))
} else if #available(iOS 14.0, *) {
if #available(iOS 14.0, *) {
return SharedFont(uiFont: uiFont, font: .title3)
} else if #available(iOS 13.0, *) {
return SharedFont(uiFont: uiFont, font: Font(uiFont))
} else {
return SharedFont(uiFont: uiFont)
}
@ -146,10 +146,10 @@ extension ElementFonts: Fonts {
public var title3SB: SharedFont {
let uiFont = self.title3.uiFont.vc_semiBold
if #available(iOS 13.0, *) {
return SharedFont(uiFont: uiFont, font: Font(uiFont))
} else if #available(iOS 14.0, *) {
if #available(iOS 14.0, *) {
return SharedFont(uiFont: uiFont, font: .title3.weight(.semibold))
} else if #available(iOS 13.0, *) {
return SharedFont(uiFont: uiFont, font: Font(uiFont))
} else {
return SharedFont(uiFont: uiFont)
}
@ -258,10 +258,10 @@ extension ElementFonts: Fonts {
public var caption2: SharedFont {
let uiFont = self.font(forTextStyle: .caption2)
if #available(iOS 13.0, *) {
return SharedFont(uiFont: uiFont, font: Font(uiFont))
} else if #available(iOS 14.0, *) {
if #available(iOS 14.0, *) {
return SharedFont(uiFont: uiFont, font: .caption2)
} else if #available(iOS 13.0, *) {
return SharedFont(uiFont: uiFont, font: Font(uiFont))
} else {
return SharedFont(uiFont: uiFont)
}
@ -270,10 +270,10 @@ extension ElementFonts: Fonts {
public var caption2SB: SharedFont {
let uiFont = self.caption2.uiFont.vc_semiBold
if #available(iOS 13.0, *) {
return SharedFont(uiFont: uiFont, font: Font(uiFont))
} else if #available(iOS 14.0, *) {
if #available(iOS 14.0, *) {
return SharedFont(uiFont: uiFont, font: .caption2.weight(.semibold))
} else if #available(iOS 13.0, *) {
return SharedFont(uiFont: uiFont, font: Font(uiFont))
} else {
return SharedFont(uiFont: uiFont)
}

View file

@ -3,7 +3,7 @@ GEM
specs:
CFPropertyList (3.0.5)
rexml
activesupport (6.1.4.4)
activesupport (6.1.5)
concurrent-ruby (~> 1.0, >= 1.0.2)
i18n (>= 1.6, < 2)
minitest (>= 5.1)
@ -17,27 +17,27 @@ GEM
artifactory (3.0.15)
atomos (0.1.3)
aws-eventstream (1.2.0)
aws-partitions (1.541.0)
aws-sdk-core (3.124.0)
aws-partitions (1.568.0)
aws-sdk-core (3.130.0)
aws-eventstream (~> 1, >= 1.0.2)
aws-partitions (~> 1, >= 1.525.0)
aws-sigv4 (~> 1.1)
jmespath (~> 1.0)
aws-sdk-kms (1.52.0)
aws-sdk-core (~> 3, >= 3.122.0)
aws-sdk-kms (1.55.0)
aws-sdk-core (~> 3, >= 3.127.0)
aws-sigv4 (~> 1.1)
aws-sdk-s3 (1.109.0)
aws-sdk-core (~> 3, >= 3.122.0)
aws-sdk-s3 (1.113.0)
aws-sdk-core (~> 3, >= 3.127.0)
aws-sdk-kms (~> 1)
aws-sigv4 (~> 1.4)
aws-sigv4 (1.4.0)
aws-eventstream (~> 1, >= 1.0.2)
babosa (1.0.4)
claide (1.0.3)
cocoapods (1.11.2)
cocoapods (1.11.3)
addressable (~> 2.8)
claide (>= 1.0.2, < 2.0)
cocoapods-core (= 1.11.2)
cocoapods-core (= 1.11.3)
cocoapods-deintegrate (>= 1.0.3, < 2.0)
cocoapods-downloader (>= 1.4.0, < 2.0)
cocoapods-plugins (>= 1.0.0, < 2.0)
@ -52,7 +52,7 @@ GEM
nap (~> 1.0)
ruby-macho (>= 1.0, < 3.0)
xcodeproj (>= 1.21.0, < 2.0)
cocoapods-core (1.11.2)
cocoapods-core (1.11.3)
activesupport (>= 5.0, < 7)
addressable (~> 2.8)
algoliasearch (~> 1.0)
@ -86,17 +86,18 @@ GEM
escape (0.0.4)
ethon (0.15.0)
ffi (>= 1.15.0)
excon (0.89.0)
faraday (1.8.0)
excon (0.92.1)
faraday (1.10.0)
faraday-em_http (~> 1.0)
faraday-em_synchrony (~> 1.0)
faraday-excon (~> 1.1)
faraday-httpclient (~> 1.0.1)
faraday-httpclient (~> 1.0)
faraday-multipart (~> 1.0)
faraday-net_http (~> 1.0)
faraday-net_http_persistent (~> 1.1)
faraday-net_http_persistent (~> 1.0)
faraday-patron (~> 1.0)
faraday-rack (~> 1.0)
multipart-post (>= 1.2, < 3)
faraday-retry (~> 1.0)
ruby2_keywords (>= 0.0.4)
faraday-cookie_jar (0.0.7)
faraday (>= 0.8.0)
@ -105,14 +106,17 @@ GEM
faraday-em_synchrony (1.0.0)
faraday-excon (1.1.0)
faraday-httpclient (1.0.1)
faraday-multipart (1.0.3)
multipart-post (>= 1.2, < 3)
faraday-net_http (1.0.1)
faraday-net_http_persistent (1.2.0)
faraday-patron (1.0.0)
faraday-rack (1.0.0)
faraday-retry (1.0.3)
faraday_middleware (1.2.0)
faraday (~> 1.0)
fastimage (2.2.6)
fastlane (2.199.0)
fastlane (2.205.0)
CFPropertyList (>= 2.3, < 4.0.0)
addressable (>= 2.8, < 3.0.0)
artifactory (~> 3.0)
@ -157,13 +161,13 @@ GEM
fastlane-plugin-versioning (0.5.0)
fastlane-plugin-xcodegen (1.1.0)
fastlane-plugin-brew (~> 0.1.1)
ffi (1.15.4)
ffi (1.15.5)
fourflusher (2.3.1)
fuzzy_match (2.0.4)
gh_inspector (1.1.3)
google-apis-androidpublisher_v3 (0.14.0)
google-apis-androidpublisher_v3 (0.16.0)
google-apis-core (>= 0.4, < 2.a)
google-apis-core (0.4.1)
google-apis-core (0.4.2)
addressable (~> 2.5, >= 2.5.1)
googleauth (>= 0.16.2, < 2.a)
httpclient (>= 2.8.1, < 3.a)
@ -172,11 +176,11 @@ GEM
retriable (>= 2.0, < 4.a)
rexml
webrick
google-apis-iamcredentials_v1 (0.9.0)
google-apis-iamcredentials_v1 (0.10.0)
google-apis-core (>= 0.4, < 2.a)
google-apis-playcustomapp_v1 (0.6.0)
google-apis-playcustomapp_v1 (0.7.0)
google-apis-core (>= 0.4, < 2.a)
google-apis-storage_v1 (0.10.0)
google-apis-storage_v1 (0.11.0)
google-apis-core (>= 0.4, < 2.a)
google-cloud-core (1.6.0)
google-cloud-env (~> 1.0)
@ -184,7 +188,7 @@ GEM
google-cloud-env (1.5.0)
faraday (>= 0.17.3, < 2.0)
google-cloud-errors (1.2.0)
google-cloud-storage (1.35.0)
google-cloud-storage (1.36.1)
addressable (~> 2.8)
digest-crc (~> 0.4)
google-apis-iamcredentials_v1 (~> 0.1)
@ -192,8 +196,8 @@ GEM
google-cloud-core (~> 1.6)
googleauth (>= 0.16.2, < 2.a)
mini_mime (~> 1.0)
googleauth (1.1.0)
faraday (>= 0.17.3, < 2.0)
googleauth (1.1.2)
faraday (>= 0.17.3, < 3.a)
jwt (>= 1.4, < 3.0)
memoist (~> 0.16)
multi_json (~> 1.11)
@ -204,15 +208,15 @@ GEM
http-cookie (1.0.4)
domain_name (~> 0.5)
httpclient (2.8.3)
i18n (1.8.11)
i18n (1.10.0)
concurrent-ruby (~> 1.0)
jmespath (1.4.0)
jmespath (1.6.1)
json (2.6.1)
jwt (2.3.0)
memoist (0.16.2)
mime-types (3.4.1)
mime-types-data (~> 3.2015)
mime-types-data (3.2021.1115)
mime-types-data (3.2022.0105)
mini_magick (4.11.0)
mini_mime (1.1.2)
minitest (5.15.0)
@ -244,9 +248,9 @@ GEM
ruby2_keywords (0.0.5)
rubyzip (2.3.2)
security (0.1.3)
signet (0.16.0)
signet (0.16.1)
addressable (~> 2.8)
faraday (>= 0.17.3, < 2.0)
faraday (>= 0.17.5, < 3.0)
jwt (>= 1.5, < 3.0)
multi_json (~> 1.10)
simctl (1.6.8)
@ -267,7 +271,7 @@ GEM
uber (0.1.0)
unf (0.1.4)
unf_ext
unf_ext (0.0.8)
unf_ext (0.0.8.1)
unicode-display_width (1.8.0)
webrick (1.7.0)
word_wrap (1.0.0)
@ -285,7 +289,7 @@ GEM
rouge (~> 2.0.7)
xcpretty-travis-formatter (1.0.1)
xcpretty (~> 0.2, >= 0.0.7)
zeitwerk (2.5.1)
zeitwerk (2.5.4)
PLATFORMS
ruby
@ -299,4 +303,4 @@ DEPENDENCIES
xcode-install
BUNDLED WITH
2.2.32
2.3.9

View file

@ -13,7 +13,7 @@ use_frameworks!
# - `{ :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
$matrixSDKVersion = '= 0.22.6'
$matrixSDKVersion = '= 0.23.1'
# $matrixSDKVersion = :local
# $matrixSDKVersion = { :branch => 'develop'}
# $matrixSDKVersion = { :specHash => { git: 'https://git.io/fork123', branch: 'fix' } }

View file

@ -56,16 +56,16 @@ PODS:
- LoggerAPI (1.9.200):
- Logging (~> 1.1)
- Logging (1.4.0)
- MatrixSDK (0.22.6):
- MatrixSDK/Core (= 0.22.6)
- MatrixSDK/Core (0.22.6):
- MatrixSDK (0.23.1):
- MatrixSDK/Core (= 0.23.1)
- MatrixSDK/Core (0.23.1):
- AFNetworking (~> 4.0.0)
- GZIP (~> 1.3.0)
- libbase58 (~> 0.1.4)
- OLMKit (~> 3.2.5)
- Realm (= 10.16.0)
- SwiftyBeaver (= 1.9.5)
- MatrixSDK/JingleCallStack (0.22.6):
- MatrixSDK/JingleCallStack (0.23.1):
- JitsiMeetSDK (= 3.10.2)
- MatrixSDK/Core
- OLMKit (3.2.5):
@ -115,8 +115,8 @@ DEPENDENCIES:
- KeychainAccess (~> 4.2.2)
- KTCenterFlowLayout (~> 1.3.1)
- libPhoneNumber-iOS (~> 0.9.13)
- MatrixSDK (= 0.22.6)
- MatrixSDK/JingleCallStack (= 0.22.6)
- MatrixSDK (= 0.23.1)
- MatrixSDK/JingleCallStack (= 0.23.1)
- OLMKit
- PostHog (~> 1.4.4)
- ReadMoreTextView (~> 3.0.1)
@ -208,7 +208,7 @@ SPEC CHECKSUMS:
libPhoneNumber-iOS: 0a32a9525cf8744fe02c5206eb30d571e38f7d75
LoggerAPI: ad9c4a6f1e32f518fdb43a1347ac14d765ab5e3d
Logging: beeb016c9c80cf77042d62e83495816847ef108b
MatrixSDK: f61997c146a03dfbf2ec0442bcae362c50666284
MatrixSDK: 54d16aa08f3043fb1bcf639ef1ac5c589100f39f
OLMKit: 9fb4799c4a044dd2c06bda31ec31a12191ad30b5
PostHog: 4b6321b521569092d4ef3a02238d9435dbaeb99f
ReadMoreTextView: 19147adf93abce6d7271e14031a00303fe28720d
@ -225,6 +225,6 @@ SPEC CHECKSUMS:
zxcvbn-ios: fef98b7c80f1512ff0eec47ac1fa399fc00f7e3c
ZXingObjC: fdbb269f25dd2032da343e06f10224d62f537bdb
PODFILE CHECKSUM: 16aaf5e59ec902619fbfd799939f044728a92ab7
PODFILE CHECKSUM: 820f04e07aa252459ecfa88d04da729daca4fcbb
COCOAPODS: 1.11.3

View file

@ -0,0 +1,15 @@
{
"images" : [
{
"filename" : "onboarding_avatar_camera.svg",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
},
"properties" : {
"preserves-vector-representation" : true
}
}

View file

@ -0,0 +1,4 @@
<svg width="23" height="19" viewBox="0 0 23 19" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M9.26126 0.625C7.93955 0.625 6.8205 1.56156 6.56548 2.83072C6.52727 3.02086 6.45364 3.20457 6.32574 3.35036L5.77376 3.97953C5.59971 4.17792 5.3486 4.29167 5.08469 4.29167H2.16659C1.15406 4.29167 0.333252 5.11248 0.333252 6.125V16.2083C0.333252 17.2209 1.15406 18.0417 2.16658 18.0417H20.4999C21.5124 18.0417 22.3333 17.2209 22.3333 16.2083V6.125C22.3333 5.11248 21.5124 4.29167 20.4999 4.29167H17.5818C17.3179 4.29167 17.0668 4.17792 16.8927 3.97953L16.3408 3.35036C16.2129 3.20457 16.1392 3.02086 16.101 2.83071C15.846 1.56156 14.727 0.625 13.4052 0.625H9.26126ZM14.9999 10.7083C14.9999 12.7333 13.3583 14.3749 11.3333 14.3749C9.30821 14.3749 7.66659 12.7333 7.66659 10.7083C7.66659 8.68321 9.30821 7.04159 11.3333 7.04159C13.3583 7.04159 14.9999 8.68321 14.9999 10.7083Z" fill="#737D8C"/>
<path d="M2.62492 2.91659C2.37179 2.91659 2.16659 3.12179 2.16659 3.37492C2.16659 3.62805 2.37179 3.83325 2.62492 3.83325H4.45825C4.71138 3.83325 4.91659 3.62805 4.91659 3.37492C4.91659 3.12179 4.71138 2.91659 4.45825 2.91659H2.62492Z" fill="#737D8C"/>
</svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

View file

@ -0,0 +1,15 @@
{
"images" : [
{
"filename" : "onboarding_avatar_edit.svg",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
},
"properties" : {
"preserves-vector-representation" : true
}
}

View file

@ -0,0 +1,4 @@
<svg width="22" height="21" viewBox="0 0 22 21" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M3.63013 13.0435C3.64248 12.9655 3.67763 12.8929 3.73116 12.8349L15.1405 0.462333C15.2903 0.299929 15.5434 0.289681 15.7058 0.439442L18.0582 2.60876C18.2206 2.75852 18.2309 3.01158 18.0811 3.17399L6.67174 15.5465C6.6182 15.6046 6.54868 15.6455 6.47192 15.6641L3.66863 16.3437C3.39123 16.411 3.13469 16.1744 3.1793 15.8925L3.63013 13.0435Z" fill="#737D8C"/>
<path d="M1.83301 17.2204C1.00458 17.2204 0.333008 17.892 0.333008 18.7204C0.333008 19.5488 1.00458 20.2204 1.83301 20.2204L19.833 20.2204C20.6614 20.2204 21.333 19.5488 21.333 18.7204C21.333 17.892 20.6614 17.2204 19.833 17.2204L1.83301 17.2204Z" fill="#737D8C"/>
</svg>

After

Width:  |  Height:  |  Size: 734 B

View file

@ -0,0 +1,15 @@
{
"images" : [
{
"filename" : "onboarding_celebration_icon.svg",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
},
"properties" : {
"preserves-vector-representation" : true
}
}

View file

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg width="100%" height="100%" viewBox="0 0 70 70" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
<path d="M35,70C54.33,70 70,54.33 70,35C70,15.67 54.33,0 35,0C15.67,0 0,15.67 0,35C0,54.33 15.67,70 35,70ZM34.863,42.213L37.589,50.392C37.725,50.801 38.107,51.076 38.538,51.076C38.968,51.076 39.35,50.801 39.487,50.392L42.213,42.213C42.213,42.213 50.392,39.487 50.392,39.487C50.8,39.351 51.076,38.969 51.076,38.538C51.076,38.108 50.8,37.726 50.392,37.589L42.213,34.863C42.213,34.863 39.487,26.684 39.487,26.684C39.35,26.276 38.968,26 38.538,26C38.107,26 37.725,26.276 37.589,26.684L34.863,34.863C34.863,34.863 26.684,37.589 26.684,37.589C26.275,37.726 26,38.108 26,38.538C26,38.969 26.275,39.351 26.684,39.487L34.863,42.213ZM19.882,23L18.106,26.553C17.913,26.938 17.989,27.403 18.293,27.707C18.597,28.011 19.062,28.087 19.447,27.894L23,26.118C23,26.118 26.553,27.894 26.553,27.894C26.938,28.087 27.403,28.011 27.707,27.707C28.011,27.403 28.087,26.938 27.894,26.553L26.118,23C26.118,23 27.894,19.447 27.894,19.447C28.087,19.062 28.011,18.597 27.707,18.293C27.403,17.989 26.938,17.913 26.553,18.106L23,19.882C23,19.882 19.447,18.106 19.447,18.106C19.062,17.913 18.597,17.989 18.293,18.293C17.989,18.597 17.913,19.062 18.106,19.447L19.882,23Z" style="fill:rgb(13,189,139);"/>
</svg>

After

Width:  |  Height:  |  Size: 1.6 KiB

View file

@ -0,0 +1,26 @@
{
"images" : [
{
"filename" : "live_location_icon.png",
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "live_location_icon@2x.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"filename" : "live_location_icon@3x.png",
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
},
"properties" : {
"template-rendering-intent" : "template"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 390 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 710 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

View file

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4 KiB

View file

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

View file

@ -19,5 +19,8 @@
"info" : {
"author" : "xcode",
"version" : 1
},
"properties" : {
"template-rendering-intent" : "template"
}
}

View file

@ -16,8 +16,29 @@
/** These strings will be ignored by Weblate. Useful for WIP **/
// MARK: Onboarding Personalisation WIP
// MARK: Onboarding Personalization WIP
"onboarding_congratulations_title" = "Congratulations!";
"onboarding_congratulations_message" = "Your account\n%@\nhas been created.";
"onboarding_congratulations_personalise_button" = "Personalise profile";
"onboarding_congratulations_message" = "Your account %@ has been created.";
"onboarding_congratulations_personalize_button" = "Personalise profile";
"onboarding_congratulations_home_button" = "Take me home";
"onboarding_personalization_save" = "Save and continue";
"onboarding_personalization_skip" = "Skip this step";
"onboarding_display_name_title" = "Choose a display name";
"onboarding_display_name_message" = "This will be shown when you send messages.";
"onboarding_display_name_placeholder" = "Display Name";
"onboarding_display_name_hint" = "You can change this later";
"onboarding_display_name_max_length" = "Your display name must be less than 256 characters";
"onboarding_avatar_title" = "Add a profile picture";
"onboarding_avatar_message" = "You can change this anytime.";
"onboarding_avatar_accessibility_label" = "Profile picture";
"onboarding_celebration_title" = "Youre all set!";
"onboarding_celebration_message" = "Your preferences have been saved.";
"onboarding_celebration_button" = "Let's go";
"image_picker_action_files" = "Choose from files";
"spaces_feature_not_available" = "This feature isn't available here. For now, you can do this with %@ on your computer.";

View file

@ -57,9 +57,9 @@
"rename" = "Rename";
"collapse" = "collapse";
"send_to" = "Send to %@";
"sending" = "Sending";
"close" = "Close";
"skip" = "Skip";
"joining" = "Joining";
"joined" = "Joined";
"switch" = "Switch";
"more" = "More";
@ -75,6 +75,12 @@
"ok" = "OK";
"error" = "Error";
"suggest" = "Suggest";
"edit" = "Edit";
// Activities
"loading" = "Loading";
"sending" = "Sending";
"saving" = "Saving";
// Call Bar
"callbar_only_single_active" = "Tap to return to the call (%@)";
@ -632,6 +638,7 @@ Tap the + to start adding people.";
"settings_labs_enable_ringing_for_group_calls" = "Ring for group calls";
"settings_labs_enabled_polls" = "Polls";
"settings_labs_enable_threads" = "Threaded messaging";
"settings_labs_enable_auto_report_decryption_errors" = "Auto Report Decryption Errors";
"settings_labs_use_only_latest_user_avatar_and_name" = "Show latest avatar and name for users in message history";
"settings_version" = "Version %@";
@ -1026,6 +1033,7 @@ Tap the + to start adding people.";
"call_incoming_video" = "Incoming video call…";
"call_already_displayed" = "There is already a call in progress.";
"call_jitsi_error" = "Failed to join the conference call.";
"call_jitsi_unable_to_start" = "Unable to start conference call";
"call_no_stun_server_error_title" ="Call failed due to misconfigured server";
"call_no_stun_server_error_message_1" ="Please ask the administrator of your homeserver %@ to configure a TURN server in order for calls to work reliably.";
@ -1762,6 +1770,7 @@ Tap the + to start adding people.";
"create_room_placeholder_address" = "#testroom:matrix.org";
"create_room_suggest_room" = "Suggest to space members";
"create_room_suggest_room_footer" = "Suggested rooms are promoted to space members as good to join.";
"create_room_processing" = "Creating room";
// MARK: - Room Info
@ -2065,8 +2074,6 @@ Tap the + to start adding people.";
"location_sharing_close_action" = "Close";
"location_sharing_share_action" = "Share";
"location_sharing_post_failure_title" = "We couldnt send your location";
"location_sharing_post_failure_subtitle" = "%@ could not send your location. Please try again later.";
@ -2091,6 +2098,13 @@ Tap the + to start adding people.";
"location_sharing_settings_toggle_title" = "Enable location sharing";
// MARK: Live location sharing
"location_sharing_live_share_title" = "Share live location";
"live_location_sharing_banner_title" = "Live location enabled";
"live_location_sharing_banner_stop" = "Stop";
"location_sharing_static_share_title" = "Send my current location";
"location_sharing_pin_drop_share_title" = "Send this location";
// MARK: - MatrixKit
@ -2427,7 +2441,7 @@ Tap the + to start adding people.";
"language_picker_title" = "Choose a language";
"language_picker_default_language" = "Default (%@)";
/* -*-
/* -*-
Automatic localization for en
The following key/value pairs were extracted from the android i18n file:
@ -2510,17 +2524,17 @@ Tap the + to start adding people.";
"notice_room_history_visible_to_members_from_joined_point_by_you" = "You made future room history visible to all room members, from the point they joined.";
"notice_room_history_visible_to_members_from_joined_point_by_you_for_dm" = "You made future messages visible to everyone, from when they joined.";
// Room Screen
// Room Screen
// general errors
// general errors
// Home Screen
// Home Screen
// Last seen time
// Last seen time
// call events
// call events
/* -*-
/* -*-
Automatic localization for en
The following key/value pairs were extracted from the android i18n file:
@ -2528,9 +2542,9 @@ Tap the + to start adding people.";
*/
// titles
// titles
// button names
// button names
"send" = "Send";
"copy_button_name" = "Copy";
"resend" = "Resend";
@ -2538,7 +2552,7 @@ Tap the + to start adding people.";
"share" = "Share";
"delete" = "Delete";
// actions
// actions
"action_logout" = "Logout";
"create_room" = "Create Room";
"login" = "Login";
@ -2553,32 +2567,32 @@ Tap the + to start adding people.";
"unban" = "Un-ban";
"message_unsaved_changes" = "There are unsaved changes. Leaving will discard them.";
// Login Screen
// Login Screen
"login_error_already_logged_in" = "Already logged in";
"login_error_must_start_http" = "URL must start with http[s]://";
// members list Screen
// members list Screen
// accounts list Screen
// accounts list Screen
// image size selection
// image size selection
// invitation members list Screen
// invitation members list Screen
// room creation dialog Screen
// room creation dialog Screen
// room info dialog Screen
// room details dialog screen
// contacts list screen
// contacts list screen
"invitation_message" = "I\'d like to chat with you with matrix. Please, visit the website http://matrix.org to have more information.";
// Settings screen
// Settings screen
"settings_title_config" = "Configuration";
"settings_title_notifications" = "Notifications";
// Notification settings screen
// Notification settings screen
"notification_settings_disable_all" = "Disable all notifications";
"notification_settings_enable_notifications" = "Enable notifications";
"notification_settings_enable_notifications_warning" = "All notifications are currently disabled for all devices.";
@ -2605,10 +2619,10 @@ Tap the + to start adding people.";
"notification_settings_by_default" = "By default...";
"notification_settings_notify_all_other" = "Notify for all other messages/rooms";
// gcm section
// gcm section
"settings_config_identity_server" = "Identity server: %@";
// Settings keys
// Settings keys
// call string
"call_connecting" = "Connecting…";
@ -2641,4 +2655,3 @@ Tap the + to start adding people.";
"ssl_unexpected_existing_expl" = "The certificate has changed from one that was trusted by your phone. This is HIGHLY UNUSUAL. It is recommended that you DO NOT ACCEPT this new certificate.";
"ssl_expected_existing_expl" = "The certificate has changed from a previously trusted one to one that is not trusted. The server may have renewed its certificate. Contact the server administrator for the expected fingerprint.";
"ssl_only_accept" = "ONLY accept the certificate if the server administrator has published a fingerprint that matches the one above.";

View file

@ -30,4 +30,9 @@ public extension Bundle {
}
return bundle
}
/// Whether or not the bundle is the RiotShareExtension.
var isShareExtension: Bool {
bundleURL.lastPathComponent.contains("RiotShareExtension.appex")
}
}

View file

@ -0,0 +1,26 @@
//
// 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
extension Encodable {
/// Convenience method to get the json string of an Encodable
var jsonString: String? {
let encoder = JSONEncoder()
guard let jsonData = try? encoder.encode(self) else { return nil }
return String(data: jsonData, encoding: .utf8)
}
}

View file

@ -0,0 +1,114 @@
//
// 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 MatrixSDK
import GBDeviceInfo
extension MXBugReportRestClient {
@objc static func vc_bugReportRestClient(appName: String) -> MXBugReportRestClient {
let client = MXBugReportRestClient(bugReportEndpoint: BuildSettings.bugReportEndpointUrlString)
// App info
client.appName = appName
client.version = AppDelegate.theDelegate().appVersion
client.build = AppDelegate.theDelegate().build
client.deviceModel = GBDeviceInfo.deviceInfo().modelString
client.deviceOS = "\(UIDevice.current.systemName) \(UIDevice.current.systemVersion)"
return client
}
@objc func vc_sendBugReport(
description: String,
sendLogs: Bool,
sendCrashLog: Bool,
sendFiles: [URL]? = nil,
additionalLabels: [String]? = nil,
customFields: [String: String]? = nil,
progress: ((MXBugReportState, Progress?) -> Void)? = nil,
success: ((String?) -> Void)? = nil,
failure: ((Error?) -> Void)? = nil
) {
// User info (TODO: handle multi-account and find a way to expose them in rageshake API)
var userInfo = [String: String]()
let mainAccount = MXKAccountManager.shared().accounts.first
if let userId = mainAccount?.mxSession.myUser.userId {
userInfo["user_id"] = userId
}
if let deviceId = mainAccount?.mxSession.matrixRestClient.credentials.deviceId {
userInfo["device_id"] = deviceId
}
userInfo["locale"] = NSLocale.preferredLanguages[0]
userInfo["default_app_language"] = Bundle.main.preferredLocalizations[0] // The language chosen by the OS
userInfo["app_language"] = Bundle.mxk_language() ?? userInfo["default_app_language"] // The language chosen by the user
// Application settings
userInfo["lazy_loading"] = MXKAppSettings.standard().syncWithLazyLoadOfRoomMembers ? "ON" : "OFF"
let currentDate = Date()
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "yyyy-MM-dd HH:mm:ss"
userInfo["local_time"] = dateFormatter.string(from: currentDate)
dateFormatter.timeZone = TimeZone(identifier: "UTC")
userInfo["utc_time"] = dateFormatter.string(from: currentDate)
if let customFields = customFields {
// combine userInfo with custom fields overriding with custom where there is a conflict
userInfo.merge(customFields) { (_, new) in new }
}
others = userInfo
var labels: [String] = additionalLabels ?? [String]()
// Add a Github label giving information about the version
if var versionLabel = version, let buildLabel = build {
// If this is not the app store version, be more accurate on the build origin
if buildLabel == VectorL10n.settingsConfigNoBuildInfo {
// This is a debug session from Xcode
versionLabel += "-debug"
} else if !buildLabel.contains("master") {
// This is a Jenkins build. Add the branch and the build number
let buildString = buildLabel.replacingOccurrences(of: " ", with: "-")
versionLabel += "-\(buildString)"
}
labels += [versionLabel]
}
if sendCrashLog {
labels += ["crash"]
}
var sendDescription = description
if sendCrashLog,
let crashLogFile = MXLogger.crashLog(),
let crashLog = try? String(contentsOfFile: crashLogFile, encoding: .utf8) {
// Append the crash dump to the user description in order to ease triaging of GH issues
sendDescription += "\n\n\n--------------------------------------------------------------------------------\n\n\(crashLog)"
}
sendBugReport(sendDescription,
sendLogs: sendLogs,
sendCrashLog: sendCrashLog,
sendFiles: sendFiles,
attachGitHubLabels: labels,
progress: progress,
success: success,
failure: failure)
}
}

View file

@ -59,7 +59,7 @@
success:(void (^)(BOOL canEnableE2E))success
failure:(void (^)(NSError *error))failure;
{
if ([self vc_homeserverConfiguration].isE2EEByDefaultEnabled)
if ([self vc_homeserverConfiguration].encryption.isE2EEByDefaultEnabled)
{
return [self canEnableE2EByDefaultInNewRoomWithUsers:userIds success:success failure:failure];
}

View file

@ -0,0 +1,37 @@
//
// 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 Combine
@available(iOS 14.0, *)
extension Publisher {
///
/// Buffer upstream items and guarantee a time interval spacing out the published items.
/// - Parameters:
/// - spacingDelay: A delay in seconds to guarantee between emissions
/// - scheduler: The `DispatchQueue` on which to schedule emissions.
/// - Returns: The new wrapped publisher
func bufferAndSpace(spacingDelay: Int, scheduler: DispatchQueue = DispatchQueue.main) -> Publishers.FlatMap<
Publishers.SetFailureType<Publishers.Delay<Just<Publishers.Buffer<Self>.Output>, DispatchQueue>, Publishers.Buffer<Self>.Failure>,
Publishers.Buffer<Self>
> {
return buffer(size: .max, prefetch: .byRequest, whenFull: .dropNewest)
.flatMap(maxPublishers: .max(1)) {
Just($0).delay(for: .seconds(spacingDelay), scheduler: scheduler)
}
}
}

View file

@ -14,7 +14,7 @@
limitations under the License.
*/
import Foundation
import UIKit
extension UIButton {
@ -51,4 +51,10 @@ extension UIButton {
self.titleLabel?.adjustsFontForContentSizeCategory = newValue
}
}
/// Set title font and enable Dynamic Type support
func vc_setTitleFont(_ font: UIFont) {
self.vc_adjustsFontForContentSizeCategory = true
self.titleLabel?.font = font
}
}

View file

@ -137,4 +137,18 @@ extension UIViewController {
// set split view display mode button as left bar button item
self.navigationItem.leftBarButtonItem = splitViewController.displayModeButtonItem
}
/// Set the view controller to be displayed in fullscreen modal presentation style on any iOS version.
///
/// - Parameters:
/// - isFullScreen: whether view controller should be displayed full screen
/// - Returns: the view controller
@discardableResult
func vc_setModalFullScreen(_ isFullScreen: Bool) -> UIViewController {
if #available(iOS 13.0, *) {
self.modalPresentationStyle = isFullScreen ? .fullScreen : .automatic
}
return self
}
}

View file

@ -124,6 +124,9 @@ internal class Asset: NSObject {
internal static let onboardingSplashScreenPage3Dark = ImageAsset(name: "OnboardingSplashScreenPage3Dark")
internal static let onboardingSplashScreenPage4 = ImageAsset(name: "OnboardingSplashScreenPage4")
internal static let onboardingSplashScreenPage4Dark = ImageAsset(name: "OnboardingSplashScreenPage4Dark")
internal static let onboardingAvatarCamera = ImageAsset(name: "onboarding_avatar_camera")
internal static let onboardingAvatarEdit = ImageAsset(name: "onboarding_avatar_edit")
internal static let onboardingCelebrationIcon = ImageAsset(name: "onboarding_celebration_icon")
internal static let onboardingCongratulationsIcon = ImageAsset(name: "onboarding_congratulations_icon")
internal static let onboardingUseCaseCommunity = ImageAsset(name: "onboarding_use_case_community")
internal static let onboardingUseCaseCommunityDark = ImageAsset(name: "onboarding_use_case_community_dark")
@ -169,7 +172,10 @@ internal class Asset: NSObject {
internal static let videoCall = ImageAsset(name: "video_call")
internal static let voiceCallHangonIcon = ImageAsset(name: "voice_call_hangon_icon")
internal static let voiceCallHangupIcon = ImageAsset(name: "voice_call_hangup_icon")
internal static let liveLocationIcon = ImageAsset(name: "live_location_icon")
internal static let locationLiveIcon = ImageAsset(name: "location_live_icon")
internal static let locationMarkerIcon = ImageAsset(name: "location_marker_icon")
internal static let locationPinIcon = ImageAsset(name: "location_pin_icon")
internal static let locationShareIcon = ImageAsset(name: "location_share_icon")
internal static let locationUserMarker = ImageAsset(name: "location_user_marker")
internal static let pollCheckboxDefault = ImageAsset(name: "poll_checkbox_default")

View file

@ -687,6 +687,10 @@ public class VectorL10n: NSObject {
public static var callJitsiError: String {
return VectorL10n.tr("Vector", "call_jitsi_error")
}
/// Unable to start conference call
public static var callJitsiUnableToStart: String {
return VectorL10n.tr("Vector", "call_jitsi_unable_to_start")
}
/// Device Speaker
public static var callMoreActionsAudioUseDevice: String {
return VectorL10n.tr("Vector", "call_more_actions_audio_use_device")
@ -931,6 +935,10 @@ public class VectorL10n: NSObject {
public static var createRoomPlaceholderTopic: String {
return VectorL10n.tr("Vector", "create_room_placeholder_topic")
}
/// Creating room
public static var createRoomProcessing: String {
return VectorL10n.tr("Vector", "create_room_processing")
}
/// PROMOTION
public static var createRoomPromotionHeader: String {
return VectorL10n.tr("Vector", "create_room_promotion_header")
@ -1651,6 +1659,10 @@ public class VectorL10n: NSObject {
public static var e2eRoomKeyRequestTitle: String {
return VectorL10n.tr("Vector", "e2e_room_key_request_title")
}
/// Edit
public static var edit: String {
return VectorL10n.tr("Vector", "edit")
}
/// Activities
public static var emojiPickerActivityCategory: String {
return VectorL10n.tr("Vector", "emoji_picker_activity_category")
@ -2211,6 +2223,10 @@ public class VectorL10n: NSObject {
public static var joined: String {
return VectorL10n.tr("Vector", "joined")
}
/// Joining
public static var joining: String {
return VectorL10n.tr("Vector", "joining")
}
/// Done
public static var keyBackupRecoverDoneAction: String {
return VectorL10n.tr("Vector", "key_backup_recover_done_action")
@ -2703,6 +2719,18 @@ public class VectorL10n: NSObject {
public static var less: String {
return VectorL10n.tr("Vector", "less")
}
/// Stop
public static var liveLocationSharingBannerStop: String {
return VectorL10n.tr("Vector", "live_location_sharing_banner_stop")
}
/// Live location enabled
public static var liveLocationSharingBannerTitle: String {
return VectorL10n.tr("Vector", "live_location_sharing_banner_title")
}
/// Loading
public static var loading: String {
return VectorL10n.tr("Vector", "loading")
}
/// To discover contacts already using Matrix, %@ can send email addresses and phone numbers in your address book to your chosen Matrix identity server. Where supported, personal data is hashed before sending - please check your identity server's privacy policy for more details.
public static func localContactsAccessDiscoveryWarning(_ p1: String) -> String {
return VectorL10n.tr("Vector", "local_contacts_access_discovery_warning", p1)
@ -2731,6 +2759,10 @@ public class VectorL10n: NSObject {
public static var locationSharingInvalidAuthorizationSettings: String {
return VectorL10n.tr("Vector", "location_sharing_invalid_authorization_settings")
}
/// Share live location
public static var locationSharingLiveShareTitle: String {
return VectorL10n.tr("Vector", "location_sharing_live_share_title")
}
/// %@ could not load the map. Please try again later.
public static func locationSharingLoadingMapErrorTitle(_ p1: String) -> String {
return VectorL10n.tr("Vector", "location_sharing_loading_map_error_title", p1)
@ -2751,6 +2783,10 @@ public class VectorL10n: NSObject {
public static var locationSharingOpenOpenStreetMaps: String {
return VectorL10n.tr("Vector", "location_sharing_open_open_street_maps")
}
/// Send this location
public static var locationSharingPinDropShareTitle: String {
return VectorL10n.tr("Vector", "location_sharing_pin_drop_share_title")
}
/// %@ could not send your location. Please try again later.
public static func locationSharingPostFailureSubtitle(_ p1: String) -> String {
return VectorL10n.tr("Vector", "location_sharing_post_failure_subtitle", p1)
@ -2767,9 +2803,9 @@ public class VectorL10n: NSObject {
public static var locationSharingSettingsToggleTitle: String {
return VectorL10n.tr("Vector", "location_sharing_settings_toggle_title")
}
/// Share
public static var locationSharingShareAction: String {
return VectorL10n.tr("Vector", "location_sharing_share_action")
/// Send my current location
public static var locationSharingStaticShareTitle: String {
return VectorL10n.tr("Vector", "location_sharing_static_share_title")
}
/// Location
public static var locationSharingTitle: String {
@ -5723,6 +5759,10 @@ public class VectorL10n: NSObject {
public static var save: String {
return VectorL10n.tr("Vector", "save")
}
/// Saving
public static var saving: String {
return VectorL10n.tr("Vector", "saving")
}
/// Search
public static var searchDefaultPlaceholder: String {
return VectorL10n.tr("Vector", "search_default_placeholder")
@ -6667,6 +6707,10 @@ public class VectorL10n: NSObject {
public static var settingsLabsE2eEncryptionPromptMessage: String {
return VectorL10n.tr("Vector", "settings_labs_e2e_encryption_prompt_message")
}
/// Auto Report Decryption Errors
public static var settingsLabsEnableAutoReportDecryptionErrors: String {
return VectorL10n.tr("Vector", "settings_labs_enable_auto_report_decryption_errors")
}
/// Ring for group calls
public static var settingsLabsEnableRingingForGroupCalls: String {
return VectorL10n.tr("Vector", "settings_labs_enable_ringing_for_group_calls")

View file

@ -10,22 +10,82 @@ import Foundation
// swiftlint:disable function_parameter_count identifier_name line_length type_body_length
public extension VectorL10n {
/// Choose from files
static var imagePickerActionFiles: String {
return VectorL10n.tr("Untranslated", "image_picker_action_files")
}
/// Profile picture
static var onboardingAvatarAccessibilityLabel: String {
return VectorL10n.tr("Untranslated", "onboarding_avatar_accessibility_label")
}
/// You can change this anytime.
static var onboardingAvatarMessage: String {
return VectorL10n.tr("Untranslated", "onboarding_avatar_message")
}
/// Add a profile picture
static var onboardingAvatarTitle: String {
return VectorL10n.tr("Untranslated", "onboarding_avatar_title")
}
/// Let's go
static var onboardingCelebrationButton: String {
return VectorL10n.tr("Untranslated", "onboarding_celebration_button")
}
/// Your preferences have been saved.
static var onboardingCelebrationMessage: String {
return VectorL10n.tr("Untranslated", "onboarding_celebration_message")
}
/// Youre all set!
static var onboardingCelebrationTitle: String {
return VectorL10n.tr("Untranslated", "onboarding_celebration_title")
}
/// Take me home
static var onboardingCongratulationsHomeButton: String {
return VectorL10n.tr("Untranslated", "onboarding_congratulations_home_button")
}
/// Your account\n%@\nhas been created.
public static func onboardingCongratulationsMessage(_ p1: String) -> String {
/// Your account %@ has been created.
static func onboardingCongratulationsMessage(_ p1: String) -> String {
return VectorL10n.tr("Untranslated", "onboarding_congratulations_message", p1)
}
/// Personalise profile
static var onboardingCongratulationsPersonaliseButton: String {
return VectorL10n.tr("Untranslated", "onboarding_congratulations_personalise_button")
static var onboardingCongratulationsPersonalizeButton: String {
return VectorL10n.tr("Untranslated", "onboarding_congratulations_personalize_button")
}
/// Congratulations!
static var onboardingCongratulationsTitle: String {
return VectorL10n.tr("Untranslated", "onboarding_congratulations_title")
}
/// You can change this later
static var onboardingDisplayNameHint: String {
return VectorL10n.tr("Untranslated", "onboarding_display_name_hint")
}
/// Your display name must be less than 256 characters
static var onboardingDisplayNameMaxLength: String {
return VectorL10n.tr("Untranslated", "onboarding_display_name_max_length")
}
/// This will be shown when you send messages.
static var onboardingDisplayNameMessage: String {
return VectorL10n.tr("Untranslated", "onboarding_display_name_message")
}
/// Display Name
static var onboardingDisplayNamePlaceholder: String {
return VectorL10n.tr("Untranslated", "onboarding_display_name_placeholder")
}
/// Choose a display name
static var onboardingDisplayNameTitle: String {
return VectorL10n.tr("Untranslated", "onboarding_display_name_title")
}
/// Save and continue
static var onboardingPersonalizationSave: String {
return VectorL10n.tr("Untranslated", "onboarding_personalization_save")
}
/// Skip this step
static var onboardingPersonalizationSkip: String {
return VectorL10n.tr("Untranslated", "onboarding_personalization_skip")
}
/// This feature isn't available here. For now, you can do this with %@ on your computer.
static func spacesFeatureNotAvailable(_ p1: String) -> String {
return VectorL10n.tr("Untranslated", "spaces_feature_not_available", p1)
}
}
// swiftlint:enable function_parameter_count identifier_name line_length type_body_length

View file

@ -263,24 +263,25 @@ class CallPresenter: NSObject {
func processWidgetEvent(_ event: MXEvent, inSession session: MXSession) {
MXLog.debug("[CallPresenter] processWidgetEvent")
guard JMCallKitProxy.isProviderConfigured() else {
// CallKit proxy is not configured, no benefit in parsing the event
MXLog.debug("[CallPresenter] processWidgetEvent: JMCallKitProxy not configured")
return
}
guard let widget = Widget(widgetEvent: event, inMatrixSession: session) else {
MXLog.debug("[CallPresenter] processWidgetEvent: widget couldn't be created")
return
}
if let uuid = self.jitsiCalls.first(where: { $0.value.widgetId == widget.widgetId })?.key {
// this Jitsi call is already managed by this class, no need to report the call again
MXLog.debug("[CallPresenter] processWidgetEvent: Jitsi call already managed with id: \(uuid.uuidString)")
guard JMCallKitProxy.isProviderConfigured() else {
// CallKit proxy is not configured, no benefit in parsing the event
MXLog.debug("[CallPresenter] processWidgetEvent: JMCallKitProxy not configured")
hangupUnhandledCallIfNeeded(widget)
return
}
if widget.isActive {
if let uuid = self.jitsiCalls.first(where: { $0.value.widgetId == widget.widgetId })?.key {
// this Jitsi call is already managed by this class, no need to report the call again
MXLog.debug("[CallPresenter] processWidgetEvent: Jitsi call already managed with id: \(uuid.uuidString)")
return
}
guard widget.type == kWidgetTypeJitsiV1 || widget.type == kWidgetTypeJitsiV2 else {
// not a Jitsi widget, ignore
MXLog.debug("[CallPresenter] processWidgetEvent: not a Jitsi widget")
@ -337,6 +338,7 @@ class CallPresenter: NSObject {
guard let uuid = self.jitsiCalls.first(where: { $0.value.widgetId == widget.widgetId })?.key else {
// this Jitsi call is not managed by this class
MXLog.debug("[CallPresenter] processWidgetEvent: not managed Jitsi call: \(widget.widgetId)")
hangupUnhandledCallIfNeeded(widget)
return
}
MXLog.debug("[CallPresenter] processWidgetEvent: ended call with id: \(uuid.uuidString)")
@ -722,6 +724,15 @@ class CallPresenter: NSObject {
uiOperationQueue.addOperation(operation)
}
/// Hangs up current Jitsi call, if it is inactive and associated with given widget.
/// Should be used for calls that are not handled through JMCallKitProxy,
/// as these should be removed regardless.
private func hangupUnhandledCallIfNeeded(_ widget: Widget) {
guard !widget.isActive, widget.widgetId == jitsiVC?.widget.widgetId else { return }
MXLog.debug("[CallPresenter] hangupUnhandledCallIfNeeded: ending call with Widget id: %@", widget.widgetId)
endActiveJitsiCall()
}
}
// MARK: - MXKCallViewControllerDelegate

View file

@ -108,10 +108,12 @@ NS_ASSUME_NONNULL_BEGIN
@param pushNotificationService PushNotificationService object.
@param roomId Room identifier to be navigated.
@param userId ID of sender of the notification.
*/
- (void)pushNotificationService:(PushNotificationService *)pushNotificationService
shouldNavigateToRoomWithId:(NSString *)roomId
threadId:(nullable NSString *)threadId;
threadId:(nullable NSString *)threadId
sender:(nullable NSString *)userId;
@end;

View file

@ -364,6 +364,7 @@ Matrix session observer used to detect new opened sessions.
NSString *actionIdentifier = [response actionIdentifier];
NSString *roomId = content.userInfo[@"room_id"];
NSString *threadId = content.userInfo[@"thread_id"];
NSString *userId = content.userInfo[@"user_id"];
if ([actionIdentifier isEqualToString:@"inline-reply"])
{
@ -403,7 +404,7 @@ Matrix session observer used to detect new opened sessions.
}
else if ([actionIdentifier isEqualToString:UNNotificationDefaultActionIdentifier])
{
[self notifyNavigateToRoomById:roomId threadId:threadId];
[self notifyNavigateToRoomById:roomId threadId:threadId sender:userId];
completionHandler();
}
else
@ -567,11 +568,11 @@ Matrix session observer used to detect new opened sessions.
#pragma mark - Delegate Notifiers
- (void)notifyNavigateToRoomById:(NSString *)roomId threadId:(NSString *)threadId
- (void)notifyNavigateToRoomById:(NSString *)roomId threadId:(NSString *)threadId sender:(NSString *)userId
{
if ([_delegate respondsToSelector:@selector(pushNotificationService:shouldNavigateToRoomWithId:threadId:)])
if ([_delegate respondsToSelector:@selector(pushNotificationService:shouldNavigateToRoomWithId:threadId:sender:)])
{
[_delegate pushNotificationService:self shouldNavigateToRoomWithId:roomId threadId:threadId];
[_delegate pushNotificationService:self shouldNavigateToRoomWithId:roomId threadId:threadId sender:userId];
}
}

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
import Combine
extension RiotSettings {
@available(iOS 13.0, *)
func publisher(for key: String) -> AnyPublisher<Notification, Never> {
return NotificationCenter.default.publisher(for: .userDefaultValueUpdated)
.filter({ $0.object as? String == key })
.eraseToAnyPublisher()
}
}

View file

@ -30,6 +30,7 @@ final class RiotSettings: NSObject {
static let pinRoomsWithMissedNotificationsOnHome = "pinRoomsWithMissedNotif"
static let pinRoomsWithUnreadMessagesOnHome = "pinRoomsWithUnread"
static let showAllRoomsInHomeSpace = "showAllRoomsInHomeSpace"
static let enableUISIAutoReporting = "enableUISIAutoReporting"
}
static let shared = RiotSettings()
@ -146,6 +147,10 @@ final class RiotSettings: NSObject {
@UserDefault(key: "enableThreads", defaultValue: false, storage: defaults)
var enableThreads
/// Indicates if auto reporting of decryption errors is enabled
@UserDefault(key: UserDefaultsKeys.enableUISIAutoReporting, defaultValue: BuildSettings.cryptoUISIAutoReportingEnabled, storage: defaults)
var enableUISIAutoReporting
// MARK: Calls
/// Indicate if `allowStunServerFallback` settings has been set once.

View file

@ -0,0 +1,236 @@
//
// 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 MatrixSDK
import Combine
struct UISIAutoReportData {
let eventId: String?
let roomId: String?
let senderKey: String?
let deviceId: String?
let userId: String?
let sessionId: String?
}
extension UISIAutoReportData: Codable {
enum CodingKeys: String, CodingKey {
case eventId = "event_id"
case roomId = "room_id"
case senderKey = "sender_key"
case deviceId = "device_id"
case userId = "user_id"
case sessionId = "session_id"
}
}
/// Listens for failed decryption events and silently sends reports RageShake server.
/// Also requests that message senders send a matching report to have both sides of the interaction.
@available(iOS 14.0, *)
@objcMembers class UISIAutoReporter: NSObject, UISIDetectorDelegate {
struct ReportInfo: Hashable {
let roomId: String
let sessionId: String
}
// MARK: - Properties
private static let autoRsRequest = "im.vector.auto_rs_request"
private static let reportSpacing = 60
private let bugReporter: MXBugReportRestClient
private let dispatchQueue = DispatchQueue(label: "io.element.UISIAutoReporter.queue")
// Simple in memory cache of already sent report
private var alreadyReportedUisi = Set<ReportInfo>()
private let e2eDetectedSubject = PassthroughSubject<UISIDetectedMessage, Never>()
private let matchingRSRequestSubject = PassthroughSubject<MXEvent, Never>()
private var cancellables = Set<AnyCancellable>()
private var sessions = [MXSession]()
private var enabled = false {
didSet {
guard oldValue != enabled else { return }
detector.enabled = enabled
}
}
// MARK: - Setup
override init() {
self.bugReporter = MXBugReportRestClient.vc_bugReportRestClient(appName: BuildSettings.bugReportUISIId)
super.init()
// Simple rate limiting, for any rage-shakes emitted we guarantee a spacing between requests.
e2eDetectedSubject
.bufferAndSpace(spacingDelay: Self.reportSpacing)
.sink { [weak self] in
guard let self = self else { return }
self.sendRageShake(source: $0)
}.store(in: &cancellables)
matchingRSRequestSubject
.bufferAndSpace(spacingDelay: Self.reportSpacing)
.sink { [weak self] in
guard let self = self else { return }
self.sendMatchingRageShake(source: $0)
}.store(in: &cancellables)
self.enabled = RiotSettings.shared.enableUISIAutoReporting
RiotSettings.shared.publisher(for: RiotSettings.UserDefaultsKeys.enableUISIAutoReporting)
.sink { [weak self] _ in
guard let self = self else { return }
self.enabled = RiotSettings.shared.enableUISIAutoReporting
}
.store(in: &cancellables)
}
private lazy var detector: UISIDetector = {
let detector = UISIDetector()
detector.delegate = self
return detector
}()
var reciprocateToDeviceEventType: String {
return Self.autoRsRequest
}
// MARK: - Public
func uisiDetected(source: UISIDetectedMessage) {
dispatchQueue.async {
let reportInfo = ReportInfo(roomId: source.roomId, sessionId: source.sessionId)
let alreadySent = self.alreadyReportedUisi.contains(reportInfo)
if !alreadySent {
self.alreadyReportedUisi.insert(reportInfo)
self.e2eDetectedSubject.send(source)
}
}
}
func add(_ session: MXSession) {
sessions.append(session)
detector.enabled = enabled
session.eventStreamService.add(eventStreamListener: detector)
}
func remove(_ session: MXSession) {
if let index = sessions.firstIndex(of: session) {
sessions.remove(at: index)
}
session.eventStreamService.remove(eventStreamListener: detector)
}
func uisiReciprocateRequest(source: MXEvent) {
guard source.type == Self.autoRsRequest else { return }
self.matchingRSRequestSubject.send(source)
}
// MARK: - Private
private func sendRageShake(source: UISIDetectedMessage) {
MXLog.debug("[UISIAutoReporter] sendRageShake")
guard let session = sessions.first else { return }
let uisiData = UISIAutoReportData(
eventId: source.eventId,
roomId: source.roomId,
senderKey: source.senderKey,
deviceId: source.senderDeviceId,
userId: source.senderUserId,
sessionId: source.sessionId
).jsonString ?? ""
self.bugReporter.vc_sendBugReport(
description: "Auto-reporting decryption error",
sendLogs: true,
sendCrashLog: true,
additionalLabels: [
"Z-UISI",
"ios",
"uisi-recipient"
],
customFields: ["auto_uisi": uisiData],
success: { reportUrl in
let contentMap = MXUsersDevicesMap<NSDictionary>()
let content = [
"event_id": source.eventId,
"room_id": source.roomId,
"session_id": source.sessionId,
"device_id": source.senderDeviceId,
"user_id": source.senderUserId,
"sender_key": source.senderKey,
"recipient_rageshake": reportUrl
]
contentMap.setObject(content as NSDictionary, forUser: source.senderUserId, andDevice: source.senderDeviceId)
session.matrixRestClient.sendDirectToDevice(
eventType: Self.autoRsRequest,
contentMap: contentMap,
txnId: nil
) { response in
if response.isFailure {
MXLog.warning("failed to send auto-uisi to device")
}
}
},
failure: { [weak self] error in
guard let self = self else { return }
self.dispatchQueue.async {
self.alreadyReportedUisi.remove(ReportInfo(roomId: source.roomId, sessionId: source.sessionId))
}
})
}
private func sendMatchingRageShake(source: MXEvent) {
MXLog.debug("[UISIAutoReporter] sendMatchingRageShake")
let eventId = source.content["event_id"] as? String
let roomId = source.content["room_id"] as? String
let sessionId = source.content["session_id"] as? String
let deviceId = source.content["device_id"] as? String
let userId = source.content["user_id"] as? String
let senderKey = source.content["sender_key"] as? String
let matchingIssue = source.content["recipient_rageshake"] as? String
var description = "Auto-reporting decryption error (sender)"
if let matchingIssue = matchingIssue {
description += "\nRecipient rageshake: \(matchingIssue)"
}
let uisiData = UISIAutoReportData(
eventId: eventId,
roomId: roomId,
senderKey: senderKey,
deviceId: deviceId,
userId: userId,
sessionId: sessionId
).jsonString ?? ""
self.bugReporter.vc_sendBugReport(
description: description,
sendLogs: true,
sendCrashLog: true,
additionalLabels: [
"Z-UISI",
"ios",
"uisi-sender"
],
customFields: [
"auto_uisi": uisiData,
"recipient_rageshake": matchingIssue ?? ""
]
)
}
}

View file

@ -0,0 +1,115 @@
//
// 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 MatrixSDK
import Foundation
protocol UISIDetectorDelegate: AnyObject {
var reciprocateToDeviceEventType: String { get }
func uisiDetected(source: UISIDetectedMessage)
func uisiReciprocateRequest(source: MXEvent)
}
struct UISIDetectedMessage {
let eventId: String
let roomId: String
let senderUserId: String
let senderDeviceId: String
let senderKey: String
let sessionId: String
static func fromEvent(event: MXEvent) -> UISIDetectedMessage {
return UISIDetectedMessage(
eventId: event.eventId ?? "",
roomId: event.roomId,
senderUserId: event.sender,
senderDeviceId: event.wireContent["device_id"] as? String ?? "",
senderKey: event.wireContent["sender_key"] as? String ?? "",
sessionId: event.wireContent["session_id"] as? String ?? ""
)
}
}
/// Detects decryption errors that occur and don't recover within a grace period.
/// see `UISIDetectorDelegate` for listening to detections.
class UISIDetector: MXLiveEventListener {
weak var delegate: UISIDetectorDelegate?
var enabled = false
var initialSyncCompleted = false
private var trackedUISIs = [String: DispatchSourceTimer]()
private let dispatchQueue = DispatchQueue(label: "io.element.UISIDetector.queue")
private static let gracePeriodSeconds = 30
// MARK: - Public
func onSessionStateChanged(state: MXSessionState) {
dispatchQueue.async {
self.initialSyncCompleted = state == .running
}
}
func onLiveEventDecryptionAttempted(event: MXEvent, result: MXEventDecryptionResult) {
guard enabled, let eventId = event.eventId, let roomId = event.roomId else { return }
dispatchQueue.async {
let trackedId = Self.trackedEventId(roomId: eventId, eventId: roomId)
if let timer = self.trackedUISIs[trackedId],
result.clearEvent != nil {
// successfully decrypted during grace period, cancel timer.
self.trackedUISIs[trackedId] = nil
timer.cancel()
return
}
guard self.initialSyncCompleted,
result.clearEvent == nil
else { return }
// track uisi and report it only if it is not decrypted before grade period ends
let timer = DispatchSource.makeTimerSource(queue: self.dispatchQueue)
timer.schedule(deadline: .now() + .seconds(Self.gracePeriodSeconds))
timer.setEventHandler { [weak self] in
guard let self = self else { return }
self.trackedUISIs[trackedId] = nil
MXLog.verbose("[UISIDetector] onLiveEventDecryptionAttempted: Timeout on \(eventId)")
self.triggerUISI(source: UISIDetectedMessage.fromEvent(event: event))
}
self.trackedUISIs[trackedId] = timer
timer.activate()
}
}
func onLiveToDeviceEvent(event: MXEvent) {
guard enabled, event.type == delegate?.reciprocateToDeviceEventType else { return }
delegate?.uisiReciprocateRequest(source: event)
}
// MARK: - Private
private func triggerUISI(source: UISIDetectedMessage) {
guard enabled else { return }
MXLog.info("[UISIDetector] triggerUISI: Unable To Decrypt \(source)")
self.delegate?.uisiDetected(source: source)
}
// MARK: - Static
private static func trackedEventId(roomId: String, eventId: String) -> String {
return "\(roomId)-\(eventId)"
}
}

View file

@ -42,7 +42,8 @@ typedef enum : NSUInteger
WidgetManagerErrorCodeNoIntegrationsServerConfigured,
WidgetManagerErrorCodeDisabledIntegrationsServer,
WidgetManagerErrorCodeFailedToConnectToIntegrationsServer,
WidgetManagerErrorCodeTermsNotSigned
WidgetManagerErrorCodeTermsNotSigned,
WidgetManagerErrorCodeUnavailableJitsiURL
}
WidgetManagerErrorCode;

View file

@ -278,6 +278,13 @@ NSString *const WidgetManagerErrorDomain = @"WidgetManagerErrorDomain";
NSString *widgetId = [NSString stringWithFormat:@"%@_%@_%@", kWidgetTypeJitsiV1, room.mxSession.myUser.userId, @((uint64_t)([[NSDate date] timeIntervalSince1970] * 1000))];
NSURL *preferredJitsiServerUrl = [room.mxSession vc_homeserverConfiguration].jitsi.serverURL;
if (!preferredJitsiServerUrl)
{
MXLogDebug(@"[WidgetManager] createJitsiWidgetInRoom: Error: No Jitsi server URL provided");
failure(self.errorForUnavailableJitsiURL);
return nil;
}
JitsiService *jitsiService = JitsiService.shared;
@ -807,4 +814,11 @@ NSString *const WidgetManagerErrorDomain = @"WidgetManagerErrorDomain";
userInfo:@{NSLocalizedDescriptionKey: [VectorL10n widgetIntegrationManagerDisabled]}];
}
- (NSError*)errorForUnavailableJitsiURL
{
return [NSError errorWithDomain:WidgetManagerErrorDomain
code:WidgetManagerErrorCodeUnavailableJitsiURL
userInfo:@{NSLocalizedDescriptionKey: VectorL10n.callJitsiUnableToStart}];
}
@end

View file

@ -22,14 +22,14 @@ final class HomeserverConfiguration: NSObject {
// Note: Use an object per configuration subject when there is multiple properties related
let jitsi: HomeserverJitsiConfiguration
let isE2EEByDefaultEnabled: Bool
let encryption: HomeserverEncryptionConfiguration
let tileServer: HomeserverTileServerConfiguration
init(jitsi: HomeserverJitsiConfiguration,
isE2EEByDefaultEnabled: Bool,
encryption: HomeserverEncryptionConfiguration,
tileServer: HomeserverTileServerConfiguration) {
self.jitsi = jitsi
self.isE2EEByDefaultEnabled = isE2EEByDefaultEnabled
self.encryption = encryption
self.tileServer = tileServer
}
}

View file

@ -28,10 +28,6 @@ final class HomeserverConfigurationBuilder: NSObject {
/// Create an `HomeserverConfiguration` from an HS Well-Known when possible otherwise it takes hardcoded values from BuildSettings by default.
func build(from wellKnown: MXWellKnown?) -> HomeserverConfiguration {
let isE2EEByDefaultEnabled: Bool
let jitsiPreferredDomain: String
var vectorWellKnownEncryptionConfiguration: VectorWellKnownEncryptionConfiguration?
var vectorWellKnownJitsiConfiguration: VectorWellKnownJitsiConfiguration?
@ -39,23 +35,34 @@ final class HomeserverConfigurationBuilder: NSObject {
vectorWellKnownEncryptionConfiguration = self.getEncryptionConfiguration(from: vectorWellKnown)
vectorWellKnownJitsiConfiguration = self.getJitsiConfiguration(from: vectorWellKnown)
}
// Encryption configuration
// Enable E2EE by default when there is no value
isE2EEByDefaultEnabled = vectorWellKnownEncryptionConfiguration?.isE2EEByDefaultEnabled ?? true
let isE2EEByDefaultEnabled = vectorWellKnownEncryptionConfiguration?.isE2EEByDefaultEnabled ?? true
// Disable mandatory secure backup when there is no value
let isSecureBackupRequired = vectorWellKnownEncryptionConfiguration?.isSecureBackupRequired ?? false
// Defaults to all secure backup methods available when there is no value
let secureBackupSetupMethods: [VectorWellKnownBackupSetupMethod]
if let backupSetupMethods = vectorWellKnownEncryptionConfiguration?.secureBackupSetupMethods {
secureBackupSetupMethods = backupSetupMethods.isEmpty ? VectorWellKnownBackupSetupMethod.allCases : backupSetupMethods
} else {
secureBackupSetupMethods = VectorWellKnownBackupSetupMethod.allCases
}
let encryptionConfiguration = HomeserverEncryptionConfiguration(isE2EEByDefaultEnabled: isE2EEByDefaultEnabled,
isSecureBackupRequired: isSecureBackupRequired,
secureBackupSetupMethods: secureBackupSetupMethods)
// Jitsi configuration
let jitsiServerURL: URL
let hardcodedJitsiServerURL: URL = BuildSettings.jitsiServerUrl
let jitsiPreferredDomain: String?
let jitsiServerURL: URL?
let hardcodedJitsiServerURL: URL? = BuildSettings.jitsiServerUrl
if let preferredDomain = vectorWellKnownJitsiConfiguration?.preferredDomain {
jitsiPreferredDomain = preferredDomain
jitsiServerURL = self.jitsiServerURL(from: preferredDomain) ?? hardcodedJitsiServerURL
} else {
guard let hardcodedJitsiDomain = hardcodedJitsiServerURL.host else {
fatalError("[HomeserverConfigurationBuilder] Fail to get Jitsi domain from hardcoded Jitsi URL")
}
jitsiPreferredDomain = hardcodedJitsiDomain
jitsiPreferredDomain = hardcodedJitsiServerURL?.host
jitsiServerURL = hardcodedJitsiServerURL
}
@ -77,7 +84,7 @@ final class HomeserverConfigurationBuilder: NSObject {
serverURL: jitsiServerURL)
return HomeserverConfiguration(jitsi: jitsiConfiguration,
isE2EEByDefaultEnabled: isE2EEByDefaultEnabled,
encryption: encryptionConfiguration,
tileServer: tileServerConfiguration)
}

View file

@ -0,0 +1,35 @@
//
// Copyright 2022 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
/// `HomeserverEncryptionConfiguration` gives encryption configuration used by homeserver
@objcMembers
final class HomeserverEncryptionConfiguration: NSObject {
let isE2EEByDefaultEnabled: Bool
let isSecureBackupRequired: Bool
let secureBackupSetupMethods: [VectorWellKnownBackupSetupMethod]
init(isE2EEByDefaultEnabled: Bool,
isSecureBackupRequired: Bool,
secureBackupSetupMethods: [VectorWellKnownBackupSetupMethod]) {
self.isE2EEByDefaultEnabled = isE2EEByDefaultEnabled
self.isSecureBackupRequired = isSecureBackupRequired
self.secureBackupSetupMethods = secureBackupSetupMethods
super.init()
}
}

View file

@ -19,10 +19,10 @@ import Foundation
/// `HomeserverJitsiConfiguration` gives Jitsi widget configuration used by homeserver
@objcMembers
final class HomeserverJitsiConfiguration: NSObject {
let serverDomain: String
let serverURL: URL
let serverDomain: String?
let serverURL: URL?
init(serverDomain: String, serverURL: URL) {
init(serverDomain: String?, serverURL: URL?) {
self.serverDomain = serverDomain
self.serverURL = serverURL

View file

@ -41,14 +41,29 @@ extension VectorWellKnown: Decodable {
}
// MARK: - Encryption
struct VectorWellKnownEncryptionConfiguration: Decodable {
struct VectorWellKnownEncryptionConfiguration {
/// Indicate if E2EE is enabled by default
let isE2EEByDefaultEnabled: Bool?
/// Check if secure backup (SSSS) is mandatory.
let isSecureBackupRequired: Bool?
/// Methods to use to setup secure backup (SSSS).
let secureBackupSetupMethods: [VectorWellKnownBackupSetupMethod]?
}
extension VectorWellKnownEncryptionConfiguration: Decodable {
/// JSON keys associated to `VectorWellKnownEncryptionConfiguration`
enum CodingKeys: String, CodingKey {
case isE2EEByDefaultEnabled = "default"
case isSecureBackupRequired = "secure_backup_required"
case secureBackupSetupMethods = "secure_backup_setup_methods"
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
isE2EEByDefaultEnabled = try? container.decode(Bool.self, forKey: .isE2EEByDefaultEnabled)
isSecureBackupRequired = try? container.decode(Bool.self, forKey: .isSecureBackupRequired)
let secureBackupSetupMethodsKeys = try? container.decode([String].self, forKey: .secureBackupSetupMethods)
secureBackupSetupMethods = secureBackupSetupMethodsKeys?.compactMap { VectorWellKnownBackupSetupMethod(key: $0) }
}
}

View file

@ -0,0 +1,39 @@
//
// Copyright 2022 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
/// Methods to use to setup secure backup (SSSS).
@objc enum VectorWellKnownBackupSetupMethod: Int, CaseIterable {
case passphrase = 0
case key
private enum Constants {
static let setupMethodPassphrase: String = "passphrase"
static let setupMethodKey: String = "key"
}
init?(key: String) {
switch key {
case Constants.setupMethodPassphrase:
self = .passphrase
case Constants.setupMethodKey:
self = .key
default:
return nil
}
}
}

View file

@ -121,8 +121,13 @@ import AnalyticsEvents
MXLog.debug("[Analytics] Started.")
// Catch and log crashes
MXLogger.logCrashes(true)
if Bundle.main.isShareExtension {
// Don't log crashes in the share extension
} else {
// Catch and log crashes
MXLogger.logCrashes(true)
}
MXLogger.setBuildVersion(AppInfo.current.buildInfo.readableBuildVersion)
}

View file

@ -251,7 +251,8 @@ extension AppCoordinator: LegacyAppDelegateDelegate {
func legacyAppDelegate(_ legacyAppDelegate: LegacyAppDelegate!, didAddMatrixSession session: MXSession!) {
}
func legacyAppDelegate(_ legacyAppDelegate: LegacyAppDelegate!, didRemoveMatrixSession session: MXSession!) {
func legacyAppDelegate(_ legacyAppDelegate: LegacyAppDelegate!, didRemoveMatrixSession session: MXSession?) {
guard let session = session else { return }
// Handle user session removal on clear cache. On clear cache the account has his session closed but the account is not removed.
self.userSessionsService.removeUserSession(relatedToMatrixSession: session)
}

View file

@ -227,14 +227,6 @@ UINavigationControllerDelegate
// Reopen an existing direct room with this userId or creates a new one (if it doesn't exist)
- (void)startDirectChatWithUserId:(NSString*)userId completion:(void (^)(void))completion;
/**
Process the fragment part of a vector.im link.
@param fragment the fragment part of the universal link.
@return YES in case of processing success.
*/
- (BOOL)handleUniversalLinkFragment:(NSString*)fragment;
/**
Process the fragment part of a vector.im link.

View file

@ -85,7 +85,7 @@ NSString *const AppDelegateDidValidateEmailNotificationClientSecretKey = @"AppDe
NSString *const AppDelegateUniversalLinkDidChangeNotification = @"AppDelegateUniversalLinkDidChangeNotification";
@interface LegacyAppDelegate () <GDPRConsentViewControllerDelegate, KeyVerificationCoordinatorBridgePresenterDelegate, PushNotificationServiceDelegate, SetPinCoordinatorBridgePresenterDelegate, CallPresenterDelegate, SpaceDetailPresenterDelegate>
@interface LegacyAppDelegate () <GDPRConsentViewControllerDelegate, KeyVerificationCoordinatorBridgePresenterDelegate, PushNotificationServiceDelegate, SetPinCoordinatorBridgePresenterDelegate, CallPresenterDelegate, SpaceDetailPresenterDelegate, SecureBackupSetupCoordinatorBridgePresenterDelegate>
{
/**
Reachability observer
@ -129,6 +129,11 @@ NSString *const AppDelegateUniversalLinkDidChangeNotification = @"AppDelegateUni
*/
KeyVerificationCoordinatorBridgePresenter *keyVerificationCoordinatorBridgePresenter;
/**
Currently displayed secure backup setup
*/
SecureBackupSetupCoordinatorBridgePresenter *secureBackupSetupCoordinatorBridgePresenter;
/**
Account picker used in case of multiple account.
*/
@ -220,6 +225,7 @@ NSString *const AppDelegateUniversalLinkDidChangeNotification = @"AppDelegateUni
@property (nonatomic, strong) PushNotificationStore *pushNotificationStore;
@property (nonatomic, strong) LocalAuthenticationService *localAuthenticationService;
@property (nonatomic, strong, readwrite) CallPresenter *callPresenter;
@property (nonatomic, strong, readwrite) id uisiAutoReporter;
@property (nonatomic, strong) MajorUpdateManager *majorUpdateManager;
@ -388,6 +394,11 @@ NSString *const AppDelegateUniversalLinkDidChangeNotification = @"AppDelegateUni
[NSBundle mxk_setLanguage:language];
[NSBundle mxk_setFallbackLanguage:@"en"];
if (BuildSettings.disableRightToLeftLayout)
{
[[UIView appearance] setSemanticContentAttribute:UISemanticContentAttributeForceLeftToRight];
}
// Set app info now as Mac (Designed for iPad) accesses it before didFinishLaunching is called
self.appInfo = AppInfo.current;
@ -466,14 +477,23 @@ NSString *const AppDelegateUniversalLinkDidChangeNotification = @"AppDelegateUni
self.spaceFeatureUnavailablePresenter = [SpaceFeatureUnavailablePresenter new];
if (@available(iOS 14.0, *)) {
self.uisiAutoReporter = [[UISIAutoReporter alloc] init];
}
// Add matrix observers, and initialize matrix sessions if the app is not launched in background.
[self initMatrixSessions];
#ifdef CALL_STACK_JINGLE
// Setup Jitsi
[JitsiService.shared configureDefaultConferenceOptionsWith:BuildSettings.jitsiServerUrl];
NSURL *jitsiServerUrl = BuildSettings.jitsiServerUrl;
if (jitsiServerUrl)
{
[JitsiService.shared configureDefaultConferenceOptionsWith:jitsiServerUrl];
[JitsiService.shared application:application didFinishLaunchingWithOptions:launchOptions];
}
[JitsiService.shared application:application didFinishLaunchingWithOptions:launchOptions];
#endif
self.majorUpdateManager = [MajorUpdateManager new];
@ -927,6 +947,7 @@ NSString *const AppDelegateUniversalLinkDidChangeNotification = @"AppDelegateUni
NSString *title = [error.userInfo valueForKey:NSLocalizedFailureReasonErrorKey];
NSString *msg = [error.userInfo valueForKey:NSLocalizedDescriptionKey];
NSString *localizedDescription = error.localizedDescription;
if (!title)
{
if (msg)
@ -934,6 +955,10 @@ NSString *const AppDelegateUniversalLinkDidChangeNotification = @"AppDelegateUni
title = msg;
msg = nil;
}
else if (localizedDescription.length > 0)
{
title = localizedDescription;
}
else
{
title = [VectorL10n error];
@ -1103,6 +1128,7 @@ NSString *const AppDelegateUniversalLinkDidChangeNotification = @"AppDelegateUni
- (void)pushNotificationService:(PushNotificationService *)pushNotificationService
shouldNavigateToRoomWithId:(NSString *)roomId
threadId:(NSString *)threadId
sender:(NSString *)userId
{
if (roomId)
{
@ -1118,7 +1144,7 @@ NSString *const AppDelegateUniversalLinkDidChangeNotification = @"AppDelegateUni
}
_lastNavigatedRoomIdFromPush = roomId;
[self navigateToRoomById:roomId threadId:threadId];
[self navigateToRoomById:roomId threadId:threadId sender:userId];
}
#pragma mark - Badge Count
@ -1258,15 +1284,14 @@ NSString *const AppDelegateUniversalLinkDidChangeNotification = @"AppDelegateUni
return [self handleUniversalLinkFragment:webURL.fragment fromURL:webURL];
}
- (BOOL)handleUniversalLinkFragment:(NSString*)fragment
{
return [self handleUniversalLinkFragment:fragment fromURL:nil];
}
- (BOOL)handleUniversalLinkFragment:(NSString*)fragment fromURL:(NSURL*)universalLinkURL
{
if (!fragment || !universalLinkURL)
{
MXLogDebug(@"[AppDelegate] Cannot handle universal link with missing data: %@ %@", fragment, universalLinkURL);
return NO;
}
ScreenPresentationParameters *presentationParameters = [[ScreenPresentationParameters alloc] initWithRestoreInitialDisplay:YES stackAboveVisibleViews:NO];
UniversalLinkParameters *parameters = [[UniversalLinkParameters alloc] initWithFragment:fragment universalLinkURL:universalLinkURL presentationParameters:presentationParameters];
@ -1467,7 +1492,7 @@ NSString *const AppDelegateUniversalLinkDidChangeNotification = @"AppDelegateUni
// Ask the HS to resolve the room alias into a room id and then retry
self->universalLinkFragmentPending = fragment;
MXKAccount* account = accountManager.activeAccounts.firstObject;
[account.mxSession.matrixRestClient roomIDForRoomAlias:roomIdOrAlias success:^(NSString *roomId) {
[account.mxSession.matrixRestClient resolveRoomAlias:roomIdOrAlias success:^(MXRoomAliasResolution *resolution) {
// Note: the activity indicator will not disappear if the session is not ready
[homeViewController stopActivityIndicator];
@ -1475,34 +1500,20 @@ NSString *const AppDelegateUniversalLinkDidChangeNotification = @"AppDelegateUni
// Check that 'fragment' has not been cancelled
if ([self->universalLinkFragmentPending isEqualToString:fragment])
{
// Retry opening the link but with the returned room id
NSString *newUniversalLinkFragment =
[fragment stringByReplacingOccurrencesOfString:[MXTools encodeURIComponent:roomIdOrAlias]
withString:[MXTools encodeURIComponent:roomId]
];
// The previous operation can fail because of percent encoding
// TBH we are not clean on data inputs. For the moment, just give another try with no encoding
// TODO: Have a dedicated module and tests to handle universal links (matrix.to, email link, etc)
if ([newUniversalLinkFragment isEqualToString:fragment])
NSString *newFragment = resolution.deeplinkFragment;
if (newFragment && ![newFragment isEqualToString:fragment])
{
newUniversalLinkFragment =
[fragment stringByReplacingOccurrencesOfString:roomIdOrAlias
withString:[MXTools encodeURIComponent:roomId]];
}
if (![newUniversalLinkFragment isEqualToString:fragment])
{
self->universalLinkFragmentPendingRoomAlias = @{roomId: roomIdOrAlias};
UniversalLinkParameters *newParameters = [[UniversalLinkParameters alloc] initWithFragment:newUniversalLinkFragment universalLinkURL:universalLinkURL presentationParameters:presentationParameters];
self->universalLinkFragmentPendingRoomAlias = @{resolution.roomId: roomIdOrAlias};
UniversalLinkParameters *newParameters = [[UniversalLinkParameters alloc] initWithFragment:newFragment
universalLinkURL:universalLinkURL
presentationParameters:presentationParameters];
[self handleUniversalLinkWithParameters:newParameters];
}
else
{
// Do not continue. Else we will loop forever
MXLogDebug(@"[AppDelegate] Universal link: Error: Cannot resolve alias in %@ to the room id %@", fragment, roomId);
MXLogDebug(@"[AppDelegate] Universal link: Error: Cannot resolve alias in %@ to the room id %@", fragment, resolution.roomId);
}
}
@ -2048,7 +2059,11 @@ NSString *const AppDelegateUniversalLinkDidChangeNotification = @"AppDelegateUni
[[NSNotificationCenter defaultCenter] addObserverForName:kMXKAccountManagerDidSoftlogoutAccountNotification object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *notif) {
MXKAccount *account = notif.object;
[self removeMatrixSession:account.mxSession];
if (account.mxSession)
{
[self removeMatrixSession:account.mxSession];
}
// Return to authentication screen
[self.masterTabBarController showSoftLogoutOnboardingFlowWithCredentials:account.mxCredentials];
@ -2139,6 +2154,16 @@ NSString *const AppDelegateUniversalLinkDidChangeNotification = @"AppDelegateUni
// register the session to the call service
[_callPresenter addMatrixSession:mxSession];
// register the session to the uisi auto-reporter
if (_uisiAutoReporter != nil)
{
if (@available(iOS 14.0, *))
{
UISIAutoReporter* uisiAutoReporter = (UISIAutoReporter*)_uisiAutoReporter;
[uisiAutoReporter add:mxSession];
}
}
[mxSessionArray addObject:mxSession];
// Do the one time check on device id
@ -2154,6 +2179,16 @@ NSString *const AppDelegateUniversalLinkDidChangeNotification = @"AppDelegateUni
// remove session from the call service
[_callPresenter removeMatrixSession:mxSession];
// register the session to the uisi auto-reporter
if (_uisiAutoReporter != nil)
{
if (@available(iOS 14.0, *))
{
UISIAutoReporter* uisiAutoReporter = (UISIAutoReporter*)_uisiAutoReporter;
[uisiAutoReporter remove:mxSession];
}
}
// Update the widgets manager
[[WidgetManager sharedManager] removeMatrixSession:mxSession];
@ -2404,6 +2439,7 @@ NSString *const AppDelegateUniversalLinkDidChangeNotification = @"AppDelegateUni
isLaunching = YES;
break;
case MXSessionStateStoreDataReady:
case MXSessionStateProcessingBackgroundSyncCache:
case MXSessionStateSyncInProgress:
// Stay in launching during the first server sync if the store is empty.
isLaunching = (mainSession.rooms.count == 0 && launchAnimationContainerView);
@ -2443,6 +2479,15 @@ NSString *const AppDelegateUniversalLinkDidChangeNotification = @"AppDelegateUni
// wait for another session state change to check room list data is ready
return;
}
if (mainSession.vc_homeserverConfiguration.encryption.isSecureBackupRequired
&& mainSession.vc_canSetupSecureBackup)
{
// This only happens at the first login
// Or when migrating an existing user
MXLogDebug(@"[AppDelegate] handleAppState: Force SSSS setup");
[self presentSecureBackupSetupForSession:mainSession];
}
void (^finishAppLaunch)(void) = ^{
[self hideLaunchAnimation];
@ -2893,7 +2938,7 @@ NSString *const AppDelegateUniversalLinkDidChangeNotification = @"AppDelegateUni
#pragma mark - Matrix Rooms handling
- (void)navigateToRoomById:(NSString *)roomId threadId:(NSString *)threadId
- (void)navigateToRoomById:(NSString *)roomId threadId:(NSString *)threadId sender:(NSString *)userId
{
if (roomId.length)
{
@ -2930,7 +2975,8 @@ NSString *const AppDelegateUniversalLinkDidChangeNotification = @"AppDelegateUni
[self showRoom:roomId
threadId:threadId
andEventId:nil
withMatrixSession:dedicatedAccount.mxSession];
withMatrixSession:dedicatedAccount.mxSession
sender:userId];
}
else
{
@ -2948,7 +2994,6 @@ NSString *const AppDelegateUniversalLinkDidChangeNotification = @"AppDelegateUni
{
NSString *roomId = parameters.roomId;
MXSession *mxSession = parameters.mxSession;
BOOL restoreInitialDisplay = parameters.presentationParameters.restoreInitialDisplay;
if (roomId && mxSession)
{
@ -2959,21 +3004,38 @@ NSString *const AppDelegateUniversalLinkDidChangeNotification = @"AppDelegateUni
[Analytics.shared trackViewRoom:room];
}
// Indicates that spaces are not supported
if (room.summary.roomType == MXRoomTypeSpace)
if (!room)
{
[self.spaceFeatureUnavailablePresenter presentUnavailableFeatureFrom:self.presentedViewController animated:YES];
if (completion)
{
completion();
}
MXWeakify(self);
[mxSession.matrixRestClient roomSummaryWith:roomId via:@[] success:^(MXPublicRoom *room) {
MXStrongifyAndReturnIfNil(self);
if ([room.roomTypeString isEqualToString:MXRoomTypeStringSpace])
{
SpacePreviewNavigationParameters *spacePreviewNavigationParameters = [[SpacePreviewNavigationParameters alloc] initWithPublicRoom:room mxSession:mxSession senderId:parameters.senderId presentationParameters:parameters.presentationParameters];
[self showSpacePreviewWithParameters:spacePreviewNavigationParameters];
}
else
{
[self finaliseShowRoomWithParameters:parameters completion:completion];
}
} failure:^(NSError *error) {
MXStrongifyAndReturnIfNil(self);
[self finaliseShowRoomWithParameters:parameters completion:completion];
}];
return;
}
}
[self finaliseShowRoomWithParameters:parameters completion:completion];
}
- (void)finaliseShowRoomWithParameters:(RoomNavigationParameters*)parameters completion:(void (^)(void))completion
{
NSString *roomId = parameters.roomId;
BOOL restoreInitialDisplay = parameters.presentationParameters.restoreInitialDisplay;
void (^selectRoom)(void) = ^() {
// Select room to display its details (dispatch this action in order to let TabBarController end its refresh)
@ -3002,10 +3064,10 @@ NSString *const AppDelegateUniversalLinkDidChangeNotification = @"AppDelegateUni
- (void)showRoom:(NSString*)roomId andEventId:(NSString*)eventId withMatrixSession:(MXSession*)mxSession
{
[self showRoom:roomId threadId:nil andEventId:eventId withMatrixSession:mxSession];
[self showRoom:roomId threadId:nil andEventId:eventId withMatrixSession:mxSession sender:nil];
}
- (void)showRoom:(NSString*)roomId threadId:(NSString*)threadId andEventId:(NSString*)eventId withMatrixSession:(MXSession*)mxSession
- (void)showRoom:(NSString*)roomId threadId:(NSString*)threadId andEventId:(NSString*)eventId withMatrixSession:(MXSession*)mxSession sender:(NSString*)userId
{
// Ask to restore initial display
ScreenPresentationParameters *presentationParameters = [[ScreenPresentationParameters alloc] initWithRestoreInitialDisplay:YES];
@ -3019,6 +3081,7 @@ NSString *const AppDelegateUniversalLinkDidChangeNotification = @"AppDelegateUni
RoomNavigationParameters *parameters = [[RoomNavigationParameters alloc] initWithRoomId:roomId
eventId:eventId
mxSession:mxSession
senderId:userId
threadParameters:threadParameters
presentationParameters:presentationParameters];
@ -3078,6 +3141,7 @@ NSString *const AppDelegateUniversalLinkDidChangeNotification = @"AppDelegateUni
void(^showSpace)(void) = ^{
[self.spaceDetailPresenter presentForSpaceWithPublicRoom:parameters.publicRoom
senderId:parameters.senderId
from:presentingViewController
sourceView:sourceView
session:parameters.mxSession
@ -4312,6 +4376,7 @@ NSString *const AppDelegateUniversalLinkDidChangeNotification = @"AppDelegateUni
if (!keyVerificationCoordinatorBridgePresenter.isPresenting)
{
keyVerificationCoordinatorBridgePresenter = [[KeyVerificationCoordinatorBridgePresenter alloc] initWithSession:mxSession];
keyVerificationCoordinatorBridgePresenter.cancellable = !mxSession.vc_homeserverConfiguration.encryption.isSecureBackupRequired;
keyVerificationCoordinatorBridgePresenter.delegate = self;
[keyVerificationCoordinatorBridgePresenter presentCompleteSecurityFrom:self.presentedViewController isNewSignIn:NO animated:YES];
@ -4692,4 +4757,37 @@ NSString *const AppDelegateUniversalLinkDidChangeNotification = @"AppDelegateUni
[self openSpaceWithId:spaceId];
}
#pragma mark - Mandatory SSSS setup
- (void)presentSecureBackupSetupForSession:(MXSession*)mxSession
{
MXLogDebug(@"[AppDelegate][Mandatory SSSS] presentSecureBackupSetupForSession");
if (!secureBackupSetupCoordinatorBridgePresenter.isPresenting)
{
secureBackupSetupCoordinatorBridgePresenter = [[SecureBackupSetupCoordinatorBridgePresenter alloc] initWithSession:mxSession allowOverwrite:false];
secureBackupSetupCoordinatorBridgePresenter.delegate = self;
[secureBackupSetupCoordinatorBridgePresenter presentFrom:self.masterTabBarController animated:NO cancellable:NO];
}
else
{
MXLogDebug(@"[AppDelegate][Mandatory SSSS] presentSecureBackupSetupForSession: Controller already presented")
}
}
#pragma mark - SecureBackupSetupCoordinatorBridgePresenterDelegate
- (void)secureBackupSetupCoordinatorBridgePresenterDelegateDidComplete:(SecureBackupSetupCoordinatorBridgePresenter *)coordinatorBridgePresenter
{
[secureBackupSetupCoordinatorBridgePresenter dismissWithAnimated:YES completion:nil];
secureBackupSetupCoordinatorBridgePresenter = nil;
}
- (void)secureBackupSetupCoordinatorBridgePresenterDelegateDidCancel:(SecureBackupSetupCoordinatorBridgePresenter *)coordinatorBridgePresenter
{
[secureBackupSetupCoordinatorBridgePresenter dismissWithAnimated:YES completion:nil];
secureBackupSetupCoordinatorBridgePresenter = nil;
}
@end

View file

@ -58,6 +58,9 @@ class RoomNavigationParameters: NSObject {
/// If `true`, the room settings screen will be initially displayed. Default `false`
let showSettingsInitially: Bool
/// ID of the sender of the notification. Default `nil`
let senderId: String?
// MARK: - Setup
init(roomId: String,
@ -71,6 +74,24 @@ class RoomNavigationParameters: NSObject {
self.threadParameters = threadParameters
self.presentationParameters = presentationParameters
self.showSettingsInitially = false
self.senderId = nil
super.init()
}
init(roomId: String,
eventId: String?,
mxSession: MXSession,
senderId: String?,
threadParameters: ThreadParameters?,
presentationParameters: ScreenPresentationParameters) {
self.roomId = roomId
self.eventId = eventId
self.mxSession = mxSession
self.threadParameters = threadParameters
self.presentationParameters = presentationParameters
self.showSettingsInitially = false
self.senderId = senderId
super.init()
}
@ -86,7 +107,8 @@ class RoomNavigationParameters: NSObject {
self.presentationParameters = presentationParameters
self.showSettingsInitially = showSettingsInitially
self.threadParameters = nil
self.senderId = nil
super.init()
}
}

View file

@ -25,12 +25,28 @@ class SpacePreviewNavigationParameters: SpaceNavigationParameters {
/// The data for the room preview
let publicRoom: MXPublicRoom
/// The ID of the sender of the invite
let senderId: String?
// MARK: - Setup
init(publicRoom: MXPublicRoom,
mxSession: MXSession,
presentationParameters: ScreenPresentationParameters) {
self.publicRoom = publicRoom
self.senderId = nil
super.init(roomId: publicRoom.roomId,
mxSession: mxSession,
presentationParameters: presentationParameters)
}
init(publicRoom: MXPublicRoom,
mxSession: MXSession,
senderId: String?,
presentationParameters: ScreenPresentationParameters) {
self.publicRoom = publicRoom
self.senderId = senderId
super.init(roomId: publicRoom.roomId,
mxSession: mxSession,

View file

@ -127,7 +127,8 @@ final class AuthenticationCoordinator: NSObject, AuthenticationCoordinatorProtoc
}
let isNewSignIn = true
let keyVerificationCoordinator = KeyVerificationCoordinator(session: session, flow: .completeSecurity(isNewSignIn))
let cancellable = !session.vc_homeserverConfiguration().encryption.isSecureBackupRequired
let keyVerificationCoordinator = KeyVerificationCoordinator(session: session, flow: .completeSecurity(isNewSignIn), cancellable: cancellable)
keyVerificationCoordinator.delegate = self
let presentable = keyVerificationCoordinator.toPresentable()
@ -176,7 +177,7 @@ final class AuthenticationCoordinator: NSObject, AuthenticationCoordinatorProtoc
// TODO: This is still not sure we want to disable the automatic cross-signing bootstrap
// if the admin disabled e2e by default.
// Do like riot-web for the moment
if session.vc_homeserverConfiguration().isE2EEByDefaultEnabled {
if session.vc_homeserverConfiguration().encryption.isE2EEByDefaultEnabled {
// Bootstrap cross-signing on user's account
// We do it for both registration and new login as long as cross-signing does not exist yet
if let password = self.password, !password.isEmpty {

View file

@ -31,8 +31,6 @@
@property (weak, nonatomic) IBOutlet UIButton *skipButton;
@property (weak, nonatomic) IBOutlet UIButton *forgotPasswordButton;
@property (weak, nonatomic) IBOutlet NSLayoutConstraint *submitButtonMinLeadingConstraint;
@property (weak, nonatomic) IBOutlet UIView *serverOptionsContainer;
@property (weak, nonatomic) IBOutlet UIButton *customServersTickButton;
@property (weak, nonatomic) IBOutlet UIView *customServersContainer;

View file

@ -1058,17 +1058,6 @@ static const CGFloat kAuthInputContainerViewMinHeightConstraintConstant = 150.0;
}
self.forgotPasswordButton.hidden = !showForgotPasswordButton;
// Adjust minimum leading constraint of the submit button
if (self.forgotPasswordButton.isHidden)
{
self.submitButtonMinLeadingConstraint.constant = 19;
}
else
{
CGRect frame = self.forgotPasswordButton.frame;
self.submitButtonMinLeadingConstraint.constant = frame.origin.x + frame.size.width + 10;
}
}
- (void)afterSetPinFlowCompletedWithCredentials:(MXCredentials*)credentials

View file

@ -1,9 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="18122" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="20037" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
<device id="retina5_9" orientation="portrait" appearance="light"/>
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="18093"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="20020"/>
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
<capability name="System colors in document resources" minToolsVersion="11.0"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
@ -44,7 +44,6 @@
<outlet property="softLogoutClearDataContainer" destination="vX2-5Y-rQc" id="mgK-41-cnX"/>
<outlet property="softLogoutClearDataLabel" destination="QYL-Lo-tmH" id="ks9-5X-xfs"/>
<outlet property="submitButton" destination="k3J-Eg-itz" id="fiZ-wK-6YM"/>
<outlet property="submitButtonMinLeadingConstraint" destination="bEB-EO-b14" id="Iz5-ks-nSX"/>
<outlet property="view" destination="5rn-KE-plm" id="bFJ-yJ-vc0"/>
<outlet property="welcomeImageView" destination="d8r-TX-pwX" id="vzD-zK-EeC"/>
</connections>
@ -57,7 +56,7 @@
<scrollView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" keyboardDismissMode="interactive" translatesAutoresizingMaskIntoConstraints="NO" id="OHV-KQ-Ww0">
<rect key="frame" x="0.0" y="44" width="375" height="768"/>
<subviews>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="rhx-dD-4EJ" userLabel="Content View">
<view contentMode="scaleToFill" layoutMarginsFollowReadableWidth="YES" translatesAutoresizingMaskIntoConstraints="NO" id="rhx-dD-4EJ" userLabel="Content View">
<rect key="frame" x="0.0" y="0.0" width="375" height="485"/>
<subviews>
<imageView userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="horizontal_logo" translatesAutoresizingMaskIntoConstraints="NO" id="d8r-TX-pwX" userLabel="Welcome Image View">
@ -91,7 +90,7 @@
</constraints>
</view>
<label hidden="YES" opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Currently we do not support authentication flows defined by this homeserver" textAlignment="center" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" preferredMaxLayoutWidth="0.0" translatesAutoresizingMaskIntoConstraints="NO" id="54b-4O-ip9" userLabel="noFlowLabel">
<rect key="frame" x="28" y="8" width="319.33333333333331" height="33.666666666666664"/>
<rect key="frame" x="28" y="8" width="319" height="33.666666666666664"/>
<fontDescription key="fontDescription" type="system" pointSize="14"/>
<color key="textColor" red="1" green="0.0" blue="0.0" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<nil key="highlightedColor"/>
@ -129,63 +128,73 @@
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="Gg0-TE-OGb">
<rect key="frame" x="0.0" y="360" width="375" height="125"/>
<subviews>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" hasAttributedTitle="YES" translatesAutoresizingMaskIntoConstraints="NO" id="AJ2-lJ-NUq">
<rect key="frame" x="19" y="33" width="122" height="30"/>
<accessibility key="accessibilityConfiguration" identifier="AuthenticationVCForgotPasswordButton"/>
<constraints>
<constraint firstAttribute="height" constant="30" id="1mr-dZ-KtP"/>
</constraints>
<state key="normal">
<attributedString key="attributedTitle">
<fragment content="Forgot password?">
<attributes>
<color key="NSColor" white="0.66666666666666663" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<font key="NSFont" size="15" name="HelveticaNeue"/>
</attributes>
</fragment>
</attributedString>
</state>
<connections>
<action selector="onButtonPressed:" destination="-1" eventType="touchUpInside" id="UVJ-Re-xe2"/>
</connections>
</button>
<button hidden="YES" opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="wEJ-AF-rdH">
<rect key="frame" x="11" y="33" width="106" height="30"/>
<color key="backgroundColor" red="0.028153735480000001" green="0.82494870580000002" blue="0.051896891280000003" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<accessibility key="accessibilityConfiguration" identifier="AuthenticationVCSkipButton"/>
<constraints>
<constraint firstAttribute="height" constant="30" id="HIX-iq-vTC"/>
</constraints>
<fontDescription key="fontDescription" type="system" weight="semibold" pointSize="16"/>
<inset key="contentEdgeInsets" minX="30" minY="0.0" maxX="30" maxY="0.0"/>
<state key="normal" title="Skip">
<color key="titleColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<color key="titleShadowColor" red="0.5" green="0.5" blue="0.5" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</state>
<connections>
<action selector="onButtonPressed:" destination="-1" eventType="touchUpInside" id="iEr-Vf-f6P"/>
</connections>
</button>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="k3J-Eg-itz" userLabel="SubmitBtn">
<rect key="frame" x="258" y="33" width="106" height="30"/>
<color key="backgroundColor" red="0.028153735480000001" green="0.82494870580000002" blue="0.051896891280000003" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<accessibility key="accessibilityConfiguration" identifier="AuthenticationVCLoginButton"/>
<constraints>
<constraint firstAttribute="width" relation="greaterThanOrEqual" constant="100" id="3ST-q4-gu8"/>
<constraint firstAttribute="height" constant="30" id="rR8-KH-2z5"/>
</constraints>
<fontDescription key="fontDescription" type="system" weight="semibold" pointSize="16"/>
<inset key="contentEdgeInsets" minX="30" minY="0.0" maxX="30" maxY="0.0"/>
<state key="normal" title="Log In">
<color key="titleColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<color key="titleShadowColor" red="0.5" green="0.5" blue="0.5" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</state>
<connections>
<action selector="onButtonPressed:" destination="-1" eventType="touchUpInside" id="Ocd-Ag-6hf"/>
</connections>
</button>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="FIn-2w-e6H">
<rect key="frame" x="0.0" y="68" width="375" height="178"/>
<stackView opaque="NO" contentMode="scaleToFill" axis="vertical" alignment="top" spacing="5" translatesAutoresizingMaskIntoConstraints="NO" id="7OO-nL-hff">
<rect key="frame" x="11" y="33" width="353" height="65"/>
<subviews>
<stackView opaque="NO" contentMode="scaleToFill" distribution="fillProportionally" spacing="20" translatesAutoresizingMaskIntoConstraints="NO" id="Oeh-7N-HNr">
<rect key="frame" x="0.0" y="0.0" width="106" height="30"/>
<subviews>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="k3J-Eg-itz" userLabel="SubmitBtn">
<rect key="frame" x="0.0" y="0.0" width="106" height="30"/>
<color key="backgroundColor" red="0.028153735480000001" green="0.82494870580000002" blue="0.051896891280000003" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<accessibility key="accessibilityConfiguration" identifier="AuthenticationVCLoginButton"/>
<constraints>
<constraint firstAttribute="height" constant="30" id="BPY-Sb-k8F"/>
<constraint firstAttribute="width" relation="greaterThanOrEqual" constant="100" id="L9J-9d-puh"/>
</constraints>
<fontDescription key="fontDescription" type="system" weight="semibold" pointSize="16"/>
<inset key="contentEdgeInsets" minX="30" minY="0.0" maxX="30" maxY="0.0"/>
<state key="normal" title="Log In">
<color key="titleColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<color key="titleShadowColor" red="0.5" green="0.5" blue="0.5" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</state>
<connections>
<action selector="onButtonPressed:" destination="-1" eventType="touchUpInside" id="Ocd-Ag-6hf"/>
</connections>
</button>
<button hidden="YES" opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="wEJ-AF-rdH">
<rect key="frame" x="0.0" y="0.0" width="0.0" height="30"/>
<color key="backgroundColor" red="0.028153735480000001" green="0.82494870580000002" blue="0.051896891280000003" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<accessibility key="accessibilityConfiguration" identifier="AuthenticationVCSkipButton"/>
<fontDescription key="fontDescription" type="system" weight="semibold" pointSize="16"/>
<inset key="contentEdgeInsets" minX="30" minY="0.0" maxX="30" maxY="0.0"/>
<state key="normal" title="Skip">
<color key="titleColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<color key="titleShadowColor" red="0.5" green="0.5" blue="0.5" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</state>
<connections>
<action selector="onButtonPressed:" destination="-1" eventType="touchUpInside" id="iEr-Vf-f6P"/>
</connections>
</button>
</subviews>
<constraints>
<constraint firstItem="k3J-Eg-itz" firstAttribute="height" secondItem="wEJ-AF-rdH" secondAttribute="height" id="hhT-7b-PVV"/>
</constraints>
</stackView>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" hasAttributedTitle="YES" translatesAutoresizingMaskIntoConstraints="NO" id="AJ2-lJ-NUq">
<rect key="frame" x="0.0" y="35" width="122" height="30"/>
<accessibility key="accessibilityConfiguration" identifier="AuthenticationVCForgotPasswordButton"/>
<constraints>
<constraint firstAttribute="height" constant="30" id="1mr-dZ-KtP"/>
</constraints>
<state key="normal">
<attributedString key="attributedTitle">
<fragment content="Forgot password?">
<attributes>
<color key="NSColor" white="0.66666666666666663" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<font key="NSFont" size="15" name="HelveticaNeue"/>
</attributes>
</fragment>
</attributedString>
</state>
<connections>
<action selector="onButtonPressed:" destination="-1" eventType="touchUpInside" id="UVJ-Re-xe2"/>
</connections>
</button>
</subviews>
</stackView>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="FIn-2w-e6H" userLabel="Server Options">
<rect key="frame" x="0.0" y="103" width="375" height="178"/>
<subviews>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="left" contentVerticalAlignment="center" lineBreakMode="middleTruncation" hasAttributedTitle="YES" translatesAutoresizingMaskIntoConstraints="NO" id="6yx-o1-vbD">
<rect key="frame" x="19" y="5" width="337" height="30"/>
@ -333,8 +342,8 @@
<constraint firstAttribute="trailing" secondItem="6yx-o1-vbD" secondAttribute="trailing" constant="19" id="rWk-Mp-KPS"/>
</constraints>
</view>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="vX2-5Y-rQc">
<rect key="frame" x="0.0" y="96" width="375" height="170"/>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="vX2-5Y-rQc" userLabel="Soft Logout">
<rect key="frame" x="0.0" y="148" width="375" height="170"/>
<subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="QYL-Lo-tmH">
<rect key="frame" x="10" y="0.0" width="355" height="121"/>
@ -384,21 +393,16 @@ Clear it if you're finished using this device, or want to sign in to another acc
<accessibility key="accessibilityConfiguration" identifier="AuthenticationVCOptionsContainer"/>
<constraints>
<constraint firstItem="FIn-2w-e6H" firstAttribute="leading" secondItem="Gg0-TE-OGb" secondAttribute="leading" id="3G8-Tb-KaN"/>
<constraint firstItem="wEJ-AF-rdH" firstAttribute="width" secondItem="k3J-Eg-itz" secondAttribute="width" id="7sB-YJ-eX4"/>
<constraint firstAttribute="trailing" secondItem="vX2-5Y-rQc" secondAttribute="trailing" id="DPh-Jx-WP1"/>
<constraint firstItem="FIn-2w-e6H" firstAttribute="top" secondItem="7OO-nL-hff" secondAttribute="bottom" constant="5" id="Ilt-ab-dEa"/>
<constraint firstItem="vX2-5Y-rQc" firstAttribute="top" secondItem="7OO-nL-hff" secondAttribute="bottom" constant="50" id="LTk-s1-SEs"/>
<constraint firstItem="7OO-nL-hff" firstAttribute="leading" secondItem="Gg0-TE-OGb" secondAttribute="leading" constant="11" id="Otw-gC-R6C"/>
<constraint firstItem="vX2-5Y-rQc" firstAttribute="top" secondItem="Gg0-TE-OGb" secondAttribute="top" priority="100" id="QSG-jB-VcV"/>
<constraint firstItem="wEJ-AF-rdH" firstAttribute="centerY" secondItem="k3J-Eg-itz" secondAttribute="centerY" id="Vze-EI-oXj"/>
<constraint firstAttribute="trailing" secondItem="k3J-Eg-itz" secondAttribute="trailing" constant="11" id="ZyA-Tq-Sfq"/>
<constraint firstItem="k3J-Eg-itz" firstAttribute="leading" relation="greaterThanOrEqual" secondItem="Gg0-TE-OGb" secondAttribute="leading" constant="150" id="bEB-EO-b14"/>
<constraint firstItem="vX2-5Y-rQc" firstAttribute="top" secondItem="k3J-Eg-itz" secondAttribute="bottom" constant="33" id="db7-2y-vPZ"/>
<constraint firstItem="AJ2-lJ-NUq" firstAttribute="centerY" secondItem="k3J-Eg-itz" secondAttribute="centerY" id="dcE-Vs-7Rt"/>
<constraint firstItem="7OO-nL-hff" firstAttribute="top" secondItem="Gg0-TE-OGb" secondAttribute="top" constant="33" id="e4H-ld-bD7"/>
<constraint firstAttribute="trailing" secondItem="FIn-2w-e6H" secondAttribute="trailing" id="kFj-6g-v3H"/>
<constraint firstItem="vX2-5Y-rQc" firstAttribute="leading" secondItem="Gg0-TE-OGb" secondAttribute="leading" id="kYN-Lj-zYP"/>
<constraint firstAttribute="height" priority="700" constant="300" id="lXv-gM-CjN"/>
<constraint firstItem="k3J-Eg-itz" firstAttribute="top" secondItem="Gg0-TE-OGb" secondAttribute="top" constant="33" id="mor-t9-7Ke"/>
<constraint firstItem="FIn-2w-e6H" firstAttribute="top" secondItem="k3J-Eg-itz" secondAttribute="bottom" constant="5" id="oTS-5o-MMW"/>
<constraint firstItem="wEJ-AF-rdH" firstAttribute="leading" secondItem="Gg0-TE-OGb" secondAttribute="leading" constant="11" id="sax-RY-aOJ"/>
<constraint firstItem="AJ2-lJ-NUq" firstAttribute="leading" secondItem="Gg0-TE-OGb" secondAttribute="leading" constant="19" id="xFm-Bs-yzw"/>
<constraint firstAttribute="trailing" secondItem="7OO-nL-hff" secondAttribute="trailing" constant="11" id="pPK-AD-wiD"/>
</constraints>
</view>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="TjK-XL-dQS">
@ -413,21 +417,21 @@ Clear it if you're finished using this device, or want to sign in to another acc
<accessibility key="accessibilityConfiguration" identifier="AuthContentView"/>
<constraints>
<constraint firstAttribute="height" constant="485" id="6v6-fz-e8o"/>
<constraint firstItem="Gg0-TE-OGb" firstAttribute="width" secondItem="rhx-dD-4EJ" secondAttribute="width" id="EBX-KN-pRT"/>
<constraint firstItem="TjK-XL-dQS" firstAttribute="leading" secondItem="rhx-dD-4EJ" secondAttribute="leading" id="MId-fr-A7b"/>
<constraint firstItem="TjK-XL-dQS" firstAttribute="leading" secondItem="rhx-dD-4EJ" secondAttribute="leadingMargin" id="MId-fr-A7b"/>
<constraint firstAttribute="bottom" secondItem="TjK-XL-dQS" secondAttribute="bottom" id="PKr-aT-3Qe"/>
<constraint firstItem="TjK-XL-dQS" firstAttribute="top" secondItem="Gg0-TE-OGb" secondAttribute="bottom" id="Sfy-Zn-JkS"/>
<constraint firstItem="Gg0-TE-OGb" firstAttribute="top" secondItem="rhx-dD-4EJ" secondAttribute="top" priority="250" constant="384" id="UEM-Mh-0H9"/>
<constraint firstItem="xWb-IJ-v7F" firstAttribute="leading" secondItem="rhx-dD-4EJ" secondAttribute="leading" id="YnP-Nk-QxR"/>
<constraint firstItem="xWb-IJ-v7F" firstAttribute="leading" secondItem="rhx-dD-4EJ" secondAttribute="leadingMargin" id="YnP-Nk-QxR"/>
<constraint firstItem="Gg0-TE-OGb" firstAttribute="top" secondItem="xWb-IJ-v7F" secondAttribute="bottom" id="aGH-m3-xL3"/>
<constraint firstAttribute="trailing" secondItem="TjK-XL-dQS" secondAttribute="trailing" id="d1j-OA-Wwm"/>
<constraint firstAttribute="trailing" secondItem="xWb-IJ-v7F" secondAttribute="trailing" id="hko-ol-XDd"/>
<constraint firstAttribute="trailingMargin" secondItem="TjK-XL-dQS" secondAttribute="trailing" id="d1j-OA-Wwm"/>
<constraint firstAttribute="trailingMargin" secondItem="xWb-IJ-v7F" secondAttribute="trailing" id="hko-ol-XDd"/>
<constraint firstItem="xWb-IJ-v7F" firstAttribute="top" secondItem="rhx-dD-4EJ" secondAttribute="top" constant="160" id="khR-Uj-OTH"/>
<constraint firstItem="d8r-TX-pwX" firstAttribute="top" secondItem="rhx-dD-4EJ" secondAttribute="top" constant="35" id="l68-Ta-YKg"/>
<constraint firstAttribute="centerX" secondItem="d8r-TX-pwX" secondAttribute="centerX" id="l6k-EH-Yb8"/>
<constraint firstItem="Gg0-TE-OGb" firstAttribute="leading" secondItem="rhx-dD-4EJ" secondAttribute="leading" id="rS3-go-zbf"/>
<constraint firstItem="TjK-XL-dQS" firstAttribute="width" secondItem="rhx-dD-4EJ" secondAttribute="width" id="zIZ-fl-i3i"/>
<constraint firstItem="Gg0-TE-OGb" firstAttribute="leading" secondItem="rhx-dD-4EJ" secondAttribute="leadingMargin" id="rS3-go-zbf"/>
<constraint firstAttribute="trailingMargin" secondItem="Gg0-TE-OGb" secondAttribute="trailing" id="umc-1H-5ho"/>
</constraints>
<edgeInsets key="layoutMargins" top="0.0" left="0.0" bottom="0.0" right="0.0"/>
</view>
</subviews>
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>

View file

@ -295,47 +295,8 @@
{
self.isSendingLogs = YES;
// Setup data to send
bugReportRestClient = [[MXBugReportRestClient alloc] initWithBugReportEndpoint:BuildSettings.bugReportEndpointUrlString];
// App info
bugReportRestClient.appName = BuildSettings.bugReportApplicationId;
bugReportRestClient.version = [AppDelegate theDelegate].appVersion;
bugReportRestClient.build = [AppDelegate theDelegate].build;
// Device info
bugReportRestClient.deviceModel = [GBDeviceInfo deviceInfo].modelString;
bugReportRestClient.deviceOS = [NSString stringWithFormat:@"%@ %@", [[UIDevice currentDevice] systemName], [[UIDevice currentDevice] systemVersion]];
// User info (TODO: handle multi-account and find a way to expose them in rageshake API)
NSMutableDictionary *userInfo = [NSMutableDictionary dictionary];
MXKAccount *mainAccount = [MXKAccountManager sharedManager].accounts.firstObject;
if (mainAccount.mxSession.myUser.userId)
{
userInfo[@"user_id"] = mainAccount.mxSession.myUser.userId;
}
if (mainAccount.mxSession.matrixRestClient.credentials.deviceId)
{
userInfo[@"device_id"] = mainAccount.mxSession.matrixRestClient.credentials.deviceId;
}
userInfo[@"locale"] = [NSLocale preferredLanguages][0];
userInfo[@"default_app_language"] = [[NSBundle mainBundle] preferredLocalizations][0]; // The language chosen by the OS
userInfo[@"app_language"] = [NSBundle mxk_language] ? [NSBundle mxk_language] : userInfo[@"default_app_language"]; // The language chosen by the user
// Application settings
userInfo[@"lazy_loading"] = [MXKAppSettings standardAppSettings].syncWithLazyLoadOfRoomMembers ? @"ON" : @"OFF";
NSDate *currentDate = [NSDate date];
NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
[dateFormatter setDateFormat:@"yyyy-MM-dd HH:mm:ss"];
userInfo[@"local_time"] = [dateFormatter stringFromDate:currentDate];
[dateFormatter setTimeZone:[NSTimeZone timeZoneWithName:@"UTC"]];
userInfo[@"utc_time"] = [dateFormatter stringFromDate:currentDate];
bugReportRestClient.others = userInfo;
bugReportRestClient = [MXBugReportRestClient vc_bugReportRestClientWithAppName:BuildSettings.bugReportApplicationId];
// Screenshot
NSArray<NSURL*> *files;
if (_screenshot && _sendScreenshot)
@ -347,56 +308,23 @@
files = @[screenShotFile];
}
// Prepare labels to attach to the GitHub issue
NSMutableArray<NSString*> *gitHubLabels = [NSMutableArray array];
if (_reportCrash)
{
// Label the GH issue as "crash"
[gitHubLabels addObject:@"crash"];
}
// Add a Github label giving information about the version
if (bugReportRestClient.version && bugReportRestClient.build)
{
NSString *build = bugReportRestClient.build;
NSString *versionLabel = bugReportRestClient.version;
// If this is not the app store version, be more accurate on the build origin
if ([build isEqualToString:[VectorL10n settingsConfigNoBuildInfo]])
{
// This is a debug session from Xcode
versionLabel = [versionLabel stringByAppendingString:@"-debug"];
}
else if (build && ![build containsString:@"master"])
{
// This is a Jenkins build. Add the branch and the build number
NSString *buildString = [build stringByReplacingOccurrencesOfString:@" " withString:@"-"];
versionLabel = [[versionLabel stringByAppendingString:@"-"] stringByAppendingString:buildString];
}
[gitHubLabels addObject:versionLabel];
}
NSMutableString *bugReportDescription = [NSMutableString stringWithString:_bugReportDescriptionTextView.text];
if (_reportCrash)
{
// Append the crash dump to the user description in order to ease triaging of GH issues
NSString *crashLogFile = [MXLogger crashLog];
NSString *crashLog = [NSString stringWithContentsOfFile:crashLogFile encoding:NSUTF8StringEncoding error:nil];
[bugReportDescription appendFormat:@"\n\n\n--------------------------------------------------------------------------------\n\n%@", crashLog];
}
// starting a background task to have a bit of extra time in case of user forgets about the report and sends the app to background
__block UIBackgroundTaskIdentifier operationBackgroundId = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^{
[[UIApplication sharedApplication] endBackgroundTask:operationBackgroundId];
operationBackgroundId = UIBackgroundTaskInvalid;
}];
// Submit
[bugReportRestClient sendBugReport:bugReportDescription sendLogs:_sendLogs sendCrashLog:_reportCrash sendFiles:files attachGitHubLabels:gitHubLabels progress:^(MXBugReportState state, NSProgress *progress) {
[bugReportRestClient vc_sendBugReportWithDescription:bugReportDescription
sendLogs:_sendLogs
sendCrashLog:_reportCrash
sendFiles:files
additionalLabels:nil
customFields:nil
progress:^(MXBugReportState state, NSProgress *progress) {
switch (state)
{
case MXBugReportStateProgressZipping:
@ -413,7 +341,7 @@
self.sendingProgress.progress = progress.fractionCompleted;
} success:^{
} success:^(NSString *reportUrl){
self->bugReportRestClient = nil;

View file

@ -19,7 +19,7 @@ import UIKit
import AVFoundation
@objc protocol CameraPresenterDelegate: AnyObject {
func cameraPresenter(_ presenter: CameraPresenter, didSelectImageData imageData: Data, withUTI uti: MXKUTI?)
func cameraPresenter(_ presenter: CameraPresenter, didSelectImage image: UIImage)
func cameraPresenter(_ presenter: CameraPresenter, didSelectVideoAt url: URL)
func cameraPresenterDidCancel(_ cameraPresenter: CameraPresenter)
}
@ -27,12 +27,6 @@ import AVFoundation
/// CameraPresenter enables to present native camera
@objc final class CameraPresenter: NSObject {
// MARK: - Constants
private enum Constants {
static let jpegCompressionQuality: CGFloat = 1.0
}
// MARK: - Properties
// MARK: Private
@ -131,8 +125,8 @@ extension CameraPresenter: UIImagePickerControllerDelegate {
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey: Any]) {
if let videoURL = info[.mediaURL] as? URL {
self.delegate?.cameraPresenter(self, didSelectVideoAt: videoURL)
} else if let image = (info[.editedImage] ?? info[.originalImage]) as? UIImage, let imageData = image.jpegData(compressionQuality: Constants.jpegCompressionQuality) {
self.delegate?.cameraPresenter(self, didSelectImageData: imageData, withUTI: MXKUTI.jpeg)
} else if let image = (info[.editedImage] ?? info[.originalImage]) as? UIImage {
self.delegate?.cameraPresenter(self, didSelectImage: image)
}
}

View file

@ -1,56 +0,0 @@
//
// 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 CommonKit
/// A convenience objc-compatible wrapper around `UserIndicatorTypePresenterProtocol`.
///
/// This class wraps swift-only protocol by exposing multiple methods instead of accepting struct types
/// and it keeps a track of `UserIndicator`s instead of returning them to the caller.
@objc final class UserIndicatorPresenterWrapper: NSObject {
private let presenter: UserIndicatorTypePresenterProtocol
private var loadingIndicator: UserIndicator?
private var otherIndicators = [UserIndicator]()
init(presenter: UserIndicatorTypePresenterProtocol) {
self.presenter = presenter
}
@objc func presentLoadingIndicator() {
presentLoadingIndicator(label: VectorL10n.homeSyncing)
}
@objc func presentLoadingIndicator(label: String) {
guard loadingIndicator == nil else {
// The app is very liberal with calling `presentLoadingIndicator` (often not matched by corresponding `dismissLoadingIndicator`),
// so there is no reason to keep adding new indiciators if there is one already showing.
return
}
MXLog.debug("[UserIndicatorPresenterWrapper] Present loading indicator")
loadingIndicator = presenter.present(.loading(label: label, isInteractionBlocking: false))
}
@objc func dismissLoadingIndicator() {
MXLog.debug("[UserIndicatorPresenterWrapper] Dismiss loading indicator")
loadingIndicator = nil
}
@objc func presentSuccess(label: String) {
presenter.present(.success(label: label)).store(in: &otherIndicators)
}
}

View file

@ -19,7 +19,7 @@
@class RootTabEmptyView;
@class AnalyticsScreenTracker;
@class UserIndicatorPresenterWrapper;
@class UserIndicatorStore;
/**
Notification to be posted when recents data is ready. Notification object will be the RecentsViewController instance.
@ -98,9 +98,10 @@ FOUNDATION_EXPORT NSString *const RecentsViewControllerDataReadyNotification;
@property (nonatomic) AnalyticsScreenTracker *screenTracker;
/**
Presenter for displaying app-wide user indicators. If not set, the view controller will use legacy activity indicators
A store of user indicators that lets the room present and dismiss indicators without
worrying about the presentation context or memory management.
*/
@property (nonatomic, strong) UserIndicatorPresenterWrapper *indicatorPresenter;
@property (nonatomic, strong) UserIndicatorStore *userIndicatorStore;
/**
Return the sticky header for the specified section of the table view

View file

@ -36,7 +36,7 @@
NSString *const RecentsViewControllerDataReadyNotification = @"RecentsViewControllerDataReadyNotification";
@interface RecentsViewController () <CreateRoomCoordinatorBridgePresenterDelegate, RoomsDirectoryCoordinatorBridgePresenterDelegate, RoomNotificationSettingsCoordinatorBridgePresenterDelegate, DialpadViewControllerDelegate, ExploreRoomCoordinatorBridgePresenterDelegate>
@interface RecentsViewController () <CreateRoomCoordinatorBridgePresenterDelegate, RoomsDirectoryCoordinatorBridgePresenterDelegate, RoomNotificationSettingsCoordinatorBridgePresenterDelegate, DialpadViewControllerDelegate, ExploreRoomCoordinatorBridgePresenterDelegate, SpaceChildRoomDetailBridgePresenterDelegate>
{
// Tell whether a recents refresh is pending (suspended during editing mode).
BOOL isRefreshPending;
@ -69,6 +69,9 @@ NSString *const RecentsViewControllerDataReadyNotification = @"RecentsViewContro
// Observe kThemeServiceDidChangeThemeNotification to handle user interface theme change.
__weak id kThemeServiceDidChangeThemeNotificationObserver;
// Cancel handler of any ongoing loading indicator
UserIndicatorCancel loadingIndicatorCancel;
}
@property (nonatomic, strong) CreateRoomCoordinatorBridgePresenter *createRoomCoordinatorBridgePresenter;
@ -83,6 +86,8 @@ NSString *const RecentsViewControllerDataReadyNotification = @"RecentsViewContro
@property (nonatomic, strong) RoomNotificationSettingsCoordinatorBridgePresenter *roomNotificationSettingsCoordinatorBridgePresenter;
@property (nonatomic, strong) SpaceChildRoomDetailBridgePresenter *spaceChildPresenter;
@end
@implementation RecentsViewController
@ -1303,7 +1308,7 @@ NSString *const RecentsViewControllerDataReadyNotification = @"RecentsViewContro
{
typeof(self) self = weakSelf;
[self stopActivityIndicator];
[self.indicatorPresenter presentSuccessWithLabel:[VectorL10n roomParticipantsLeaveSuccess]];
[self.userIndicatorStore presentSuccessWithLabel:[VectorL10n roomParticipantsLeaveSuccess]];
// Force table refresh
[self cancelEditionMode:YES];
}
@ -1564,7 +1569,8 @@ NSString *const RecentsViewControllerDataReadyNotification = @"RecentsViewContro
{
// Open the room or preview it
NSString *fragment = [NSString stringWithFormat:@"/room/%@", [MXTools encodeURIComponent:roomIdOrAlias]];
[[AppDelegate theDelegate] handleUniversalLinkFragment:fragment];
NSURL *url = [NSURL URLWithString:[MXTools permalinkToRoom:fragment]];
[[AppDelegate theDelegate] handleUniversalLinkFragment:fragment fromURL:url];
}
[tableView deselectRowAtIndexPath:indexPath animated:NO];
}
@ -2170,18 +2176,13 @@ NSString *const RecentsViewControllerDataReadyNotification = @"RecentsViewContro
[self showRoomWithRoomId:roomId inMatrixSession:matrixSession];
}
- (void)recentListViewController:(MXKRecentListViewController *)recentListViewController didSelectSuggestedRoom:(MXSpaceChildInfo *)childInfo
- (void)recentListViewController:(MXKRecentListViewController *)recentListViewController didSelectSuggestedRoom:(MXSpaceChildInfo *)childInfo from:(UIView* _Nullable)sourceView
{
Analytics.shared.joinedRoomTrigger = AnalyticsJoinedRoomTriggerSpaceHierarchy;
RoomPreviewData *previewData = [[RoomPreviewData alloc] initWithSpaceChildInfo:childInfo andSession:self.mainSession];
[self startActivityIndicator];
MXWeakify(self);
[previewData peekInRoom:^(BOOL succeeded) {
MXStrongifyAndReturnIfNil(self);
[self stopActivityIndicator];
[self showRoomPreviewWithData:previewData];
}];
self.spaceChildPresenter = [[SpaceChildRoomDetailBridgePresenter alloc] initWithSession:self.mainSession childInfo:childInfo];
self.spaceChildPresenter.delegate = self;
[self.spaceChildPresenter presentFrom:self sourceView:sourceView animated:YES];
}
#pragma mark - UISearchBarDelegate
@ -2432,31 +2433,55 @@ NSString *const RecentsViewControllerDataReadyNotification = @"RecentsViewContro
self.roomNotificationSettingsCoordinatorBridgePresenter = nil;
}
#pragma mark - SpaceChildRoomDetailBridgePresenterDelegate
- (void)spaceChildRoomDetailBridgePresenterDidCancel:(SpaceChildRoomDetailBridgePresenter *)coordinator
{
[self.spaceChildPresenter dismissWithAnimated:YES completion:^{
self.spaceChildPresenter = nil;
}];
}
- (void)spaceChildRoomDetailBridgePresenter:(SpaceChildRoomDetailBridgePresenter *)coordinator didOpenRoomWith:(NSString *)roomId
{
[self showRoomWithRoomId:roomId inMatrixSession:self.mainSession];
[self.spaceChildPresenter dismissWithAnimated:YES completion:^{
self.spaceChildPresenter = nil;
}];
}
#pragma mark - Activity Indicator
- (BOOL)providesCustomActivityIndicator {
return self.indicatorPresenter != nil;
return self.userIndicatorStore != nil;
}
- (void)startActivityIndicatorWithLabel:(NSString *)label {
if (self.indicatorPresenter && isViewVisible) {
[self.indicatorPresenter presentLoadingIndicatorWithLabel:label];
if (self.userIndicatorStore && isViewVisible) {
// The app is very liberal with calling `startActivityIndicator` (often not matched by corresponding `stopActivityIndicator`),
// so there is no reason to keep adding new indicators if there is one already showing.
if (loadingIndicatorCancel) {
return;
}
MXLogDebug(@"[RecentsViewController] Present loading indicator")
loadingIndicatorCancel = [self.userIndicatorStore presentLoadingWithLabel:label isInteractionBlocking:NO];
} else {
[super startActivityIndicator];
}
}
- (void)startActivityIndicator {
if (self.indicatorPresenter && isViewVisible) {
[self.indicatorPresenter presentLoadingIndicator];
} else {
[super startActivityIndicator];
}
[self startActivityIndicatorWithLabel:[VectorL10n homeSyncing]];
}
- (void)stopActivityIndicator {
if (self.indicatorPresenter) {
[self.indicatorPresenter dismissLoadingIndicator];
if (self.userIndicatorStore) {
if (loadingIndicatorCancel) {
MXLogDebug(@"[RecentsViewController] Present loading indicator")
loadingIndicatorCancel();
loadingIndicatorCancel = nil;
}
} else {
[super stopActivityIndicator];
}

View file

@ -0,0 +1,27 @@
//
// Copyright 2022 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.
//
#ifndef UserIndicatorCancel_h
#define UserIndicatorCancel_h
/**
Callback function to cancel a `UserIndicator` without needing a direct reference to the object
Note: the function is defined in Objective-C (instead of Swift) to be accessible by both languages.
*/
typedef void (^UserIndicatorCancel)(void);
#endif /* UserIndicatorCancel_h */

View file

@ -0,0 +1,62 @@
//
// Copyright 2022 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 CommonKit
/// An abstraction on top of `UserIndicatorTypePresenterProtocol` which manages and stores the individual user indicators.
/// When used to present an indicator the `UserIndicatorStore` will instead returns a simple callback function to the clients
/// letting them cancel the indicators without worrying about memory.
@objc final class UserIndicatorStore: NSObject {
private let presenter: UserIndicatorTypePresenterProtocol
private var indicators: [UserIndicator]
init(presenter: UserIndicatorTypePresenterProtocol) {
self.presenter = presenter
self.indicators = []
}
/// Present a new type of user indicator, such as loading spinner or success message.
/// To remove an indicator, call the returned `UserIndicatorCancel` function
func present(type: UserIndicatorType) -> UserIndicatorCancel {
let indicator = presenter.present(type)
indicators.append(indicator)
return {
indicator.cancel()
}
}
/// Present a loading indicator.
/// To remove the indicator call the returned `UserIndicatorCancel` function
///
/// Note: This is a convenience function callable by objective-c code
@objc func presentLoading(label: String, isInteractionBlocking: Bool) -> UserIndicatorCancel {
present(
type: .loading(
label: label,
isInteractionBlocking: isInteractionBlocking
)
)
}
/// Present a success message that will be automatically dismissed after a few seconds.
///
/// Note: This is a convenience function callable by objective-c code
@objc func presentSuccess(label: String) {
let indicator = presenter.present(.success(label: label))
indicators.append(indicator)
}
}

View file

@ -867,14 +867,13 @@
__weak typeof(self) weakSelf = self;
[self startActivityIndicator];
[self.mxSession.matrixRestClient roomIDForRoomAlias:roomIdOrAlias success:^(NSString *roomId) {
[self.mxSession.matrixRestClient resolveRoomAlias:roomIdOrAlias success:^(MXRoomAliasResolution *resolution) {
if (roomId && weakSelf)
{
typeof(self) self = weakSelf;
[self stopActivityIndicator];
[self didSelectRoomId:roomId];
[self didSelectRoomId:resolution.roomId];
}
} failure:^(NSError *error) {
@ -890,7 +889,7 @@
// Open the group or preview it
NSString *fragment = [NSString stringWithFormat:@"/group/%@",
[MXTools encodeURIComponent:absoluteURLString]];
[[AppDelegate theDelegate] handleUniversalLinkFragment:fragment];
[[AppDelegate theDelegate] handleUniversalLinkFragment:fragment fromURL:URL];
}
return shouldInteractWithURL;

View file

@ -17,6 +17,7 @@
*/
import UIKit
import CommonKit
final class EnterNewRoomDetailsViewController: UIViewController {
@ -47,7 +48,9 @@ final class EnterNewRoomDetailsViewController: UIViewController {
private var theme: Theme!
private var keyboardAvoider: KeyboardAvoider?
private var errorPresenter: MXKErrorPresentation!
private var activityPresenter: ActivityIndicatorPresenter!
private var userIndicatorPresenter: UserIndicatorTypePresenterProtocol!
private var loadingIndicator: UserIndicator?
private lazy var createBarButtonItem: MXKBarButtonItem = {
let title: String
switch viewModel.actionType {
@ -262,7 +265,7 @@ final class EnterNewRoomDetailsViewController: UIViewController {
self.setupViews()
self.keyboardAvoider = KeyboardAvoider(scrollViewContainerView: self.view, scrollView: self.mainTableView)
self.activityPresenter = ActivityIndicatorPresenter()
self.userIndicatorPresenter = UserIndicatorTypePresenter(presentingViewController: self)
self.errorPresenter = MXKErrorAlertPresentation()
self.registerThemeServiceDidChangeThemeNotification()
@ -352,11 +355,11 @@ final class EnterNewRoomDetailsViewController: UIViewController {
}
private func renderLoading() {
self.activityPresenter.presentActivityIndicator(on: self.view, animated: true)
loadingIndicator = userIndicatorPresenter.present(.loading(label: VectorL10n.createRoomProcessing, isInteractionBlocking: true))
}
private func render(error: Error) {
self.activityPresenter.removeCurrentActivityIndicator(animated: true)
loadingIndicator = nil
self.errorPresenter.presentError(from: self, forError: error, animated: true, handler: nil)
}

View file

@ -51,7 +51,7 @@ final class EnterNewRoomDetailsViewModel: EnterNewRoomDetailsViewModelType {
init(session: MXSession, parentSpace: MXSpace?) {
self.session = session
self.parentSpace = parentSpace
roomCreationParameters.isEncrypted = session.vc_homeserverConfiguration().isE2EEByDefaultEnabled && RiotSettings.shared.roomCreationScreenRoomIsEncrypted
roomCreationParameters.isEncrypted = session.vc_homeserverConfiguration().encryption.isE2EEByDefaultEnabled && RiotSettings.shared.roomCreationScreenRoomIsEncrypted
roomCreationParameters.joinRule = RiotSettings.shared.roomCreationScreenRoomIsPublic ? .public : .private
viewState = .loaded
}
@ -115,6 +115,7 @@ final class EnterNewRoomDetailsViewModel: EnterNewRoomDetailsViewModelType {
fatalError("[EnterNewRoomDetailsViewModel] createRoom: room name cannot be nil.")
}
viewState = .loading
currentOperation = session.createRoom(
withName: roomName,
joinRule: roomCreationParameters.joinRule,
@ -125,6 +126,8 @@ final class EnterNewRoomDetailsViewModel: EnterNewRoomDetailsViewModelType {
completion: { response in
switch response {
case .success(let room):
self.viewState = .loaded
if let parentSpace = self.parentSpace {
self.add(room, to: parentSpace)
} else {

View file

@ -0,0 +1,43 @@
//
// Copyright 2022 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 MatrixSDK
@objc extension MXRoomAliasResolution {
/// Deeplink fragment using a room identifier and a list of servers aware of this identifier
///
/// For more details see
/// https://github.com/matrix-org/matrix-spec-proposals/blob/old_master/proposals/1704-matrix.to-permalinks.md
var deeplinkFragment: String? {
guard let roomId = roomId else {
MXLog.debug("[MXRoomAliasResolution]: Missing room identifier")
return nil
}
return MXTools.encodeURIComponent(
fragment(for: roomId)
)
}
private func fragment(for roomId: String) -> String {
guard let servers = servers, !servers.isEmpty else {
return roomId
}
return roomId + "?via=" + servers.joined(separator: "&via=")
}
}

View file

@ -22,7 +22,7 @@ class UniversalLinkParameters: NSObject {
// MARK: - Properties
/// The unprocessed the universal link URL
/// The unprocessed universal link URL
let universalLinkURL: URL
/// The fragment part of the universal link

View file

@ -108,13 +108,11 @@
[AppDelegate theDelegate].masterTabBarController.tabBar.tintColor = ThemeService.shared.theme.tintColor;
if (recentsDataSource)
if (recentsDataSource.recentsDataSourceMode != RecentsDataSourceModeHome)
{
// Take the lead on the shared data source.
[recentsDataSource setDelegate:self andRecentsDataSourceMode:RecentsDataSourceModeHome];
}
[self moveAllCollectionsToLeft];
}
}
- (void)viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(id <UIViewControllerTransitionCoordinator>)coordinator
@ -133,26 +131,6 @@
[super destroy];
}
- (void)moveAllCollectionsToLeft
{
selectedCollectionViewContentOffset = -1;
// Scroll all rooms collections to their beginning
for (NSInteger section = 0; section < [self numberOfSectionsInTableView:self.recentsTableView]; section++)
{
UITableViewCell *firstSectionCell = [self.recentsTableView cellForRowAtIndexPath:[NSIndexPath indexPathForRow:0 inSection:section]];
if (firstSectionCell && [firstSectionCell isKindOfClass:TableViewCellWithCollectionView.class])
{
TableViewCellWithCollectionView *tableViewCell = (TableViewCellWithCollectionView*)firstSectionCell;
if ([tableViewCell.collectionView numberOfItemsInSection:0] > 0)
{
[tableViewCell.collectionView scrollToItemAtIndexPath:[NSIndexPath indexPathForItem:0 inSection:0] atScrollPosition:UICollectionViewScrollPositionLeft animated:NO];
}
}
}
}
- (SecureBackupBannerCell *)secureBackupBannerPrototypeCell
{
if (!_secureBackupBannerPrototypeCell)
@ -627,7 +605,8 @@
if (renderedCellData.isSuggestedRoom)
{
[self.delegate recentListViewController:self
didSelectSuggestedRoom:renderedCellData.roomSummary.spaceChildInfo];
didSelectSuggestedRoom:renderedCellData.roomSummary.spaceChildInfo
from:roomCollectionViewCell];
}
else
{

View file

@ -19,11 +19,15 @@ import Foundation
#if canImport(JitsiMeetSDK)
import JitsiMeetSDK
enum JitsiServiceError: Error {
enum JitsiServiceError: LocalizedError {
case widgetContentCreationFailed
case emptyResponse
case noWellKnown
case unknown
var errorDescription: String? {
return VectorL10n.callJitsiUnableToStart
}
}
private enum HTTPStatusCodes {
@ -143,32 +147,31 @@ final class JitsiService: NSObject {
}
return self.getWellKnown(for: jitsiServerURL) { (result) in
var continueOperation: Bool = false
var authType: JitsiAuthenticationType?
func continueOperation(authType: JitsiAuthenticationType?) {
guard let widgetContent = self.createJitsiWidgetContent(serverDomain: serverDomain,
authenticationType: authType,
roomID: roomID,
isAudioOnly: isAudioOnly)
else {
failure(JitsiServiceError.widgetContentCreationFailed)
return
}
success(widgetContent)
}
switch result {
case .success(let jitsiWellKnown):
authType = jitsiWellKnown.authenticationType
continueOperation = true
continueOperation(authType: jitsiWellKnown.authenticationType)
case .failure(let error):
MXLog.debug("[JitsiService] Fail to get Jitsi Well Known with error: \(error)")
if let error = error as? JitsiServiceError, error == .noWellKnown {
// no well-known, continue with no auth
continueOperation = true
continueOperation(authType: nil)
} else {
failure(error)
}
}
if continueOperation,
let widgetContent = self.createJitsiWidgetContent(serverDomain: serverDomain,
authenticationType: authType,
roomID: roomID,
isAudioOnly: isAudioOnly) {
success(widgetContent)
} else {
failure(JitsiServiceError.widgetContentCreationFailed)
}
}
}

View file

@ -92,7 +92,7 @@ final class KeyBackupSetupCoordinator: KeyBackupSetupCoordinatorType {
self.createKeyBackupUsingSecureBackup(privateKey: privateKey, completion: completion)
}
let coordinator = SecretsRecoveryCoordinator(session: self.session, recoveryMode: .passphraseOrKey, recoveryGoal: recoveryGoal, navigationRouter: self.navigationRouter)
let coordinator = SecretsRecoveryCoordinator(session: self.session, recoveryMode: .passphraseOrKey, recoveryGoal: recoveryGoal, navigationRouter: self.navigationRouter, cancellable: true)
coordinator.delegate = self
coordinator.start()
self.add(childCoordinator: coordinator)

View file

@ -29,6 +29,7 @@ final class KeyVerificationCoordinator: KeyVerificationCoordinatorType {
private let session: MXSession
private let verificationFlow: KeyVerificationFlow
private let verificationKind: KeyVerificationKind
private let cancellable: Bool
private weak var completeSecurityCoordinator: KeyVerificationSelfVerifyWaitCoordinatorType?
private var otherUserId: String {
@ -86,7 +87,8 @@ final class KeyVerificationCoordinator: KeyVerificationCoordinatorType {
/// - session: The MXSession.
/// - flow: The wanted key verification flow.
/// - navigationRouter: Existing NavigationRouter from which present the flow (optional).
init(session: MXSession, flow: KeyVerificationFlow, navigationRouter: NavigationRouterType? = nil) {
/// - cancellable: Whether key verification process can be cancelled.
init(session: MXSession, flow: KeyVerificationFlow, navigationRouter: NavigationRouterType? = nil, cancellable: Bool) {
self.navigationRouter = navigationRouter ?? NavigationRouter(navigationController: RiotNavigationController())
self.session = session
@ -113,6 +115,7 @@ final class KeyVerificationCoordinator: KeyVerificationCoordinatorType {
}
self.verificationKind = verificationKind
self.cancellable = cancellable
}
// MARK: - Public methods
@ -155,7 +158,9 @@ final class KeyVerificationCoordinator: KeyVerificationCoordinatorType {
}
func toPresentable() -> UIViewController {
return self.navigationRouter.toPresentable()
return self.navigationRouter
.toPresentable()
.vc_setModalFullScreen(!self.cancellable)
}
// MARK: - Private methods
@ -177,7 +182,7 @@ final class KeyVerificationCoordinator: KeyVerificationCoordinatorType {
}
private func createCompleteSecurityCoordinator(isNewSignIn: Bool) -> KeyVerificationSelfVerifyWaitCoordinatorType {
let coordinator = KeyVerificationSelfVerifyWaitCoordinator(session: self.session, isNewSignIn: isNewSignIn)
let coordinator = KeyVerificationSelfVerifyWaitCoordinator(session: self.session, isNewSignIn: isNewSignIn, cancellable: self.cancellable)
coordinator.delegate = self
coordinator.start()
@ -185,7 +190,7 @@ final class KeyVerificationCoordinator: KeyVerificationCoordinatorType {
}
private func showSecretsRecovery(with recoveryMode: SecretsRecoveryMode) {
let coordinator = SecretsRecoveryCoordinator(session: self.session, recoveryMode: recoveryMode, recoveryGoal: .verifyDevice, navigationRouter: self.navigationRouter)
let coordinator = SecretsRecoveryCoordinator(session: self.session, recoveryMode: recoveryMode, recoveryGoal: .verifyDevice, navigationRouter: self.navigationRouter, cancellable: self.cancellable)
coordinator.delegate = self
coordinator.start()

View file

@ -38,6 +38,7 @@ final class KeyVerificationCoordinatorBridgePresenter: NSObject {
// MARK: Public
weak var delegate: KeyVerificationCoordinatorBridgePresenterDelegate?
var cancellable: Bool = true
var isPresenting: Bool {
return self.coordinator != nil
@ -61,7 +62,7 @@ final class KeyVerificationCoordinatorBridgePresenter: NSObject {
MXLog.debug("[KeyVerificationCoordinatorBridgePresenter] Present from \(viewController)")
let keyVerificationCoordinator = KeyVerificationCoordinator(session: self.session, flow: .verifyDevice(userId: otherUserId, deviceId: otherDeviceId))
let keyVerificationCoordinator = KeyVerificationCoordinator(session: self.session, flow: .verifyDevice(userId: otherUserId, deviceId: otherDeviceId), cancellable: self.cancellable)
self.present(coordinator: keyVerificationCoordinator, from: viewController, animated: animated)
}
@ -69,7 +70,7 @@ final class KeyVerificationCoordinatorBridgePresenter: NSObject {
MXLog.debug("[KeyVerificationCoordinatorBridgePresenter] Present from \(viewController)")
let keyVerificationCoordinator = KeyVerificationCoordinator(session: self.session, flow: .verifyUser(roomMember))
let keyVerificationCoordinator = KeyVerificationCoordinator(session: self.session, flow: .verifyUser(roomMember), cancellable: self.cancellable)
self.present(coordinator: keyVerificationCoordinator, from: viewController, animated: animated)
}
@ -77,7 +78,7 @@ final class KeyVerificationCoordinatorBridgePresenter: NSObject {
MXLog.debug("[KeyVerificationCoordinatorBridgePresenter] Present incoming verification from \(viewController)")
let keyVerificationCoordinator = KeyVerificationCoordinator(session: self.session, flow: .incomingSASTransaction(incomingTransaction))
let keyVerificationCoordinator = KeyVerificationCoordinator(session: self.session, flow: .incomingSASTransaction(incomingTransaction), cancellable: self.cancellable)
self.present(coordinator: keyVerificationCoordinator, from: viewController, animated: animated)
}
@ -85,7 +86,7 @@ final class KeyVerificationCoordinatorBridgePresenter: NSObject {
MXLog.debug("[KeyVerificationCoordinatorBridgePresenter] Present incoming key verification request from \(viewController)")
let keyVerificationCoordinator = KeyVerificationCoordinator(session: self.session, flow: .incomingRequest(incomingKeyVerificationRequest))
let keyVerificationCoordinator = KeyVerificationCoordinator(session: self.session, flow: .incomingRequest(incomingKeyVerificationRequest), cancellable: self.cancellable)
self.present(coordinator: keyVerificationCoordinator, from: viewController, animated: animated)
}
@ -93,7 +94,7 @@ final class KeyVerificationCoordinatorBridgePresenter: NSObject {
MXLog.debug("[KeyVerificationCoordinatorBridgePresenter] Present complete security from \(viewController)")
let keyVerificationCoordinator = KeyVerificationCoordinator(session: self.session, flow: .completeSecurity(isNewSignIn))
let keyVerificationCoordinator = KeyVerificationCoordinator(session: self.session, flow: .completeSecurity(isNewSignIn), cancellable: self.cancellable)
self.present(coordinator: keyVerificationCoordinator, from: viewController, animated: animated)
}
@ -103,7 +104,7 @@ final class KeyVerificationCoordinatorBridgePresenter: NSObject {
let navigationRouter = NavigationRouterStore.shared.navigationRouter(for: navigationController)
let keyVerificationCoordinator = KeyVerificationCoordinator(session: self.session, flow: .completeSecurity(isNewSignIn), navigationRouter: navigationRouter)
let keyVerificationCoordinator = KeyVerificationCoordinator(session: self.session, flow: .completeSecurity(isNewSignIn), navigationRouter: navigationRouter, cancellable: self.cancellable)
keyVerificationCoordinator.delegate = self
keyVerificationCoordinator.start() // Will trigger view controller push

View file

@ -28,6 +28,7 @@ final class KeyVerificationSelfVerifyWaitCoordinator: KeyVerificationSelfVerifyW
private let session: MXSession
private var keyVerificationSelfVerifyWaitViewModel: KeyVerificationSelfVerifyWaitViewModelType
private let keyVerificationSelfVerifyWaitViewController: KeyVerificationSelfVerifyWaitViewController
private let cancellable: Bool
// MARK: Public
@ -38,13 +39,14 @@ final class KeyVerificationSelfVerifyWaitCoordinator: KeyVerificationSelfVerifyW
// MARK: - Setup
init(session: MXSession, isNewSignIn: Bool) {
init(session: MXSession, isNewSignIn: Bool, cancellable: Bool) {
self.session = session
let keyVerificationSelfVerifyWaitViewModel = KeyVerificationSelfVerifyWaitViewModel(session: self.session, isNewSignIn: isNewSignIn)
let keyVerificationSelfVerifyWaitViewController = KeyVerificationSelfVerifyWaitViewController.instantiate(with: keyVerificationSelfVerifyWaitViewModel)
let keyVerificationSelfVerifyWaitViewController = KeyVerificationSelfVerifyWaitViewController.instantiate(with: keyVerificationSelfVerifyWaitViewModel, cancellable: cancellable)
self.keyVerificationSelfVerifyWaitViewModel = keyVerificationSelfVerifyWaitViewModel
self.keyVerificationSelfVerifyWaitViewController = keyVerificationSelfVerifyWaitViewController
self.cancellable = cancellable
}
// MARK: - Public methods
@ -55,6 +57,7 @@ final class KeyVerificationSelfVerifyWaitCoordinator: KeyVerificationSelfVerifyW
func toPresentable() -> UIViewController {
return self.keyVerificationSelfVerifyWaitViewController
.vc_setModalFullScreen(!self.cancellable)
}
}

View file

@ -47,6 +47,7 @@ final class KeyVerificationSelfVerifyWaitViewController: UIViewController {
// MARK: Private
private var viewModel: KeyVerificationSelfVerifyWaitViewModelType!
private var cancellable: Bool!
private var theme: Theme!
private var errorPresenter: MXKErrorPresentation!
private var activityPresenter: ActivityIndicatorPresenter!
@ -55,9 +56,10 @@ final class KeyVerificationSelfVerifyWaitViewController: UIViewController {
// MARK: - Setup
class func instantiate(with viewModel: KeyVerificationSelfVerifyWaitViewModelType) -> KeyVerificationSelfVerifyWaitViewController {
class func instantiate(with viewModel: KeyVerificationSelfVerifyWaitViewModelType, cancellable: Bool) -> KeyVerificationSelfVerifyWaitViewController {
let viewController = StoryboardScene.KeyVerificationSelfVerifyWaitViewController.initialScene.instantiate()
viewController.viewModel = viewModel
viewController.cancellable = cancellable
viewController.theme = ThemeService.shared().theme
return viewController
}
@ -112,15 +114,17 @@ final class KeyVerificationSelfVerifyWaitViewController: UIViewController {
}
private func setupViews() {
let cancelBarButtonItem = MXKBarButtonItem(title: VectorL10n.skip, style: .plain) { [weak self] in
self?.cancelButtonAction()
if self.cancellable {
let cancelBarButtonItem = MXKBarButtonItem(title: VectorL10n.skip, style: .plain) { [weak self] in
self?.cancelButtonAction()
}
self.vc_removeBackTitle()
self.navigationItem.rightBarButtonItem = cancelBarButtonItem
self.cancelBarButtonItem = cancelBarButtonItem
}
self.vc_removeBackTitle()
self.navigationItem.rightBarButtonItem = cancelBarButtonItem
self.cancelBarButtonItem = cancelBarButtonItem
self.title = VectorL10n.deviceVerificationSelfVerifyWaitTitle
self.informationLabel.text = VectorL10n.deviceVerificationSelfVerifyWaitInformation(AppInfo.current.displayName)

View file

@ -118,7 +118,7 @@ final class UserVerificationCoordinator: NSObject, UserVerificationCoordinatorTy
private func presentDeviceVerification(for deviceId: String) {
let keyVerificationCoordinator = KeyVerificationCoordinator(session: self.session, flow: .verifyDevice(userId: self.userId, deviceId: deviceId), navigationRouter: self.navigationRouter)
let keyVerificationCoordinator = KeyVerificationCoordinator(session: self.session, flow: .verifyDevice(userId: self.userId, deviceId: deviceId), navigationRouter: self.navigationRouter, cancellable: true)
keyVerificationCoordinator.delegate = self
keyVerificationCoordinator.start()

View file

@ -41,8 +41,9 @@ limitations under the License.
@param recentListViewController the `MXKRecentListViewController` instance.
@param childInfo the `MXSpaceChildInfo` instance that describes the selected room.
@param sourceView the view the modal has to be presented from.
*/
-(void)recentListViewController:(MXKRecentListViewController *)recentListViewController didSelectSuggestedRoom:(MXSpaceChildInfo *)childInfo;
-(void)recentListViewController:(MXKRecentListViewController *)recentListViewController didSelectSuggestedRoom:(MXSpaceChildInfo *)childInfo from:(UIView* _Nullable)sourceView;
@end

View file

@ -169,9 +169,6 @@
// Observe the server sync
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(onSyncNotification) name:kMXSessionDidSyncNotification object:nil];
// Do a full reload
[self refreshRecentsTable];
}
- (void)viewWillDisappear:(BOOL)animated
@ -180,12 +177,6 @@
// The user may still press search button whereas the view disappears
ignoreSearchRequest = YES;
// Leave potential search session
if (!self.recentsSearchBar.isHidden)
{
[self searchBarCancelButtonClicked:self.recentsSearchBar];
}
[[NSNotificationCenter defaultCenter] removeObserver:self name:kMXKRoomDataSourceSyncStatusChanged object:nil];
[[NSNotificationCenter defaultCenter] removeObserver:self name:kMXSessionDidSyncNotification object:nil];
@ -451,7 +442,8 @@
if (recentCellData.isSuggestedRoom)
{
[_delegate recentListViewController:self
didSelectSuggestedRoom:recentCellData.roomSummary.spaceChildInfo];
didSelectSuggestedRoom:recentCellData.roomSummary.spaceChildInfo
from:selectedCell];
}
else
{

View file

@ -15,3 +15,4 @@
#import "MXKRoomInputToolbarView.h"
#import "MXKImageView.h"
#import "MXKRoomBubbleCellData.h"
#import "UserIndicatorCancel.h"

View file

@ -2073,6 +2073,11 @@ static NSArray<NSNumber*> *initialSyncSilentErrorsHTTPStatusCodes;
mxSession.syncFilterId, syncFilter.JSONDictionary);
completion(NO);
}
else if (!mxSession.store.allFilterIds.count)
{
MXLogDebug(@"[MXKAccount] There are no filters stored in this session, proceed as if no /sync was done before");
completion(YES);
}
else
{
// Check the filter is the one previously set

View file

@ -1075,8 +1075,11 @@ typedef NS_ENUM (NSUInteger, MXKRoomDataSourceError) {
}
// Register a listener to handle redaction which can affect live and past timelines
MXWeakify(self);
redactionListener = [_timeline listenToEventsOfTypes:@[kMXEventTypeStringRoomRedaction] onEvent:^(MXEvent *redactionEvent, MXTimelineDirection direction, MXRoomState *roomState) {
MXStrongifyAndReturnIfNil(self);
// Consider only live redaction events
if (direction == MXTimelineDirectionForwards)
{

View file

@ -61,7 +61,7 @@ static NSRegularExpression *htmlTagsRegex;
eventIdRegex = [NSRegularExpression regularExpressionWithPattern:kMXToolsRegexStringForMatrixEventIdentifier options:NSRegularExpressionCaseInsensitive error:nil];
groupIdRegex = [NSRegularExpression regularExpressionWithPattern:kMXToolsRegexStringForMatrixGroupIdentifier options:NSRegularExpressionCaseInsensitive error:nil];
httpLinksRegex = [NSRegularExpression regularExpressionWithPattern:@"(?i)\\b(https?://.*)\\b" options:NSRegularExpressionCaseInsensitive error:nil];
httpLinksRegex = [NSRegularExpression regularExpressionWithPattern:@"(?i)\\b(https?://\\S*)\\b" options:NSRegularExpressionCaseInsensitive error:nil];
htmlTagsRegex = [NSRegularExpression regularExpressionWithPattern:@"<(\\w+)[^>]*>" options:NSRegularExpressionCaseInsensitive error:nil];
});
}

View file

@ -27,6 +27,12 @@ import AVFoundation
@objcMembers
final class SingleImagePickerPresenter: NSObject {
// MARK: - Constants
private enum Constants {
static let jpegCompressionQuality: CGFloat = 1.0
}
// MARK: - Properties
// MARK: Private
@ -117,8 +123,10 @@ final class SingleImagePickerPresenter: NSObject {
// MARK: - CameraPresenterDelegate
extension SingleImagePickerPresenter: CameraPresenterDelegate {
func cameraPresenter(_ cameraPresenter: CameraPresenter, didSelectImageData imageData: Data, withUTI uti: MXKUTI?) {
self.delegate?.singleImagePickerPresenter(self, didSelectImageData: imageData, withUTI: uti)
func cameraPresenter(_ cameraPresenter: CameraPresenter, didSelectImage image: UIImage) {
if let imageData = image.jpegData(compressionQuality: Constants.jpegCompressionQuality) {
self.delegate?.singleImagePickerPresenter(self, didSelectImageData: imageData, withUTI: MXKUTI.jpeg)
}
}
func cameraPresenterDidCancel(_ cameraPresenter: CameraPresenter) {

View file

@ -0,0 +1,110 @@
//
// Copyright 2022 New Vector Ltd
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
import UIKit
import PhotosUI
import CommonKit
@available(iOS 14.0, *)
protocol MediaPickerPresenterDelegate: AnyObject {
func mediaPickerPresenter(_ presenter: MediaPickerPresenter, didPickImage image: UIImage)
func mediaPickerPresenterDidCancel(_ presenter: MediaPickerPresenter)
}
/// A picker for photos and videos from the user's photo library on iOS 14+ using the
/// new `PHPickerViewController` that doesn't require permission to be granted.
///
/// **Note:** If you need to support iOS 12 & 13, then you will need to use the older
/// `MediaPickerCoordinator`/`MediaPickerViewController` instead.
@available(iOS 14.0, *)
final class MediaPickerPresenter: NSObject {
// MARK: - Properties
// MARK: Private
private weak var pickerViewController: UIViewController?
private var indicatorPresenter: UserIndicatorTypePresenterProtocol?
private var loadingIndicator: UserIndicator?
// MARK: Public
weak var delegate: MediaPickerPresenterDelegate?
// MARK: - Public
// TODO: Support videos and multi-selection
func presentPicker(from presentingViewController: UIViewController, with filter: PHPickerFilter?, animated: Bool) {
var configuration = PHPickerConfiguration(photoLibrary: .shared())
configuration.selectionLimit = 1
configuration.filter = filter
let pickerViewController = PHPickerViewController(configuration: configuration)
pickerViewController.delegate = self
self.pickerViewController = pickerViewController
indicatorPresenter = UserIndicatorTypePresenter(presentingViewController: pickerViewController)
presentingViewController.present(pickerViewController, animated: true, completion: nil)
}
func dismiss(animated: Bool, completion: (() -> Void)?) {
guard let pickerViewController = pickerViewController else { return }
pickerViewController.dismiss(animated: animated, completion: completion)
}
// MARK: - Private
func showLoadingIndicator() {
loadingIndicator = indicatorPresenter?.present(.loading(label: VectorL10n.loading, isInteractionBlocking: true))
}
func hideLoadingIndicator() {
loadingIndicator = nil
}
}
// MARK: - PHPickerViewControllerDelegate
@available(iOS 14, *)
extension MediaPickerPresenter: PHPickerViewControllerDelegate {
func picker(_ picker: PHPickerViewController, didFinishPicking results: [PHPickerResult]) {
// TODO: Handle videos and multi-selection
guard let provider = results.first?.itemProvider, provider.canLoadObject(ofClass: UIImage.self) else {
self.delegate?.mediaPickerPresenterDidCancel(self)
return
}
showLoadingIndicator()
provider.loadObject(ofClass: UIImage.self) { [weak self] image, error in
guard let self = self else { return }
guard let image = image as? UIImage else {
DispatchQueue.main.async {
self.hideLoadingIndicator()
self.delegate?.mediaPickerPresenterDidCancel(self)
}
return
}
DispatchQueue.main.async {
self.hideLoadingIndicator()
self.delegate?.mediaPickerPresenter(self, didPickImage: image)
}
}
}
}

View file

@ -65,6 +65,9 @@ final class OnboardingCoordinator: NSObject, OnboardingCoordinatorProtocol {
private var authenticationType: MXKAuthenticationType?
private var session: MXSession?
private var shouldShowDisplayNameScreen = false
private var shouldShowAvatarScreen = false
/// Whether all of the onboarding steps have been completed or not. `false` if there are more screens to be shown.
private var onboardingFinished = false
/// Whether authentication is complete. `true` once authenticated, verified and the app is ready to be shown.
@ -182,6 +185,7 @@ final class OnboardingCoordinator: NSObject, OnboardingCoordinatorProtocol {
}
/// Displays the next view in the flow after the use case screen.
@available(iOS 14.0, *)
private func useCaseSelectionCoordinator(_ coordinator: OnboardingUseCaseSelectionCoordinator, didCompleteWith result: OnboardingUseCaseViewModelResult) {
useCaseResult = result
showAuthenticationScreen()
@ -247,12 +251,13 @@ final class OnboardingCoordinator: NSObject, OnboardingCoordinatorProtocol {
self.session = session
self.authenticationType = authenticationType
// May need to move the spinner and key verification up to here in order to coordinate properly.
// Check whether another screen should be shown.
if #available(iOS 14.0, *) {
if authenticationType == .register, let userId = session.credentials.userId, BuildSettings.onboardingShowAccountPersonalisation {
showCongratulationsScreen(userId: userId)
if authenticationType == .register,
let userId = session.credentials.userId,
let userSession = UserSessionsService.shared.userSession(withUserId: userId),
BuildSettings.onboardingShowAccountPersonalization {
checkHomeserverCapabilities(for: userSession)
return
} else if Analytics.shared.shouldShowAnalyticsPrompt {
showAnalyticsPrompt(for: session)
@ -265,6 +270,24 @@ final class OnboardingCoordinator: NSObject, OnboardingCoordinatorProtocol {
completeIfReady()
}
/// Checks the capabilities of the user's homeserver in order to determine
/// whether or not the display name and avatar can be updated.
///
/// Once complete this method will start the post authentication flow automatically.
@available(iOS 14.0, *)
private func checkHomeserverCapabilities(for userSession: UserSession) {
userSession.matrixSession.matrixRestClient.capabilities { [weak self] capabilities in
guard let self = self else { return }
self.shouldShowDisplayNameScreen = capabilities?.setDisplayName?.isEnabled == true
self.shouldShowAvatarScreen = capabilities?.setAvatarUrl?.isEnabled == true
self.beginPostAuthentication(for: userSession)
} failure: { [weak self] _ in
MXLog.warning("[OnboardingCoordinator] Homeserver capabilities not returned. Skipping personalisation")
self?.beginPostAuthentication(for: userSession)
}
}
/// Displays the next view in the flow after the authentication screen.
private func authenticationCoordinatorDidComplete(_ coordinator: AuthenticationCoordinatorProtocol) {
isShowingAuthentication = false
@ -287,11 +310,19 @@ final class OnboardingCoordinator: NSObject, OnboardingCoordinatorProtocol {
// MARK: - Post-Authentication
/// Starts the part of the flow that comes after authentication for new users.
@available(iOS 14.0, *)
private func showCongratulationsScreen(userId: String) {
private func beginPostAuthentication(for userSession: UserSession) {
showCongratulationsScreen(for: userSession)
}
/// Show the congratulations screen for new users. The screen will be configured based on the homeserver's capabilities.
@available(iOS 14.0, *)
private func showCongratulationsScreen(for userSession: UserSession) {
MXLog.debug("[OnboardingCoordinator] showCongratulationsScreen")
let parameters = OnboardingCongratulationsCoordinatorParameters(userId: userId)
let parameters = OnboardingCongratulationsCoordinatorParameters(userSession: userSession,
personalizationDisabled: !shouldShowDisplayNameScreen && !shouldShowAvatarScreen)
let coordinator = OnboardingCongratulationsCoordinator(parameters: parameters)
coordinator.completion = { [weak self, weak coordinator] result in
@ -308,21 +339,25 @@ final class OnboardingCoordinator: NSObject, OnboardingCoordinatorProtocol {
}
}
/// Displays the next view in the flow after the congratulations screen.
@available(iOS 14.0, *)
private func congratulationsCoordinator(_ coordinator: OnboardingCongratulationsCoordinator, didCompleteWith result: OnboardingCongratulationsViewModelResult) {
if let session = session {
switch result {
case .personaliseProfile:
// TODO: Profile screens here instead.
if Analytics.shared.shouldShowAnalyticsPrompt {
showAnalyticsPrompt(for: session)
return
}
case .takeMeHome:
if Analytics.shared.shouldShowAnalyticsPrompt {
showAnalyticsPrompt(for: session)
return
}
private func congratulationsCoordinator(_ coordinator: OnboardingCongratulationsCoordinator, didCompleteWith result: OnboardingCongratulationsCoordinatorResult) {
switch result {
case .personalizeProfile(let userSession):
if shouldShowDisplayNameScreen {
showDisplayNameScreen(for: userSession)
return
} else if shouldShowAvatarScreen {
showAvatarScreen(for: userSession)
return
} else if Analytics.shared.shouldShowAnalyticsPrompt {
showAnalyticsPrompt(for: userSession.matrixSession)
return
}
case .takeMeHome(let userSession):
if Analytics.shared.shouldShowAnalyticsPrompt {
showAnalyticsPrompt(for: userSession.matrixSession)
return
}
}
@ -330,6 +365,104 @@ final class OnboardingCoordinator: NSObject, OnboardingCoordinatorProtocol {
completeIfReady()
}
/// Show the display name personalization screen for new users using the supplied user session.
@available(iOS 14.0, *)
private func showDisplayNameScreen(for userSession: UserSession) {
MXLog.debug("[OnboardingCoordinator]: showDisplayNameScreen")
let parameters = OnboardingDisplayNameCoordinatorParameters(userSession: userSession)
let coordinator = OnboardingDisplayNameCoordinator(parameters: parameters)
coordinator.completion = { [weak self, weak coordinator] session in
guard let self = self, let coordinator = coordinator else { return }
self.displayNameCoordinator(coordinator, didCompleteWith: session)
}
add(childCoordinator: coordinator)
coordinator.start()
navigationRouter.setRootModule(coordinator, hideNavigationBar: false, animated: true) { [weak self] in
self?.remove(childCoordinator: coordinator)
}
}
/// Displays the next view in the flow after the display name screen.
@available(iOS 14.0, *)
private func displayNameCoordinator(_ coordinator: OnboardingDisplayNameCoordinator, didCompleteWith userSession: UserSession) {
if shouldShowAvatarScreen {
showAvatarScreen(for: userSession)
} else {
showCelebrationScreen(for: userSession)
}
}
/// Show the avatar personalization screen for new users using the supplied user session.
@available(iOS 14.0, *)
private func showAvatarScreen(for userSession: UserSession) {
MXLog.debug("[OnboardingCoordinator]: showAvatarScreen")
let parameters = OnboardingAvatarCoordinatorParameters(userSession: userSession)
let coordinator = OnboardingAvatarCoordinator(parameters: parameters)
coordinator.completion = { [weak self, weak coordinator] session in
guard let self = self, let coordinator = coordinator else { return }
self.avatarCoordinator(coordinator, didCompleteWith: session)
}
add(childCoordinator: coordinator)
coordinator.start()
if navigationRouter.modules.isEmpty || !shouldShowDisplayNameScreen {
navigationRouter.setRootModule(coordinator, hideNavigationBar: false, animated: true) { [weak self] in
self?.remove(childCoordinator: coordinator)
}
} else {
navigationRouter.push(coordinator, animated: true) { [weak self] in
self?.remove(childCoordinator: coordinator)
}
}
}
/// Displays the next view in the flow after the avatar screen.
@available(iOS 14.0, *)
private func avatarCoordinator(_ coordinator: OnboardingAvatarCoordinator, didCompleteWith userSession: UserSession) {
showCelebrationScreen(for: userSession)
}
@available(iOS 14.0, *)
private func showCelebrationScreen(for userSession: UserSession) {
MXLog.debug("[OnboardingCoordinator] showCelebrationScreen")
let parameters = OnboardingCelebrationCoordinatorParameters(userSession: userSession)
let coordinator = OnboardingCelebrationCoordinator(parameters: parameters)
coordinator.completion = { [weak self, weak coordinator] userSession in
guard let self = self, let coordinator = coordinator else { return }
self.celebrationCoordinator(coordinator, didCompleteWith: userSession)
}
add(childCoordinator: coordinator)
coordinator.start()
navigationRouter.setRootModule(coordinator, hideNavigationBar: true, animated: true) { [weak self] in
self?.remove(childCoordinator: coordinator)
}
}
@available(iOS 14.0, *)
private func celebrationCoordinator(_ coordinator: OnboardingCelebrationCoordinator, didCompleteWith userSession: UserSession) {
if Analytics.shared.shouldShowAnalyticsPrompt {
showAnalyticsPrompt(for: userSession.matrixSession)
return
}
onboardingFinished = true
completeIfReady()
}
/// Shows the analytics prompt for the supplied session.
///
/// Check `Analytics.shared.shouldShowAnalyticsPrompt` before calling this method.
@available(iOS 14.0, *)
private func showAnalyticsPrompt(for session: MXSession) {
MXLog.debug("[OnboardingCoordinator]: Invite the user to send analytics")
@ -351,6 +484,7 @@ final class OnboardingCoordinator: NSObject, OnboardingCoordinatorProtocol {
}
}
/// Displays the next view in the flow after the analytics screen.
private func analyticsPromptCoordinatorDidComplete(_ coordinator: AnalyticsPromptCoordinator) {
onboardingFinished = true
completeIfReady()
@ -358,6 +492,8 @@ final class OnboardingCoordinator: NSObject, OnboardingCoordinatorProtocol {
// MARK: - Finished
/// Calls the coordinator's completion handler if both `onboardingFinished` and `authenticationFinished`
/// are true. Otherwise displays any pending screens and waits to be called again.
private func completeIfReady() {
guard onboardingFinished else {
MXLog.debug("[OnboardingCoordinator] Delaying onboarding completion until all screens have been shown.")

View file

@ -20,6 +20,7 @@ import Mapbox
class LocationMarkerView: MGLAnnotationView, NibLoadable {
@IBOutlet private var backgroundImageView: UIImageView!
@IBOutlet private var avatarView: UserAvatarView!
override func awakeFromNib() {
@ -27,7 +28,9 @@ class LocationMarkerView: MGLAnnotationView, NibLoadable {
translatesAutoresizingMaskIntoConstraints = false
}
func setAvatarData(_ avatarData: AvatarViewDataProtocol) {
func setAvatarData(_ avatarData: AvatarViewDataProtocol, avatarBackgroundColor: UIColor) {
backgroundImageView.image = Asset.Images.locationUserMarker.image
backgroundImageView.tintColor = avatarBackgroundColor
avatarView.fill(with: avatarData)
}
}

View file

@ -1,9 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="18122" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
<device id="retina6_1" orientation="portrait" appearance="light"/>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="19529" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="18093"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="19519"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<objects>
@ -65,6 +63,7 @@
<freeformSimulatedSizeMetrics key="simulatedDestinationMetrics"/>
<connections>
<outlet property="avatarView" destination="qut-wn-BX3" id="wHA-bz-A2y"/>
<outlet property="backgroundImageView" destination="ldO-kc-R5W" id="52a-Fs-iu7"/>
</connections>
<point key="canvasLocation" x="58.695652173913047" y="4.6875"/>
</view>

View file

@ -39,6 +39,7 @@ class RoomTimelineLocationView: UIView, NibLoadable, Themable, MGLMapViewDelegat
private var mapView: MGLMapView!
private var annotationView: LocationMarkerView?
private static var usernameColorGenerator = UserNameColorGenerator()
// MARK: Public
@ -82,7 +83,8 @@ class RoomTimelineLocationView: UIView, NibLoadable, Themable, MGLMapViewDelegat
annotationView = LocationMarkerView.loadFromNib()
if let userAvatarData = userAvatarData {
annotationView?.setAvatarData(userAvatarData)
let avatarBackgroundColor = Self.usernameColorGenerator.color(from: userAvatarData.matrixItemId)
annotationView?.setAvatarData(userAvatarData, avatarBackgroundColor: avatarBackgroundColor)
}
if let annotations = mapView.annotations {
@ -99,6 +101,7 @@ class RoomTimelineLocationView: UIView, NibLoadable, Themable, MGLMapViewDelegat
// MARK: - Themable
func update(theme: Theme) {
Self.usernameColorGenerator.update(theme: theme)
descriptionLabel.textColor = theme.colors.primaryContent
descriptionLabel.font = theme.fonts.footnote
descriptionIcon.tintColor = theme.colors.accent

View file

@ -0,0 +1,96 @@
//
// Copyright 2022 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 Reusable
import UIKit
@objcMembers
final class LiveLocationSharingBannerView: UIView, NibLoadable, Themable {
// MARK: - Properties
// MARK: Outlets
@IBOutlet private weak var iconImageView: UIImageView!
@IBOutlet private weak var titleLabel: UILabel!
@IBOutlet private weak var stopButton: UIButton!
// MARK: Private
private var theme: Theme!
// MARK: Public
var didTapBackground: (() -> Void)?
var didTapStopButton: (() -> Void)?
// MARK: - Setup
static func instantiate() -> LiveLocationSharingBannerView {
let view = LiveLocationSharingBannerView.loadFromNib()
view.update(theme: ThemeService.shared().theme)
return view
}
// MARK: - Life cycle
override func awakeFromNib() {
super.awakeFromNib()
self.setupBackgroundTapGestureRecognizer()
self.titleLabel.text = VectorL10n.liveLocationSharingBannerTitle
self.stopButton.setTitle(VectorL10n.liveLocationSharingBannerStop, for: .normal)
}
// MARK: - Public
func update(theme: Theme) {
self.theme = theme
let tintColor = theme.colors.background
self.backgroundColor = theme.tintColor
self.iconImageView.tintColor = tintColor
self.titleLabel.textColor = tintColor
self.titleLabel.font = theme.fonts.footnote
self.stopButton.vc_setTitleFont(theme.fonts.footnote)
self.stopButton.tintColor = tintColor
self.stopButton.setTitleColor(tintColor, for: .normal)
self.stopButton.setTitleColor(tintColor.withAlphaComponent(0.5), for: .highlighted)
}
// MARK: - Private
private func setupBackgroundTapGestureRecognizer() {
let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(handleBackgroundViewTap(_:)))
self.addGestureRecognizer(tapGestureRecognizer)
}
// MARK: - Actions
@objc private func handleBackgroundViewTap(_ gestureRecognizer: UITapGestureRecognizer) {
self.didTapBackground?()
}
@IBAction private func stopButtonAction(_ sender: Any) {
self.didTapStopButton?()
}
}

View file

@ -0,0 +1,72 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="19529" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
<device id="retina6_1" orientation="portrait" appearance="light"/>
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="19519"/>
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<objects>
<placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner"/>
<placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
<view contentMode="scaleToFill" id="iN0-l3-epB" customClass="LiveLocationSharingBannerView" customModule="Riot" customModuleProvider="target">
<rect key="frame" x="0.0" y="0.0" width="414" height="104"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="live_location_icon" translatesAutoresizingMaskIntoConstraints="NO" id="hDt-s2-HU0">
<rect key="frame" x="15" y="62" width="24" height="24"/>
<constraints>
<constraint firstAttribute="width" secondItem="hDt-s2-HU0" secondAttribute="height" multiplier="1:1" id="LUH-tx-Vu9"/>
<constraint firstAttribute="width" constant="24" id="jBe-4U-Yb7"/>
</constraints>
</imageView>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Live location enabled" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="7UG-mn-oZ3">
<rect key="frame" x="49" y="49" width="128.5" height="50"/>
<fontDescription key="fontDescription" type="system" pointSize="13"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="H2w-fv-3ba">
<rect key="frame" x="355" y="60.5" width="44" height="27"/>
<constraints>
<constraint firstAttribute="width" relation="greaterThanOrEqual" constant="44" id="WZJ-7H-OFh"/>
</constraints>
<fontDescription key="fontDescription" style="UICTFontTextStyleFootnote"/>
<inset key="imageEdgeInsets" minX="0.0" minY="0.0" maxX="2.2250738585072014e-308" maxY="0.0"/>
<state key="normal" title="Stop"/>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="boolean" keyPath="vc_adjustsFontForContentSizeCategory" value="YES"/>
</userDefinedRuntimeAttributes>
<connections>
<action selector="stopButtonAction:" destination="iN0-l3-epB" eventType="touchUpInside" id="rVA-YA-4J6"/>
</connections>
</button>
</subviews>
<viewLayoutGuide key="safeArea" id="vUN-kp-3ea"/>
<color key="backgroundColor" red="0.050980392156862744" green="0.74117647058823533" blue="0.54509803921568623" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<constraints>
<constraint firstItem="vUN-kp-3ea" firstAttribute="trailing" secondItem="H2w-fv-3ba" secondAttribute="trailing" constant="15" id="29I-X9-nEy"/>
<constraint firstAttribute="bottomMargin" secondItem="7UG-mn-oZ3" secondAttribute="bottom" constant="5" id="5TD-eA-uqi"/>
<constraint firstItem="7UG-mn-oZ3" firstAttribute="top" secondItem="iN0-l3-epB" secondAttribute="topMargin" constant="5" id="919-NN-C1l"/>
<constraint firstItem="hDt-s2-HU0" firstAttribute="leading" secondItem="vUN-kp-3ea" secondAttribute="leading" constant="15" id="FcV-b9-J6v"/>
<constraint firstItem="7UG-mn-oZ3" firstAttribute="leading" secondItem="hDt-s2-HU0" secondAttribute="trailing" constant="10" id="Lsz-9Z-xLO"/>
<constraint firstItem="H2w-fv-3ba" firstAttribute="centerY" secondItem="vUN-kp-3ea" secondAttribute="centerY" id="RTT-lt-BtI"/>
<constraint firstItem="hDt-s2-HU0" firstAttribute="centerY" secondItem="vUN-kp-3ea" secondAttribute="centerY" id="XgN-M3-jLz"/>
<constraint firstItem="H2w-fv-3ba" firstAttribute="leading" relation="greaterThanOrEqual" secondItem="7UG-mn-oZ3" secondAttribute="trailing" constant="10" id="zWz-Me-wx1"/>
</constraints>
<nil key="simulatedTopBarMetrics"/>
<nil key="simulatedBottomBarMetrics"/>
<freeformSimulatedSizeMetrics key="simulatedDestinationMetrics"/>
<connections>
<outlet property="iconImageView" destination="hDt-s2-HU0" id="1HC-Kc-Rxp"/>
<outlet property="stopButton" destination="H2w-fv-3ba" id="g7S-JF-xw5"/>
<outlet property="titleLabel" destination="7UG-mn-oZ3" id="bxZ-zI-Q7J"/>
</connections>
<point key="canvasLocation" x="137.68115942028987" y="-122.54464285714285"/>
</view>
</objects>
<resources>
<image name="live_location_icon" width="24" height="24"/>
</resources>
</document>

Some files were not shown because too many files have changed in this diff Show more