Merge branch 'develop' into ismail/5770_threads_notice
79
CHANGES.md
|
@ -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
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -67,5 +67,4 @@ public protocol Colors {
|
|||
/// - Names in chat timeline
|
||||
/// - Avatars default states that include first name letter
|
||||
var namesAndAvatars: [ColorType] { get }
|
||||
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
70
Gemfile.lock
|
@ -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
|
||||
|
|
2
Podfile
|
@ -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' } }
|
||||
|
|
16
Podfile.lock
|
@ -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
|
||||
|
|
15
Riot/Assets/Images.xcassets/Onboarding/onboarding_avatar_camera.imageset/Contents.json
vendored
Normal file
|
@ -0,0 +1,15 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "onboarding_avatar_camera.svg",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
},
|
||||
"properties" : {
|
||||
"preserves-vector-representation" : true
|
||||
}
|
||||
}
|
|
@ -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 |
15
Riot/Assets/Images.xcassets/Onboarding/onboarding_avatar_edit.imageset/Contents.json
vendored
Normal file
|
@ -0,0 +1,15 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "onboarding_avatar_edit.svg",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
},
|
||||
"properties" : {
|
||||
"preserves-vector-representation" : true
|
||||
}
|
||||
}
|
|
@ -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 |
15
Riot/Assets/Images.xcassets/Onboarding/onboarding_celebration_icon.imageset/Contents.json
vendored
Normal file
|
@ -0,0 +1,15 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "onboarding_celebration_icon.svg",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
},
|
||||
"properties" : {
|
||||
"preserves-vector-representation" : true
|
||||
}
|
||||
}
|
|
@ -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 |
26
Riot/Assets/Images.xcassets/Room/Location/live_location_icon.imageset/Contents.json
vendored
Normal 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"
|
||||
}
|
||||
}
|
BIN
Riot/Assets/Images.xcassets/Room/Location/live_location_icon.imageset/live_location_icon.png
vendored
Normal file
After Width: | Height: | Size: 390 B |
BIN
Riot/Assets/Images.xcassets/Room/Location/live_location_icon.imageset/live_location_icon@2x.png
vendored
Normal file
After Width: | Height: | Size: 710 B |
BIN
Riot/Assets/Images.xcassets/Room/Location/live_location_icon.imageset/live_location_icon@3x.png
vendored
Normal file
After Width: | Height: | Size: 1.1 KiB |
23
Riot/Assets/Images.xcassets/Room/Location/location_live_icon.imageset/Contents.json
vendored
Normal 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
|
||||
}
|
||||
}
|
BIN
Riot/Assets/Images.xcassets/Room/Location/location_live_icon.imageset/location_live_icon.png
vendored
Normal file
After Width: | Height: | Size: 1.4 KiB |
BIN
Riot/Assets/Images.xcassets/Room/Location/location_live_icon.imageset/location_live_icon@2x.png
vendored
Normal file
After Width: | Height: | Size: 2.7 KiB |
BIN
Riot/Assets/Images.xcassets/Room/Location/location_live_icon.imageset/location_live_icon@3x.png
vendored
Normal file
After Width: | Height: | Size: 4 KiB |
23
Riot/Assets/Images.xcassets/Room/Location/location_pin_icon.imageset/Contents.json
vendored
Normal 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
|
||||
}
|
||||
}
|
BIN
Riot/Assets/Images.xcassets/Room/Location/location_pin_icon.imageset/location_pin_icon.png
vendored
Normal file
After Width: | Height: | Size: 1.1 KiB |
BIN
Riot/Assets/Images.xcassets/Room/Location/location_pin_icon.imageset/location_pin_icon@2x.png
vendored
Normal file
After Width: | Height: | Size: 1.9 KiB |
BIN
Riot/Assets/Images.xcassets/Room/Location/location_pin_icon.imageset/location_pin_icon@3x.png
vendored
Normal file
After Width: | Height: | Size: 2.8 KiB |
|
@ -19,5 +19,8 @@
|
|||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
},
|
||||
"properties" : {
|
||||
"template-rendering-intent" : "template"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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" = "You’re 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.";
|
||||
|
|
|
@ -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 couldn’t 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
|
||||
|
||||
|
@ -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.";
|
||||
|
||||
|
|
|
@ -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")
|
||||
}
|
||||
}
|
||||
|
|
26
Riot/Categories/Codable.swift
Normal 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)
|
||||
}
|
||||
}
|
114
Riot/Categories/MXBugReportRestClient+Riot.swift
Normal 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)
|
||||
}
|
||||
|
||||
}
|
|
@ -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];
|
||||
}
|
||||
|
|
37
Riot/Categories/Publisher+Riot.swift
Normal 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)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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")
|
||||
}
|
||||
/// You’re 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
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
if widget.isActive {
|
||||
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
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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];
|
||||
}
|
||||
}
|
||||
|
||||
|
|
29
Riot/Managers/Settings/RiotSettings+Publisher.swift
Normal 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()
|
||||
}
|
||||
|
||||
}
|
|
@ -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.
|
||||
|
|
236
Riot/Managers/UISIAutoReporter/UISIAutoReporter.swift
Normal 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 ?? ""
|
||||
]
|
||||
)
|
||||
}
|
||||
|
||||
}
|
115
Riot/Managers/UISIAutoReporter/UISIDetector.swift
Normal 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)"
|
||||
}
|
||||
}
|
|
@ -42,7 +42,8 @@ typedef enum : NSUInteger
|
|||
WidgetManagerErrorCodeNoIntegrationsServerConfigured,
|
||||
WidgetManagerErrorCodeDisabledIntegrationsServer,
|
||||
WidgetManagerErrorCodeFailedToConnectToIntegrationsServer,
|
||||
WidgetManagerErrorCodeTermsNotSigned
|
||||
WidgetManagerErrorCodeTermsNotSigned,
|
||||
WidgetManagerErrorCodeUnavailableJitsiURL
|
||||
}
|
||||
WidgetManagerErrorCode;
|
||||
|
||||
|
|
|
@ -279,6 +279,13 @@ NSString *const WidgetManagerErrorDomain = @"WidgetManagerErrorDomain";
|
|||
|
||||
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;
|
||||
|
||||
operation = [jitsiService createJitsiWidgetContentWithJitsiServerURL:preferredJitsiServerUrl roomID:room.roomId isAudioOnly:!video success:^(NSDictionary * _Nonnull widgetContent) {
|
||||
|
@ -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
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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?
|
||||
|
||||
|
@ -42,20 +38,31 @@ final class HomeserverConfigurationBuilder: NSObject {
|
|||
|
||||
// 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)
|
||||
}
|
||||
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
}
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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) }
|
||||
}
|
||||
}
|
||||
|
||||
|
|
39
Riot/Model/WellKnown/VectorWellKnownBackupSetupMethod.swift
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
|
@ -121,8 +121,13 @@ import AnalyticsEvents
|
|||
|
||||
MXLog.debug("[Analytics] Started.")
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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.
|
||||
|
||||
|
|
|
@ -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];
|
||||
}
|
||||
|
||||
#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;
|
||||
|
||||
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
|
||||
|
@ -2155,6 +2180,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);
|
||||
|
@ -2444,6 +2480,15 @@ NSString *const AppDelegateUniversalLinkDidChangeNotification = @"AppDelegateUni
|
|||
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)
|
||||
MXWeakify(self);
|
||||
[mxSession.matrixRestClient roomSummaryWith:roomId via:@[] success:^(MXPublicRoom *room) {
|
||||
MXStrongifyAndReturnIfNil(self);
|
||||
if ([room.roomTypeString isEqualToString:MXRoomTypeStringSpace])
|
||||
{
|
||||
completion();
|
||||
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
|
||||
|
|
|
@ -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,6 +107,7 @@ class RoomNavigationParameters: NSObject {
|
|||
self.presentationParameters = presentationParameters
|
||||
self.showSettingsInitially = showSettingsInitially
|
||||
self.threadParameters = nil
|
||||
self.senderId = nil
|
||||
|
||||
super.init()
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,8 +128,51 @@
|
|||
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="Gg0-TE-OGb">
|
||||
<rect key="frame" x="0.0" y="360" width="375" height="125"/>
|
||||
<subviews>
|
||||
<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="19" y="33" width="122" height="30"/>
|
||||
<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"/>
|
||||
|
@ -149,43 +191,10 @@
|
|||
<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"/>
|
||||
</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"/>
|
||||
|
|
|
@ -295,46 +295,7 @@
|
|||
{
|
||||
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;
|
||||
|
@ -348,54 +309,21 @@
|
|||
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)
|
||||
{
|
||||
|
@ -413,7 +341,7 @@
|
|||
|
||||
self.sendingProgress.progress = progress.fractionCompleted;
|
||||
|
||||
} success:^{
|
||||
} success:^(NSString *reportUrl){
|
||||
|
||||
self->bugReportRestClient = nil;
|
||||
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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];
|
||||
}
|
||||
|
|
27
Riot/Modules/Common/UserIndicators/UserIndicatorCancel.h
Normal 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 */
|
62
Riot/Modules/Common/UserIndicators/UserIndicatorStore.swift
Normal 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)
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
||||
|
|
|
@ -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 {
|
||||
|
|
43
Riot/Modules/DeepLink/MXRoomAliasResolution+Deeplink.swift
Normal 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=")
|
||||
}
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
{
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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()
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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,6 +114,7 @@ final class KeyVerificationSelfVerifyWaitViewController: UIViewController {
|
|||
}
|
||||
|
||||
private func setupViews() {
|
||||
if self.cancellable {
|
||||
let cancelBarButtonItem = MXKBarButtonItem(title: VectorL10n.skip, style: .plain) { [weak self] in
|
||||
self?.cancelButtonAction()
|
||||
}
|
||||
|
@ -120,6 +123,7 @@ final class KeyVerificationSelfVerifyWaitViewController: UIViewController {
|
|||
|
||||
self.navigationItem.rightBarButtonItem = cancelBarButtonItem
|
||||
self.cancelBarButtonItem = cancelBarButtonItem
|
||||
}
|
||||
|
||||
self.title = VectorL10n.deviceVerificationSelfVerifyWaitTitle
|
||||
|
||||
|
|
|
@ -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()
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
@ -181,12 +178,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
|
||||
{
|
||||
|
|
|
@ -15,3 +15,4 @@
|
|||
#import "MXKRoomInputToolbarView.h"
|
||||
#import "MXKImageView.h"
|
||||
#import "MXKRoomBubbleCellData.h"
|
||||
#import "UserIndicatorCancel.h"
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
{
|
||||
|
|
|
@ -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];
|
||||
});
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
110
Riot/Modules/MediaPickerV2/MediaPickerPresenter.swift
Normal 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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,28 +339,130 @@ 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 {
|
||||
private func congratulationsCoordinator(_ coordinator: OnboardingCongratulationsCoordinator, didCompleteWith result: OnboardingCongratulationsCoordinatorResult) {
|
||||
switch result {
|
||||
case .personaliseProfile:
|
||||
// TODO: Profile screens here instead.
|
||||
if Analytics.shared.shouldShowAnalyticsPrompt {
|
||||
showAnalyticsPrompt(for: session)
|
||||
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:
|
||||
case .takeMeHome(let userSession):
|
||||
if Analytics.shared.shouldShowAnalyticsPrompt {
|
||||
showAnalyticsPrompt(for: session)
|
||||
showAnalyticsPrompt(for: userSession.matrixSession)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onboardingFinished = true
|
||||
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.")
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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?()
|
||||
}
|
||||
}
|
|
@ -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>
|
|
@ -27,6 +27,8 @@
|
|||
#import "MXKAttachmentsViewController.h"
|
||||
#import "MXKAttachmentAnimator.h"
|
||||
|
||||
@class UserIndicatorStore;
|
||||
|
||||
typedef NS_ENUM(NSUInteger, MXKRoomViewControllerJoinRoomResult) {
|
||||
MXKRoomViewControllerJoinRoomResultSuccess,
|
||||
MXKRoomViewControllerJoinRoomResultFailureRoomEmpty,
|
||||
|
@ -198,6 +200,12 @@ typedef NS_ENUM(NSUInteger, MXKRoomViewControllerJoinRoomResult) {
|
|||
*/
|
||||
@property NSTimeInterval resizeComposerAnimationDuration;
|
||||
|
||||
/**
|
||||
A store of user indicators that lets the room present and dismiss indicators without
|
||||
worrying about the presentation context or memory management.
|
||||
*/
|
||||
@property (strong, nonatomic) UserIndicatorStore *userIndicatorStore;
|
||||
|
||||
/**
|
||||
This object is defined when the displayed room is left. It is added into the bubbles table header.
|
||||
This label is used to display the reason why the room has been left.
|
||||
|
|